Files
SpineViewer/tests/preview-compositing.html
2026-09-10 18:51:29 +08:00

43 lines
3.0 KiB
HTML

<!doctype html><meta charset="utf-8"><title>Spine preview compositing tests</title>
<pre id="result">RUNNING</pre>
<script>var module={exports:{}},__dirname='';function require(n){return n==='path'?{join:()=>''}:{}}</script>
<script src="../js/spine-webgl-rasterizer.js"></script>
<script>const rasterizer=module.exports;module={exports:{}};</script>
<script src="../js/spine-background.js"></script>
<script>const background=module.exports;module={exports:{}};</script>
<script src="../js/spine-badge.js"></script>
<script>
try {
const badge=module.exports;
function check(ok,message){if(!ok)throw Error(message)}
function canvas(w,h){const c=document.createElement('canvas');c.width=w;c.height=h;return c.getContext('2d')}
function pixel(c,x,y){return c.getImageData(x,y,1,1).data}
const grid=canvas(220,220);grid.translate(10,20);grid.scale(2,2);background.drawCheckerboard(grid,220,220);
check(pixel(grid,109,70)[0]===90 && pixel(grid,110,70)[0]===80,'50-unit cells must scale to 100px and follow translation');
const source=canvas(8,8);source.fillStyle='rgb(128,64,192)';source.fillRect(0,0,8,8);
const destination=[80,120,160],color=[128,64,192];
for(const mode of ['source-over','lighter','multiply','screen']) for(const alpha of [1,0.5]){
const target=canvas(64,64);target.fillStyle='rgb(80,120,160)';target.fillRect(0,0,64,64);
const raster=rasterizer.createRasterizer(target);check(raster&&raster.begin(),'WebGL unavailable');
target.globalAlpha=alpha;target.globalCompositeOperation=mode;
raster.triangle(source.canvas,8,8,0,0,56,8,1,0,56,56,1,1);
raster.triangle(source.canvas,8,8,0,0,56,56,1,1,8,56,0,1);raster.end();
const actual=pixel(target,32,32);
for(let channel=0;channel<3;channel++){
const s=color[channel]*alpha,d=destination[channel];
const expected=mode==='lighter'?Math.min(255,s+d):mode==='multiply'?d*(s/255+1-alpha):mode==='screen'?s+d*(1-s/255):s+d*(1-alpha);
check(Math.abs(actual[channel]-expected)<2,mode+' '+alpha+' channel '+channel+': '+actual[channel]+' vs '+expected);
}
check(actual[3]===255,'background alpha must remain opaque');
target.globalAlpha=1;target.globalCompositeOperation='source-over';target.fillStyle='#ff0000';target.fillRect(0,0,64,64);
raster.begin();raster.end();check(pixel(target,32,32)[0]===255&&pixel(target,32,32)[1]===0,'background texture must refresh each frame');
raster.dispose();
}
const stamp=canvas(400,400);let text='';const fillText=stamp.fillText.bind(stamp);stamp.fillText=(s,...args)=>{text=s;fillText(s,...args)};
badge.drawVersion(stamp,400,400,'4.3.75-beta');check(text==='4.3.75-beta','beta version watermark');
let white=0;for(const value of stamp.getImageData(12,354,170,34).data)if(value>200)white++;
check(white>100,'watermark pixels');
document.getElementById('result').textContent='PASS: moving/scaling 50-unit checkerboard, four blend modes at two opacities, background refresh, 4.3 beta watermark';
}catch(error){document.getElementById('result').textContent='FAIL: '+error.stack}
</script>