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:
grokbot_ty
2026-09-26 09:11:34 -07:00
parent df87d97a7d
commit 1b5ad112cc
17 changed files with 246 additions and 69 deletions
+18 -7
View File
@@ -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,
};
}