Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 44d428b6f4 | |||
| f20d84c543 |
+2
-2
@@ -109,7 +109,7 @@ On Calculate, the app builds the inventory image from the approved mask:
|
||||
1. Keep slab pixels from the source photo. Removed pixels are transparent.
|
||||
2. Place the slab in a 3:4 portrait frame, centered.
|
||||
3. Scale so the widest and tallest points of the slab fill 80% of the frame. Extra margin where the slab is narrower.
|
||||
4. After that crop, shorter side ≥ 1600px when the source can supply it. Otherwise warn and require a new photo. No upscale from an undersized source.
|
||||
4. After that crop, shorter side ≥ 800px when the source can supply it. Otherwise warn and require a new photo. No upscale from an undersized source.
|
||||
5. Encode as PNG with alpha.
|
||||
|
||||
## Device draft
|
||||
@@ -134,7 +134,7 @@ The first inventory photo is the measurement reference. Later inventory photos u
|
||||
| Alpha | Kept through encode and upload |
|
||||
| Aspect | 3:4 portrait |
|
||||
| Layout | Slab centered, 80% fill at the widest and tallest points |
|
||||
| Size | After crop, shorter side ≥ 1600px when the source can supply it |
|
||||
| Size | After crop, shorter side ≥ 800px when the source can supply it |
|
||||
|
||||
Source below that floor: warn and require a new photo. No upscale from an undersized source.
|
||||
Editor preview of alpha is a light gray checkerboard. The storefront uses the theme background.
|
||||
|
||||
@@ -107,7 +107,7 @@
|
||||
<div><span>Scale</span><span>{state.results.scale.toFixed(2)} px/in</span></div>
|
||||
</div>
|
||||
{#if state.results.sourceBelowFloor}
|
||||
<div class="warn">Source is below the 1600px floor for the inventory PNG. Retake a larger photo.</div>
|
||||
<div class="warn">Source is below the 800px floor for the inventory PNG. Retake a larger photo.</div>
|
||||
{/if}
|
||||
{#if pngUrl && !state.results.sourceBelowFloor}
|
||||
<div class="png-wrap">
|
||||
|
||||
@@ -31,7 +31,7 @@ test('alpha contract: removed pixels transparent, slab pixels keep RGB at alpha
|
||||
const out = composeInventoryFrame(src, maskFor(bitset, { x: 1, y: 1, w: 4, h: 4 }), source);
|
||||
|
||||
assert.equal(out.sourceBelowFloor, true);
|
||||
assert.ok(out.width < 1600, 'undersized source stays small');
|
||||
assert.ok(out.width < 800, 'undersized source stays small');
|
||||
|
||||
let opaque = 0;
|
||||
let transparent = 0;
|
||||
@@ -56,7 +56,7 @@ test('alpha contract: removed pixels transparent, slab pixels keep RGB at alpha
|
||||
assert.ok(transparent > 0, 'frame has transparent pixels');
|
||||
});
|
||||
|
||||
test('3:4 frame at the floor: 2000×1800 slab → 1600×2133', () => {
|
||||
test('3:4 frame at the floor: 2000×1800 slab → 800×1067', () => {
|
||||
const source: Pick<Source, 'widthPx' | 'heightPx'> = { widthPx: 2000, heightPx: 1800 };
|
||||
const bitset = new Uint8Array(source.widthPx * source.heightPx).fill(1);
|
||||
const out = composeInventoryFrame(
|
||||
@@ -64,11 +64,23 @@ test('3:4 frame at the floor: 2000×1800 slab → 1600×2133', () => {
|
||||
maskFor(bitset, { x: 0, y: 0, w: 2000, h: 1800 }),
|
||||
source,
|
||||
);
|
||||
assert.equal(out.width, 1600);
|
||||
assert.equal(out.height, 2133);
|
||||
assert.equal(out.width, 800);
|
||||
assert.equal(out.height, 1067);
|
||||
assert.equal(out.sourceBelowFloor, false);
|
||||
});
|
||||
|
||||
test('mid-band: slab shorter side 1000 (between 800 and 1600) → at floor, not belowFloor', () => {
|
||||
const source: Pick<Source, 'widthPx' | 'heightPx'> = { widthPx: 1200, heightPx: 1000 };
|
||||
const bitset = new Uint8Array(source.widthPx * source.heightPx).fill(1);
|
||||
const out = composeInventoryFrame(
|
||||
makePhoto(source.widthPx, source.heightPx),
|
||||
maskFor(bitset, { x: 0, y: 0, w: 1200, h: 1000 }),
|
||||
source,
|
||||
);
|
||||
assert.equal(out.sourceBelowFloor, false);
|
||||
assert.equal(out.width, 800);
|
||||
});
|
||||
|
||||
test('80% fill: limiting dimension maps to 0.8 × frame side', () => {
|
||||
const source: Pick<Source, 'widthPx' | 'heightPx'> = { widthPx: 2200, heightPx: 2000 };
|
||||
const bitset = new Uint8Array(source.widthPx * source.heightPx).fill(1);
|
||||
@@ -91,11 +103,11 @@ test('80% fill: limiting dimension maps to 0.8 × frame side', () => {
|
||||
const xs = [minx, maxx], ys = [miny, maxy];
|
||||
const wExtent = Math.max(...xs) - Math.min(...xs) + 1;
|
||||
const hExtent = Math.max(...ys) - Math.min(...ys) + 1;
|
||||
assert.ok(Math.abs(wExtent - 1280) <= 2, `width extent ${wExtent} ≈ 1280 (0.8×1600)`);
|
||||
assert.ok(Math.abs(hExtent - 1152) <= 2, `height extent ${hExtent} ≈ 1152 (0.8×1800×0.64)`);
|
||||
assert.ok(Math.abs(wExtent - 640) <= 2, `width extent ${wExtent} ≈ 640 (0.8×800)`);
|
||||
assert.ok(Math.abs(hExtent - 576) <= 2, `height extent ${hExtent} ≈ 576 (1800×0.32)`);
|
||||
});
|
||||
|
||||
test('belowFloor: small slab → sourceBelowFloor, outW < 1600', () => {
|
||||
test('belowFloor: small slab → sourceBelowFloor, outW capped at floor (no upscale)', () => {
|
||||
const source: Pick<Source, 'widthPx' | 'heightPx'> = { widthPx: 900, heightPx: 700 };
|
||||
const bitset = new Uint8Array(source.widthPx * source.heightPx).fill(1);
|
||||
const out = composeInventoryFrame(
|
||||
@@ -104,5 +116,5 @@ test('belowFloor: small slab → sourceBelowFloor, outW < 1600', () => {
|
||||
source,
|
||||
);
|
||||
assert.equal(out.sourceBelowFloor, true);
|
||||
assert.ok(out.width < 1600, `outW ${out.width}`);
|
||||
assert.ok(out.width <= 800, `outW ${out.width} must not exceed the floor`);
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Mask, Source } from '../capture/capture.types';
|
||||
|
||||
const FLOOR = 1600; // shorter side, px
|
||||
const FLOOR = 800; // shorter side, px
|
||||
const FILL = 0.8; // 80% fill at the widest and tallest points of the slab
|
||||
const ASPECT_W = 3;
|
||||
const ASPECT_H = 4;
|
||||
|
||||
Reference in New Issue
Block a user