Add WATERMARK tab with custom draggable text; hide FRAME in camera; fix double-dark after highlight capture
This commit is contained in:
+44
-21
@@ -34,6 +34,10 @@ export interface ExportOptions {
|
||||
// viewfinder displayed. WALL FRAME ignores this (frame keeps its fixed
|
||||
// opening and export size). 'full'/undefined = no crop.
|
||||
aspect?: AspectRatio;
|
||||
// Custom text watermark: typed text + 0..1 fractions of the photo area
|
||||
// (BASELINE anchor + 0.35em drop — same formula as the viewfinder overlay,
|
||||
// so the export lands where the preview showed it). Amber mono, no icons.
|
||||
watermark?: { text: string; x: number; y: number } | null;
|
||||
}
|
||||
|
||||
// Lightroom-style "Screen" output sharpen: subtle unsharp mask. A 3x3
|
||||
@@ -407,8 +411,11 @@ export async function processAndExportPhoto(
|
||||
drawFrameOnCanvas(canvas, width, height, frameId);
|
||||
}
|
||||
|
||||
// 8. Render GPS Geotag Watermark
|
||||
if (useGeotag && gpsInfo) {
|
||||
// 8. Render GPS Geotag Watermark + Custom Text Watermark. One asset load +
|
||||
// one amber-mono paint path serves both: GPS keeps its two icon lines; the
|
||||
// custom watermark is plain text drawn at the user-dragged position.
|
||||
const customWm = options?.watermark ?? null;
|
||||
if ((useGeotag && gpsInfo) || customWm) {
|
||||
const fontAsset = Asset.fromModule(require('../../assets/Cousine-Regular.ttf'));
|
||||
const iconFontAsset = Asset.fromModule(require('../../assets/NotoEmoji-GPS.ttf'));
|
||||
await Promise.all([fontAsset.downloadAsync(), iconFontAsset.downloadAsync()]);
|
||||
@@ -428,28 +435,44 @@ export async function processAndExportPhoto(
|
||||
const iconPaint = Skia.Paint();
|
||||
iconPaint.setColor(Skia.Color('#ffffff')); // icons white, distinct from text
|
||||
|
||||
const locationName = gpsInfo.locality || 'STREET VIEW';
|
||||
const d = new Date(gpsInfo.timestamp);
|
||||
const pad = (n: number) => String(n).padStart(2, '0');
|
||||
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)
|
||||
// GPS lines keep the historical bottom-left anchor (framed -> inside
|
||||
// the photo window; other frames -> bottom-left of the image).
|
||||
if (useGeotag && gpsInfo) {
|
||||
const locationName = gpsInfo.locality || 'STREET VIEW';
|
||||
const d = new Date(gpsInfo.timestamp);
|
||||
const pad = (n: number) => String(n).padStart(2, '0');
|
||||
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)
|
||||
|
||||
// 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 (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;
|
||||
let baseX: number;
|
||||
let baseY: number;
|
||||
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;
|
||||
}
|
||||
|
||||
stampCanvas.drawText('📍', baseX, baseY - fontSize * 1.4, iconPaint, iconFont);
|
||||
stampCanvas.drawText(locationName, baseX + gap, baseY - fontSize * 1.4, textPaint, font);
|
||||
stampCanvas.drawText('📷', baseX, baseY, iconPaint, iconFont);
|
||||
stampCanvas.drawText(timestampStr, baseX + gap, baseY, textPaint, font);
|
||||
}
|
||||
|
||||
stampCanvas.drawText('📍', baseX, baseY - fontSize * 1.4, iconPaint, iconFont);
|
||||
stampCanvas.drawText(locationName, baseX + gap, baseY - fontSize * 1.4, textPaint, font);
|
||||
stampCanvas.drawText('📷', baseX, baseY, iconPaint, iconFont);
|
||||
stampCanvas.drawText(timestampStr, baseX + gap, baseY, textPaint, font);
|
||||
// Custom watermark: baseline at the dragged fraction of the photo/window
|
||||
// area + ~0.35em drop (the grabbed anchor reads as the text's visual
|
||||
// center, not its baseline) — the exact formula Viewfinder uses, so
|
||||
// preview == export.
|
||||
if (customWm) {
|
||||
const area = framedWindow
|
||||
? { dx: framedWindow.x, dy: framedWindow.y, dw: framedWindow.w, dh: framedWindow.h }
|
||||
: { dx: 0, dy: 0, dw: stampW, dh: stampH };
|
||||
const clamp01 = (v: number) => Math.max(0, Math.min(1, v));
|
||||
const tx = area.dx + clamp01(customWm.x) * area.dw;
|
||||
const ty = area.dy + clamp01(customWm.y) * area.dh + Math.round(fontSize * 0.35);
|
||||
stampCanvas.drawText(customWm.text, tx, ty, textPaint, font);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user