upng-js
Advanced tools
Comparing version 1.0.1 to 2.0.0
{ | ||
"name": "upng-js", | ||
"description": "Fast and advanced PNG encoder/decoder", | ||
"version": "1.0.1", | ||
"description": "Small, fast and advanced PNG / APNG encoder and decoder", | ||
"version": "2.0.0", | ||
"homepage": "https://github.com/photopea/UPNG.js", | ||
@@ -18,2 +18,3 @@ "author": "photopea (https://github.com/photopea)", | ||
"png", | ||
"apng", | ||
"image", | ||
@@ -20,0 +21,0 @@ "conversion" |
# UPNG.js | ||
A small, fast and advanced PNG encoder and decoder. It is the main PNG engine for [Photopea image editor](https://www.photopea.com). | ||
A small, fast and advanced PNG / APNG encoder and decoder. It is the main PNG engine for [Photopea image editor](https://www.photopea.com). | ||
* [Examples of PNGs minified by UPNG.js](https://blog.photopea.com/png-minifier-inside-photopea.html#examples) | ||
* [Try UPNG.js in Photopea](https://www.photopea.com) - open an image and press File - Save for web, play with the Quality | ||
* [UPNG.Photopea.com](//upng.photopea.com) - a separate minifier app, that uses UPNG.js | ||
* [UPNG.Photopea.com](http://upng.photopea.com) - a separate minifier app, that uses UPNG.js | ||
* Support us by [making a donation](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=ivan%2ekuckir%40gmail%2ecom&lc=CZ&item_name=UPNG%2ejs¤cy_code=USD&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted). | ||
@@ -17,6 +17,9 @@ | ||
#### `UPNG.encode(rgba, w, h, cnum)` | ||
* `rgba`: ArrayBuffer containing the pixel data (RGBA, 8 bits per channel) | ||
UPNG.js supports APNG and the interface expects "frames". Regular PNG is just a single-frame animation (single-item array). | ||
#### `UPNG.encode(imgs, w, h, cnum, [dels])` | ||
* `imgs`: array of frames. A frame is an ArrayBuffer containing the pixel data (RGBA, 8 bits per channel) | ||
* `w`, `h` : width and height of the image | ||
* `cnum`: number of colors in the result; 0: all colors (lossless PNG) | ||
* `dels`: array of delays for each frame (only when 2 or more frames) | ||
* returns an ArrayBuffer with binary data of a PNG file | ||
@@ -39,2 +42,3 @@ | ||
* * `ctype`: color type of the file (Truecolor, Grayscale, Palette ...) | ||
* * `frames`: additional info about frames (frame delays etc.) | ||
* * `tabs`: additional chunks of the PNG file | ||
@@ -47,8 +51,8 @@ * * `data`: pixel data of the image | ||
* `img`: PNG image object (returned by UPNG.decode()) | ||
* returns Uint8Array of the image in RGBA format, 8 bits per channel (ready to use in ctx.putImageData() etc.) | ||
* returns an array of frames. A frame is ArrayBuffer of the image in RGBA format, 8 bits per channel. | ||
### Example | ||
var img = UPNG.decode(buff); // put ArrayBuffer into UPNG.decode | ||
var rgba = UPNG.toRGBA8(img).buffer; // UPNG.toRGBA8 returns Uint8Array, size: width * height * 4 bytes. | ||
var img = UPNG.decode(buff); // put ArrayBuffer of the PNG file into UPNG.decode | ||
var rgba = UPNG.toRGBA8(img)[0]; // UPNG.toRGBA8 returns array of frames, size: width * height * 4 bytes. | ||
PNG format uses the Inflate algorithm. Right now, UPNG.js calls [Pako.js](https://github.com/nodeca/pako) for the Inflate and Deflate method. |
458
UPNG.js
@@ -5,21 +5,48 @@ ;(function(){ | ||
// Make available for import by `require()` | ||
if (typeof module == "object") {module.exports = UPNG;} | ||
else {window.UPNG = UPNG;} | ||
var pako; | ||
if (typeof require == "function") {pako = require("pako");} | ||
else {pako = window.pako;} | ||
if (typeof module == "object") {module.exports = UPNG;} else {window.UPNG = UPNG;} | ||
if (typeof require == "function") {pako = require("pako");} else {pako = window.pako;} | ||
function log() { if (typeof process=="undefined" || process.env.NODE_ENV=="development") console.log.apply(console, arguments); } | ||
(function(UPNG, pako){ | ||
UPNG.toRGBA8 = function(out) | ||
{ | ||
//console.log(out.ctype, out.depth); | ||
var w = out.width, h = out.height, area = w*h, bpp = UPNG.decode._getBPP(out); | ||
var w = out.width, h = out.height; | ||
if(out.tabs.acTL==null) return [UPNG.toRGBA8.decodeImage(out.data, w, h, out).buffer]; | ||
var frms = []; | ||
if(out.frames[0].data==null) out.frames[0].data = out.data; | ||
var img, empty = new Uint8Array(w*h*4); | ||
for(var i=0; i<out.frames.length; i++) | ||
{ | ||
var frm = out.frames[i]; | ||
var fx=frm.rect.x, fy=frm.rect.y, fw = frm.rect.width, fh = frm.rect.height; | ||
var fdata = UPNG.toRGBA8.decodeImage(frm.data, fw,fh, out); | ||
if(i==0) img = fdata; | ||
else if(frm.blend ==0) UPNG._copyTile(fdata, fw, fh, img, w, h, fx, fy, 0); | ||
else if(frm.blend ==1) UPNG._copyTile(fdata, fw, fh, img, w, h, fx, fy, 1); | ||
frms.push(img.buffer); img = img.slice(0); | ||
if (frm.dispose==0) {} | ||
else if(frm.dispose==1) UPNG._copyTile(empty, fw, fh, img, w, h, fx, fy, 0); | ||
else if(frm.dispose==2) { | ||
var pi = i-1; | ||
while(out.frames[pi].dispose==2) pi--; | ||
img = new Uint8Array(frms[pi]).slice(0); | ||
} | ||
} | ||
return frms; | ||
} | ||
UPNG.toRGBA8.decodeImage = function(data, w, h, out) | ||
{ | ||
var area = w*h, bpp = UPNG.decode._getBPP(out); | ||
var bpl = Math.ceil(w*bpp/8); // bytes per line | ||
var bf = new Uint8Array(area*4), bf32 = new Uint32Array(bf.buffer); | ||
var data = out.data, ctype = out.ctype, depth = out.depth; | ||
var ctype = out.ctype, depth = out.depth; | ||
var rs = UPNG._bin.readUshort; | ||
@@ -65,21 +92,19 @@ | ||
} | ||
else log("unsupported color type", ctype); | ||
return bf; | ||
} | ||
UPNG.encode = function(buff, w, h, ps) | ||
UPNG.encode = function(bufs, w, h, ps, dels) | ||
{ | ||
if(ps==null) ps=0; | ||
var img = new Uint8Array(buff); | ||
var data = new Uint8Array(img.length+100); | ||
var data = new Uint8Array(bufs[0].byteLength*bufs.length+100); | ||
var wr=[0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]; | ||
for(var i=0; i<8; i++) data[i]=wr[i]; | ||
var offset = 8, bin = UPNG._bin, crc = UPNG.crc.crc; | ||
var offset = 8, bin = UPNG._bin, crc = UPNG.crc.crc, wUi = bin.writeUint, wUs = bin.writeUshort, wAs = bin.writeASCII; | ||
var nimg = UPNG.encode.compress(img, w, h, ps); | ||
var nimg = UPNG.encode.compressPNG(bufs, w, h, ps); | ||
bin.writeUint (data,offset, 13); offset+=4; | ||
bin.writeASCII(data,offset,"IHDR"); offset+=4; | ||
bin.writeUint (data,offset,w); offset+=4; | ||
bin.writeUint (data,offset,h); offset+=4; | ||
wUi(data,offset, 13); offset+=4; | ||
wAs(data,offset,"IHDR"); offset+=4; | ||
wUi(data,offset,w); offset+=4; | ||
wUi(data,offset,h); offset+=4; | ||
data[offset] = nimg.depth; offset++; // depth | ||
@@ -90,15 +115,23 @@ data[offset] = nimg.ctype; offset++; // ctype | ||
data[offset] = 0; offset++; // interlace | ||
bin.writeUint (data,offset,crc(data,offset-17,17)); offset+=4; // crc | ||
wUi(data,offset,crc(data,offset-17,17)); offset+=4; // crc | ||
// 9 bytes to say, that it is sRGB | ||
bin.writeUint (data,offset, 1); offset+=4; | ||
bin.writeASCII(data,offset,"sRGB"); offset+=4; | ||
wUi(data,offset, 1); offset+=4; | ||
wAs(data,offset,"sRGB"); offset+=4; | ||
data[offset] = 1; offset++; | ||
bin.writeUint (data,offset,crc(data,offset-5,5)); offset+=4; // crc | ||
wUi(data,offset,crc(data,offset-5,5)); offset+=4; // crc | ||
var anim = bufs.length>1; | ||
if(anim) { | ||
wUi(data,offset, 8); offset+=4; | ||
wAs(data,offset,"acTL"); offset+=4; | ||
wUi(data,offset, bufs.length); offset+=4; | ||
wUi(data,offset, 0); offset+=4; | ||
wUi(data,offset,crc(data,offset-12,12)); offset+=4; // crc | ||
} | ||
if(nimg.ctype==3) { | ||
var dl = nimg.plte.length; | ||
bin.writeUint (data,offset, dl*3); offset+=4; | ||
bin.writeASCII(data,offset,"PLTE"); offset+=4; | ||
wUi(data,offset, dl*3); offset+=4; | ||
wAs(data,offset,"PLTE"); offset+=4; | ||
for(var i=0; i<dl; i++){ | ||
@@ -109,58 +142,147 @@ var ti=i*3, c=nimg.plte[i], r=(c)&255, g=(c>>8)&255, b=(c>>16)&255; | ||
offset+=dl*3; | ||
bin.writeUint (data,offset,crc(data,offset-dl*3-4,dl*3+4)); offset+=4; // crc | ||
wUi(data,offset,crc(data,offset-dl*3-4,dl*3+4)); offset+=4; // crc | ||
if(nimg.gotAlpha) { | ||
bin.writeUint (data,offset, dl); offset+=4; | ||
bin.writeASCII(data,offset,"tRNS"); offset+=4; | ||
wUi(data,offset, dl); offset+=4; | ||
wAs(data,offset,"tRNS"); offset+=4; | ||
for(var i=0; i<dl; i++) data[offset+i]=(nimg.plte[i]>>24)&255; | ||
offset+=dl; | ||
bin.writeUint (data,offset,crc(data,offset-dl-4,dl+4)); offset+=4; // crc | ||
wUi(data,offset,crc(data,offset-dl-4,dl+4)); offset+=4; // crc | ||
} | ||
} | ||
var fi = 0; | ||
for(var j=0; j<nimg.frames.length; j++) | ||
{ | ||
var fr = nimg.frames[j]; | ||
if(anim) { | ||
wUi(data,offset, 26); offset+=4; | ||
wAs(data,offset,"fcTL"); offset+=4; | ||
wUi(data, offset, fi++); offset+=4; | ||
wUi(data, offset, fr.rect.width ); offset+=4; | ||
wUi(data, offset, fr.rect.height); offset+=4; | ||
wUi(data, offset, fr.rect.x); offset+=4; | ||
wUi(data, offset, fr.rect.y); offset+=4; | ||
wUs(data, offset, dels[j]); offset+=2; | ||
wUs(data, offset, 1000); offset+=2; | ||
data[offset] = fr.dispose; offset++; // dispose | ||
data[offset] = fr.blend ; offset++; // blend | ||
wUi(data,offset,crc(data,offset-30,30)); offset+=4; // crc | ||
} | ||
var imgd = fr.cimg, dl = imgd.length; | ||
wUi(data,offset, dl+(j==0?0:4)); offset+=4; | ||
var ioff = offset; | ||
wAs(data,offset,(j==0)?"IDAT":"fdAT"); offset+=4; | ||
if(j!=0) { wUi(data, offset, fi++); offset+=4; } | ||
for(var i=0; i<dl; i++) data[offset+i] = imgd[i]; | ||
offset += dl; | ||
wUi(data,offset,crc(data,ioff,offset-ioff)); offset+=4; // crc | ||
} | ||
var dl = nimg.data.length; | ||
bin.writeUint (data,offset, dl); offset+=4; | ||
bin.writeASCII(data,offset,"IDAT"); offset+=4; | ||
for(var i=0; i<dl; i++) data[offset+i] = nimg.data[i]; | ||
offset += dl; | ||
bin.writeUint (data,offset,crc(data,offset-dl-4,dl+4)); offset+=4; // crc | ||
wUi(data,offset, 0); offset+=4; | ||
wAs(data,offset,"IEND"); offset+=4; | ||
wUi(data,offset,crc(data,offset-4,4)); offset+=4; // crc | ||
bin.writeUint (data,offset, 0); offset+=4; | ||
bin.writeASCII(data,offset,"IEND"); offset+=4; | ||
bin.writeUint (data,offset,crc(data,offset-4,4)); offset+=4; // crc | ||
return data.buffer.slice(0,offset); | ||
} | ||
UPNG.encode.compress = function(img, w, h, ps) | ||
UPNG.encode.compressPNG = function(bufs, w, h, ps) | ||
{ | ||
if(ps!=0) img = UPNG.quantize(img, w, h, ps); | ||
var out = UPNG.encode.compress(bufs, w, h, ps, false); | ||
for(var i=0; i<bufs.length; i++) { | ||
var frm = out.frames[i], nw=frm.rect.width, nh=frm.rect.height, bpl=frm.bpl, bpp=frm.bpp; | ||
var fdata = new Uint8Array(nh*bpl+nh); | ||
frm.cimg = UPNG.encode._filterZero(frm.img,nh,bpp,bpl,fdata); | ||
} | ||
return out; | ||
} | ||
var ctype = 6, depth = 8, plte=[], bpp = 4, bpl = 4*w; | ||
var img32 = new Uint32Array(img.buffer); | ||
var gotAlpha=false, cmap=[]; | ||
for(var i=0; i<img.length; i+=4) { | ||
var c = img32[i>>2]; if(plte.length<600 && cmap[c]==null) { cmap[c]=plte.length; plte.push(c); } | ||
if(img[i+3]!=255) gotAlpha = true; | ||
UPNG.encode.compress = function(bufs, w, h, ps, forGIF) | ||
{ | ||
if(ps!=0) bufs = UPNG.quantize(bufs, ps-1, forGIF); | ||
var ctype = 6, depth = 8, plte=[0], bpp = 4; | ||
var gotAlpha=false, cmap={}; cmap[0]=0; | ||
for(var j=0; j<bufs.length; j++) // when not quantized, other frames can contain colors, that are not in an initial frame | ||
{ | ||
var img = new Uint8Array(bufs[j]), img32 = new Uint32Array(img.buffer); | ||
var pr32 = (j==0 ? null : new Uint32Array(bufs[j-1])); | ||
for(var i=0; i<img.length; i+=4) { | ||
var c = img32[i>>2]; if((j==0 || c!=pr32[i>>2]) && plte.length<600 && cmap[c]==null) { cmap[c]=plte.length; plte.push(c); } | ||
gotAlpha = gotAlpha || (img[i+3]!=255); | ||
} | ||
} | ||
var cc=plte.length; | ||
var brute = forGIF; | ||
if(!gotAlpha) brute = false; //console.log(gotAlpha, brute); | ||
var cc=plte.length; //console.log(cc); | ||
if(cc<=256) { | ||
if(cc<= 2) depth=1; else if(cc<= 4) depth=2; else if(cc<=16) depth=4; else depth=8; | ||
bpl = Math.ceil(depth*w/8), nimg = new Uint8Array(bpl*h); | ||
for(var y=0; y<h; y++) { var i=y*bpl, ii=y*w; | ||
if(depth==1) for(var x=0; x<w; x++) nimg[i+(x>>3)] |= (cmap[img32[ii+x]]<<(7-(x&7) )); | ||
if(depth==2) for(var x=0; x<w; x++) nimg[i+(x>>2)] |= (cmap[img32[ii+x]]<<(6-(x&3)*2)); | ||
if(depth==4) for(var x=0; x<w; x++) nimg[i+(x>>1)] |= (cmap[img32[ii+x]]<<(4-(x&1)*4)); | ||
if(depth==8) for(var x=0; x<w; x++) nimg[i+ x ] = cmap[img32[ii+x]]; | ||
if(forGIF) depth=8; | ||
gotAlpha = true; | ||
} | ||
var frms = []; | ||
for(var j=0; j<bufs.length; j++) | ||
{ | ||
var cimg = new Uint8Array(bufs[j]), cimg32 = new Uint32Array(cimg.buffer); | ||
var nx=0, ny=0, nw=w, nh=h, blend=0; | ||
if(j!=0 && !brute) { | ||
var tlim = (forGIF || j==1 || frms[frms.length-2].dispose==2)?1:2, tstp = 0, tarea = 1e9; | ||
for(var it=0; it<tlim; it++) | ||
{ | ||
var pimg = new Uint8Array(bufs[j-1-it]), p32 = new Uint32Array(bufs[j-1-it]); | ||
var mix=w,miy=h,max=-1,may=-1; | ||
for(var y=0; y<h; y++) for(var x=0; x<w; x++) { | ||
var i = y*w+x; | ||
if(cimg32[i]!=p32[i]) { | ||
if(x<mix) mix=x; if(x>max) max=x; | ||
if(y<miy) miy=y; if(y>may) may=y; | ||
} | ||
} | ||
var sarea = (max==-1) ? 1 : (max-mix+1)*(may-miy+1); | ||
if(sarea<tarea) { | ||
tarea = sarea; tstp = it; | ||
if(max==-1) { nx=ny=0; nw=nh=1; } | ||
else { nx = mix; ny = miy; nw = max-mix+1; nh = may-miy+1; } | ||
} | ||
} | ||
var pimg = new Uint8Array(bufs[j-1-tstp]); | ||
if(tstp==1) frms[frms.length-1].dispose = 2; | ||
var nimg = new Uint8Array(nw*nh*4), nimg32 = new Uint32Array(nimg.buffer); | ||
UPNG. _copyTile(pimg,w,h, nimg,nw,nh, -nx,-ny, 0); | ||
if(UPNG._copyTile(cimg,w,h, nimg,nw,nh, -nx,-ny, 3)) { | ||
UPNG._copyTile(cimg,w,h, nimg,nw,nh, -nx,-ny, 2); blend = 1; | ||
} | ||
else { | ||
UPNG._copyTile(cimg,w,h, nimg,nw,nh, -nx,-ny, 0); blend = 0; | ||
} | ||
cimg = nimg; cimg32 = new Uint32Array(cimg.buffer); | ||
} | ||
img=nimg; ctype=3; bpp=1; | ||
var bpl = 4*nw; | ||
if(cc<=256) { | ||
bpl = Math.ceil(depth*nw/8); | ||
var nimg = new Uint8Array(bpl*nh); | ||
for(var y=0; y<nh; y++) { var i=y*bpl, ii=y*nw; | ||
if(depth==1) for(var x=0; x<nw; x++) nimg[i+(x>>3)] |= (cmap[cimg32[ii+x]]<<(7-(x&7) )); | ||
if(depth==2) for(var x=0; x<nw; x++) nimg[i+(x>>2)] |= (cmap[cimg32[ii+x]]<<(6-(x&3)*2)); | ||
if(depth==4) for(var x=0; x<nw; x++) nimg[i+(x>>1)] |= (cmap[cimg32[ii+x]]<<(4-(x&1)*4)); | ||
if(depth==8) for(var x=0; x<nw; x++) nimg[i+ x ] = cmap[cimg32[ii+x]]; | ||
} | ||
cimg=nimg; ctype=3; bpp=1; | ||
} | ||
else if(gotAlpha==false && bufs.length==1) { // some next "reduced" frames may contain alpha for blending | ||
var nimg = new Uint8Array(nw*nh*3), area=nw*nh; | ||
for(var i=0; i<area; i++) { var ti=i*3, qi=i*4; nimg[ti]=cimg[qi]; nimg[ti+1]=cimg[qi+1]; nimg[ti+2]=cimg[qi+2]; } | ||
cimg=nimg; ctype=2; bpp=3; bpl=3*nw; | ||
} | ||
frms.push({rect:{x:nx,y:ny,width:nw,height:nh}, img:cimg, bpl:bpl, bpp:bpp, blend:blend, dispose:brute?1:0}); | ||
} | ||
else if(gotAlpha==false) { | ||
var nimg = new Uint8Array(w*h*3), area=w*h; | ||
for(var i=0; i<area; i++) { var ti=i*3, qi=i*4; nimg[ti]=img[qi]; nimg[ti+1]=img[qi+1]; nimg[ti+2]=img[qi+2]; } | ||
img=nimg; ctype=2; bpp=3; bpl=3*w; | ||
} | ||
var data = new Uint8Array(w*h*bpp+h); | ||
return {ctype:ctype, depth:depth, plte:plte, gotAlpha:gotAlpha, data: UPNG.encode._filterZero(img,h,bpp,bpl,data) }; | ||
return {ctype:ctype, depth:depth, plte:plte, gotAlpha:gotAlpha, frames:frms }; | ||
} | ||
@@ -177,3 +299,2 @@ | ||
for(var i=0; i<fls.length; i++) if(fls[i].length<tsize) { ti=i; tsize=fls[i].length; } | ||
//log("top filter", ti); | ||
return fls[ti]; | ||
@@ -225,9 +346,10 @@ } | ||
UPNG.quantize = function(img, w, h, ps) | ||
{ | ||
var nimg = new Uint8Array(img.length), pind = new Uint16Array(w*h), area=w*h, edist=UPNG.quantize.dist; | ||
for(var i=0; i<area; i++) { | ||
var qi=i<<2, a=img[qi+3]/255; | ||
nimg[qi+0] = img[qi+0]*a; nimg[qi+1] = img[qi+1]*a; nimg[qi+2] = img[qi+2]*a; nimg[qi+3] = img[qi+3]; | ||
} | ||
UPNG.quantize = function(bufs, ps, roundAlpha) | ||
{ | ||
var imgs = []; | ||
for(var i=0; i<bufs.length; i++) imgs.push(UPNG.quantize.alphaMul(new Uint8Array(bufs[i]), roundAlpha)); | ||
var nimg = imgs[0]; | ||
var area=nimg.length>>2; | ||
var plte=[], used=[], pr=0, plim = Math.max(100, 10*ps); | ||
@@ -246,3 +368,3 @@ while(true) { | ||
} | ||
if(pr==0 && plte.length<=ps) return img; | ||
if(pr==0 && plte.length<=ps) return bufs; | ||
plte.sort(function(a,b) {return b.occ-a.occ;}); | ||
@@ -256,41 +378,71 @@ | ||
var icnt = Math.max(1, Math.min(10, Math.floor(1024/ps))); | ||
var pind = new Uint16Array(area); | ||
for(var it=0; it<icnt; it++) | ||
{ | ||
var hist=new Uint32Array(ps), nplt=new Uint32Array(ps*4); | ||
var ndst=new Uint32Array(ps), nind=new Uint32Array(ps ); | ||
for(var i=0; i<ps; i++) { var qi=i<<2; | ||
var r=plte[qi], g=plte[qi+1], b=plte[qi+2], a=plte[qi+3]; | ||
var ci=0; cd=1e9; | ||
for(var j=0; j<ps; j++) { if(j==i) continue; | ||
var dst = edist(r,g,b,a,plte,j<<2); | ||
if(dst<cd) { ci=j; cd=dst; } | ||
} | ||
ndst[i]=cd; nind[i]=ci; | ||
} | ||
for(var i=0; i<area; i++) { var qi=i<<2; | ||
var r=nimg[qi], g=nimg[qi+1], b=nimg[qi+2], a=nimg[qi+3]; | ||
var ci=0, cd=1e9; | ||
ci=pind[i]; cd=edist(r,g,b,a,plte,ci<<2); if(cd<=(ndst[ci]>>1)) {} else | ||
for(var j=0; j<ps; j++) { | ||
var dst = edist(r,g,b,a,plte,j<<2); | ||
if(dst<cd) { ci=j; cd=dst; | ||
if(dst<=(ndst[ci]>>1)) break; | ||
var dst = edist(r,g,b,a,plte,nind[j]<<2); | ||
if(dst<=(ndst[ci]>>1)) { ci=nind[j]; break; } | ||
} | ||
} | ||
pind[i]=ci; hist[ci]++; var qci=ci<<2; | ||
nplt[qci]+=r; nplt[qci+1]+=g; nplt[qci+2]+=b; nplt[qci+3]+=a; | ||
} | ||
for(var i=0; i<ps; i++) { var qi=i<<2, den=1/hist[i]; | ||
plte[qi]=nplt[qi]*den; plte[qi+1]=nplt[qi+1]*den; plte[qi+2]=nplt[qi+2]*den; plte[qi+3]=nplt[qi+3]*den; | ||
} | ||
UPNG.quantize.kMeans(nimg, plte, pind, ps, true); | ||
} | ||
UPNG.quantize.applyPalette(nimg, plte, pind); | ||
// remap remaining frames according to palette | ||
for(var i=1; i<imgs.length; i++) { | ||
UPNG.quantize.kMeans(imgs[i], plte, pind, ps, false); // "classify" into pind | ||
UPNG.quantize.applyPalette(imgs[i], plte, pind); // remap according to pind | ||
} | ||
//UPNG.quantize.dither(nimg, w,h, pind,plte, ps); // I think that (current) dithering is not worth it | ||
for(var i=0; i<bufs.length; i++) imgs[i] = imgs[i].buffer; | ||
return imgs; | ||
} | ||
UPNG.quantize.alphaMul = function(img, roundA) { | ||
var nimg = new Uint8Array(img.length), area = img.length>>2; | ||
for(var i=0; i<area; i++) { | ||
var qi=i<<2, ci=pind[i], qci=ci<<2, ia = plte[qci+3]==0 ? 0 : 255/plte[qci+3]; | ||
nimg[qi+0] = plte[qci+0]*ia; nimg[qi+1] = plte[qci+1]*ia; nimg[qi+2] = plte[qci+2]*ia; nimg[qi+3] = plte[qci+3]; | ||
var qi=i<<2, ia=img[qi+3]; | ||
if(roundA) ia = ((ia<128))?0:255; | ||
var a = ia*(1/255); | ||
nimg[qi+0] = img[qi+0]*a; nimg[qi+1] = img[qi+1]*a; nimg[qi+2] = img[qi+2]*a; nimg[qi+3] = ia; | ||
} | ||
return nimg; | ||
} | ||
UPNG.quantize.applyPalette = function(nimg, plte, pind) | ||
{ | ||
var area = pind.length, rnd=Math.round; | ||
for(var i=0; i<area; i++) { | ||
var qi=i<<2, ci=pind[i], qci=ci<<2, ia = plte[qci+3]==0 ? 0 : 255/plte[qci+3]; | ||
nimg[qi+0] = rnd(plte[qci+0]*ia); nimg[qi+1] = rnd(plte[qci+1]*ia); nimg[qi+2] = rnd(plte[qci+2]*ia); nimg[qi+3] = plte[qci+3]; | ||
} | ||
} | ||
UPNG.quantize.kMeans = function(nimg, plte, pind, ps, update) | ||
{ | ||
var area = pind.length, rnd=Math.round, edist=UPNG.quantize.dist; | ||
var ndst=new Uint32Array(ps), nind=new Uint32Array(ps ); // for each color, find the nearest color (in palette) | ||
for(var i=0; i<ps; i++) { var qi=i<<2; | ||
var r=plte[qi], g=plte[qi+1], b=plte[qi+2], a=plte[qi+3]; | ||
var ci=0; cd=1e9; | ||
for(var j=0; j<ps; j++) { if(j==i) continue; | ||
var dst = edist(r,g,b,a,plte,j<<2); | ||
if(dst<cd) { ci=j; cd=dst; } | ||
} | ||
ndst[i]=cd; nind[i]=ci; | ||
} | ||
var hist=new Uint32Array(ps), nplt=new Uint32Array(ps*4); // classify each pixel | ||
for(var i=0; i<area; i++) { var qi=i<<2; | ||
var r=nimg[qi], g=nimg[qi+1], b=nimg[qi+2], a=nimg[qi+3]; | ||
var ci=0, cd=1e9; | ||
ci=pind[i]; cd=edist(r,g,b,a,plte,ci<<2); if(cd<=(ndst[ci]>>1)) {} else | ||
for(var j=0; j<ps; j++) { | ||
var dst = edist(r,g,b,a,plte,j<<2); | ||
if(dst<cd) { ci=j; cd=dst; | ||
if(dst<=(ndst[ci]>>1)) break; | ||
var dst = edist(r,g,b,a,plte,nind[j]<<2); | ||
if(dst<=(ndst[ci]>>1)) { ci=nind[j]; break; } | ||
} | ||
} | ||
pind[i]=ci; hist[ci]++; var qci=ci<<2; | ||
nplt[qci]+=r; nplt[qci+1]+=g; nplt[qci+2]+=b; nplt[qci+3]+=a; | ||
} | ||
if(update) // update palette | ||
for(var i=0; i<ps; i++) { var qi=i<<2, den=1/hist[i]; | ||
plte[qi]=rnd(nplt[qi]*den); plte[qi+1]=rnd(nplt[qi+1]*den); plte[qi+2]=rnd(nplt[qi+2]*den); plte[qi+3]=rnd(nplt[qi+3]*den); | ||
} | ||
} | ||
UPNG.quantize.dist = function(r,g,b,a,ba,bi) | ||
@@ -326,14 +478,18 @@ { | ||
UPNG.decode = function(buff) | ||
{ | ||
var data = new Uint8Array(buff), offset = 8, bin = UPNG._bin, rUs = bin.readUshort; | ||
var out = {tabs:{}}; | ||
var data = new Uint8Array(buff), offset = 8, bin = UPNG._bin, rUs = bin.readUshort, rUi = bin.readUint; | ||
var out = {tabs:{}, frames:[]}; | ||
var dd = new Uint8Array(data.length), doff = 0; // put all IDAT data into it | ||
var fd, foff = 0; // frames | ||
while(true) | ||
{ | ||
var len = bin.readUint(data, offset); offset += 4; | ||
var len = bin.readUint(data, offset); offset += 4; | ||
var type = bin.readASCII(data, offset, 4); offset += 4; | ||
//log(offset, len, type); | ||
//log(type,len); | ||
if (type=="IHDR") { UPNG.decode._IHDR(data, offset, out); } | ||
@@ -344,2 +500,20 @@ else if(type=="IDAT") { | ||
} | ||
else if(type=="acTL") { | ||
out.tabs[type] = { num_frames:rUi(data, offset), num_plays:rUi(data, offset+4) }; | ||
fd = new Uint8Array(data.length); | ||
} | ||
else if(type=="fcTL") { | ||
if(foff!=0) { var fr = out.frames[out.frames.length-1]; | ||
fr.data = UPNG.decode._decompress(out, fd.slice(0,foff), fr.rect.width, fr.rect.height); foff=0; | ||
} | ||
var rct = {x:rUi(data, offset+12),y:rUi(data, offset+16),width:rUi(data, offset+4),height:rUi(data, offset+8)}; | ||
var del = rUs(data, offset+22); del = rUs(data, offset+20) / (del==0?100:del); | ||
var frm = {rect:rct, delay:Math.round(del*1000), dispose:data[offset+24], blend:data[offset+25]}; | ||
//console.log(frm); | ||
out.frames.push(frm); | ||
} | ||
else if(type=="fdAT") { | ||
for(var i=0; i<len-4; i++) fd[foff+i] = data[offset+i+4]; | ||
foff += len-4; | ||
} | ||
else if(type=="pHYs") { | ||
@@ -369,3 +543,3 @@ out.tabs[type] = [bin.readUint(data, offset), bin.readUint(data, offset+4), data[offset+8]]; | ||
var tkeyw = bin.readUTF8(data, off, nz-off); off = nz + 1; | ||
var text = bin.readUTF8(data, off, len-(off-offset)); | ||
var text = bin.readUTF8(data, off, len-(off-offset)); | ||
out.tabs[type][keyw] = text; | ||
@@ -384,3 +558,3 @@ } | ||
else if(out.ctype==2) out.tabs[type] = [ rUs(data,offset),rUs(data,offset+2),rUs(data,offset+4) ]; | ||
else log("tRNS for unsupported color type",out.ctype, len); | ||
//else console.log("tRNS for unsupported color type",out.ctype, len); | ||
} | ||
@@ -396,12 +570,6 @@ else if(type=="gAMA") out.tabs[type] = bin.readUint(data, offset)/100000; | ||
else if(type=="IEND") { | ||
if(out.compress ==0) dd = UPNG.decode._inflate(dd); | ||
else log("unsupported compression method:", out.interlace); | ||
if(out.filter!=0) log("unsupported filter method:", out.filter); | ||
if(out.interlace==0) out.data = UPNG.decode._filterZero(dd, out, 0, out.width, out.height); | ||
else if(out.interlace==1) out.data = UPNG.decode._readInterlace(dd, out); | ||
else log("unsupported interlace method:", out.interlace); | ||
break; | ||
if(foff!=0) { var fr = out.frames[out.frames.length-1]; | ||
fr.data = UPNG.decode._decompress(out, fd.slice(0,foff), fr.rect.width, fr.rect.height); foff=0; | ||
} | ||
out.data = UPNG.decode._decompress(out, dd, out.width, out.height); break; | ||
} | ||
@@ -416,2 +584,10 @@ else { log("unknown chunk type", type, len); } | ||
UPNG.decode._decompress = function(out, dd, w, h) { | ||
if(out.compress ==0) dd = UPNG.decode._inflate(dd); | ||
if (out.interlace==0) dd = UPNG.decode._filterZero(dd, out, 0, w, h); | ||
else if(out.interlace==1) dd = UPNG.decode._readInterlace(dd, out); | ||
return dd; | ||
} | ||
UPNG.decode._inflate = function(data) { return pako["inflate"](data); } | ||
@@ -477,3 +653,2 @@ | ||
var noc = [1,null,3,1,2,null,4][out.ctype]; | ||
if(noc==null) log("unsupported color type", out.ctype); | ||
return noc * out.depth; | ||
@@ -553,4 +728,41 @@ } | ||
} | ||
UPNG._copyTile = function(sb, sw, sh, tb, tw, th, xoff, yoff, mode) | ||
{ | ||
var w = Math.min(sw,tw), h = Math.min(sh,th); | ||
var si=0, ti=0; | ||
for(var y=0; y<h; y++) | ||
for(var x=0; x<w; x++) | ||
{ | ||
if(xoff>=0 && yoff>=0) { si = (y*sw+x)<<2; ti = (( yoff+y)*tw+xoff+x)<<2; } | ||
else { si = ((-yoff+y)*sw-xoff+x)<<2; ti = (y*tw+x)<<2; } | ||
if (mode==0) { tb[ti] = sb[si]; tb[ti+1] = sb[si+1]; tb[ti+2] = sb[si+2]; tb[ti+3] = sb[si+3]; } | ||
else if(mode==1) { | ||
var fa = sb[si+3]*(1/255), fr=sb[si]*fa, fg=sb[si+1]*fa, fb=sb[si+2]*fa; | ||
var ba = tb[ti+3]*(1/255), br=tb[ti]*ba, bg=tb[ti+1]*ba, bb=tb[ti+2]*ba; | ||
var ifa=1-fa, oa = fa+ba*ifa, ioa = (oa==0?0:1/oa); | ||
tb[ti+3] = 255*oa; | ||
tb[ti+0] = (fr+br*ifa)*ioa; | ||
tb[ti+1] = (fg+bg*ifa)*ioa; | ||
tb[ti+2] = (fb+bb*ifa)*ioa; | ||
} | ||
else if(mode==2){ // copy only differences, otherwise zero | ||
var fa = sb[si+3], fr=sb[si], fg=sb[si+1], fb=sb[si+2]; | ||
var ba = tb[ti+3], br=tb[ti], bg=tb[ti+1], bb=tb[ti+2]; | ||
if(fa==ba && fr==br && fg==bg && fb==bb) { tb[ti]=0; tb[ti+1]=0; tb[ti+2]=0; tb[ti+3]=0; } | ||
else { tb[ti]=fr; tb[ti+1]=fg; tb[ti+2]=fb; tb[ti+3]=fa; } | ||
} | ||
else if(mode==3){ // check if can be blended | ||
var fa = sb[si+3], fr=sb[si], fg=sb[si+1], fb=sb[si+2]; | ||
var ba = tb[ti+3], br=tb[ti], bg=tb[ti+1], bb=tb[ti+2]; | ||
if(fa==ba && fr==br && fg==bg && fb==bb) continue; | ||
//if(fa!=255 && ba!=0) return false; | ||
if(fa<220 && ba>20) return false; | ||
} | ||
} | ||
return true; | ||
} | ||
})(UPNG, pako); | ||
})(); | ||
})(); |
34577
681
56