web: mirror the frame with H-FLIP and V-FLIP, and stamp a typed place
ROTATE gains the two mirrors: H-FLIP and V-FLIP toggle one at a time and stay on through the quarter turns and STRAIGHTEN, which makes them compose with every rotation the strip already offers. ROTATE's own RESET levels the whole frame, mirrors included. The flip itself lands last, in screen space, so a mirrored photo is what the eye sees rather than what the sensor saw; the pixels are copied axis-aligned, so there is nothing to resample. Session state carries the two flags, so a reopened photo comes back mirrored. Also fixes the stamp: a typed PLACE NAME with no GPS fix now prints on its own (latitude/longitude ride in as NaN), instead of the whole stamp and its box being skipped for want of coordinates.
This commit is contained in:
@@ -76,13 +76,47 @@ export function rotateSkImageBy(image: SkImage | null, degrees: number): SkImage
|
||||
}
|
||||
}
|
||||
|
||||
// Mirror the photo left<->right (H-FLIP) and/or top<->bottom (V-FLIP). An
|
||||
// axis-aligned mirror lands whole pixels on whole pixels, so the plain
|
||||
// drawImage stays exact: no sampling option needed the way STRAIGHTEN's rotated
|
||||
// edge does. The dimensions never change, so the crop stays valid.
|
||||
// ponytail: one offscreen surface per call, same trade as rotateSkImage90.
|
||||
export function flipSkImage(
|
||||
image: SkImage | null,
|
||||
horizontal: boolean,
|
||||
vertical: boolean
|
||||
): SkImage | null {
|
||||
if (!image || (!horizontal && !vertical)) return image;
|
||||
const w = image.width();
|
||||
const h = image.height();
|
||||
if (!(w > 0 && h > 0)) return null;
|
||||
const surface = Skia.Surface.Make(w, h);
|
||||
if (!surface) return image; // keep the unflipped photo on allocation failure
|
||||
try {
|
||||
const canvas = surface.getCanvas();
|
||||
canvas.clear(Skia.Color('transparent'));
|
||||
// A negative scale turns every pixel coordinate around, so the translate
|
||||
// puts the mirrored copy back inside the surface.
|
||||
canvas.scale(horizontal ? -1 : 1, vertical ? -1 : 1);
|
||||
canvas.translate(horizontal ? -w : 0, vertical ? -h : 0);
|
||||
canvas.drawImage(image, 0, 0);
|
||||
return surface.makeImageSnapshot() ?? image;
|
||||
} finally {
|
||||
surface.dispose(); // see rotateSkImage90
|
||||
}
|
||||
}
|
||||
|
||||
// The whole FRAME-tab photo orientation in one call: the quarter turn first
|
||||
// (rotateSkImage90 swaps the pixel dimensions, which then propagates through
|
||||
// every consumer unchanged), then the fine STRAIGHTEN on top of it.
|
||||
// every consumer unchanged), then the fine STRAIGHTEN on top of it, then the
|
||||
// two mirrors — a flip is what the user sees, so it mirrors the photo as it
|
||||
// stands, whatever turn and angle are already on it.
|
||||
export function applyPhotoRotation(
|
||||
image: SkImage | null,
|
||||
quarter = 0,
|
||||
straighten = 0
|
||||
straighten = 0,
|
||||
flipH = false,
|
||||
flipV = false
|
||||
): SkImage | null {
|
||||
let out = image;
|
||||
const turns = Math.round(quarter / 90) % 4;
|
||||
@@ -98,5 +132,10 @@ export function applyPhotoRotation(
|
||||
if (s && s !== out && out !== image) out.dispose(); // see above
|
||||
out = s ?? out;
|
||||
}
|
||||
if (flipH || flipV) {
|
||||
const f = flipSkImage(out, flipH, flipV);
|
||||
if (f && f !== out && out !== image) out.dispose(); // see above
|
||||
out = f ?? out;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user