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 /**
 30  * A png file reader
 31  * @name cc.tiffReader
 32  */
 33 cc.PNGReader = cc.Class.extend({
 34     ctor:function(data){
 35         var chunkSize, colors, delayDen, delayNum, frame, i, index, key, section, ccshort, text, _i, _j, _ref;
 36         this.data = data;
 37         this.pos = 8;
 38         this.palette = [];
 39         this.imgData = [];
 40         this.transparency = {};
 41         this.animation = null;
 42         this.text = {};
 43         frame = null;
 44         while (true) {
 45             chunkSize = this.readUInt32();
 46             section = ((function() {
 47                 var _i, _results;
 48                 _results = [];
 49                 for (i = _i = 0; _i < 4; i = ++_i) {
 50                     _results.push(String.fromCharCode(this.data[this.pos++]));
 51                 }
 52                 return _results;
 53             }).call(this)).join('');
 54             switch (section) {
 55                 case 'IHDR':
 56                     this.width = this.readUInt32();
 57                     this.height = this.readUInt32();
 58                     this.bits = this.data[this.pos++];
 59                     this.colorType = this.data[this.pos++];
 60                     this.compressionMethod = this.data[this.pos++];
 61                     this.filterMethod = this.data[this.pos++];
 62                     this.interlaceMethod = this.data[this.pos++];
 63                     break;
 64                 case 'acTL':
 65                     this.animation = {
 66                         numFrames: this.readUInt32(),
 67                         numPlays: this.readUInt32() || Infinity,
 68                         frames: []
 69                     };
 70                     break;
 71                 case 'PLTE':
 72                     this.palette = this.read(chunkSize);
 73                     break;
 74                 case 'fcTL':
 75                     if (frame) {
 76                         this.animation.frames.push(frame);
 77                     }
 78                     this.pos += 4;
 79                     frame = {
 80                         width: this.readUInt32(),
 81                         height: this.readUInt32(),
 82                         xOffset: this.readUInt32(),
 83                         yOffset: this.readUInt32()
 84                     };
 85                     delayNum = this.readUInt16();
 86                     delayDen = this.readUInt16() || 100;
 87                     frame.delay = 1000 * delayNum / delayDen;
 88                     frame.disposeOp = this.data[this.pos++];
 89                     frame.blendOp = this.data[this.pos++];
 90                     frame.data = [];
 91                     break;
 92                 case 'IDAT':
 93                 case 'fdAT':
 94                     if (section === 'fdAT') {
 95                         this.pos += 4;
 96                         chunkSize -= 4;
 97                     }
 98                     data = (frame != null ? frame.data : void 0) || this.imgData;
 99                     for (i = _i = 0; 0 <= chunkSize ? _i < chunkSize : _i > chunkSize; i = 0 <= chunkSize ? ++_i : --_i) {
100                         data.push(this.data[this.pos++]);
101                     }
102                     break;
103                 case 'tRNS':
104                     this.transparency = {};
105                     switch (this.colorType) {
106                         case 3:
107                             this.transparency.indexed = this.read(chunkSize);
108                             ccshort = 255 - this.transparency.indexed.length;
109                             if (ccshort > 0) {
110                                 for (i = _j = 0; 0 <= ccshort ? _j < ccshort : _j > ccshort; i = 0 <= ccshort ? ++_j : --_j) {
111                                     this.transparency.indexed.push(255);
112                                 }
113                             }
114                             break;
115                         case 0:
116                             this.transparency.grayscale = this.read(chunkSize)[0];
117                             break;
118                         case 2:
119                             this.transparency.rgb = this.read(chunkSize);
120                     }
121                     break;
122                 case 'tEXt':
123                     text = this.read(chunkSize);
124                     index = text.indexOf(0);
125                     key = String.fromCharCode.apply(String, text.slice(0, index));
126                     this.text[key] = String.fromCharCode.apply(String, text.slice(index + 1));
127                     break;
128                 case 'IEND':
129                     if (frame) {
130                         this.animation.frames.push(frame);
131                     }
132                     this.colors = (function() {
133                         switch (this.colorType) {
134                             case 0:
135                             case 3:
136                             case 4:
137                                 return 1;
138                             case 2:
139                             case 6:
140                                 return 3;
141                         }
142                     }).call(this);
143                     this.hasAlphaChannel = (_ref = this.colorType) === 4 || _ref === 6;
144                     colors = this.colors + (this.hasAlphaChannel ? 1 : 0);
145                     this.pixelBitlength = this.bits * colors;
146                     this.colorSpace = (function() {
147                         switch (this.colors) {
148                             case 1:
149                                 return 'DeviceGray';
150                             case 3:
151                                 return 'DeviceRGB';
152                         }
153                     }).call(this);
154                     if(Uint8Array != Array)
155                         this.imgData = new Uint8Array(this.imgData);
156                     return;
157                 default:
158                     this.pos += chunkSize;
159             }
160             this.pos += 4;
161             if (this.pos > this.data.length) {
162                 throw new Error("Incomplete or corrupt PNG file");
163             }
164         }
165     },
166     read:function(bytes){
167         var i, _i, _results;
168         _results = [];
169         for (i = _i = 0; 0 <= bytes ? _i < bytes : _i > bytes; i = 0 <= bytes ? ++_i : --_i) {
170             _results.push(this.data[this.pos++]);
171         }
172         return _results;
173     },
174     readUInt32:function(){
175         var b1, b2, b3, b4;
176         b1 = this.data[this.pos++] << 24;
177         b2 = this.data[this.pos++] << 16;
178         b3 = this.data[this.pos++] << 8;
179         b4 = this.data[this.pos++];
180         return b1 | b2 | b3 | b4;
181     },
182     readUInt16:function(){
183         var b1, b2;
184         b1 = this.data[this.pos++] << 8;
185         b2 = this.data[this.pos++];
186         return b1 | b2;
187     },
188     decodePixels:function(data){
189         var ccbyte, c, col, i, left, length, p, pa, paeth, pb, pc, pixelBytes, pixels, pos, row, scanlineLength, upper, upperLeft, _i, _j, _k, _l, _m;
190         if (data == null) {
191             data = this.imgData;
192         }
193         if (data.length === 0) {
194             return new Uint8Array(0);
195         }
196         var inflate = new Zlib.Inflate(data,{index:0, verify:false});
197         data = inflate.decompress();
198 
199         pixelBytes = this.pixelBitlength / 8;
200         scanlineLength = pixelBytes * this.width;
201         pixels = new Uint8Array(scanlineLength * this.height);
202         length = data.length;
203         row = 0;
204         pos = 0;
205         c = 0;
206         while (pos < length) {
207             switch (data[pos++]) {
208                 case 0:
209                     for (i = _i = 0; _i < scanlineLength; i = _i += 1) {
210                         pixels[c++] = data[pos++];
211                     }
212                     break;
213                 case 1:
214                     for (i = _j = 0; _j < scanlineLength; i = _j += 1) {
215                         ccbyte = data[pos++];
216                         left = i < pixelBytes ? 0 : pixels[c - pixelBytes];
217                         pixels[c++] = (ccbyte + left) % 256;
218                     }
219                     break;
220                 case 2:
221                     for (i = _k = 0; _k < scanlineLength; i = _k += 1) {
222                         ccbyte = data[pos++];
223                         col = (i - (i % pixelBytes)) / pixelBytes;
224                         upper = row && pixels[(row - 1) * scanlineLength + col * pixelBytes + (i % pixelBytes)];
225                         pixels[c++] = (upper + ccbyte) % 256;
226                     }
227                     break;
228                 case 3:
229                     for (i = _l = 0; _l < scanlineLength; i = _l += 1) {
230                         ccbyte = data[pos++];
231                         col = (i - (i % pixelBytes)) / pixelBytes;
232                         left = i < pixelBytes ? 0 : pixels[c - pixelBytes];
233                         upper = row && pixels[(row - 1) * scanlineLength + col * pixelBytes + (i % pixelBytes)];
234                         pixels[c++] = (ccbyte + Math.floor((left + upper) / 2)) % 256;
235                     }
236                     break;
237                 case 4:
238                     for (i = _m = 0; _m < scanlineLength; i = _m += 1) {
239                         ccbyte = data[pos++];
240                         col = (i - (i % pixelBytes)) / pixelBytes;
241                         left = i < pixelBytes ? 0 : pixels[c - pixelBytes];
242                         if (row === 0) {
243                             upper = upperLeft = 0;
244                         } else {
245                             upper = pixels[(row - 1) * scanlineLength + col * pixelBytes + (i % pixelBytes)];
246                             upperLeft = col && pixels[(row - 1) * scanlineLength + (col - 1) * pixelBytes + (i % pixelBytes)];
247                         }
248                         p = left + upper - upperLeft;
249                         pa = Math.abs(p - left);
250                         pb = Math.abs(p - upper);
251                         pc = Math.abs(p - upperLeft);
252                         if (pa <= pb && pa <= pc) {
253                             paeth = left;
254                         } else if (pb <= pc) {
255                             paeth = upper;
256                         } else {
257                             paeth = upperLeft;
258                         }
259                         pixels[c++] = (ccbyte + paeth) % 256;
260                     }
261                     break;
262                 default:
263                     throw new Error("Invalid filter algorithm: " + data[pos - 1]);
264             }
265             row++;
266         }
267         return pixels;
268     },
269     copyToImageData:function(imageData,pixels){
270         var alpha, colors, data, i, input, j, k, length, palette, v, _ref;
271         colors = this.colors;
272         palette = null;
273         alpha = this.hasAlphaChannel;
274         if (this.palette.length) {
275             palette = (_ref = this._decodedPalette) != null ? _ref : this._decodedPalette = this.decodePalette();
276             colors = 4;
277             alpha = true;
278         }
279         data = imageData.data || imageData;
280         length = data.length;
281         input = palette || pixels;
282         i = j = 0;
283         if (colors === 1) {
284             while (i < length) {
285                 k = palette ? pixels[i / 4] * 4 : j;
286                 v = input[k++];
287                 data[i++] = v;
288                 data[i++] = v;
289                 data[i++] = v;
290                 data[i++] = alpha ? input[k++] : 255;
291                 j = k;
292             }
293         } else {
294             while (i < length) {
295                 k = palette ? pixels[i / 4] * 4 : j;
296                 data[i++] = input[k++];
297                 data[i++] = input[k++];
298                 data[i++] = input[k++];
299                 data[i++] = alpha ? input[k++] : 255;
300                 j = k;
301             }
302         }
303     },
304     decodePalette:function(){
305         var c, i, palette, pos, ret, transparency, _i, _ref, _ref1;
306         palette = this.palette;
307         transparency = this.transparency.indexed || [];
308         ret = new Uint8Array((transparency.length || 0) + palette.length);
309         pos = 0;
310         c = 0;
311         for (i = _i = 0, _ref = palette.length; _i < _ref; i = _i += 3) {
312             ret[pos++] = palette[i];
313             ret[pos++] = palette[i + 1];
314             ret[pos++] = palette[i + 2];
315             ret[pos++] = (_ref1 = transparency[c++]) != null ? _ref1 : 255;
316         }
317         return ret;
318     },
319     render: function (canvas) {
320         var ctx, data;
321         canvas.width = this.width;
322         canvas.height = this.height;
323         ctx = canvas.getContext("2d");
324         data = ctx.createImageData(this.width, this.height);
325         this.copyToImageData(data, this.decodePixels());
326         return ctx.putImageData(data, 0, 0);
327 
328     }
329 });
330 
331