fix(capture): warn 300–799px PNG floor; hard-block only below 300
Split FLOOR_WARN_PX=800 / FLOOR_BLOCK_PX=300. Warn-band still encodes and includes the photo (Photos-ready + batchApply ok+warn). Block excludes siblings and clean cal. CAPTURE-105 / CAPTURE.md / PHOTOS.md updated.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import type { Mask, Source } from '../capture/capture.types';
|
||||
import { ASPECT_H, ASPECT_W, FLOOR_PX, DEFAULT_CROP_FILL } from './crop';
|
||||
import { ASPECT_H, ASPECT_W, FLOOR_WARN_PX, FLOOR_BLOCK_PX, DEFAULT_CROP_FILL } from './crop';
|
||||
import { getCropFill } from '../settings/cropFill';
|
||||
|
||||
type MaskPick = Pick<Mask, 'bitset' | 'slabBounds'>;
|
||||
@@ -17,7 +17,10 @@ export interface Frame {
|
||||
width: number;
|
||||
height: number;
|
||||
rgba: Uint8Array;
|
||||
sourceBelowFloor: boolean;
|
||||
/** Shorter side of slab bbox is below FLOOR_BLOCK_PX (hard fail). */
|
||||
sourceBelowBlock: boolean;
|
||||
/** Shorter side is in [FLOOR_BLOCK_PX, FLOOR_WARN_PX) — encode/include with warn. */
|
||||
sourceBelowWarn: boolean;
|
||||
}
|
||||
|
||||
export function composeInventoryFrame(
|
||||
@@ -34,8 +37,12 @@ export function composeInventoryFrame(
|
||||
const y1 = Math.min(source.heightPx, y0 + Math.max(1, Math.round(b.h)));
|
||||
const sw = x1 - x0;
|
||||
const sh = y1 - y0;
|
||||
const belowFloor = Math.min(sw, sh) < FLOOR_PX;
|
||||
const outW = belowFloor ? Math.max(1, Math.min(sw, FLOOR_PX)) : FLOOR_PX;
|
||||
const shortSide = Math.min(sw, sh);
|
||||
const sourceBelowBlock = shortSide < FLOOR_BLOCK_PX;
|
||||
const sourceBelowWarn = !sourceBelowBlock && shortSide < FLOOR_WARN_PX;
|
||||
// Undersized vs warn floor: frame from available source (no upscale to 800).
|
||||
const undersized = shortSide < FLOOR_WARN_PX;
|
||||
const outW = undersized ? Math.max(1, Math.min(sw, FLOOR_WARN_PX)) : FLOOR_WARN_PX;
|
||||
const outH = Math.round((outW * ASPECT_H) / ASPECT_W);
|
||||
const scale = Math.min((outW * FILL) / Math.max(sw, 1), (outH * FILL) / Math.max(sh, 1));
|
||||
const dw = sw * scale;
|
||||
@@ -60,12 +67,12 @@ export function composeInventoryFrame(
|
||||
rgba[di + 3] = 255;
|
||||
}
|
||||
}
|
||||
return { width: outW, height: outH, rgba, sourceBelowFloor: belowFloor };
|
||||
return { width: outW, height: outH, rgba, sourceBelowBlock, sourceBelowWarn };
|
||||
}
|
||||
|
||||
export async function encodeInventoryPng(
|
||||
input: InventoryInput,
|
||||
): Promise<{ blob: Blob; sourceBelowFloor: boolean }> {
|
||||
): Promise<{ blob: Blob; sourceBelowBlock: boolean; sourceBelowWarn: boolean }> {
|
||||
const { bitmap, mask, source } = input;
|
||||
const fill = input.cropFill ?? getCropFill();
|
||||
const srcCanvas = new OffscreenCanvas(source.widthPx, source.heightPx);
|
||||
@@ -82,5 +89,9 @@ export async function encodeInventoryPng(
|
||||
const ctx = out.getContext('2d')!;
|
||||
ctx.putImageData(new ImageData(new Uint8ClampedArray(frame.rgba), frame.width, frame.height), 0, 0);
|
||||
const blob = await out.convertToBlob({ type: 'image/png' });
|
||||
return { blob, sourceBelowFloor: frame.sourceBelowFloor };
|
||||
return {
|
||||
blob,
|
||||
sourceBelowBlock: frame.sourceBelowBlock,
|
||||
sourceBelowWarn: frame.sourceBelowWarn,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user