Files
RecipesCam/src/dev/nativeBenchProbe.ts
T

76 lines
3.3 KiB
TypeScript

import RecipescamExport from '../../modules/recipescam-export';
import { applyExposureGain, getSkiaColorMatrix } from '../utils/colorUtils';
import { BaseFilter, ColorAdjustments } from '../types';
const RUNS = 2;
const IDENTITY = (() => {
const m = new Array(20).fill(0);
m[0] = m[6] = m[12] = m[18] = 1;
return m;
})();
const NEUTRAL_ADJ: ColorAdjustments = {
exposure: 0,
contrast: 0,
saturation: 0,
temperature: 5500,
tint: 0,
highlight: 0,
shadow: 0,
denoise: 0,
clarity: 0,
grain: 0,
colorChrome: 'none',
colorChromeBlue: 'none',
dynamicRange: 100,
sharpening: 0,
exposureCompensation: 0,
};
/**
* P0/P1/P2 probe harness (temporary — removed at P7).
* Native logs (RecipesCamExport tag) are the only visible channel: release
* Hermes strips console.*. Each gate prints its own centerPixel / timing lines.
*/
export async function nativeBenchProbe(): Promise<void> {
const m = RecipescamExport as any;
try {
const pong = await m.ping();
const src: string = await m.materializeBenchAsset('wallframe');
// P1 parity: classic-neg matrix on black -> expect center ~(13,5,0).
const matrix = applyExposureGain(getSkiaColorMatrix('classic-neg' as BaseFilter, NEUTRAL_ADJ), 0);
const black = await m.materializeSolidAsset(0, 0, 0);
const blackOut = black.replace(/\.png$/, `-neg-${Date.now()}.jpg`);
await m.processColorAsync(black, blackOut, matrix, null, 95);
console.error('[BENCH] classic-neg-on-black matrix[0..4]=' + matrix.slice(0, 5).join(','));
// P2 tone parity on solid ramps (expected values computed from TONE_SKSL):
// white(255) hl=-1 -> 140; gray64 sh=+1 -> 75; white dr=1 -> 209;
// gray26 sh=+1 -> 104; gray26 hl=+1 sh=-1 -> 11.
const toneGates: Array<{ rgb: [number, number, number]; tone: { dr: number; hl: number; sh: number }; tag: string }> = [
{ rgb: [255, 255, 255], tone: { dr: 0, hl: -1, sh: 0 }, tag: 'white-hl-1' },
{ rgb: [64, 64, 64], tone: { dr: 0, hl: 0, sh: 1 }, tag: 'gray64-sh+1' },
{ rgb: [255, 255, 255], tone: { dr: 1, hl: 0, sh: 0 }, tag: 'white-dr1' },
{ rgb: [26, 26, 26], tone: { dr: 0, hl: 0, sh: 1 }, tag: 'gray26-sh+1' },
{ rgb: [26, 26, 26], tone: { dr: 0, hl: 1, sh: -1 }, tag: 'gray26-hl+1-sh-1' },
];
for (const g of toneGates) {
const solid = await m.materializeSolidAsset(g.rgb[0], g.rgb[1], g.rgb[2]);
const out = solid.replace(/\.png$/, `-tone-${Date.now()}.jpg`);
const t = await m.processPhotoAsync(solid, out, IDENTITY, null, g.tone.dr, g.tone.hl, g.tone.sh, 95);
console.error('[BENCH] toneGate ' + g.tag + ' totalMs=' + t.totalMs + ' toneMs=' + (t.toneMs ?? 0));
}
// Full-artwork run: provia + mild tone to sanity-check timing on 12MP.
const provia = applyExposureGain(getSkiaColorMatrix('provia' as BaseFilter, NEUTRAL_ADJ), 0);
for (let i = 0; i < RUNS; i++) {
const dst = src.replace(/\.png$/, `-full-${Date.now()}-${i}.jpg`);
const t = await m.processPhotoAsync(src, dst, provia, null, 0.33, 0.3, 0.3, 95);
console.error('[BENCH] fullRun=' + i + ' decodeMs=' + t.decodeMs + ' toneMs=' + (t.toneMs ?? 0) + ' colorMs=' + t.colorMs + ' encodeWriteMs=' + t.encodeWriteMs + ' totalMs=' + t.totalMs + ' pong=' + pong);
}
} catch (e) {
console.error('[BENCH] failed', e);
}
}