fix(camera): constrain brightness and adjust side photo previews
This commit is contained in:
@@ -51,14 +51,16 @@ try {
|
||||
AudioRecorder.prototype = OriginalAudioContext.prototype;
|
||||
window.AudioContext = window.webkitAudioContext = AudioRecorder;
|
||||
const canvas = document.createElement('canvas'); canvas.width = 800; canvas.height = 600;
|
||||
let currentImage, offsetX = 0;
|
||||
let currentImage, offsetX = 0, lightGain = 1;
|
||||
function draw() {
|
||||
if (!currentImage) return;
|
||||
const c = canvas.getContext('2d'); c.fillStyle = '#111'; c.fillRect(0, 0, canvas.width, canvas.height);
|
||||
c.filter='brightness('+lightGain+')';
|
||||
c.drawImage(currentImage, offsetX, 0, canvas.width, canvas.height);
|
||||
c.filter='none';
|
||||
}
|
||||
setInterval(draw, 100);
|
||||
window.__cameraFixture = { canvas, set: async name => {
|
||||
window.__cameraFixture = { canvas, light:gain=>{lightGain=gain;draw();}, set: async name => {
|
||||
const image = new Image(); image.src = 'sample-' + name; await image.decode();
|
||||
// Simulate a person following the centering prompt, not an off-center
|
||||
// landscape source stretched into a portrait camera frame.
|
||||
@@ -115,6 +117,10 @@ try {
|
||||
assert((await page.locator('#cam-step').textContent()).includes('1/3'), 'invalid frontal angle never advances');
|
||||
assert.equal(await page.evaluate(() => window.__voice.some(row => /^hint-turn-|^step[23]-|^shot-done$/.test(row.id))), false, 'no turn/next-step/completion audio before a successful front photo');
|
||||
await page.evaluate(() => window.__cameraFixture.set('C3_180.jpg'));
|
||||
await page.waitForFunction(()=>window.__evaluations.some(row=>row.target==='front'&&!row.ok&&row.hint==='请到光线充足处'));
|
||||
assert((await page.locator('#cam-step').textContent()).includes('1/3'),'dim sample cannot advance');
|
||||
// Simulate improving room lighting, not bypassing the actual quality gate.
|
||||
await page.evaluate(()=>window.__cameraFixture.light(1.3));
|
||||
try { await page.waitForFunction(() => document.querySelector('#cam-step').textContent.includes('2/3')); }
|
||||
catch (error) {
|
||||
console.log(await page.evaluate(async () => {
|
||||
@@ -146,7 +152,7 @@ try {
|
||||
for (const key of ['front', 'left', 'right']) {
|
||||
const row = body.capture_diagnostics.views[key];
|
||||
assert.equal(row.mode, 'live'); assert.equal(row.direction_checked, true);
|
||||
assert(row.score >= .4 && row.luma >= 100 && row.luma <= 235);
|
||||
assert(row.score >= .4 && row.luma >= 150 && row.luma <= 220);
|
||||
assert(Math.abs(row.roll) <= 18);
|
||||
}
|
||||
assert(Math.abs(body.capture_diagnostics.views.front.yaw) <= 18);
|
||||
|
||||
@@ -24,7 +24,7 @@ function points(yaw = 0, pitch = 0, roll = 0) {
|
||||
return result;
|
||||
}
|
||||
const face = (yaw = 0, pitch = 0, roll = 0) => ({ detection: { score: .95, box: { x: 170, y: 160, width: 260, height: 300 } }, landmarks: { positions: points(yaw, pitch, roll) } });
|
||||
const options = { count: 1, target: 'front', width: 600, height: 600, offsetX: 0, luma: 120, mirrored: false };
|
||||
const options = { count: 1, target: 'front', width: 600, height: 600, offsetX: 0, luma: 180, mirrored: false };
|
||||
const check = (d, changes) => Quality.evaluate(d, { ...options, ...changes });
|
||||
assert(check(face()).ok);
|
||||
assert(!check(null).ok); assert(!check(face(), { count: 2 }).ok);
|
||||
@@ -61,8 +61,9 @@ assert.equal(check(null).reason, 'quality');
|
||||
assert.equal(check(face(), { count: 2 }).reason, 'quality');
|
||||
const brokenLandmarks = face(); brokenLandmarks.landmarks.positions = [];
|
||||
assert.equal(check(brokenLandmarks).reason, 'quality', 'unreadable pose is not confirmed wrong angle');
|
||||
for (const luma of [100, 100.01, 230, 235]) assert(check(face(), { luma }).ok);
|
||||
for (const luma of [65,70,99.99]) assert.equal(check(face(),{luma}).hint,'请到光线充足处');
|
||||
for (const luma of [150, 150.01, 180, 219.99, 220]) assert(check(face(), { luma }).ok);
|
||||
for (const luma of [65,100,120,149.99]) assert.equal(check(face(),{luma}).hint,'请到光线充足处');
|
||||
for (const luma of [220.01,235,255]) assert.equal(check(face(),{luma}).hint,'光线过强,请避开强光');
|
||||
assert(check(face(), { offsetX: .15 }).ok);
|
||||
assert(!check(face(), { offsetX: .17 }).ok);
|
||||
for (const changes of [{ height: 190 }, { height: 400 }, { y: 8 }]) {
|
||||
@@ -115,7 +116,7 @@ assert.equal(Diagnostics.assess(raw).views.right.status, 'valid');
|
||||
const normalized = Diagnostics.normalize({ version: Quality.config.version, secret: 'discard', views: { front: { mode: 'live', yaw: 1, score: .9, luma: Infinity, secret: 'discard' }, left: { mode: 'unchecked', reason: 'model-unavailable' } } });
|
||||
assert.equal(normalized.policy.capture.holdMs, 450);
|
||||
assert.equal(normalized.policy.capture.minFrames, 2);
|
||||
assert.equal(normalized.version, '2026-10-15-dark-100-v4');
|
||||
assert.equal(normalized.version, '2026-10-15-light-150-220-v5');
|
||||
assert.equal(normalized.views.front.luma, undefined);
|
||||
assert(!JSON.stringify(normalized).includes('discard'));
|
||||
assert.equal(Diagnostics.normalize({ version: '<script>' }).version, 'unknown');
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
'use strict';
|
||||
const assert=require('node:assert/strict'),fs=require('node:fs'),vm=require('node:vm');
|
||||
const source=fs.readFileSync(require('node:path').join(__dirname,'../js/app.js'),'utf8');
|
||||
const helper=source.slice(source.indexOf(' function reportZoomSource('),source.indexOf(" document.querySelectorAll('.card-detail').forEach(function (card, group)"));
|
||||
const drawn=[],canvases=[];
|
||||
const frame={getBoundingClientRect:()=>({left:0,top:0,width:100,height:90})};
|
||||
const img={naturalWidth:1000,naturalHeight:1000,src:'original',getBoundingClientRect:()=>({left:-90,top:-104,width:280,height:252})};
|
||||
const context={getComputedStyle:e=>e===frame?{backgroundColor:'#f6f6f6'}:{objectFit:'cover',objectPosition:'50% 50%'},document:{createElement:()=>{
|
||||
const canvas={getContext:()=>({fillRect(){},drawImage(...args){drawn.push(args);}}),toDataURL:()=> 'cropped'};canvases.push(canvas);return canvas;
|
||||
}}};
|
||||
vm.runInNewContext(helper,context);
|
||||
assert.equal(context.reportZoomSource(img,frame,1),'cropped');
|
||||
assert.equal(context.reportZoomSource(img,frame,.5),'cropped');
|
||||
assert.equal(drawn[1][3],drawn[0][3]*.5);assert.equal(drawn[1][4],drawn[0][4]*.5);
|
||||
assert.equal(canvases[0].width,canvases[1].width);assert.equal(canvases[0].height,canvases[1].height);
|
||||
assert(drawn[1][1]<=0&&drawn[1][1]+drawn[1][3]>=canvases[1].width);
|
||||
assert(drawn[1][2]<=0&&drawn[1][2]+drawn[1][4]>=canvases[1].height);
|
||||
assert.equal(context.reportZoomSource(img,frame,.5,.5),'cropped');
|
||||
assert.equal(drawn[2][2]-drawn[1][2],drawn[1][4]*.5,'move down by exactly half the scaled photo height');
|
||||
assert.equal(drawn[2][1],drawn[1][1]);assert.equal(drawn[2][3],drawn[1][3]);assert.equal(drawn[2][4],drawn[1][4]);
|
||||
assert.equal(canvases[2].height,canvases[1].height);
|
||||
assert(source.includes('sideButton ? .5 : 1'),'half-sized fill applies only to side-photo large previews');
|
||||
console.log('PASS side zoom fill: size halved, then moved down exactly 50% photo height; preview frame/front/small images unchanged');
|
||||
Reference in New Issue
Block a user