import json import subprocess import textwrap from pathlib import Path ROOT = Path(__file__).resolve().parents[1] MODULE = (ROOT / 'static/js/editor/retouch.js').as_uri() def run_node(body): script = textwrap.dedent(f""" import * as R from {json.dumps(MODULE)}; {body} """) result = subprocess.run( ['node', '--input-type=module', '-e', script], cwd=ROOT, text=True, capture_output=True, check=True, ) return json.loads(result.stdout) def test_auto_tone_lifts_dark_low_contrast_and_stretches(): out = run_node(""" const w=40,h=40; const d=new Uint8ClampedArray(w*h*4); for(let i=0;i 0) or (out.get('gamma') is not None and out['gamma'] < 1) assert out['meanAfter'] > out['meanBefore'] + 20 def test_skin_mask_detects_skin_and_excludes_blue(): out = run_node(""" const w=4,h=1; const d=new Uint8ClampedArray(w*h*4); const px=(i,r,g,b)=>{d[i]=r;d[i+1]=g;d[i+2]=b;d[i+3]=255;}; px(0,224,180,150); // skin px(4,30,60,200); // blue px(8,60,130,60); // green (not skin) px(12,200,170,140); // skin const m=R.skinMaskFromData(d,w,h,null); console.log(JSON.stringify({ a0:m[3], a1:m[7], a2:m[11], a3:m[15] })); """) assert out['a0'] == 255 and out['a3'] == 255 assert out['a1'] == 0 and out['a2'] == 0 def test_frequency_separation_smooths_low_keeps_marker_and_teeth_reduces_yellow(): out = run_node(""" const w=9,h=9; const d=new Uint8ClampedArray(w*h*4); for(let i=0;i=3&&x<=5&&y>=3&&y<=5)? 40:0; d[i]=150+blot; d[i+1]=150+blot; d[i+2]=150+blot; d[i+3]=255; } const mask=new Uint8ClampedArray(w*h*4); for(let i=0;i 0 assert out['teethB'] > 120 # blue channel raised -> yellow reduced def test_frequency_separation_default_is_gentle_and_never_brightens(): out = run_node(""" const w=9,h=9; const d=new Uint8ClampedArray(w*h*4); for(let i=0;i=3&&x<=5&&y>=3&&y<=5)? 4:0; // slightly bright blotch const dark = (x<=1&&y<=1)? -60:0; // a dark pore region d[i]=150+blot+dark; d[i+1]=150+blot+dark; d[i+2]=150+blot+dark; d[i+3]=255; } const mask=new Uint8ClampedArray(w*h*4); for(let i=0;id[i]){ anyUp=true; if(s[i]-d[i]>maxUp)maxUp=s[i]-d[i]; } } const ci=(4*w+4)*4; console.log(JSON.stringify({ anyUp, maxUp, centre:s[ci], orig:d[ci] })); """) # Gentle skin smoothing must NEVER lighten the face (the "pale/airbrushed" bug). assert out['anyUp'] is False assert out['centre'] <= out['orig'] def test_mean_abs_delta_identity_is_zero(): out = run_node(""" const w=5,h=5; const d=new Uint8ClampedArray(w*h*4).fill(120); for(let i=3;ii%4===3?v:v),w,h) })); """) assert out['same'] == 0 def test_guided_clarity_is_edge_aware_and_halo_free(): out = run_node(""" const w=64,h=64; const d=new Uint8ClampedArray(w*h*4); for(let i=0;i=32)v+=100; d[i]=v;d[i+1]=v;d[i+2]=v;d[i+3]=255; } const src=new Uint8ClampedArray(d); const o=R.guidedClarity(d,w,h,{amount:80,radius:12,clamp:12}); const at=(x,y)=>{const i=(y*w+x)*4; return o[i];}; let maxFlat=0; for(let y=8;y<56;y++){ for(let x=6;x<26;x++){ const i=(y*w+x)*4; maxFlat=Math.max(maxFlat,Math.abs(o[i]-src[i])); } } let maxEdge=0; for(let y=8;y<56;y++){ maxEdge=Math.max(maxEdge, Math.abs(at(31,y)-src[(y*w+31)*4]), Math.abs(at(32,y)-src[(y*w+32)*4])); } let maxAny=0; for(let i=0;i 2 # clarity actually enhances flat/texture regions assert out['maxEdge'] <= 2 # no overshoot/halo at a strong edge assert out['maxAny'] <= 12 # clamped