Give LITE the bundled recipes, three of its own, and its own mark
LITE used to hand back the RECIPESCAM mark instead of the user's own
watermark and treated every saved recipe as PRO. That is the wrong half of
the product: the looks the user dials in are the reason to shoot, the
printing and the paid extras are the reason to pay.
What LITE now owns outright: the eight film sims, the bundled recipes, up
to three recipes of its own, its own watermark text (in one basic face,
the platform families stay PRO), the plain frames, and FAVORITED. What
needs PRO: the three printed frames, the GPS stamp, the other fonts, and
the next recipe past the limit.
- entitlement: `Look` is {frame, gpsWm}; `isFreeRecipe` is gone, the
recipe gate is a count now, decided in App (`liteSaveBlocked`).
- export: a LITE export draws the custom mark AND the RECIPESCAM mark, so
`liteMark` joins `watermark` in the options and the stamp block loops
over both with one set of bounds.
- panel: past the limit the strip's trailing chip is a `+` carrying the
PRO tag, which names the build instead of opening the form.
This commit is contained in:
+33
-17
@@ -59,6 +59,16 @@ export interface ExportOptions {
|
||||
size?: number;
|
||||
font?: string | null;
|
||||
} | null;
|
||||
// The free build's own mark (RECIPESCAM), drawn in ADDITION to `watermark`:
|
||||
// the custom text is free in LITE, the mark is what LITE leaves behind.
|
||||
liteMark?: {
|
||||
text: string;
|
||||
x: number;
|
||||
y: number;
|
||||
color?: string;
|
||||
size?: number;
|
||||
font?: string | null;
|
||||
} | null;
|
||||
// WATERMARK tab ROTATE chip: clockwise degrees (0/90/180/270) applied to
|
||||
// BOTH the GPS lines and the custom text, each around its own anchor (the
|
||||
// same anchor Viewfinder rotates around, so the preview matches the file).
|
||||
@@ -627,7 +637,8 @@ export async function processAndExportPhoto(
|
||||
// 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 liteWm = options?.liteMark ?? null;
|
||||
if ((useGeotag && gpsInfo) || customWm || liteWm) {
|
||||
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()]);
|
||||
@@ -686,28 +697,33 @@ export async function processAndExportPhoto(
|
||||
// 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) {
|
||||
if (customWm || liteWm) {
|
||||
// The mark lives on the FRAME: a framed export's canvas IS the
|
||||
// card/frame (see the branches above), so the whole stamp is the
|
||||
// area — the same 0..1 fractions the viewfinder drag produced.
|
||||
const area = { dx: 0, dy: 0, dw: stampW, dh: stampH };
|
||||
const clamp01 = (v: number) => Math.max(0, Math.min(1, v));
|
||||
const wmSize = Math.round(fontSize * (customWm.size ?? 1));
|
||||
const wmFont = customWm.font
|
||||
? Skia.Font(
|
||||
Skia.FontMgr.System().matchFamilyStyle(customWm.font, FontStyle.Normal),
|
||||
wmSize
|
||||
)
|
||||
: Skia.Font(typeface, wmSize);
|
||||
const wmPaint = Skia.Paint();
|
||||
wmPaint.setColor(Skia.Color(customWm.color ?? '#f59e0b'));
|
||||
const tx = area.dx + clamp01(customWm.x) * area.dw;
|
||||
const ty = area.dy + clamp01(customWm.y) * area.dh + Math.round(wmSize * 0.35);
|
||||
const wmRot = options?.watermarkRotation ?? 0;
|
||||
stampCanvas.save();
|
||||
if (wmRot) stampCanvas.rotate(wmRot, tx, ty);
|
||||
stampCanvas.drawText(customWm.text, tx, ty, wmPaint, wmFont);
|
||||
stampCanvas.restore();
|
||||
// Both marks share this path: the user's text (free in every tier)
|
||||
// and, in LITE, the build's own RECIPESCAM mark next to it.
|
||||
for (const mark of [customWm, liteWm]) {
|
||||
if (!mark) continue;
|
||||
const wmSize = Math.round(fontSize * (mark.size ?? 1));
|
||||
const wmFont = mark.font
|
||||
? Skia.Font(
|
||||
Skia.FontMgr.System().matchFamilyStyle(mark.font, FontStyle.Normal),
|
||||
wmSize
|
||||
)
|
||||
: Skia.Font(typeface, wmSize);
|
||||
const wmPaint = Skia.Paint();
|
||||
wmPaint.setColor(Skia.Color(mark.color ?? '#f59e0b'));
|
||||
const tx = area.dx + clamp01(mark.x) * area.dw;
|
||||
const ty = area.dy + clamp01(mark.y) * area.dh + Math.round(wmSize * 0.35);
|
||||
stampCanvas.save();
|
||||
if (wmRot) stampCanvas.rotate(wmRot, tx, ty);
|
||||
stampCanvas.drawText(mark.text, tx, ty, wmPaint, wmFont);
|
||||
stampCanvas.restore();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user