1 /**************************************************************************** 2 Copyright (c) 2011 Devon Govett 3 Copyright (c) 2008-2010 Ricardo Quesada 4 Copyright (c) 2011-2012 cocos2d-x.org 5 Copyright (c) 2013-2014 Chukong Technologies Inc. 6 7 http://www.cocos2d-x.org 8 9 10 Permission is hereby granted, free of charge, to any person obtaining a copy 11 of this software and associated documentation files (the "Software"), to deal 12 in the Software without restriction, including without limitation the rights 13 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 copies of the Software, and to permit persons to whom the Software is 15 furnished to do so, subject to the following conditions: 16 17 The above copyright notice and this permission notice shall be included in 18 all copies or substantial portions of the Software. 19 20 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 THE SOFTWARE. 27 ****************************************************************************/ 28 29 cc.PNGReader = cc.Class.extend({ 30 ctor:function(data){ 31 var chunkSize, colors, delayDen, delayNum, frame, i, index, key, section, ccshort, text, _i, _j, _ref; 32 this.data = data; 33 this.pos = 8; 34 this.palette = []; 35 this.imgData = []; 36 this.transparency = {}; 37 this.animation = null; 38 this.text = {}; 39 frame = null; 40 while (true) { 41 chunkSize = this.readUInt32(); 42 section = ((function() { 43 var _i, _results; 44 _results = []; 45 for (i = _i = 0; _i < 4; i = ++_i) { 46 _results.push(String.fromCharCode(this.data[this.pos++])); 47 } 48 return _results; 49 }).call(this)).join(''); 50 switch (section) { 51 case 'IHDR': 52 this.width = this.readUInt32(); 53 this.height = this.readUInt32(); 54 this.bits = this.data[this.pos++]; 55 this.colorType = this.data[this.pos++]; 56 this.compressionMethod = this.data[this.pos++]; 57 this.filterMethod = this.data[this.pos++]; 58 this.interlaceMethod = this.data[this.pos++]; 59 break; 60 case 'acTL': 61 this.animation = { 62 numFrames: this.readUInt32(), 63 numPlays: this.readUInt32() || Infinity, 64 frames: [] 65 }; 66 break; 67 case 'PLTE': 68 this.palette = this.read(chunkSize); 69 break; 70 case 'fcTL': 71 if (frame) { 72 this.animation.frames.push(frame); 73 } 74 this.pos += 4; 75 frame = { 76 width: this.readUInt32(), 77 height: this.readUInt32(), 78 xOffset: this.readUInt32(), 79 yOffset: this.readUInt32() 80 }; 81 delayNum = this.readUInt16(); 82 delayDen = this.readUInt16() || 100; 83 frame.delay = 1000 * delayNum / delayDen; 84 frame.disposeOp = this.data[this.pos++]; 85 frame.blendOp = this.data[this.pos++]; 86 frame.data = []; 87 break; 88 case 'IDAT': 89 case 'fdAT': 90 if (section === 'fdAT') { 91 this.pos += 4; 92 chunkSize -= 4; 93 } 94 data = (frame != null ? frame.data : void 0) || this.imgData; 95 for (i = _i = 0; 0 <= chunkSize ? _i < chunkSize : _i > chunkSize; i = 0 <= chunkSize ? ++_i : --_i) { 96 data.push(this.data[this.pos++]); 97 } 98 break; 99 case 'tRNS': 100 this.transparency = {}; 101 switch (this.colorType) { 102 case 3: 103 this.transparency.indexed = this.read(chunkSize); 104 ccshort = 255 - this.transparency.indexed.length; 105 if (ccshort > 0) { 106 for (i = _j = 0; 0 <= ccshort ? _j < ccshort : _j > ccshort; i = 0 <= ccshort ? ++_j : --_j) { 107 this.transparency.indexed.push(255); 108 } 109 } 110 break; 111 case 0: 112 this.transparency.grayscale = this.read(chunkSize)[0]; 113 break; 114 case 2: 115 this.transparency.rgb = this.read(chunkSize); 116 } 117 break; 118 case 'tEXt': 119 text = this.read(chunkSize); 120 index = text.indexOf(0); 121 key = String.fromCharCode.apply(String, text.slice(0, index)); 122 this.text[key] = String.fromCharCode.apply(String, text.slice(index + 1)); 123 break; 124 case 'IEND': 125 if (frame) { 126 this.animation.frames.push(frame); 127 } 128 this.colors = (function() { 129 switch (this.colorType) { 130 case 0: 131 case 3: 132 case 4: 133 return 1; 134 case 2: 135 case 6: 136 return 3; 137 } 138 }).call(this); 139 this.hasAlphaChannel = (_ref = this.colorType) === 4 || _ref === 6; 140 colors = this.colors + (this.hasAlphaChannel ? 1 : 0); 141 this.pixelBitlength = this.bits * colors; 142 this.colorSpace = (function() { 143 switch (this.colors) { 144 case 1: 145 return 'DeviceGray'; 146 case 3: 147 return 'DeviceRGB'; 148 } 149 }).call(this); 150 if(Uint8Array != Array) 151 this.imgData = new Uint8Array(this.imgData); 152 return; 153 default: 154 this.pos += chunkSize; 155 } 156 this.pos += 4; 157 if (this.pos > this.data.length) { 158 throw new Error("Incomplete or corrupt PNG file"); 159 } 160 } 161 }, 162 read:function(bytes){ 163 var i, _i, _results; 164 _results = []; 165 for (i = _i = 0; 0 <= bytes ? _i < bytes : _i > bytes; i = 0 <= bytes ? ++_i : --_i) { 166 _results.push(this.data[this.pos++]); 167 } 168 return _results; 169 }, 170 readUInt32:function(){ 171 var b1, b2, b3, b4; 172 b1 = this.data[this.pos++] << 24; 173 b2 = this.data[this.pos++] << 16; 174 b3 = this.data[this.pos++] << 8; 175 b4 = this.data[this.pos++]; 176 return b1 | b2 | b3 | b4; 177 }, 178 readUInt16:function(){ 179 var b1, b2; 180 b1 = this.data[this.pos++] << 8; 181 b2 = this.data[this.pos++]; 182 return b1 | b2; 183 }, 184 decodePixels:function(data){ 185 var ccbyte, c, col, i, left, length, p, pa, paeth, pb, pc, pixelBytes, pixels, pos, row, scanlineLength, upper, upperLeft, _i, _j, _k, _l, _m; 186 if (data == null) { 187 data = this.imgData; 188 } 189 if (data.length === 0) { 190 return new Uint8Array(0); 191 } 192 var inflate = new Zlib.Inflate(data,{index:0, verify:false}); 193 data = inflate.decompress(); 194 195 pixelBytes = this.pixelBitlength / 8; 196 scanlineLength = pixelBytes * this.width; 197 pixels = new Uint8Array(scanlineLength * this.height); 198 length = data.length; 199 row = 0; 200 pos = 0; 201 c = 0; 202 while (pos < length) { 203 switch (data[pos++]) { 204 case 0: 205 for (i = _i = 0; _i < scanlineLength; i = _i += 1) { 206 pixels[c++] = data[pos++]; 207 } 208 break; 209 case 1: 210 for (i = _j = 0; _j < scanlineLength; i = _j += 1) { 211 ccbyte = data[pos++]; 212 left = i < pixelBytes ? 0 : pixels[c - pixelBytes]; 213 pixels[c++] = (ccbyte + left) % 256; 214 } 215 break; 216 case 2: 217 for (i = _k = 0; _k < scanlineLength; i = _k += 1) { 218 ccbyte = data[pos++]; 219 col = (i - (i % pixelBytes)) / pixelBytes; 220 upper = row && pixels[(row - 1) * scanlineLength + col * pixelBytes + (i % pixelBytes)]; 221 pixels[c++] = (upper + ccbyte) % 256; 222 } 223 break; 224 case 3: 225 for (i = _l = 0; _l < scanlineLength; i = _l += 1) { 226 ccbyte = data[pos++]; 227 col = (i - (i % pixelBytes)) / pixelBytes; 228 left = i < pixelBytes ? 0 : pixels[c - pixelBytes]; 229 upper = row && pixels[(row - 1) * scanlineLength + col * pixelBytes + (i % pixelBytes)]; 230 pixels[c++] = (ccbyte + Math.floor((left + upper) / 2)) % 256; 231 } 232 break; 233 case 4: 234 for (i = _m = 0; _m < scanlineLength; i = _m += 1) { 235 ccbyte = data[pos++]; 236 col = (i - (i % pixelBytes)) / pixelBytes; 237 left = i < pixelBytes ? 0 : pixels[c - pixelBytes]; 238 if (row === 0) { 239 upper = upperLeft = 0; 240 } else { 241 upper = pixels[(row - 1) * scanlineLength + col * pixelBytes + (i % pixelBytes)]; 242 upperLeft = col && pixels[(row - 1) * scanlineLength + (col - 1) * pixelBytes + (i % pixelBytes)]; 243 } 244 p = left + upper - upperLeft; 245 pa = Math.abs(p - left); 246 pb = Math.abs(p - upper); 247 pc = Math.abs(p - upperLeft); 248 if (pa <= pb && pa <= pc) { 249 paeth = left; 250 } else if (pb <= pc) { 251 paeth = upper; 252 } else { 253 paeth = upperLeft; 254 } 255 pixels[c++] = (ccbyte + paeth) % 256; 256 } 257 break; 258 default: 259 throw new Error("Invalid filter algorithm: " + data[pos - 1]); 260 } 261 row++; 262 } 263 return pixels; 264 }, 265 copyToImageData:function(imageData,pixels){ 266 var alpha, colors, data, i, input, j, k, length, palette, v, _ref; 267 colors = this.colors; 268 palette = null; 269 alpha = this.hasAlphaChannel; 270 if (this.palette.length) { 271 palette = (_ref = this._decodedPalette) != null ? _ref : this._decodedPalette = this.decodePalette(); 272 colors = 4; 273 alpha = true; 274 } 275 data = imageData.data || imageData; 276 length = data.length; 277 input = palette || pixels; 278 i = j = 0; 279 if (colors === 1) { 280 while (i < length) { 281 k = palette ? pixels[i / 4] * 4 : j; 282 v = input[k++]; 283 data[i++] = v; 284 data[i++] = v; 285 data[i++] = v; 286 data[i++] = alpha ? input[k++] : 255; 287 j = k; 288 } 289 } else { 290 while (i < length) { 291 k = palette ? pixels[i / 4] * 4 : j; 292 data[i++] = input[k++]; 293 data[i++] = input[k++]; 294 data[i++] = input[k++]; 295 data[i++] = alpha ? input[k++] : 255; 296 j = k; 297 } 298 } 299 }, 300 decodePalette:function(){ 301 var c, i, palette, pos, ret, transparency, _i, _ref, _ref1; 302 palette = this.palette; 303 transparency = this.transparency.indexed || []; 304 ret = new Uint8Array((transparency.length || 0) + palette.length); 305 pos = 0; 306 c = 0; 307 for (i = _i = 0, _ref = palette.length; _i < _ref; i = _i += 3) { 308 ret[pos++] = palette[i]; 309 ret[pos++] = palette[i + 1]; 310 ret[pos++] = palette[i + 2]; 311 ret[pos++] = (_ref1 = transparency[c++]) != null ? _ref1 : 255; 312 } 313 return ret; 314 }, 315 render: function (canvas) { 316 var ctx, data; 317 canvas.width = this.width; 318 canvas.height = this.height; 319 ctx = canvas.getContext("2d"); 320 data = ctx.createImageData(this.width, this.height); 321 this.copyToImageData(data, this.decodePixels()); 322 return ctx.putImageData(data, 0, 0); 323 324 } 325 }); 326 327