Add wall frame overlay with transparent PNG window
This commit is contained in:
+64
-11
@@ -7,7 +7,7 @@ import { Recipe, GPSInfo, FrameId } from '../types';
|
||||
import { getSkiaColorMatrix, applyExposureGain } from './colorUtils';
|
||||
import { TONE_SKSL, getToneUniforms, toneIsActive } from './toneShader';
|
||||
import { CINEMA_SKSL, getCinemaUniforms, cinemaIsActive } from './cinemaShader';
|
||||
import { drawFrameOnCanvas, polaroidLayout, POLAROID_CARD, POLAROID_WIN_W } from './frameUtils';
|
||||
import { drawFrameOnCanvas, polaroidLayout, POLAROID_CARD, POLAROID_WIN_W, wallframeLayout, WALLFRAME_IMG_W, WALLFRAME_IMG_H } from './frameUtils';
|
||||
import { patchJpegDpi } from './jpegDpi';
|
||||
|
||||
export interface ExportResult {
|
||||
@@ -260,13 +260,15 @@ export async function processAndExportPhoto(
|
||||
// processed picture is composited, uncropped, into the window of a bigger
|
||||
// off-white card (real print proportions, see frameUtils) — the export IS
|
||||
// the card, with the photo scaled to the window so it keeps ~WIN_W of the
|
||||
// card width. Other frames (classic white border, legacy cinematic bars)
|
||||
// draw over the photo-sized canvas as before.
|
||||
// card width. WALL FRAME: the export IS the frame PNG at native resolution,
|
||||
// with the photo cover-cropped (center) into the fixed 3:2 window (the mat
|
||||
// opening of a real frame). Other frames (classic white border, legacy
|
||||
// cinematic bars) draw over the photo-sized canvas as before.
|
||||
let outputSurface: SkSurface = surface;
|
||||
let stampCanvas = canvas;
|
||||
let stampW = width;
|
||||
let stampH = height;
|
||||
let polaroidWindow: { x: number; y: number; w: number; h: number } | null = null;
|
||||
let framedWindow: { x: number; y: number; w: number; h: number } | null = null;
|
||||
if (frameId === 'polaroid') {
|
||||
const photoImg = surface.makeImageSnapshot();
|
||||
const outW = width; // card width matches the photo width
|
||||
@@ -302,7 +304,59 @@ export async function processAndExportPhoto(
|
||||
stampCanvas = cardCanvas;
|
||||
stampW = outW;
|
||||
stampH = outH;
|
||||
polaroidWindow = win;
|
||||
framedWindow = win;
|
||||
} else if (frameId === 'wallframe') {
|
||||
// Export at the PNG's native resolution: the mat artwork stays crisp and
|
||||
// the photo crop inside the window is as large as the asset allows.
|
||||
const photoImg = surface.makeImageSnapshot();
|
||||
const pw = photoImg.width();
|
||||
const ph = photoImg.height();
|
||||
const outW = WALLFRAME_IMG_W;
|
||||
const outH = WALLFRAME_IMG_H;
|
||||
const cardSurface = createSurface(outW, outH);
|
||||
if (!cardSurface) {
|
||||
console.error('Failed to create wallframe surface');
|
||||
return null;
|
||||
}
|
||||
const cardCanvas = cardSurface.getCanvas();
|
||||
// Decode the frame PNG artwork (bundled asset → base64, same path as the
|
||||
// GPS fonts below — embedded assets may not expose a readable file URI).
|
||||
const wfAsset = Asset.fromModule(require('../../wallframe.png'));
|
||||
await wfAsset.downloadAsync();
|
||||
const wfDataStr = wfAsset.localUri
|
||||
? await FileSystem.readAsStringAsync(wfAsset.localUri, { encoding: FileSystem.EncodingType.Base64 })
|
||||
: null;
|
||||
if (!wfDataStr) {
|
||||
console.error('Failed to load wallframe.png data');
|
||||
return null;
|
||||
}
|
||||
const wfImage = Skia.Image.MakeImageFromEncoded(Skia.Data.fromBase64(wfDataStr));
|
||||
if (!wfImage) {
|
||||
console.error('Failed to decode wallframe.png');
|
||||
return null;
|
||||
}
|
||||
const wl = wallframeLayout(outW, outH, 'fill');
|
||||
const win = wl.window;
|
||||
// Cover-crop (center): scale the processed photo so it fills the fixed
|
||||
// 3:2 window and cut the overflowing edges equally on both sides.
|
||||
const plainPaint = Skia.Paint();
|
||||
const scale = Math.max(win.w / pw, win.h / ph);
|
||||
const srcW = win.w / scale;
|
||||
const srcH = win.h / scale;
|
||||
cardCanvas.drawImageRect(
|
||||
photoImg,
|
||||
Skia.XYWHRect((pw - srcW) / 2, (ph - srcH) / 2, srcW, srcH),
|
||||
Skia.XYWHRect(win.x, win.y, win.w, win.h),
|
||||
plainPaint
|
||||
);
|
||||
// Artwork over the crop: its transparent window reveals the photo, the
|
||||
// mat covers the rest.
|
||||
cardCanvas.drawImage(wfImage, 0, 0, plainPaint);
|
||||
outputSurface = cardSurface;
|
||||
stampCanvas = cardCanvas;
|
||||
stampW = outW;
|
||||
stampH = outH;
|
||||
framedWindow = win;
|
||||
} else {
|
||||
drawFrameOnCanvas(canvas, width, height, frameId);
|
||||
}
|
||||
@@ -334,14 +388,13 @@ export async function processAndExportPhoto(
|
||||
const timestampStr = `${pad(d.getDate())}/${pad(d.getMonth() + 1)}/${d.getFullYear()} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
||||
const gap = Math.round(fontSize * 1.6); // icon box (~1em) + one clear mono space (~0.6em)
|
||||
|
||||
// Polaroid: caption at the bottom-left inside the photo window (the
|
||||
// spot the deck edge meets the print). Other frames: bottom-left of
|
||||
// the image, clear of the edge.
|
||||
// Framed (polaroid/wallframe): caption at the bottom-left inside the
|
||||
// photo window. Other frames: bottom-left of the image, clear of edge.
|
||||
let baseX: number;
|
||||
let baseY: number;
|
||||
if (polaroidWindow) {
|
||||
baseX = polaroidWindow.x + polaroidWindow.w * 0.05;
|
||||
baseY = polaroidWindow.y + polaroidWindow.h - Math.round(fontSize * 1.4);
|
||||
if (framedWindow) {
|
||||
baseX = framedWindow.x + framedWindow.w * 0.05;
|
||||
baseY = framedWindow.y + framedWindow.h - Math.round(fontSize * 1.4);
|
||||
} else {
|
||||
baseX = fontSize * 1.2;
|
||||
baseY = stampH - fontSize * 1.5;
|
||||
|
||||
Reference in New Issue
Block a user