Files
RecipesCam/src/utils/skiaImage.ts
T
admin 3a8b110c1b Edit the watermark in place, rotate the frame photo, place a custom watermark
Watermark editing no longer covers the photo: the preview letterboxes the
image inside its fit rect while the deck is open, and the idle hint is gone.

The FRAME tab can rotate a library photo 90 degrees clockwise. Rotation
swaps the pixel dimensions instead of the frame, so the polaroid/wall window
keeps its aspect and every consumer (cover crop, watermark area, export) picks
up the new orientation from width()/height() with no extra layout branch. The
rotate surface is CPU-backed on purpose - a GPU snapshot loses its pixels
before the next frame and renders the photo black.

Custom watermark text can now be dragged anywhere on the photo, double-tapped
to re-edit, and is only baked into the file once ACCEPT is pressed; the export
engine takes the same position. It also gained COLOR/SIZE/FONT chips, with the
font list read from the OS via Skia's FontMgr so any installed family can be
used.
2026-09-10 09:26:55 +07:00

28 lines
1.4 KiB
TypeScript

import { Skia, type SkImage } from '@shopify/react-native-skia';
// Turn a decoded image 90deg clockwise into a fresh SkImage. Used by the FRAME
// tab so a library photo can go landscape <-> portrait before it enters a frame:
// every consumer reads width()/height(), so the pixel-dimension swap carries the
// new orientation through the polaroid window aspect, the cover crop and the
// watermark areas without a single extra layout branch.
// ponytail: offscreen surface, same fallback as exportEngine.createSurface; a
// matrix-based draw would avoid the copy if this ever shows up in a profile.
export function rotateSkImage90(image: SkImage | null): SkImage | null {
if (!image) return null;
const w = image.width();
const h = image.height();
if (!(w > 0 && h > 0)) return null;
// CPU-backed on purpose: a GPU (MakeOffscreen) snapshot keeps its pixels in a
// context texture that is gone by the time the <Canvas> draws a frame later —
// the preview then renders the photo completely black. Raster pixels survive
// the trip to the render thread.
const surface = Skia.Surface.Make(h, w);
if (!surface) return image; // keep the unrotated photo on allocation failure
const canvas = surface.getCanvas();
canvas.clear(Skia.Color('transparent'));
canvas.translate(h, 0);
canvas.rotate(90, 0, 0);
canvas.drawImage(image, 0, 0);
return surface.makeImageSnapshot() ?? image;
}