Compare commits

..

2 Commits

View File

@ -40,6 +40,7 @@ public class FpsCounter {
} }
} }
public class CameraInfo { public class CameraInfo {
public readonly Vector2i Resolution; public readonly Vector2i Resolution;
@ -52,12 +53,14 @@ 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 enum ToolState { public enum ToolState {
Active, Active,
Done, Done,
Canceled Canceled
} }
public interface ITool { public interface ITool {
void SetActivePhoto(Photo photo); void SetActivePhoto(Photo photo);
ToolState HandleInput(UiGeometry geometry, KeyboardState input, MouseState mouse, Game game); ToolState HandleInput(UiGeometry geometry, KeyboardState input, MouseState mouse, Game game);
@ -65,6 +68,7 @@ public interface ITool {
void Draw(UiGeometry geometry, Game game); void Draw(UiGeometry geometry, Game game);
} }
public class ViewTool : ITool { public class ViewTool : ITool {
Photo? activePhoto; Photo? activePhoto;
@ -77,7 +81,7 @@ public class ViewTool : ITool {
} }
public string Status() { public string Status() {
return "view"; return "";
} }
public void Draw(UiGeometry geometry, Game game) { public void Draw(UiGeometry geometry, Game game) {
@ -88,39 +92,51 @@ public class ViewTool : ITool {
// FIXME: remove unneeded dependencies on "Game" or at least refactor them a bit. // FIXME: remove unneeded dependencies on "Game" or at least refactor them a bit.
public class CropTool : ITool { public class CropTool : ITool {
Photo? activePhoto; Photo activePhoto;
Vector2i mouseDragStart; Vector2i mouseDragStart;
Vector2i mouseDragEnd; Vector2i mouseDragEnd;
string status = ""; string status = "";
public CropTool(Photo photo) {
activePhoto = photo;
}
public void SetActivePhoto(Photo photo) { public void SetActivePhoto(Photo photo) {
if (photo != activePhoto) {
mouseDragStart = Vector2i.Zero;
mouseDragEnd = Vector2i.Zero;
}
activePhoto = photo; activePhoto = photo;
} }
public ToolState HandleInput(UiGeometry geometry, KeyboardState input, MouseState mouse, Game game) { public ToolState HandleInput(UiGeometry geometry, KeyboardState input, MouseState mouse, Game game) {
Vector2i mousePosition = (Vector2i) mouse.Position; Vector2i mousePosition = (Vector2i) mouse.Position;
Vector2i imagePosition = game.ScreenToImage(mousePosition);
if (mouse.IsButtonPressed(MouseButton.Button1)) { if (mouse.IsButtonPressed(MouseButton.Button1)) {
if (geometry.PhotoBox.ContainsInclusive(mousePosition)) { if (geometry.PhotoBox.ContainsInclusive(mousePosition)) {
mouseDragStart = mousePosition; mouseDragStart = imagePosition;
} }
} }
if (mouse.IsButtonDown(MouseButton.Button1)) { if (mouse.IsButtonDown(MouseButton.Button1)) {
if (geometry.PhotoBox.ContainsInclusive(mousePosition)) { if (geometry.PhotoBox.ContainsInclusive(mousePosition)) {
// FIXME: really this should be clipped to the active photo's drawable area, not the whole photobox. mouseDragEnd = imagePosition;
mouseDragEnd = mousePosition;
} }
} }
Vector2i start = game.ScreenToImage(mouseDragStart); var (left, right, top, bottom) = GetCrop();
// FIXME: this needs to be the actual width of the computed box.
Vector2i size = game.ScreenToImage(mouseDragEnd) - start;
status = $"({start.X}, {start.Y}) {size.X}x{size.Y}"; Vector2i size = new(right - left, bottom - top);
status = $"({left}, {top}) {size.X}x{size.Y}";
Rectangle crop = Rectangle.Empty;
if (size.X > 0 && size.Y > 0) {
crop = Rectangle.FromLTRB(left, top, right, bottom);
}
activePhoto.CropRectangle = crop;
if (input.IsKeyPressed(Keys.Enter)) { if (input.IsKeyPressed(Keys.Enter)) {
ApplyCrop(game);
return ToolState.Done; return ToolState.Done;
} }
@ -139,6 +155,8 @@ public class CropTool : ITool {
// in other direction work well. // in other direction work well.
Vector2i start = mouseDragStart; Vector2i start = mouseDragStart;
Vector2i end = mouseDragEnd; Vector2i end = mouseDragEnd;
// FIXME: choose the aspect ratio based on the original image aspect ratio.
// FIXME: allow for unconstrained crop, 1:1, etc.
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);
end.X = start.X + (end.Y - start.Y) * 6 / 4; end.X = start.X + (end.Y - start.Y) * 6 / 4;
int left = Math.Min(start.X, end.X); int left = Math.Min(start.X, end.X);
@ -148,31 +166,16 @@ public class CropTool : ITool {
return (left, right, top, bottom); 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) { public void Draw(UiGeometry geometry, Game game) {
var (left, right, top, bottom) = GetCrop(); if (activePhoto.CropRectangle == Rectangle.Empty) {
int area = (right - left) * (bottom - top);
if (area == 0) {
return; return;
} }
Vector2i leftTop = game.ImageToScreen(activePhoto.CropRectangle.Left, activePhoto.CropRectangle.Top);
Vector2i rightBottom = game.ImageToScreen(activePhoto.CropRectangle.Right, activePhoto.CropRectangle.Bottom);
var (left, top) = leftTop;
var (right, bottom) = rightBottom;
Color4 shadeColor = new Color4(0, 0, 0, 0.75f); 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(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, 0, geometry.PhotoBox.Max.X, top), shadeColor);
@ -192,7 +195,7 @@ public class CropTool : ITool {
} }
} }
// FIXME: this should probably be IDisposable?
public class Photo { public class Photo {
public string Filename; public string Filename;
public bool Loaded = false; public bool Loaded = false;
@ -474,6 +477,7 @@ public class Photo {
} }
} }
public class Texture : IDisposable { public class Texture : IDisposable {
public int Handle; public int Handle;
public Vector2i Size; public Vector2i Size;
@ -524,6 +528,7 @@ public class Texture : IDisposable {
} }
} }
public class UiGeometry { public class UiGeometry {
public static Vector2i MIN_WINDOW_SIZE = new(1024, 768); public static Vector2i MIN_WINDOW_SIZE = new(1024, 768);
private static CameraInfo activeCamera = CameraInfo.CANON_EOS_R6M2; private static CameraInfo activeCamera = CameraInfo.CANON_EOS_R6M2;
@ -565,6 +570,7 @@ public class UiGeometry {
} }
} }
public static class Util { public static class Util {
public const float PI = (float) Math.PI; public const float PI = (float) Math.PI;
@ -587,7 +593,7 @@ public static class Util {
} }
// FIXME: I'm not convinced that all of these are correct, especially the // FIXME: I'm not convinced that all of these are correct, especially the
// cases that involve flipping (because whether you're flipping before or // cases that involve flipping (because whether you're flipping before or
// after rotation matters.). // after rotation matters.)
var operations = new Dictionary<ushort, (RotateMode, FlipMode)> { var operations = new Dictionary<ushort, (RotateMode, FlipMode)> {
{ 2, (RotateMode.None, FlipMode.Horizontal) }, { 2, (RotateMode.None, FlipMode.Horizontal) },
{ 3, (RotateMode.Rotate180, FlipMode.None) }, { 3, (RotateMode.Rotate180, FlipMode.None) },
@ -606,6 +612,11 @@ public static class Util {
} }
public static Texture RenderText(string text, int size) { public static Texture RenderText(string text, int size) {
// Make sure that 0-length text doesn't end up as a 0-size texture, which
// might cause problems.
if (text.Length == 0) {
text = " ";
}
Font font = SystemFonts.CreateFont("Consolas", size, FontStyle.Bold); Font font = SystemFonts.CreateFont("Consolas", size, FontStyle.Bold);
TextOptions options = new(font); TextOptions options = new(font);
FontRectangle rect = TextMeasurer.Measure(text, new TextOptions(font)); FontRectangle rect = TextMeasurer.Measure(text, new TextOptions(font));
@ -652,6 +663,7 @@ 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) {
activeTool = viewTool; activeTool = viewTool;
@ -713,8 +725,6 @@ public class Game : GameWindow {
Close(); Close();
} }
int lastPhotoIndex = photoIndex;
mousePosition = (Vector2i) MouseState.Position; mousePosition = (Vector2i) MouseState.Position;
// Look for mouse clicks on thumbnails or stars. // Look for mouse clicks on thumbnails or stars.
@ -786,10 +796,6 @@ public class Game : GameWindow {
photoIndex = Math.Clamp(photoIndex, 0, photos.Count - 1); photoIndex = Math.Clamp(photoIndex, 0, photos.Count - 1);
} }
if (photoIndex != lastPhotoIndex) {
// FIXME!!!: do something to reset tool state here
}
// Handle presses of the "rating" keys -- 0-5 and `. // Handle presses of the "rating" keys -- 0-5 and `.
// A normal press just sets the rating of the current photo. // A normal press just sets the rating of the current photo.
// If the user is holding "shift", we instead filter to only show photos of that rating or higher. // If the user is holding "shift", we instead filter to only show photos of that rating or higher.
@ -856,7 +862,7 @@ public class Game : GameWindow {
// Handle tool switching. // Handle tool switching.
if (activeTool == viewTool) { if (activeTool == viewTool) {
if (input.IsKeyPressed(Keys.C)) { if (input.IsKeyPressed(Keys.C)) {
activeTool = new CropTool(); activeTool = new CropTool(photos[photoIndex]);
} }
} }
@ -930,9 +936,9 @@ public class Game : GameWindow {
GL.VertexAttribPointer(texCoordLocation, 2, VertexAttribPointerType.Float, false, 5 * sizeof(float), 3 * sizeof(float)); GL.VertexAttribPointer(texCoordLocation, 2, VertexAttribPointerType.Float, false, 5 * sizeof(float), 3 * sizeof(float));
// 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");
@ -1109,7 +1115,9 @@ public class Game : GameWindow {
public Vector2i ScreenToImage(int x, int y) { public Vector2i ScreenToImage(int x, int y) {
int rx = (int) ((x - activeOffset.X) / activeScale); int rx = (int) ((x - activeOffset.X) / activeScale);
rx = Math.Clamp(rx, 0, photos[photoIndex].Size.X);
int ry = (int) ((y - activeOffset.Y) / activeScale); int ry = (int) ((y - activeOffset.Y) / activeScale);
ry = Math.Clamp(ry, 0, photos[photoIndex].Size.Y);
return new(rx, ry); return new(rx, ry);
} }
@ -1117,6 +1125,16 @@ public class Game : GameWindow {
return ScreenToImage(position.X, position.Y); return ScreenToImage(position.X, position.Y);
} }
public Vector2i ImageToScreen(int x, int y) {
int rx = (int) ((x * activeScale) + activeOffset.X);
int ry = (int) ((y * activeScale) + activeOffset.Y);
return new(rx, ry);
}
public Vector2i ImageToScreen(Vector2i position) {
return ImageToScreen(position.X, position.Y);
}
public 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));
} }
@ -1200,6 +1218,7 @@ public class Game : GameWindow {
} }
} }
static class Program { static class Program {
static void Main(string[] args) { static void Main(string[] args) {
List<MonitorInfo> monitors = Monitors.GetMonitors(); List<MonitorInfo> monitors = Monitors.GetMonitors();