51 lines
3.4 KiB
HTML
51 lines
3.4 KiB
HTML
<!doctype html><meta charset="utf-8"><title>PMA and vertex color regression</title>
|
|
<pre id="result">RUNNING</pre>
|
|
<script>var module={exports:{}};</script>
|
|
<script src="../js/spine-webgl-rasterizer.js"></script>
|
|
<script>
|
|
(async()=>{try{
|
|
const api=module.exports;
|
|
function check(ok,message){if(!ok)throw Error(message)}
|
|
function canvas(){const c=document.createElement('canvas');c.width=c.height=32;return c.getContext('2d')}
|
|
// Produce a PNG whose low-alpha RGB values can be represented exactly by
|
|
// Canvas's 8-bit storage; PMA consumers must preserve those encoded values.
|
|
const encoded=canvas(),pixels=encoded.createImageData(32,32);
|
|
for(let i=0;i<pixels.data.length;i+=4)pixels.data.set([64,32,16,8],i);
|
|
encoded.putImageData(pixels,0,0);
|
|
const blob=await new Promise(resolve=>encoded.canvas.toBlob(resolve));
|
|
const image=new Image();image.src=URL.createObjectURL(blob);await image.decode();
|
|
await api.prepareTexture(image,blob);
|
|
// Read the encoded PNG samples via a non-premultiplied ImageBitmap in WebGL.
|
|
const rawCanvas=document.createElement('canvas'),gl=rawCanvas.getContext('webgl');
|
|
const t=gl.createTexture();gl.bindTexture(gl.TEXTURE_2D,t);
|
|
gl.texImage2D(gl.TEXTURE_2D,0,gl.RGBA,gl.RGBA,gl.UNSIGNED_BYTE,await createImageBitmap(blob,{premultiplyAlpha:'none'}));
|
|
const fb=gl.createFramebuffer();gl.bindFramebuffer(gl.FRAMEBUFFER,fb);gl.framebufferTexture2D(gl.FRAMEBUFFER,gl.COLOR_ATTACHMENT0,gl.TEXTURE_2D,t,0);
|
|
const samples=new Uint8Array(4);gl.readPixels(0,0,1,1,gl.RGBA,gl.UNSIGNED_BYTE,samples);
|
|
gl.deleteFramebuffer(fb);gl.deleteTexture(t);
|
|
const target=canvas(),raster=api.createRasterizer(target);check(raster,'WebGL unavailable');
|
|
const modes=['source-over','lighter','multiply','screen'];
|
|
const light=[0.8,0.5,0.25,0.6],dark=[0.1,0.2,0.3,0];
|
|
for(const pma of [true,false,true])for(const mode of modes){
|
|
target.setTransform(1,0,0,1,0,0);target.globalCompositeOperation='source-over';target.fillStyle='rgb(50,60,70)';target.fillRect(0,0,32,32);
|
|
check(raster.begin(pma),'begin');target.globalCompositeOperation=mode;
|
|
const colors=[...light,...light,...light],darks=[...dark,...dark,...dark];
|
|
raster.triangle(image,0,0,0,0,32,0,1,0,32,32,1,1,colors,darks);
|
|
raster.triangle(image,0,0,0,0,32,32,1,1,0,32,0,1,colors,darks);raster.end();
|
|
const actual=target.getImageData(16,16,1,1).data,alpha=samples[3]/255*light[3];
|
|
for(let c=0;c<3;c++){
|
|
const tex=samples[c]*(pma?1:samples[3]/255),s=(tex*light[c]+(samples[3]-tex)*dark[c])*light[3],d=[50,60,70][c];
|
|
const expected=mode==='lighter'?s+d:mode==='multiply'?s*d/255+d*(1-alpha):mode==='screen'?s+d*(1-s/255):s+d*(1-alpha);
|
|
check(Math.abs(actual[c]-expected)<2.5,JSON.stringify({pma,mode,c,actual:actual[c],expected}));
|
|
}
|
|
}
|
|
raster.dispose();URL.revokeObjectURL(image.src);
|
|
// Different per-vertex colors in the same batch must interpolate separately.
|
|
const white=canvas();white.fillStyle='white';white.fillRect(0,0,32,32);
|
|
const gradient=canvas(),g=api.createRasterizer(gradient);g.begin();
|
|
g.triangle(white.canvas,0,0,0,0,32,0,1,0,0,32,0,1,[1,0,0,1,0,1,0,1,0,0,1,1]);g.end();
|
|
const pixel=gradient.getImageData(8,8,1,1).data;
|
|
check(pixel[0]>100&&pixel[1]>50&&pixel[2]>50,'per-vertex color interpolation');g.dispose();
|
|
document.getElementById('result').textContent='PASS: direct PMA samples, straight/PMA toggles, light/dark tint, four blend modes and per-vertex colors';
|
|
}catch(e){document.getElementById('result').textContent='FAIL '+e.stack}})();
|
|
</script>
|