Add Shorts look preview and visual crop editor
Show opening/closing 9:16 stills from the same filtergraph, and let users drag a keep-region on a source frame to fill the crop insets.
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
namespace AmiReel.Models;
|
||||
|
||||
/// <summary>Edge insets, in source pixels, removed by a manual crop.</summary>
|
||||
public readonly record struct PixelInsets(int Left, int Right, int Top, int Bottom)
|
||||
{
|
||||
public bool IsEmpty => Left == 0 && Right == 0 && Top == 0 && Bottom == 0;
|
||||
}
|
||||
|
||||
/// <summary>Maps a dragged display rectangle onto source-pixel crop insets.</summary>
|
||||
public static class SourceCropSelection
|
||||
{
|
||||
public const int MinimumSize = 16;
|
||||
|
||||
public static (double OffsetX, double OffsetY, double DisplayWidth, double DisplayHeight) FitUniform(
|
||||
double containerWidth, double containerHeight, int pixelWidth, int pixelHeight)
|
||||
{
|
||||
if (containerWidth <= 0 || containerHeight <= 0 || pixelWidth <= 0 || pixelHeight <= 0)
|
||||
return (0, 0, 0, 0);
|
||||
|
||||
double containerAspect = containerWidth / containerHeight;
|
||||
double imageAspect = pixelWidth / (double)pixelHeight;
|
||||
double displayWidth;
|
||||
double displayHeight;
|
||||
if (containerAspect > imageAspect)
|
||||
{
|
||||
displayHeight = containerHeight;
|
||||
displayWidth = containerHeight * imageAspect;
|
||||
}
|
||||
else
|
||||
{
|
||||
displayWidth = containerWidth;
|
||||
displayHeight = containerWidth / imageAspect;
|
||||
}
|
||||
|
||||
return ((containerWidth - displayWidth) / 2, (containerHeight - displayHeight) / 2, displayWidth, displayHeight);
|
||||
}
|
||||
|
||||
public static PixelInsets ToInsets(
|
||||
int pixelWidth,
|
||||
int pixelHeight,
|
||||
double displayWidth,
|
||||
double displayHeight,
|
||||
double x,
|
||||
double y,
|
||||
double width,
|
||||
double height)
|
||||
{
|
||||
if (pixelWidth <= 0 || pixelHeight <= 0 || displayWidth <= 0 || displayHeight <= 0)
|
||||
return default;
|
||||
|
||||
Normalize(ref x, ref y, ref width, ref height);
|
||||
x = Math.Clamp(x, 0, displayWidth);
|
||||
y = Math.Clamp(y, 0, displayHeight);
|
||||
width = Math.Clamp(width, 0, displayWidth - x);
|
||||
height = Math.Clamp(height, 0, displayHeight - y);
|
||||
|
||||
double scaleX = pixelWidth / displayWidth;
|
||||
double scaleY = pixelHeight / displayHeight;
|
||||
int left = ToEvenPixel(x * scaleX, pixelWidth - MinimumSize);
|
||||
int top = ToEvenPixel(y * scaleY, pixelHeight - MinimumSize);
|
||||
int right = ToEvenPixel((displayWidth - x - width) * scaleX, pixelWidth - MinimumSize - left);
|
||||
int bottom = ToEvenPixel((displayHeight - y - height) * scaleY, pixelHeight - MinimumSize - top);
|
||||
|
||||
if (pixelWidth - left - right < MinimumSize)
|
||||
right = Math.Max(0, pixelWidth - left - MinimumSize);
|
||||
if (pixelHeight - top - bottom < MinimumSize)
|
||||
bottom = Math.Max(0, pixelHeight - top - MinimumSize);
|
||||
|
||||
return new PixelInsets(left, right, top, bottom);
|
||||
}
|
||||
|
||||
public static (double X, double Y, double Width, double Height) ToDisplayRect(
|
||||
int pixelWidth, int pixelHeight, double displayWidth, double displayHeight, PixelInsets insets)
|
||||
{
|
||||
if (pixelWidth <= 0 || pixelHeight <= 0 || displayWidth <= 0 || displayHeight <= 0)
|
||||
return (0, 0, Math.Max(0, displayWidth), Math.Max(0, displayHeight));
|
||||
|
||||
double scaleX = displayWidth / pixelWidth;
|
||||
double scaleY = displayHeight / pixelHeight;
|
||||
double x = insets.Left * scaleX;
|
||||
double y = insets.Top * scaleY;
|
||||
double width = Math.Max(1, (pixelWidth - insets.Left - insets.Right) * scaleX);
|
||||
double height = Math.Max(1, (pixelHeight - insets.Top - insets.Bottom) * scaleY);
|
||||
return (x, y, width, height);
|
||||
}
|
||||
|
||||
public static (double X, double Y) ClampToDisplay(double x, double y, double displayWidth, double displayHeight) =>
|
||||
(Math.Clamp(x, 0, Math.Max(0, displayWidth)), Math.Clamp(y, 0, Math.Max(0, displayHeight)));
|
||||
|
||||
private static void Normalize(ref double x, ref double y, ref double width, ref double height)
|
||||
{
|
||||
if (width < 0)
|
||||
{
|
||||
x += width;
|
||||
width = -width;
|
||||
}
|
||||
|
||||
if (height < 0)
|
||||
{
|
||||
y += height;
|
||||
height = -height;
|
||||
}
|
||||
}
|
||||
|
||||
private static int ToEvenPixel(double value, int max)
|
||||
{
|
||||
int rounded = (int)Math.Round(value, MidpointRounding.AwayFromZero);
|
||||
int clamped = Math.Clamp(rounded, 0, Math.Max(0, max));
|
||||
return (clamped / 2) * 2;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user