Rotate wall frame portrait, letterbox feed to frame window

This commit is contained in:
2026-09-08 18:14:25 +07:00
parent a1769e8c84
commit a16c8734a8
3 changed files with 73 additions and 47 deletions
+36 -22
View File
@@ -32,28 +32,40 @@ export interface PolaroidLayout {
}
// Wall frame: the PNG artwork lives at the project root (wallframe.png,
// 4000x3117 RGBA, transparent window 675..3322 x 675..2441). Only the geometry
// lives here; the bitmap itself is drawn by the caller (Viewfinder overlay /
// export engine). The window is FIXED 3:2 landscape, so the photo is
// cover-cropped (center) into it — the mat of a real frame hides the print
// edges. Fractions are relative to the full PNG.
export const WALLFRAME_IMG_W = 4000;
export const WALLFRAME_IMG_H = 3117;
export const WALLFRAME = {
winLeft: 675 / 4000, // 0.16875
winTop: 675 / 3117, // ≈ 0.21655
winW: 2648 / 4000, // 0.662
winH: 1767 / 3117, // ≈ 0.56689
// 4000x3117 RGBA, transparent window cols 675..3322 x rows 675..2441). Only the
// geometry lives here; the bitmap itself is drawn by the caller. The phone is
// portrait, so the app shows the artwork ROTATED 90° CW (original top edge →
// right edge): a portrait frame whose transparent window is a fixed 2:3
// vertical opening. The photo is cover-cropped (center) into that opening — the
// mat of a real frame hides the print edges. WALLFRAME_W/H and WALLFRAME_WIN
// are the rotated display orientation; the affine in wallframeLayout maps the
// source PNG pixels onto the frame rect so the drawn opening and the photo
// window always line up.
export const WALLFRAME_PNG_W = 4000; // source PNG width
export const WALLFRAME_PNG_H = 3117; // source PNG height
export const WALLFRAME_W = 3117; // rotated display width (= PNG height)
export const WALLFRAME_H = 4000; // rotated display height (= PNG width)
// Transparent window as fractions of the rotated image, derived from the same
// affine used to draw the bitmap: X = w - (y/3117)w, Y = (x/4000)h.
export const WALLFRAME_WIN = {
left: 676 / 3117, // ≈ 0.21688
top: 675 / 4000, // 0.16875
w: 1766 / 3117, // ≈ 0.56657
h: 2647 / 4000, // 0.66175
} as const;
export interface WallframeLayout {
frame: { x: number; y: number; w: number; h: number };
window: { x: number; y: number; w: number; h: number };
// Row-major Skia matrix that rotates the source PNG (WALLFRAME_PNG_W/H) 90° CW
// into the frame rect. Feed it to <Group matrix> (viewfinder) or concat it on
// the canvas (export) before drawing the bitmap.
drawMatrix: number[];
}
// Geometry of the wall-frame PNG inside an arbitrary container. The PNG is
// contain-fitted ('contain', centered, letterboxed) for the live viewfinder, or
// stretched to fill exactly ('fill', export canvas already has PNG aspect).
// Geometry of the ROTATED wall-frame inside an arbitrary container. The frame
// is contain-fitted ('contain', centered, letterboxed) for the live viewfinder,
// or fills the container exactly ('fill', export canvas already at frame size).
export function wallframeLayout(
contW: number,
contH: number,
@@ -65,20 +77,22 @@ export function wallframeLayout(
frameW = contW;
frameH = contH;
} else {
const s = Math.min(contW / WALLFRAME_IMG_W, contH / WALLFRAME_IMG_H);
frameW = WALLFRAME_IMG_W * s;
frameH = WALLFRAME_IMG_H * s;
const s = Math.min(contW / WALLFRAME_W, contH / WALLFRAME_H);
frameW = WALLFRAME_W * s;
frameH = WALLFRAME_H * s;
}
const fx = (contW - frameW) / 2;
const fy = (contH - frameH) / 2;
const scale = frameW / WALLFRAME_W; // == frameH / WALLFRAME_H
return {
frame: { x: fx, y: fy, w: frameW, h: frameH },
window: {
x: fx + WALLFRAME.winLeft * frameW,
y: fy + WALLFRAME.winTop * frameH,
w: WALLFRAME.winW * frameW,
h: WALLFRAME.winH * frameH,
x: fx + WALLFRAME_WIN.left * frameW,
y: fy + WALLFRAME_WIN.top * frameH,
w: WALLFRAME_WIN.w * frameW,
h: WALLFRAME_WIN.h * frameH,
},
drawMatrix: [0, -scale, fx + frameW, scale, 0, fy, 0, 0, 1],
};
}