refactor a bunch of stuff into a CropTool
This commit is contained in:
parent
6d07a533d7
commit
7db0ec7e62
233
Program.cs
233
Program.cs
@ -52,6 +52,129 @@ public class CameraInfo {
|
|||||||
public static readonly CameraInfo IPHONE_12_MINI = new(new Vector2i(4032, 3024));
|
public static readonly CameraInfo IPHONE_12_MINI = new(new Vector2i(4032, 3024));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface ITool {
|
||||||
|
void SetActivePhoto(Photo photo);
|
||||||
|
void HandleInput(UiGeometry geometry, KeyboardState input, MouseState mouse, Game game);
|
||||||
|
string Status();
|
||||||
|
void Draw(UiGeometry geometry, Game game);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ViewTool : ITool {
|
||||||
|
Photo? activePhoto;
|
||||||
|
|
||||||
|
public void SetActivePhoto(Photo photo) {
|
||||||
|
activePhoto = photo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void HandleInput(UiGeometry geometry, KeyboardState input, MouseState mouse, Game game) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Status() {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Draw(UiGeometry geometry, Game game) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: remove unneeded dependencies on "Game" or at least refactor them a bit.
|
||||||
|
public class CropTool : ITool {
|
||||||
|
|
||||||
|
Photo? activePhoto;
|
||||||
|
Vector2i mouseDragStart;
|
||||||
|
Vector2i mouseDragEnd;
|
||||||
|
|
||||||
|
public void SetActivePhoto(Photo photo) {
|
||||||
|
activePhoto = photo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void HandleInput(UiGeometry geometry, KeyboardState input, MouseState mouse, Game game) {
|
||||||
|
Vector2i mousePosition = (Vector2i) mouse.Position;
|
||||||
|
|
||||||
|
if (mouse.IsButtonPressed(MouseButton.Button1)) {
|
||||||
|
if (geometry.PhotoBox.ContainsInclusive(mousePosition)) {
|
||||||
|
mouseDragStart = mousePosition;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mouse.IsButtonDown(MouseButton.Button1)) {
|
||||||
|
if (geometry.PhotoBox.ContainsInclusive(mousePosition)) {
|
||||||
|
// FIXME: really this should be clipped to the active photo's drawable area, not the whole photobox.
|
||||||
|
mouseDragEnd = mousePosition;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.IsKeyPressed(Keys.Escape)) {
|
||||||
|
mouseDragStart = new(-1, -1);
|
||||||
|
mouseDragEnd = new(-1, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: crop should be a modal tool that starts with C and ends with Enter or Escape.
|
||||||
|
if (input.IsKeyPressed(Keys.Enter)) {
|
||||||
|
ApplyCrop(game);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// left, right, top, bottom
|
||||||
|
(int, int, int, int) GetCrop() {
|
||||||
|
// FIXME: this expects the start point in the top left and the end point
|
||||||
|
// in the bottom right; some sign flipping needs to occur to make anchors
|
||||||
|
// in other direction work well.
|
||||||
|
Vector2i start = mouseDragStart;
|
||||||
|
Vector2i end = mouseDragEnd;
|
||||||
|
end.Y = Math.Min(end.Y, start.Y + (end.X - start.X) * 4 / 6);
|
||||||
|
end.X = start.X + (end.Y - start.Y) * 6 / 4;
|
||||||
|
int left = Math.Min(start.X, end.X);
|
||||||
|
int right = Math.Max(start.X, end.X);
|
||||||
|
int top = Math.Min(start.Y, end.Y);
|
||||||
|
int bottom = Math.Max(start.Y, end.Y);
|
||||||
|
return (left, right, top, bottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApplyCrop(Game game) {
|
||||||
|
var (left, right, top, bottom) = GetCrop();
|
||||||
|
int area = (right - left) * (bottom - top);
|
||||||
|
if (area == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector2i leftTop = game.ScreenToImage(left, top);
|
||||||
|
Vector2i rightBottom = game.ScreenToImage(right, bottom);
|
||||||
|
Rectangle crop = Rectangle.FromLTRB(leftTop.X, leftTop.Y, rightBottom.X, rightBottom.Y);
|
||||||
|
// FIXME: make sure this doesn't exceed image.Bounds.
|
||||||
|
// FIXME: once set, display it properly in the PhotoBox.
|
||||||
|
if (activePhoto != null) {
|
||||||
|
activePhoto.CropRectangle = crop;
|
||||||
|
}
|
||||||
|
Console.WriteLine(crop);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Draw(UiGeometry geometry, Game game) {
|
||||||
|
var (left, right, top, bottom) = GetCrop();
|
||||||
|
int area = (right - left) * (bottom - top);
|
||||||
|
|
||||||
|
if (area == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Color4 shadeColor = new Color4(0, 0, 0, 0.75f);
|
||||||
|
game.DrawFilledBox(new Box2i(0, 0, left, geometry.PhotoBox.Max.Y), shadeColor);
|
||||||
|
game.DrawFilledBox(new Box2i(left, 0, geometry.PhotoBox.Max.X, top), shadeColor);
|
||||||
|
game.DrawFilledBox(new Box2i(left, bottom, geometry.PhotoBox.Max.X, geometry.PhotoBox.Max.Y), shadeColor);
|
||||||
|
game.DrawFilledBox(new Box2i(right, top, geometry.PhotoBox.Max.X, bottom), shadeColor);
|
||||||
|
game.DrawBox(new Box2i(left, top, right, bottom), 1, Color4.White);
|
||||||
|
game.DrawBox(new Box2i(left - 1, top - 1 , right + 1, bottom + 1), 1, Color4.Black);
|
||||||
|
game.DrawBox(new Box2i(left - 2, top - 2 , right + 2, bottom + 2), 1, Color4.White);
|
||||||
|
game.DrawHorizontalLine(left, Util.Lerp(top, bottom, 1.0 / 3), right, Color4.White);
|
||||||
|
game.DrawHorizontalLine(left, Util.Lerp(top, bottom, 2.0 / 3), right, Color4.White);
|
||||||
|
game.DrawVerticalLine(Util.Lerp(left, right, 1.0 / 3), top, bottom, Color4.White);
|
||||||
|
game.DrawVerticalLine(Util.Lerp(left, right, 2.0 / 3), top, bottom, Color4.White);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Status() {
|
||||||
|
return "crop";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// FIXME: switch to immediate mode??
|
// FIXME: switch to immediate mode??
|
||||||
// https://gamedev.stackexchange.com/questions/198805/opentk-immediate-mode-on-net-core-doesnt-work
|
// https://gamedev.stackexchange.com/questions/198805/opentk-immediate-mode-on-net-core-doesnt-work
|
||||||
// https://www.youtube.com/watch?v=Q23Kf9QEaO4
|
// https://www.youtube.com/watch?v=Q23Kf9QEaO4
|
||||||
@ -662,11 +785,10 @@ public class Game : GameWindow {
|
|||||||
HashSet<Photo> loadedImages = new();
|
HashSet<Photo> loadedImages = new();
|
||||||
HashSet<Photo> loadingImages = new();
|
HashSet<Photo> loadingImages = new();
|
||||||
readonly object loadedImagesLock = new();
|
readonly object loadedImagesLock = new();
|
||||||
|
ITool activeTool = new CropTool();
|
||||||
int photoIndex = 0;
|
int photoIndex = 0;
|
||||||
int ribbonIndex = 0;
|
int ribbonIndex = 0;
|
||||||
Vector2i mousePosition;
|
Vector2i mousePosition;
|
||||||
Vector2i mouseDragStart;
|
|
||||||
Vector2i mouseDragEnd;
|
|
||||||
float activeScale = 1f;
|
float activeScale = 1f;
|
||||||
Vector2i activeOffset;
|
Vector2i activeOffset;
|
||||||
Shader shader = new();
|
Shader shader = new();
|
||||||
@ -710,22 +832,6 @@ public class Game : GameWindow {
|
|||||||
photoIndex = ribbonIndex + i;
|
photoIndex = ribbonIndex + i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (geometry.PhotoBox.ContainsInclusive(mousePosition)) {
|
|
||||||
mouseDragStart = mousePosition;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (MouseState.IsButtonDown(MouseButton.Button1)) {
|
|
||||||
if (geometry.PhotoBox.ContainsInclusive(mousePosition)) {
|
|
||||||
// FIXME: really this should be clipped to the active photo's drawable area, not the whole photobox.
|
|
||||||
mouseDragEnd = mousePosition;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (input.IsKeyPressed(Keys.Escape)) {
|
|
||||||
mouseDragStart = new(-1, -1);
|
|
||||||
mouseDragEnd = new(-1, -1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (MouseState.IsButtonPressed(MouseButton.Button4)) {
|
if (MouseState.IsButtonPressed(MouseButton.Button4)) {
|
||||||
@ -767,11 +873,6 @@ public class Game : GameWindow {
|
|||||||
photoIndex -= 5;
|
photoIndex -= 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: crop should be a modal tool that starts with C and ends with Enter or Escape.
|
|
||||||
if (input.IsKeyPressed(Keys.C)) {
|
|
||||||
ApplyCrop();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (input.IsKeyPressed(Keys.P) && altIsDown) {
|
if (input.IsKeyPressed(Keys.P) && altIsDown) {
|
||||||
ExportPhotos();
|
ExportPhotos();
|
||||||
}
|
}
|
||||||
@ -784,8 +885,7 @@ public class Game : GameWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (photoIndex != lastPhotoIndex) {
|
if (photoIndex != lastPhotoIndex) {
|
||||||
mouseDragStart = new(-1, -1);
|
// FIXME!!!: do something to reset tool state here
|
||||||
mouseDragEnd = new(-1, -1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle presses of the "rating" keys -- 0-5 and `.
|
// Handle presses of the "rating" keys -- 0-5 and `.
|
||||||
@ -850,6 +950,9 @@ public class Game : GameWindow {
|
|||||||
if (input.IsKeyPressed(Keys.Y)) {
|
if (input.IsKeyPressed(Keys.Y)) {
|
||||||
zoomLevel = 16f;
|
zoomLevel = 16f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
activeTool.SetActivePhoto(photos[photoIndex]);
|
||||||
|
activeTool.HandleInput(geometry, KeyboardState, MouseState, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FilterByRating(int rating) {
|
void FilterByRating(int rating) {
|
||||||
@ -910,9 +1013,9 @@ public class Game : GameWindow {
|
|||||||
|
|
||||||
// Load photos from a directory.
|
// Load photos from a directory.
|
||||||
// string[] files = Directory.GetFiles(@"c:\users\colin\desktop\photos-test\");
|
// string[] files = Directory.GetFiles(@"c:\users\colin\desktop\photos-test\");
|
||||||
string[] files = Directory.GetFiles(@"c:\users\colin\pictures\photos\2023\07\14\");
|
// string[] files = Directory.GetFiles(@"c:\users\colin\pictures\photos\2023\07\14\");
|
||||||
// string[] files = Directory.GetFiles(@"c:\users\colin\pictures\photos\2023\07\23\");
|
// string[] files = Directory.GetFiles(@"c:\users\colin\pictures\photos\2023\07\23\");
|
||||||
// string[] files = Directory.GetFiles(@"G:\DCIM\100EOSR6\");
|
string[] files = Directory.GetFiles(@"G:\DCIM\100EOSR6\");
|
||||||
// string[] files = Directory.GetFiles(@"c:\users\colin\desktop\totte-output\2023\07\31");
|
// string[] files = Directory.GetFiles(@"c:\users\colin\desktop\totte-output\2023\07\31");
|
||||||
// string[] files = Directory.GetFiles(@"C:\Users\colin\Pictures\photos\2018\06\23");
|
// string[] files = Directory.GetFiles(@"C:\Users\colin\Pictures\photos\2018\06\23");
|
||||||
// string[] files = Directory.GetFiles(@"C:\Users\colin\Desktop\Germany all\104D7000");
|
// string[] files = Directory.GetFiles(@"C:\Users\colin\Desktop\Germany all\104D7000");
|
||||||
@ -1022,65 +1125,11 @@ public class Game : GameWindow {
|
|||||||
DrawText("No photos found.", 10, 10);
|
DrawText("No photos found.", 10, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
DrawCropBox();
|
activeTool.Draw(geometry, this);
|
||||||
|
|
||||||
SwapBuffers();
|
SwapBuffers();
|
||||||
}
|
}
|
||||||
|
|
||||||
// left, right, top, bottom
|
|
||||||
(int, int, int, int) GetCrop() {
|
|
||||||
// FIXME: this expects the start point in the top left and the end point
|
|
||||||
// in the bottom right; some sign flipping needs to occur to make anchors
|
|
||||||
// in other direction work well.
|
|
||||||
Vector2i start = mouseDragStart;
|
|
||||||
Vector2i end = mouseDragEnd;
|
|
||||||
end.Y = Math.Min(end.Y, start.Y + (end.X - start.X) * 4 / 6);
|
|
||||||
end.X = start.X + (end.Y - start.Y) * 6 / 4;
|
|
||||||
int left = Math.Min(start.X, end.X);
|
|
||||||
int right = Math.Max(start.X, end.X);
|
|
||||||
int top = Math.Min(start.Y, end.Y);
|
|
||||||
int bottom = Math.Max(start.Y, end.Y);
|
|
||||||
return (left, right, top, bottom);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ApplyCrop() {
|
|
||||||
var (left, right, top, bottom) = GetCrop();
|
|
||||||
int area = (right - left) * (bottom - top);
|
|
||||||
if (area == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Vector2i leftTop = ScreenToImage(left, top);
|
|
||||||
Vector2i rightBottom = ScreenToImage(right, bottom);
|
|
||||||
Rectangle crop = Rectangle.FromLTRB(leftTop.X, leftTop.Y, rightBottom.X, rightBottom.Y);
|
|
||||||
Photo photo = photos[photoIndex];
|
|
||||||
// FIXME: make sure this doesn't exceed image.Bounds.
|
|
||||||
// FIXME: once set, display it properly in the PhotoBox.
|
|
||||||
photo.CropRectangle = crop;
|
|
||||||
Console.WriteLine(crop);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DrawCropBox() {
|
|
||||||
var (left, right, top, bottom) = GetCrop();
|
|
||||||
int area = (right - left) * (bottom - top);
|
|
||||||
|
|
||||||
if (area == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Color4 shadeColor = new Color4(0, 0, 0, 0.75f);
|
|
||||||
DrawFilledBox(new Box2i(0, 0, left, geometry.PhotoBox.Max.Y), shadeColor);
|
|
||||||
DrawFilledBox(new Box2i(left, 0, geometry.PhotoBox.Max.X, top), shadeColor);
|
|
||||||
DrawFilledBox(new Box2i(left, bottom, geometry.PhotoBox.Max.X, geometry.PhotoBox.Max.Y), shadeColor);
|
|
||||||
DrawFilledBox(new Box2i(right, top, geometry.PhotoBox.Max.X, bottom), shadeColor);
|
|
||||||
DrawBox(new Box2i(left, top, right, bottom), 1, Color4.White);
|
|
||||||
DrawBox(new Box2i(left - 1, top - 1 , right + 1, bottom + 1), 1, Color4.Black);
|
|
||||||
DrawBox(new Box2i(left - 2, top - 2 , right + 2, bottom + 2), 1, Color4.White);
|
|
||||||
DrawHorizontalLine(left, Util.Lerp(top, bottom, 1.0 / 3), right, Color4.White);
|
|
||||||
DrawHorizontalLine(left, Util.Lerp(top, bottom, 2.0 / 3), right, Color4.White);
|
|
||||||
DrawVerticalLine(Util.Lerp(left, right, 1.0 / 3), top, bottom, Color4.White);
|
|
||||||
DrawVerticalLine(Util.Lerp(left, right, 2.0 / 3), top, bottom, Color4.White);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DrawPhotos() {
|
void DrawPhotos() {
|
||||||
Photo activePhoto = photos[photoIndex];
|
Photo activePhoto = photos[photoIndex];
|
||||||
Texture active = activePhoto.Texture();
|
Texture active = activePhoto.Texture();
|
||||||
@ -1142,25 +1191,25 @@ public class Game : GameWindow {
|
|||||||
DrawText($"({imagePosition.X}, {imagePosition.Y})", geometry.StatusBox.Min.X + 320, y);
|
DrawText($"({imagePosition.X}, {imagePosition.Y})", geometry.StatusBox.Min.X + 320, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector2i ScreenToImage(int x, int y) {
|
public Vector2i ScreenToImage(int x, int y) {
|
||||||
return new(
|
return new(
|
||||||
(int) ((x - activeOffset.X) / activeScale),
|
(int) ((x - activeOffset.X) / activeScale),
|
||||||
(int) ((y - activeOffset.Y) / activeScale));
|
(int) ((y - activeOffset.Y) / activeScale));
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector2i ScreenToImage(Vector2i position) {
|
public Vector2i ScreenToImage(Vector2i position) {
|
||||||
return ScreenToImage(position.X, position.Y);
|
return ScreenToImage(position.X, position.Y);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawTexture(Texture texture, int x, int y) {
|
public void DrawTexture(Texture texture, int x, int y) {
|
||||||
DrawTexture(texture, Util.MakeBox(x, y, texture.Size.X, texture.Size.Y));
|
DrawTexture(texture, Util.MakeBox(x, y, texture.Size.X, texture.Size.Y));
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawTexture(Texture texture, Box2i box) {
|
public void DrawTexture(Texture texture, Box2i box) {
|
||||||
DrawTexture(texture, box, Color4.White);
|
DrawTexture(texture, box, Color4.White);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawTexture(Texture texture, Box2i box, Color4 color) {
|
public void DrawTexture(Texture texture, Box2i box, Color4 color) {
|
||||||
GL.Uniform4(shader.GetUniformLocation("color"), color);
|
GL.Uniform4(shader.GetUniformLocation("color"), color);
|
||||||
SetVertices(box.Min.X, box.Min.Y, box.Size.X, box.Size.Y);
|
SetVertices(box.Min.X, box.Min.Y, box.Size.X, box.Size.Y);
|
||||||
GL.BufferData(BufferTarget.ArrayBuffer, vertices.Length * sizeof(float), vertices, BufferUsageHint.DynamicDraw);
|
GL.BufferData(BufferTarget.ArrayBuffer, vertices.Length * sizeof(float), vertices, BufferUsageHint.DynamicDraw);
|
||||||
@ -1168,26 +1217,26 @@ public class Game : GameWindow {
|
|||||||
GL.DrawElements(PrimitiveType.Triangles, indices.Length, DrawElementsType.UnsignedInt, 0);
|
GL.DrawElements(PrimitiveType.Triangles, indices.Length, DrawElementsType.UnsignedInt, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawHorizontalLine(int left, int top, int right, Color4 color) {
|
public void DrawHorizontalLine(int left, int top, int right, Color4 color) {
|
||||||
DrawTexture(TEXTURE_WHITE, Util.MakeBox(left, top, right - left, 1), color);
|
DrawTexture(TEXTURE_WHITE, Util.MakeBox(left, top, right - left, 1), color);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawVerticalLine(int left, int top, int bottom, Color4 color) {
|
public void DrawVerticalLine(int left, int top, int bottom, Color4 color) {
|
||||||
DrawTexture(TEXTURE_WHITE, Util.MakeBox(left, top, 1, bottom - top), color);
|
DrawTexture(TEXTURE_WHITE, Util.MakeBox(left, top, 1, bottom - top), color);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawBox(Box2i box, int thickness, Color4 color) {
|
public void DrawBox(Box2i box, int thickness, Color4 color) {
|
||||||
DrawTexture(TEXTURE_WHITE, Util.MakeBox(box.Min.X, box.Min.Y, box.Size.X, thickness), color);
|
DrawTexture(TEXTURE_WHITE, Util.MakeBox(box.Min.X, box.Min.Y, box.Size.X, thickness), color);
|
||||||
DrawTexture(TEXTURE_WHITE, Util.MakeBox(box.Min.X, box.Min.Y, thickness, box.Size.Y), color);
|
DrawTexture(TEXTURE_WHITE, Util.MakeBox(box.Min.X, box.Min.Y, thickness, box.Size.Y), color);
|
||||||
DrawTexture(TEXTURE_WHITE, Util.MakeBox(box.Min.X, box.Max.Y - thickness, box.Size.X, thickness), color);
|
DrawTexture(TEXTURE_WHITE, Util.MakeBox(box.Min.X, box.Max.Y - thickness, box.Size.X, thickness), color);
|
||||||
DrawTexture(TEXTURE_WHITE, Util.MakeBox(box.Max.X - thickness, box.Min.Y, thickness, box.Size.Y), color);
|
DrawTexture(TEXTURE_WHITE, Util.MakeBox(box.Max.X - thickness, box.Min.Y, thickness, box.Size.Y), color);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawFilledBox(Box2i box, Color4 color) {
|
public void DrawFilledBox(Box2i box, Color4 color) {
|
||||||
DrawTexture(TEXTURE_WHITE, Util.MakeBox(box.Min.X, box.Min.Y, box.Size.X, box.Size.Y), color);
|
DrawTexture(TEXTURE_WHITE, Util.MakeBox(box.Min.X, box.Min.Y, box.Size.X, box.Size.Y), color);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawText(string text, int x, int y) {
|
public void DrawText(string text, int x, int y) {
|
||||||
Texture label = Util.RenderText(text);
|
Texture label = Util.RenderText(text);
|
||||||
DrawTexture(label, x, y);
|
DrawTexture(label, x, y);
|
||||||
label.Dispose();
|
label.Dispose();
|
||||||
|
Loading…
Reference in New Issue
Block a user