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