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
+21 -15
View File
@@ -26,7 +26,7 @@ import { Recipe, GPSInfo, FrameId } from '../types';
import { getSkiaColorMatrix, applyExposureGain } from '../utils/colorUtils';
import { TONE_SKSL, getToneUniforms } from '../utils/toneShader';
import { CINEMA_SKSL, getCinemaUniforms, cinemaOffFlat } from '../utils/cinemaShader';
import { polaroidLayout, wallframeLayout } from '../utils/frameUtils';
import { polaroidLayout, wallframeLayout, WALLFRAME_PNG_W, WALLFRAME_PNG_H } from '../utils/frameUtils';
interface ViewfinderProps {
mode: 'camera' | 'library';
@@ -556,10 +556,11 @@ export default function Viewfinder({
if (selectedFrame === 'wallframe') {
const geo = wallGeo;
if (!geo || !wallImage) return null; // PNG still loading → bare feed
const { frame } = geo;
// Letterbox the live feed black outside the PNG, then draw the artwork:
// its transparent window lets the fullscreen feed show through (the mat
// opening = center crop of the feed, WYSIWYG with the export crop).
const { frame, drawMatrix } = geo;
// Letterbox the live feed black outside the frame rect, then draw the
// artwork ROTATED into that rect: its transparent window shows the feed
// (cover-cropped center, WYSIWYG with the export crop) and the mat covers
// the rest of the screen — the feed never fills the whole display.
const black = '#000000';
return (
<Group>
@@ -567,7 +568,9 @@ export default function Viewfinder({
<Rect x={0} y={frame.y + frame.h} width={vw} height={vh - frame.y - frame.h} color={black} />
<Rect x={0} y={frame.y} width={frame.x} height={frame.h} color={black} />
<Rect x={frame.x + frame.w} y={frame.y} width={vw - frame.x - frame.w} height={frame.h} color={black} />
<SkiaImage image={wallImage} x={frame.x} y={frame.y} width={frame.w} height={frame.h} fit="fill" />
<Group matrix={drawMatrix}>
<SkiaImage image={wallImage} x={0} y={0} width={WALLFRAME_PNG_W} height={WALLFRAME_PNG_H} fit="fill" />
</Group>
</Group>
);
}
@@ -856,13 +859,14 @@ export default function Viewfinder({
);
}
// WALL FRAME (library): the photo is cover-cropped (center) into the PNG's
// fixed 3:2 window — the mat opening of a real frame hides the print edges.
// The window never letterboxes a mismatched aspect; the artwork PNG is drawn
// over the crop afterwards (its transparent window reveals the photo).
// WALL FRAME (library): the photo is cover-cropped (center) into the frame's
// fixed 2:3 window (rotated artwork) — the mat opening of a real frame hides
// the print edges. The window never letterboxes a mismatched aspect; the
// artwork PNG is drawn rotated over the crop afterwards (its transparent
// window reveals the photo).
if (mode === 'library' && wallGeo && wallImage && skiaImage && libraryImageUri) {
const win = wallGeo.window;
const frame = wallGeo.frame;
const drawMatrix = wallGeo.drawMatrix;
const winRect = rect(win.x, win.y, win.w, win.h);
return (
<View className="flex-1 relative overflow-hidden bg-black items-center justify-center">
@@ -905,10 +909,12 @@ export default function Viewfinder({
</Rect>
</Group>
)}
{/* Artwork over the crop: transparent window reveals it, mat covers the
rest. Drawn last so semi-transparent mat edges blend over the photo
instead of the reverse. */}
<SkiaImage image={wallImage} x={frame.x} y={frame.y} width={frame.w} height={frame.h} fit="fill" />
{/* Artwork (rotated) over the crop: transparent window reveals it, mat
covers the rest. Drawn last so semi-transparent mat edges blend over
the photo instead of the reverse. */}
<Group matrix={drawMatrix}>
<SkiaImage image={wallImage} x={0} y={0} width={WALLFRAME_PNG_W} height={WALLFRAME_PNG_H} fit="fill" />
</Group>
{renderGPSWatermark()}
</Canvas>
</View>