Files
houseplan-card/demo/benchmark_backdrop_decode.mjs
2026-09-02 21:25:30 +03:00

69 lines
3.2 KiB
JavaScript

// #39: decode/downscale calibration matrix behind the backdrop guard
// thresholds (docs/specs/039-large-backdrops.md §Research). Not a CI gate —
// rerun by hand when recalibrating WARN_DECODED_BYTES / HARD_DIMENSION.
// PLAYWRIGHT_BROWSERS_PATH=... node demo/benchmark_backdrop_decode.mjs
import { chromium } from 'playwright';
import { reportPageErrors, watchPage } from './serve.mjs';
const guardProbe = process.argv.includes('--guard-probe');
const browser = await chromium.launch({ args: ['--no-sandbox'] });
const page = watchPage(await (await browser.newContext()).newPage());
const session = await page.context().newCDPSession(page);
await page.goto('about:blank');
const metrics = async () => {
const m = await session.send('Performance.getMetrics');
const get = (n) => m.metrics.find((x) => x.name === n)?.value || 0;
return { jsHeap: Math.round(get('JSHeapUsedSize') / 1048576) };
};
const cases = [
{ mp: 4, alpha: false }, { mp: 8, alpha: false }, { mp: 16, alpha: false },
{ mp: 32, alpha: false }, { mp: 32, alpha: true }, { mp: 64, alpha: false },
{ mp: 100, alpha: false }, { mp: 165, alpha: false },
];
for (const c of guardProbe ? [] : cases) {
try {
const r = await page.evaluate(async ({ mp, alpha }) => {
const side = Math.round(Math.sqrt(mp * 1e6));
const w = side, h = side;
// сгенерировать PNG нужного размера
const cv = new OffscreenCanvas(w, h);
const ctx = cv.getContext('2d');
const g = ctx.createLinearGradient(0, 0, w, h);
g.addColorStop(0, alpha ? 'rgba(200,30,30,0.5)' : '#c81e1e');
g.addColorStop(1, '#1e50c8');
ctx.fillStyle = g; ctx.fillRect(0, 0, w, h);
for (let i = 0; i < 200; i++) { ctx.fillStyle = `hsl(${i*7},60%,50%)`; ctx.fillRect((i*97)%w, (i*61)%h, 40, 40); }
const blob = await cv.convertToBlob({ type: 'image/png' });
// decode: createImageBitmap
const t0 = performance.now();
const bmp = await createImageBitmap(blob);
const decodeMs = Math.round(performance.now() - t0);
// downscale до 4096 по длинной стороне
const target = 4096;
const scale = Math.min(1, target / Math.max(bmp.width, bmp.height));
const t1 = performance.now();
const out = new OffscreenCanvas(Math.round(bmp.width*scale), Math.round(bmp.height*scale));
out.getContext('2d').drawImage(bmp, 0, 0, out.width, out.height);
const outBlob = await out.convertToBlob({ type: 'image/jpeg', quality: 0.9 });
const downMs = Math.round(performance.now() - t1);
bmp.close();
return { w, h, fileMB: +(blob.size/1048576).toFixed(1), rgbaMB: Math.round(w*h*4/1048576),
decodeMs, downMs, outMB: +(outBlob.size/1048576).toFixed(1) };
}, c);
const m = await metrics();
console.log(JSON.stringify({ mp: c.mp, alpha: c.alpha, ...r, jsHeapMB: m.jsHeap }));
} catch (e) {
console.log(JSON.stringify({ mp: c.mp, alpha: c.alpha, FAIL: String(e).slice(0, 120) }));
}
}
if (guardProbe) {
await page.evaluate(() => {
setTimeout(() => { throw new Error('houseplan backdrop guard probe'); }, 0);
});
}
if (await reportPageErrors()) {
await browser.close();
process.exit(1);
}
await browser.close();