/** * pngdecode.js - 非圧縮png画像を読み込む * * 依存するライブラリ:なし * 影響を受けるプロパティ:なし */ function(data) { var create_stream = function(array) { if (!array) array = new Array(); array.mark = 0; array.crc = 0xffffffff; array.crctable = []; for(var i = 0; i < 256; i++) { var u = i>>>0; for(var j = 0; j < 8; j++) { if(u & 0x1) u = ((u >>> 1) ^ 0xEDB88320)>>>0; else u >>>= 1; } array.crctable[i] = u>>>0; } array.append = function(obj) { if (typeof obj == 'object' && typeof obj.length == 'number') { for (var i = 0; i < obj.length; i++) this.push(obj[i]); } else { this.push(obj); } }; array.read = function(len) { if (typeof len != 'number') len = 1; if (this.mark >= this.length) throw new Error("no more data"); if (len == 1) { this.crc = ((this.crc >>> 8) ^ this.crctable[(this[this.mark] ^ this.crc) & 0xFF])>>>0; return this[this.mark++]; } else { result = []; for (var i = 0; i < len; i++) result.push(this.read()); return result; } }; array.readShort = function() { return ((this.read()<<8) + (this.read())); }; array.readInt = function() { return ((this.read()<<24) + (this.read()<<16) + (this.read()<<8) + (this.read()))>>>0; }; array.readString = function(len) { var str = ''; for (var i = 0; i < len; i++) { str += String.fromCharCode(this.read()); } return str; }; array.readZipShort = function() { return ((this.read()) + (this.read()<<8)); }; array.hasMore = function() { if (this.mark >= this.length) return false; else return true; }; return array; }; function defilter(width, height, bpp, literal) { var linesize = width * bpp + 1; for (var y = 0; y < height; y++) { var offset = y * linesize; switch(literal[offset]) { //filter type case 0: //None break; case 1: //Sub for (var x = 1; x <= width * bpp; x++) { var left = x < bpp + 1 ? 0 : literal[offset + x - bpp]; literal[offset + x] = (literal[offset + x] + left) & 0xff; } break; case 2: //Up for (var x = 1; x <= width * bpp; x++) { var above = y <= 0 ? 0 : literal[offset - linesize + x]; literal[offset + x] = (literal[offset + x] + above) & 0xff; } break; case 3: //Average for (var x = 1; x <= width * bpp; x++) { var left = x < bpp + 1 ? 0 : literal[offset + x - bpp]; var above = y <= 0 ? 0 : literal[offset - linesize + x]; literal[offset + x] = (literal[offset + x] + Math.floor((left + above)/2)) & 0xff; } break; case 4: for (var x = 1; x <= width * bpp; x++) { var left = x < bpp + 1 ? 0 : literal[offset + x - bpp]; var above = y <= 0 ? 0 : literal[offset - linesize + x]; var upleft = y <= 0 ? 0 : (x < bpp + 1 ? 0 : literal[offset - linesize + x - bpp]); literal[offset + x] = (literal[offset + x] + (function(a, b, c) { var p = (a + b - c); var pa = Math.abs(p - a); var pb = Math.abs(p - b); var pc = Math.abs(p - c); if (pa <= pb && pa <= pc) return a; else if (pb <= pc) return b; else return c; })(left, above, upleft)) & 0xff; } break; } } }; function rgba(width, height, bpp, color, literal) { var bytes = []; var linesize = width * bpp + 1; for (var y = 0; y < height; y++) { var offset = y * linesize; for (var x = 1; x <= width*bpp; x += bpp) { var rgba = 0; switch(color) { case 0: rgba = 0xff000000 + (literal[offset + x]<<16) + (literal[offset + x]<<8) + (literal[offset + x]); break; case 2: rgba = 0xff000000 + (literal[offset + x]<<16) + (literal[offset + x + 1]<<8) + (literal[offset + x + 2]); break; case 3: rgb = 0xff000000 + literal[offset + x]; //PLTE break; case 4: rgba = (literal[offset + x + 1]<<24) + (literal[offset + x]<<16) + (literal[offset + x]<<8) + literal[offset + x]; break; case 6: rgba = (literal[offset + x + 3]<<24) + (literal[offset + x]<<16) + (literal[offset + x + 1]<<8) + literal[offset + x + 2]; break; } bytes.push(rgba>>>0); } } return bytes; }; function decodeB64(data) { var stream = create_stream(); var seed = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; data.replace(/\s+/, '') .replace(/[a-zA-Z0-9\+\/]{1,4}/g, function(match) { var c = 0; for (var i = 0; i < match.length; i++) { c = (c << 6) + (seed.indexOf(match.substr(i,1)) & 0x3f); } switch(match.length) { case 4: stream.push((c>>16)&0xff); stream.push((c>>8)&0xff); stream.push(c&0xff); break; case 3: stream.push((c>>8)&0xff); stream.push(c&0xff); break; case 2: stream.push((c>>4)&0xff); } }); return stream; }; /** * Load png image data by BASE64 encoded string or array. */ this.load = function(data) { var pngstream; if (typeof data == 'string') pngstream = decodeB64(data); else pngstream = create_stream(data); //read header var header = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]; for (var i = 0; i < header.length; i++) { var b = pngstream.read(); if (b != header[i]) throw new Error("bad header"); } //read CHUNK var chunks = {}; var width = 0, height = 0, color = 0, bpp = 0; while (pngstream.hasMore()) { var chunk = {}; chunk.size = pngstream.readInt(); pngstream.crc = 0xffffffff; //reset crc chunk.type = pngstream.readString(4); chunk.data = {}; var stream = create_stream(); for (var i = 0; i < chunk.size; i++) stream.push(pngstream.read()); var crc = pngstream.crc; chunk.crc = pngstream.readInt() >>> 0; /* if (crc != chunk.crc) console.log("bad crc:" + ('00000000' + pngstream.crc.toString(16)).substr(-8,8) + "<=>" + ('00000000' + chunk.crc.toString(16)).substr(-8,8)); */ switch(chunk.type) { case 'IHDR': chunk.data.width = width = stream.readInt(); chunk.data.height = height = stream.readInt(); chunk.data.depth = stream.read(); chunk.data.color = color = stream.read(); chunk.data.compress = stream.read(); chunk.data.filter = stream.read(); chunk.data.interlace = stream.read(); chunks[chunk.type] = chunk; break; case 'IDAT': if (!chunks['IDAT']) { chunk.data = stream; chunks['IDAT'] = chunk; } else { chunks['IDAT'].data.append(stream); } break; default: chunk.data = stream; chunks[chunk.type] = chunk; } //console.log(chunk); } //read deflated data //http://www.aya.or.jp/~sanami/peace/memorial/code21-30.html var chunk = chunks['IDAT']; var stream = chunk.data; var b = stream.read(); chunk.data.CINFO = (b>>4)&0x0f; chunk.data.CM = b&0x0f; b = stream.read(); chunk.data.FLEVEL = (b>>6)&0x03; chunk.data.FDICT = (b>>5)&0x01; chunk.data.FCHECK = b&0x1f; b = stream.read(); chunk.data.BTYPE = (b>>1)&0x03; if (chunk.data.BTYPE != 0) throw new Error('Unsupported compression ' + chunk.data.BTYPE + '!'); chunk.data.BFINAL = b&0x01; chunk.data.LEN = stream.readZipShort(); chunk.data.NLEN = stream.readZipShort(); switch(color) { case 0: bpp = 1; break; case 2: bpp = 3; break; case 3: bpp = 1; break; //PLTE case 4: bpp = 2; break; case 6: bpp = 4; break; } chunk.data.LITERAL = stream.read(chunk.data.LEN); chunk.data.ADLER32 = stream.readInt(); //defilter defilter(width, height, bpp, chunk.data.LITERAL); //rgba chunks['IDAT'].data.IMAGE = rgba(width, height, bpp, color, chunk.data.LITERAL); return this.img = { width : chunks['IHDR'].data.width, height : chunks['IHDR'].data.height, data : chunks['IDAT'].data.IMAGE }; }; /** * Create rendered image DOM document. */ this.toDOM = function(scale) { if (typeof scale != 'number') scale = 1; var dot = function(x, y, color) { var p = canvas.appendChild(document.createElement('div')); p.style.position = 'absolute'; p.style.overflow = 'hidden'; p.style.width = scale + 'px'; p.style.height = scale + 'px'; p.style.left = (scale * x) + 'px'; p.style.top = (scale * y) + 'px'; var hex = ('000000' + (color&0xffffff).toString(16)); hex = hex.substr(hex.length - 6, 6); p.style.backgroundColor = '#' + hex; var opacity = (color>>>24)/0xff; p.style.opacity = opacity; p.style.filter = 'alpha(opacity=' + (opacity*100) + ')'; } var canvas = document.createElement('div'); canvas.className = canvas.styleClass = 'png-image'; canvas.style.overflow = 'hidden'; canvas.style.width = (scale * this.img.width) + 'px'; canvas.style.height = (scale * this.img.height) + 'px'; canvas.style.position = 'relative'; canvas.style.cssFloat = canvas.style.styleFloat = 'left'; for (var y = 0; y < this.img.height; y++) { for (var x = 0; x < this.img.width; x++) { dot(x, y, this.img.data[this.img.width * y + x]); } } return canvas; }; if (data) this.load(data); }