Compare commits

...

2 Commits

Author SHA1 Message Date
473fac7a6f cropping works! janky af for now, but it works 2023-08-02 01:05:10 -04:00
e5c1b01806 more crop progress 2023-08-01 23:14:00 -04:00

View File

@ -185,6 +185,7 @@ public class Photo {
public string IsoSpeed = "<unk>"; public string IsoSpeed = "<unk>";
public int Rating = 0; public int Rating = 0;
public ushort Orientation = 1; public ushort Orientation = 1;
public Rectangle CropRectangle = Rectangle.Empty;
private static long touchCounter = 0; private static long touchCounter = 0;
private Texture texture; private Texture texture;
@ -237,6 +238,9 @@ public class Photo {
// FIXME: warn if the file already exists? // FIXME: warn if the file already exists?
using (Image<Rgba32> image = await Image.LoadAsync<Rgba32>(Filename)) { using (Image<Rgba32> image = await Image.LoadAsync<Rgba32>(Filename)) {
Util.RotateImageFromExif(image, Orientation); Util.RotateImageFromExif(image, Orientation);
if (CropRectangle != Rectangle.Empty) {
image.Mutate(x => x.Crop(CropRectangle));
}
ExifProfile exif = image.Metadata.ExifProfile ?? new(); ExifProfile exif = image.Metadata.ExifProfile ?? new();
exif.SetValue<ushort>(ExifTag.Orientation, 1); exif.SetValue<ushort>(ExifTag.Orientation, 1);
@ -504,7 +508,7 @@ public class UiGeometry {
ThumbnailBoxes.Add(box); ThumbnailBoxes.Add(box);
} }
int statusBoxHeight = 20; int statusBoxHeight = 40;
int statusBoxPadding = 4; int statusBoxPadding = 4;
PhotoBox = new Box2i(0, 0, WindowSize.X - thumbnailWidth, WindowSize.Y - statusBoxHeight - statusBoxPadding); PhotoBox = new Box2i(0, 0, WindowSize.X - thumbnailWidth, WindowSize.Y - statusBoxHeight - statusBoxPadding);
StatusBox = new Box2i(0, WindowSize.Y - statusBoxHeight, WindowSize.X - thumbnailWidth, WindowSize.Y); 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 class Game : GameWindow {
public Game(GameWindowSettings gwSettings, NativeWindowSettings nwSettings) : base(gwSettings, nwSettings) {} 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<Rgba32>(1, 1, new Rgba32(255, 255, 255))); private static Texture TEXTURE_WHITE = new(new Image<Rgba32>(1, 1, new Rgba32(255, 255, 255)));
private static Texture TEXTURE_BLACK = new(new Image<Rgba32>(1, 1, new Rgba32(0, 0, 0))); private static Texture TEXTURE_BLACK = new(new Image<Rgba32>(1, 1, new Rgba32(0, 0, 0)));
private static Texture STAR_FILLED = Util.RenderStar(20, true); private static Texture STAR_FILLED = Util.RenderStar(20, true);
@ -637,8 +644,11 @@ public class Game : GameWindow {
readonly object loadedImagesLock = new(); readonly object loadedImagesLock = new();
int photoIndex = 0; int photoIndex = 0;
int ribbonIndex = 0; int ribbonIndex = 0;
Vector2i mousePosition;
Vector2i mouseDragStart; Vector2i mouseDragStart;
Vector2i mouseDragEnd; Vector2i mouseDragEnd;
float activeScale = 1f;
Vector2i activeOffset;
Shader shader = new(); Shader shader = new();
Matrix4 projection; Matrix4 projection;
float zoomLevel = 0f; float zoomLevel = 0f;
@ -661,7 +671,7 @@ public class Game : GameWindow {
int lastPhotoIndex = photoIndex; int lastPhotoIndex = photoIndex;
Vector2i mousePosition = (Vector2i) MouseState.Position; mousePosition = (Vector2i) MouseState.Position;
// Look for mouse clicks on thumbnails or stars. // Look for mouse clicks on thumbnails or stars.
// //
@ -737,6 +747,11 @@ 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();
} }
@ -964,8 +979,6 @@ public class Game : GameWindow {
// FIXME: show a progress bar or something. // FIXME: show a progress bar or something.
private async void ExportPhotos() { private async void ExportPhotos() {
JpegEncoder encoder = new JpegEncoder() { Quality = 100 }; 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) { foreach (Photo p in photos) {
await Task.Run( () => { p.SaveAsJpegAsync(outputRoot, encoder); }); await Task.Run( () => { p.SaveAsJpegAsync(outputRoot, encoder); });
} }
@ -993,7 +1006,11 @@ public class Game : GameWindow {
SwapBuffers(); SwapBuffers();
} }
void DrawCropBox() { // 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 start = mouseDragStart;
Vector2i end = mouseDragEnd; Vector2i end = mouseDragEnd;
end.Y = Math.Min(end.Y, start.Y + (end.X - start.X) * 4 / 6); end.Y = Math.Min(end.Y, start.Y + (end.X - start.X) * 4 / 6);
@ -1002,9 +1019,31 @@ public class Game : GameWindow {
int right = Math.Max(start.X, end.X); int right = Math.Max(start.X, end.X);
int top = Math.Min(start.Y, end.Y); int top = Math.Min(start.Y, end.Y);
int bottom = Math.Max(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); int area = (right - left) * (bottom - top);
if (area < 100) { if (area == 0) {
return; return;
} }
Color4 shadeColor = new Color4(0, 0, 0, 0.75f); Color4 shadeColor = new Color4(0, 0, 0, 0.75f);
@ -1032,10 +1071,12 @@ public class Game : GameWindow {
if (zoomLevel > 0f) { if (zoomLevel > 0f) {
scale = zoomLevel; scale = zoomLevel;
} }
activeScale = scale;
Vector2i renderSize = (Vector2i) (((Vector2) active.Size) * scale); Vector2i renderSize = (Vector2i) (((Vector2) active.Size) * scale);
Vector2i center = (Vector2i) geometry.PhotoBox.Center; 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(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); DrawTexture(active, photoBox);
for (int i = 0; i < 5; i++) { for (int i = 0; i < 5; i++) {
Texture star = (activePhoto.Rating > i) ? STAR_FILLED : STAR_EMPTY; Texture star = (activePhoto.Rating > i) ? STAR_FILLED : STAR_EMPTY;
@ -1064,13 +1105,29 @@ public class Game : GameWindow {
// Draw status box. // Draw status box.
int statusPadding = 2; int statusPadding = 2;
DrawFilledBox(geometry.StatusBox, Color4.Black); 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); // First line.
DrawText(activePhoto.Description(), geometry.StatusBox.Min.X + 160, geometry.StatusBox.Min.Y + statusPadding); int y = geometry.StatusBox.Min.Y + statusPadding;
DrawText(String.Format("FPS: {0,2}", fpsCounter.Fps), geometry.StatusBox.Max.X - 66, 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) { 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) { void DrawTexture(Texture texture, int x, int y) {