fix(photos): shared EXIF-oriented decode for axis/overlay space (#72)

PhotoStage used new Image() while mask worker and calculate used bare
createImageBitmap; Orientation-8 matched-set JPEGs can disagree across
those paths so the approved pink axis is not the overlay (ux,uy).

One decodeOrientedBitmap helper always requests imageOrientation
from-image (with bare fallback), wired into PhotoStage, mask.worker,
and session calculate/siblings.
This commit is contained in:
2026-09-16 05:08:13 +00:00
parent b8e69b5239
commit 037a4b870a
6 changed files with 299 additions and 79 deletions
+37
View File
@@ -545,6 +545,43 @@ function tiltedAxis25Fixture() {
return { W, H, bitset, bbox, ux0, uy0, rx0, ry0, halfL, cx, cy };
}
test('buildOverlayPlan: angleDeg=90 vertical long side → width left→right within 2° of horizontal (#72)', () => {
// Upright slab: approved length axis is vertical (angleDeg 90). Width marks must
// run left→right ≈ horizontal (atan2 ≈ 0 or ±π), not slanted with the photo.
const W = 200, H = 400;
const bitset = new Uint8Array(W * H);
for (let y = 40; y <= 360; y++) {
for (let x = 60; x <= 140; x++) bitset[y * W + x] = 1;
}
const prev = minAreaRect(bitset, W, H)!;
const bbox = orientedRect(prev, 90);
assert.equal(bbox.angleDeg, 90);
const scale = 10; // 320px span ≈ 32"
const plan = buildOverlayPlan({
bitset,
widthPx: W,
heightPx: H,
bbox,
scale,
lengthIn: 32,
lengthLabel: '32"',
mapToInventory: (x, y) => [x, y],
});
assert.ok(plan.widths.length > 0, 'expected width stations');
for (const w of plan.widths) {
const ang = Math.atan2(w.right[1] - w.left[1], w.right[0] - w.left[0]);
// Fold to [-90, 90] so left→right and right→left both count as horizontal.
let deg = (ang * 180) / Math.PI;
if (deg > 90) deg -= 180;
if (deg < -90) deg += 180;
assert.ok(
Math.abs(deg) <= 2,
`width station ${w.stationIn} left→right must be within 2° of horizontal (atan2 deg=${deg.toFixed(2)}; left=${w.left} right=${w.right})`,
);
}
});
test('buildOverlayPlan: length a→b || approved (ux,uy); width left→right || (rx,ry) (#70)', () => {
const { W, H, bitset, bbox, cx, cy } = tiltedAxis25Fixture();
const scale = 10;