Cut 1.2.3 with a shutter that never waits on GPS

Bound the geotag fix with a 2 s deadline and prewarm it at launch, queue a
shot pressed before the camera session binds and replay it, pass the options
argument capturePhotoToFile requires, save through the native path so the
media permission is not needed, and release the Skia surface and bitmap after
every export. Store the JS bundle uncompressed and load the export pipeline on
demand. Note in RELEASE_NOTES.md that none of this moved the tap threshold.
This commit is contained in:
2026-09-17 10:02:35 +07:00
parent d07dd69f17
commit 76cf4c6d7e
12 changed files with 476 additions and 170 deletions
+36 -15
View File
@@ -18,12 +18,18 @@ export function rotateSkImage90(image: SkImage | null): SkImage | null {
// 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;
try {
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;
} finally {
// The raster snapshot refcounts the pixels, so the surface can go now
// instead of leaving one full-size copy behind per rotation.
surface.dispose();
}
}
// Fine rotation about the centre (the FRAME tab's STRAIGHTEN), keeping the
@@ -44,13 +50,17 @@ export function rotateSkImageBy(image: SkImage | null, degrees: number): SkImage
const k = Math.max((w * cos + h * sin) / w, (w * sin + h * cos) / h);
const surface = Skia.Surface.Make(w, h);
if (!surface) return image; // keep the unrotated photo on allocation failure
const canvas = surface.getCanvas();
canvas.clear(Skia.Color('transparent'));
canvas.translate(w / 2, h / 2);
canvas.rotate(degrees, 0, 0);
canvas.scale(k, k);
canvas.drawImage(image, -w / 2, -h / 2);
return surface.makeImageSnapshot() ?? image;
try {
const canvas = surface.getCanvas();
canvas.clear(Skia.Color('transparent'));
canvas.translate(w / 2, h / 2);
canvas.rotate(degrees, 0, 0);
canvas.scale(k, k);
canvas.drawImage(image, -w / 2, -h / 2);
return surface.makeImageSnapshot() ?? image;
} finally {
surface.dispose(); // see rotateSkImage90
}
}
// The whole FRAME-tab photo orientation in one call: the quarter turn first
@@ -62,7 +72,18 @@ export function applyPhotoRotation(
straighten = 0
): SkImage | null {
let out = image;
for (let i = 0; i < Math.round(quarter / 90) % 4; i++) out = rotateSkImage90(out);
if (straighten) out = rotateSkImageBy(out, straighten);
const turns = Math.round(quarter / 90) % 4;
for (let i = 0; i < turns; i++) {
const next = rotateSkImage90(out);
// A 180/270 turn allocates one full-size copy per turn: the intermediate is
// dead as soon as the next one exists (never the caller's input image).
if (next && next !== out && i > 0) out.dispose();
out = next ?? out;
}
if (straighten) {
const s = rotateSkImageBy(out, straighten);
if (s && s !== out && out !== image) out.dispose(); // see above
out = s ?? out;
}
return out;
}