diff --git a/Program.cs b/Program.cs index eb70131..b933325 100644 --- a/Program.cs +++ b/Program.cs @@ -52,6 +52,129 @@ public class CameraInfo { 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?? // https://gamedev.stackexchange.com/questions/198805/opentk-immediate-mode-on-net-core-doesnt-work // https://www.youtube.com/watch?v=Q23Kf9QEaO4 @@ -662,11 +785,10 @@ public class Game : GameWindow { HashSet loadedImages = new(); HashSet loadingImages = new(); readonly object loadedImagesLock = new(); + ITool activeTool = new CropTool(); int photoIndex = 0; int ribbonIndex = 0; Vector2i mousePosition; - Vector2i mouseDragStart; - Vector2i mouseDragEnd; float activeScale = 1f; Vector2i activeOffset; Shader shader = new(); @@ -710,22 +832,6 @@ public class Game : GameWindow { 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)) { @@ -767,11 +873,6 @@ public class Game : GameWindow { 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) { ExportPhotos(); } @@ -784,8 +885,7 @@ public class Game : GameWindow { } if (photoIndex != lastPhotoIndex) { - mouseDragStart = new(-1, -1); - mouseDragEnd = new(-1, -1); + // FIXME!!!: do something to reset tool state here } // Handle presses of the "rating" keys -- 0-5 and `. @@ -850,6 +950,9 @@ public class Game : GameWindow { if (input.IsKeyPressed(Keys.Y)) { zoomLevel = 16f; } + + activeTool.SetActivePhoto(photos[photoIndex]); + activeTool.HandleInput(geometry, KeyboardState, MouseState, this); } void FilterByRating(int rating) { @@ -910,9 +1013,9 @@ public class Game : GameWindow { // Load photos from a directory. // 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(@"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\Pictures\photos\2018\06\23"); // 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); } - DrawCropBox(); + activeTool.Draw(geometry, this); 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() { Photo activePhoto = photos[photoIndex]; Texture active = activePhoto.Texture(); @@ -1142,25 +1191,25 @@ public class Game : GameWindow { 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( (int) ((x - activeOffset.X) / activeScale), (int) ((y - activeOffset.Y) / activeScale)); } - Vector2i ScreenToImage(Vector2i position) { + public Vector2i ScreenToImage(Vector2i position) { 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)); } - void DrawTexture(Texture texture, Box2i box) { + public void DrawTexture(Texture texture, Box2i box) { 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); SetVertices(box.Min.X, box.Min.Y, box.Size.X, box.Size.Y); 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); } - 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); } - 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); } - 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, 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.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); } - void DrawText(string text, int x, int y) { + public void DrawText(string text, int x, int y) { Texture label = Util.RenderText(text); DrawTexture(label, x, y); label.Dispose();