1 /****************************************************************************
  2  Copyright (c) 2008-2010 Ricardo Quesada
  3  Copyright (c) 2011-2012 cocos2d-x.org
  4  Copyright (c) 2013-2014 Chukong Technologies Inc.
  5 
  6  http://www.cocos2d-x.org
  7 
  8  Permission is hereby granted, free of charge, to any person obtaining a copy
  9  of this software and associated documentation files (the "Software"), to deal
 10  in the Software without restriction, including without limitation the rights
 11  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 12  copies of the Software, and to permit persons to whom the Software is
 13  furnished to do so, subject to the following conditions:
 14 
 15  The above copyright notice and this permission notice shall be included in
 16  all copies or substantial portions of the Software.
 17 
 18  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 19  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 20  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 21  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 22  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 23  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 24  THE SOFTWARE.
 25  ****************************************************************************/
 26 
 27 /**
 28  * @constant
 29  * @type Number
 30  */
 31 cc.TGA_OK = 0;
 32 
 33 /**
 34  * @constant
 35  * @type Number
 36  */
 37 cc.TGA_ERROR_FILE_OPEN = 1;
 38 
 39 /**
 40  * @constant
 41  * @type Number
 42  */
 43 cc.TGA_ERROR_READING_FILE = 2;
 44 
 45 /**
 46  * @constant
 47  * @type Number
 48  */
 49 cc.TGA_ERROR_INDEXED_COLOR = 3;
 50 
 51 /**
 52  * @constant
 53  * @type Number
 54  */
 55 cc.TGA_ERROR_MEMORY = 4;
 56 
 57 /**
 58  * @constant
 59  * @type Number
 60  */
 61 cc.TGA_ERROR_COMPRESSED_FILE = 5;
 62 
 63 /**
 64  * TGA format
 65  * @param {Number} status
 66  * @param {Number} type
 67  * @param {Number} pixelDepth
 68  * @param {Number} width map width
 69  * @param {Number} height map height
 70  * @param {Array} imageData raw data
 71  * @param {Number} flipped
 72  * @constructor
 73  */
 74 cc.ImageTGA = function (status, type, pixelDepth, width, height, imageData, flipped) {
 75     this.status = status || 0;
 76     this.type = type || 0;
 77     this.pixelDepth = pixelDepth || 0;
 78     this.width = width || 0;
 79     this.height = height || 0;
 80     this.imageData = imageData || [];
 81     this.flipped = flipped || 0;
 82 };
 83 
 84 /**
 85  * load the image header field from stream. We only keep those that matter!
 86  * @param {Array} buffer
 87  * @param {Number} bufSize
 88  * @param {cc.ImageTGA} psInfo
 89  * @return {Boolean}
 90  */
 91 cc.tgaLoadHeader = function (buffer, bufSize, psInfo) {
 92     var step = 2;
 93     if (step + 1 > bufSize)
 94         return false;
 95 
 96     var binaryReader = new cc.BinaryStreamReader(buffer);
 97 
 98     binaryReader.setOffset(step);
 99     psInfo.type = binaryReader.readByte();
100     step += 10;       // . step += sizeof(unsigned char) * 2; step += sizeof(signed short) * 4;
101 
102     if (step + 4 + 1 > bufSize)
103         return false;
104     binaryReader.setOffset(step);
105     psInfo.width = binaryReader.readUnsignedShort();
106     psInfo.height = binaryReader.readUnsignedInteger();
107     psInfo.pixelDepth = binaryReader.readByte();
108 
109     step += 5;      // .  step += sizeof(unsigned char);  step += sizeof(signed short) * 2;
110     if (step + 1 > bufSize)
111         return false;
112 
113     var garbage = binaryReader.readByte();
114     psInfo.flipped = 0;
115     if (garbage & 0x20)
116         psInfo.flipped = 1;
117     return true;
118 };
119 
120 /**
121  * loads the image pixels. You shouldn't call this function directly.
122  * @param {Array} buffer
123  * @param {Number} bufSize
124  * @param {cc.ImageTGA} psInfo
125  * @return {Boolean}
126  */
127 cc.tgaLoadImageData = function (buffer, bufSize, psInfo) {
128     var mode, total, i, aux;
129     var step = 18;              // .size_t step = (sizeof(unsigned char) + sizeof(signed short)) * 6;
130 
131     // mode equal the number of components for each pixel
132     mode = 0 | (psInfo.pixelDepth / 2);
133     // total is the number of unsigned chars we'll have to read
134     total = psInfo.height * psInfo.width * mode;
135 
136     if (step + total > bufSize)
137         return false;
138 
139     psInfo.imageData = cc.__getSubArray(buffer, step, step + total);
140 
141     // mode=3 or 4 implies that the image is RGB(A). However TGA
142     // stores it as BGR(A) so we'll have to swap R and B.
143     if (mode >= 3) {
144         for (i = 0; i < total; i += mode) {
145             aux = psInfo.imageData[i];
146             psInfo.imageData[i] = psInfo.imageData[i + 2];
147             psInfo.imageData[i + 2] = aux;
148         }
149     }
150     return true;
151 };
152 
153 /**
154  * converts RGB to grayscale
155  * @param {cc.ImageTGA} psInfo
156  */
157 cc.tgaRGBtogreyscale = function (psInfo) {
158     var i, j;
159 
160     // if the image is already grayscale do nothing
161     if (psInfo.pixelDepth === 8)
162         return;
163 
164     // compute the number of actual components
165     var mode = psInfo.pixelDepth / 8;
166 
167     // allocate an array for the new image data
168     var newImageData = new Uint8Array(psInfo.height * psInfo.width);
169     if (newImageData === null)
170         return;
171 
172     // convert pixels: grayscale = o.30 * R + 0.59 * G + 0.11 * B
173     for (i = 0, j = 0; j < psInfo.width * psInfo.height; i += mode, j++)
174         newImageData[j] = (0.30 * psInfo.imageData[i] + 0.59 * psInfo.imageData[i + 1] + 0.11 * psInfo.imageData[i + 2]);
175 
176     // reassign pixelDepth and type according to the new image type
177     psInfo.pixelDepth = 8;
178     psInfo.type = 3;
179     // reassigning imageData to the new array.
180     psInfo.imageData = newImageData;
181 };
182 
183 /**
184  * releases the memory used for the image
185  * @param {cc.ImageTGA} psInfo
186  */
187 cc.tgaDestroy = function (psInfo) {
188     if (!psInfo)
189         return;
190 
191     psInfo.imageData = null;
192     psInfo = null;
193 };
194 
195 /**
196  * Load RLE image data
197  * @param buffer
198  * @param bufSize
199  * @param psInfo
200  * @returns {boolean}
201  */
202 cc.tgaLoadRLEImageData = function (buffer, bufSize, psInfo) {
203     var mode, total, i, index = 0 , skip = 0, flag = 0;
204     var aux = [], runlength = 0;
205 
206     var step = 18;                          // . size_t step = (sizeof(unsigned char) + sizeof(signed short)) * 6;
207 
208     // mode equal the number of components for each pixel
209     mode = psInfo.pixelDepth / 8;
210     // total is the number of unsigned chars we'll have to read
211     total = psInfo.height * psInfo.width;
212 
213     for (i = 0; i < total; i++) {
214         // if we have a run length pending, run it
215         if (runlength != 0) {
216             // we do, update the run length count
217             runlength--;
218             skip = (flag != 0);
219         } else {
220             // otherwise, read in the run length token
221             if (step + 1 > bufSize)
222                 break;
223             runlength = buffer[step];
224             step += 1;
225 
226             // see if it's a RLE encoded sequence
227             flag = runlength & 0x80;
228             if (flag)
229                 runlength -= 128;
230             skip = 0;
231         }
232 
233         // do we need to skip reading this pixel?
234         if (!skip) {
235             // no, read in the pixel data
236             if (step + mode > bufSize)
237                 break;
238             aux = cc.__getSubArray(buffer, step, step + mode);
239             step += mode;
240 
241             // mode=3 or 4 implies that the image is RGB(A). However TGA
242             // stores it as BGR(A) so we'll have to swap R and B.
243             if (mode >= 3) {
244                 var tmp = aux[0];
245                 aux[0] = aux[2];
246                 aux[2] = tmp;
247             }
248         }
249 
250         // add the pixel to our image
251         for (var j = 0; j < mode; j++)
252             psInfo.imageData[index + j] = aux[j];
253 
254         index += mode;
255     }
256 
257     return true;
258 };
259 
260 /**
261  * ImageTGA Flip
262  * @param {cc.ImageTGA} psInfo
263  */
264 cc.tgaFlipImage = function (psInfo) {
265     // mode equal the number of components for each pixel
266     var mode = psInfo.pixelDepth / 8;
267     var rowbytes = psInfo.width * mode;
268 
269     for (var y = 0; y < (psInfo.height / 2); y++) {
270         var row = cc.__getSubArray(psInfo.imageData, y * rowbytes, y * rowbytes + rowbytes);
271         cc.__setDataToArray(cc.__getSubArray(psInfo.imageData, (psInfo.height - (y + 1)) * rowbytes, rowbytes), psInfo.imageData, y * rowbytes);
272         cc.__setDataToArray(row, psInfo.imageData, (psInfo.height - (y + 1)) * rowbytes);
273     }
274     psInfo.flipped = 0;
275 };
276 
277 cc.__getSubArray = function (array, start, end) {
278     if (array instanceof  Array)
279         return array.slice(start, end);
280     else
281         return array.subarray(start, end);
282 };
283 
284 cc.__setDataToArray = function (sourceData, destArray, startIndex) {
285     for (var i = 0; i < sourceData.length; i++)
286         destArray[startIndex + i] = sourceData[i];
287 };
288 
289 /**
290  * Binary Stream Reader
291  *
292  * @class
293  * @param binaryData
294  */
295 cc.BinaryStreamReader = cc.Class.extend({
296     _binaryData:null,
297     _offset:0,
298 
299     /**
300      * <p>The cc.BinaryStreamReader's constructor. <br/>
301      * This function will automatically be invoked when you create a node using new construction: "var node = new cc.BinaryStreamReader()".<br/>
302      * Override it to extend its behavior, remember to call "this._super()" in the extended "ctor" function.</p>
303      * @param binaryData
304      */
305     ctor:function (binaryData) {
306         this._binaryData = binaryData;
307     },
308 
309     /**
310      * Set the binaryData.
311      * @param binaryData
312      */
313     setBinaryData:function (binaryData) {
314         this._binaryData = binaryData;
315         this._offset = 0;
316     },
317 
318     /**
319      * Gets the binaryData.
320      * @returns {Object}
321      */
322     getBinaryData:function () {
323         return this._binaryData;
324     },
325 
326     _checkSize:function (neededBits) {
327         if (!(this._offset + Math.ceil(neededBits / 8) < this._data.length))
328             throw new Error("Index out of bound");
329     },
330 
331     _decodeFloat:function (precisionBits, exponentBits) {
332         var length = precisionBits + exponentBits + 1;
333         var size = length >> 3;
334         this._checkSize(length);
335 
336         var bias = Math.pow(2, exponentBits - 1) - 1;
337         var signal = this._readBits(precisionBits + exponentBits, 1, size);
338         var exponent = this._readBits(precisionBits, exponentBits, size);
339         var significand = 0;
340         var divisor = 2;
341         var curByte = 0; //length + (-precisionBits >> 3) - 1;
342         do {
343             var byteValue = this._readByte(++curByte, size);
344             var startBit = precisionBits % 8 || 8;
345             var mask = 1 << startBit;
346             while (mask >>= 1) {
347                 if (byteValue & mask)
348                     significand += 1 / divisor;
349                 divisor *= 2;
350             }
351         } while (precisionBits -= startBit);
352 
353         this._offset += size;
354 
355         return exponent == (bias << 1) + 1 ? significand ? NaN : signal ? -Infinity : +Infinity
356             : (1 + signal * -2) * (exponent || significand ? !exponent ? Math.pow(2, -bias + 1) * significand
357             : Math.pow(2, exponent - bias) * (1 + significand) : 0);
358     },
359 
360     _readByte:function (i, size) {
361         return this._data[this._offset + size - i - 1];
362     },
363 
364     _decodeInt:function (bits, signed) {
365         var x = this._readBits(0, bits, bits / 8), max = Math.pow(2, bits);
366         var result = signed && x >= max / 2 ? x - max : x;
367 
368         this._offset += bits / 8;
369         return result;
370     },
371 
372     _shl:function (a, b) {
373         for (++b; --b; a = ((a %= 0x7fffffff + 1) & 0x40000000) == 0x40000000 ? a * 2 : (a - 0x40000000) * 2 + 0x7fffffff + 1){};
374         return a;
375     },
376 
377     _readBits:function (start, length, size) {
378         var offsetLeft = (start + length) % 8;
379         var offsetRight = start % 8;
380         var curByte = size - (start >> 3) - 1;
381         var lastByte = size + (-(start + length) >> 3);
382         var diff = curByte - lastByte;
383 
384         var sum = (this._readByte(curByte, size) >> offsetRight) & ((1 << (diff ? 8 - offsetRight : length)) - 1);
385 
386         if (diff && offsetLeft)
387             sum += (this._readByte(lastByte++, size) & ((1 << offsetLeft) - 1)) << (diff-- << 3) - offsetRight;
388 
389         while (diff)
390             sum += this._shl(this._readByte(lastByte++, size), (diff-- << 3) - offsetRight);
391 
392         return sum;
393     },
394 
395     readInteger:function () {
396         return this._decodeInt(32, true);
397     },
398 
399     readUnsignedInteger:function () {
400         return this._decodeInt(32, false);
401     },
402 
403     readSingle:function () {
404         return this._decodeFloat(23, 8);
405     },
406 
407     readShort:function () {
408         return this._decodeInt(16, true);
409     },
410 
411     readUnsignedShort:function () {
412         return this._decodeInt(16, false);
413     },
414 
415     readByte:function () {
416         var readByte = this._data[this._offset];
417         this._offset += 1;
418         return readByte;
419     },
420 
421     readData:function (start, end) {
422         if (this._binaryData instanceof Array) {
423             return this._binaryData.slice(start, end);
424         } else {
425             //typed array
426             return this._binaryData.subarray(start, end);
427         }
428     },
429 
430     setOffset:function (offset) {
431         this._offset = offset;
432     },
433 
434     getOffset:function () {
435         return this._offset;
436     }
437 });
438