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