diff --git a/Program.cs b/Program.cs index 459c9e0..0a1f021 100644 --- a/Program.cs +++ b/Program.cs @@ -185,6 +185,7 @@ public class Photo { public string IsoSpeed = ""; public int Rating = 0; public ushort Orientation = 1; + public Rectangle CropRectangle = Rectangle.Empty; private static long touchCounter = 0; private Texture texture; @@ -237,6 +238,9 @@ public class Photo { // FIXME: warn if the file already exists? using (Image image = await Image.LoadAsync(Filename)) { Util.RotateImageFromExif(image, Orientation); + if (CropRectangle != Rectangle.Empty) { + image.Mutate(x => x.Crop(CropRectangle)); + } ExifProfile exif = image.Metadata.ExifProfile ?? new(); exif.SetValue(ExifTag.Orientation, 1); @@ -504,7 +508,7 @@ public class UiGeometry { ThumbnailBoxes.Add(box); } - int statusBoxHeight = 20; + int statusBoxHeight = 40; int statusBoxPadding = 4; PhotoBox = new Box2i(0, 0, WindowSize.X - thumbnailWidth, WindowSize.Y - statusBoxHeight - statusBoxPadding); StatusBox = new Box2i(0, WindowSize.Y - statusBoxHeight, WindowSize.X - thumbnailWidth, WindowSize.Y); @@ -609,6 +613,9 @@ public static class Util { public class Game : GameWindow { public Game(GameWindowSettings gwSettings, NativeWindowSettings nwSettings) : base(gwSettings, nwSettings) {} + private static string outputRoot = @"c:\users\colin\desktop\totte-output"; + // private static string outputRoot = @"c:\users\colin\pictures\photos"; + private static Texture TEXTURE_WHITE = new(new Image(1, 1, new Rgba32(255, 255, 255))); private static Texture TEXTURE_BLACK = new(new Image(1, 1, new Rgba32(0, 0, 0))); private static Texture STAR_FILLED = Util.RenderStar(20, true); @@ -637,9 +644,11 @@ public class Game : GameWindow { readonly object loadedImagesLock = new(); int photoIndex = 0; int ribbonIndex = 0; + Vector2i mousePosition; Vector2i mouseDragStart; Vector2i mouseDragEnd; float activeScale = 1f; + Vector2i activeOffset; Shader shader = new(); Matrix4 projection; float zoomLevel = 0f; @@ -662,7 +671,7 @@ public class Game : GameWindow { int lastPhotoIndex = photoIndex; - Vector2i mousePosition = (Vector2i) MouseState.Position; + mousePosition = (Vector2i) MouseState.Position; // Look for mouse clicks on thumbnails or stars. // @@ -970,8 +979,6 @@ public class Game : GameWindow { // FIXME: show a progress bar or something. private async void ExportPhotos() { JpegEncoder encoder = new JpegEncoder() { Quality = 100 }; - string outputRoot = @"c:\users\colin\desktop\totte-output"; - // string outputRoot = @"c:\users\colin\pictures\photos"; foreach (Photo p in photos) { await Task.Run( () => { p.SaveAsJpegAsync(outputRoot, encoder); }); } @@ -1018,17 +1025,25 @@ public class Game : GameWindow { void ApplyCrop() { var (left, right, top, bottom) = GetCrop(); int area = (right - left) * (bottom - top); - if (area < 100) { + 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 < 100) { + if (area == 0) { return; } Color4 shadeColor = new Color4(0, 0, 0, 0.75f); @@ -1060,8 +1075,8 @@ public class Game : GameWindow { Vector2i renderSize = (Vector2i) (((Vector2) active.Size) * scale); Vector2i center = (Vector2i) geometry.PhotoBox.Center; - // Box2i photoBox = Util.MakeBox(center.X - renderSize.X / 2, center.Y - renderSize.Y / 2, renderSize.X, renderSize.Y); - Box2i photoBox = Util.MakeBox(0, 0, renderSize.X, renderSize.Y); + Box2i photoBox = Util.MakeBox(center.X - renderSize.X / 2, center.Y - renderSize.Y / 2, renderSize.X, renderSize.Y); + activeOffset = new(photoBox.Min.X, photoBox.Min.Y); DrawTexture(active, photoBox); for (int i = 0; i < 5; i++) { Texture star = (activePhoto.Rating > i) ? STAR_FILLED : STAR_EMPTY; @@ -1090,13 +1105,29 @@ public class Game : GameWindow { // Draw status box. int statusPadding = 2; DrawFilledBox(geometry.StatusBox, Color4.Black); - DrawText(String.Format("{0,4}/{1,-4}", photoIndex + 1, photos.Count), geometry.StatusBox.Min.X + 72, geometry.StatusBox.Min.Y + statusPadding); - DrawText(activePhoto.Description(), geometry.StatusBox.Min.X + 160, geometry.StatusBox.Min.Y + statusPadding); - DrawText(String.Format("FPS: {0,2}", fpsCounter.Fps), geometry.StatusBox.Max.X - 66, geometry.StatusBox.Min.Y + statusPadding); - + // First line. + int y = geometry.StatusBox.Min.Y + statusPadding; + DrawText(activePhoto.Description(), geometry.StatusBox.Min.X, y); + // Second line. + y += 20; + DrawText(String.Format("{0,4}/{1,-4}", photoIndex + 1, photos.Count), geometry.StatusBox.Min.X + 72, y); + DrawText(String.Format("FPS: {0,2}", fpsCounter.Fps), geometry.StatusBox.Max.X - 66, y); if (activePhoto.Loaded) { - DrawText($"{(scale * 100):F1}%", geometry.StatusBox.Min.X, geometry.StatusBox.Min.Y + statusPadding); + DrawText($"{(scale * 100):F1}%", geometry.StatusBox.Min.X, y); } + DrawText($"({mousePosition.X}, {mousePosition.Y})", geometry.StatusBox.Min.X + 160, y); + Vector2i imagePosition = ScreenToImage(mousePosition); + DrawText($"({imagePosition.X}, {imagePosition.Y})", geometry.StatusBox.Min.X + 320, y); + } + + Vector2i ScreenToImage(int x, int y) { + return new( + (int) ((x - activeOffset.X) / activeScale), + (int) ((y - activeOffset.Y) / activeScale)); + } + + Vector2i ScreenToImage(Vector2i position) { + return ScreenToImage(position.X, position.Y); } void DrawTexture(Texture texture, int x, int y) {