feat(frame): CROP ratios in the library editor, and the framed-photo fixes

CROP chip on the FRAME tab for the library's plain frames: NONE, FREE
(drag the box) and the fixed ratios 1:1, 2:3, 3:2, 3:4, 4:3, 16:9. The
preview draws the keep-rectangle over the photo and the export honours it
pixel-for-pixel, pinching and dragging inside the band to pick the framing.

- Viewfinder: build frameRect from the normalised rect so the Skia canvas
  never sees undefined width/height (nothing to commit until now), keep
  the watermark drag target across the WATERMARK tab so a zoomed photo
  no longer resets, and derive the crop band + export rect from the photo
  fit rect.
- exportEngine: crop by fraction rect (or the ratio fallback) before the
  frame is composited.
- Leaving a plain frame clears the crop, and the ratio strip closes with
  the CROP chip it belongs to.
This commit is contained in:
2026-09-10 20:12:03 +07:00
parent 299d81bade
commit 93f48537f8
5 changed files with 378 additions and 41 deletions
+24
View File
@@ -30,6 +30,30 @@ export const ASPECT_RATIO_W_H: Record<Exclude<AspectRatio, 'full'>, number> = {
'3:2': 2 / 3,
};
// FRAME-tab CROP. Ratio convention is w:h (so 2:3 is an upright crop, 16:9 a
// wide one); 'free' is a user-dragged rectangle instead of a fixed shape.
export type CropRatio = 'none' | 'free' | '1:1' | '2:3' | '3:2' | '3:4' | '4:3' | '16:9';
// Keep-rectangle as 0..1 fractions of the (post-rotation) source photo.
export interface CropRect {
x: number;
y: number;
w: number;
h: number;
}
// w/h of the kept area for each fixed crop choice.
export const CROP_W_H: Record<Exclude<CropRatio, 'none' | 'free'>, number> = {
'1:1': 1,
'2:3': 2 / 3,
'3:2': 3 / 2,
'3:4': 3 / 4,
'4:3': 4 / 3,
'16:9': 16 / 9,
};
// FREE crop starts as a visible inset rectangle the user can immediately grab.
export const DEFAULT_CROP_RECT: CropRect = { x: 0.1, y: 0.1, w: 0.8, h: 0.8 };
// Smallest FREE-crop side, in source fractions.
export const MIN_CROP_FRAC = 0.08;
// Cinematic seasonal atmosphere grade. Not a fake re-light: it reads the
// photo's EXISTING exposure — warm tint + soft glow on areas already lit,
// cool tint in shade, plus a seasonal atmospheric haze/fog veil.