1 /****************************************************************************
  2  Copyright (c) 2010-2012 cocos2d-x.org
  3  Copyright (c) 2008-2010 Ricardo Quesada
  4  Copyright (c) 2011      Zynga 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  * Image Format:JPG
 29  * @constant
 30  * @type Number
 31  */
 32 cc.FMT_JPG = 0;
 33 
 34 /**
 35  * Image Format:PNG
 36  * @constant
 37  * @type Number
 38  */
 39 cc.FMT_PNG = 1;
 40 
 41 /**
 42  * Image Format:RAWDATA
 43  * @constant
 44  * @type Number
 45  */
 46 cc.FMT_RAWDATA = 2;
 47 
 48 /**
 49  * Image Format:UNKNOWN
 50  * @constant
 51  * @type Number
 52  */
 53 cc.FMT_UNKNOWN = 3;
 54 
 55 /**
 56  * Horizontal center and vertical center.
 57  * @constant
 58  * @type Number
 59  */
 60 cc.ALIGN_CENTER = 0x33;
 61 
 62 /**
 63  * Horizontal center and vertical top.
 64  * @constant
 65  * @type Number
 66  */
 67 cc.ALIGN_TOP = 0x13;
 68 
 69 /**
 70  * Horizontal right and vertical top.
 71  * @constant
 72  * @type Number
 73  */
 74 cc.ALIGN_TOP_RIGHT = 0x12;
 75 
 76 /**
 77  * Horizontal right and vertical center.
 78  * @constant
 79  * @type Number
 80  */
 81 cc.ALIGN_RIGHT = 0x32;
 82 
 83 /**
 84  * Horizontal right and vertical bottom.
 85  * @constant
 86  * @type Number
 87  */
 88 cc.ALIGN_BOTTOM_RIGHT = 0x22;
 89 
 90 /**
 91  * Horizontal center and vertical bottom.
 92  * @constant
 93  * @type Number
 94  */
 95 cc.ALIGN_BOTTOM = 0x23;
 96 
 97 /**
 98  * Horizontal left and vertical bottom.
 99  * @constant
100  * @type Number
101  */
102 cc.ALIGN_BOTTOM_LEFT = 0x21;
103 
104 /**
105  * Horizontal left and vertical center.
106  * @constant
107  * @type Number
108  */
109 cc.ALIGN_LEFT = 0x31;
110 
111 /**
112  * Horizontal left and vertical top.
113  * @constant
114  * @type Number
115  */
116 cc.ALIGN_TOP_LEFT = 0x11;
117 
118 function cc.RGB_PREMULTIPLY_APLHA(vr, vg, vb, va) {
119     return ((vr * (va + 1)) >> 8) | ((vg * (va + 1) >> 8) << 8) | ((vb * (va + 1) >> 8) << 16) | ((va) << 24)
120 }
121 
122 /**
123  * image source
124  * @Class
125  * @Construct
126  * @param {String} data
127  * @param {Number} size
128  * @param {Number} offset
129  */
130 function tImageSource(data, size, offset) {
131     this.data = data;
132     this.size = size;
133     this.offset = offset;
134 }
135 
136 cc.pngReadCallback = function (png_ptr, data, length) {
137     var isource = new tImageSource();
138     isource = cc.png_get_io_ptr(png_ptr);
139 
140     if (isource.offset + length <= isource.size) {
141         cc.memcpy(data, isource.data + isource.offset, length);
142         isource.offset += length;
143     }
144     else {
145         cc.png_error(png_ptr, "pngReaderCallback failed");
146     }
147 };
148 
149 /**
150  * Image
151  * @class
152  * @extends cc.Class
153  */
154 cc.Image = cc.Class.extend(/** @lends cc.Image# */{
155     _width:0,
156     _height:0,
157     _bitsPerComponent:0,
158     _data:0,
159     _hasAlpha:false,
160     _preMulti:false,
161 
162     /**
163      * Load the image from the specified path.
164      * @param {String} strPath the absolute file path
165      * @param {Number} eImgFmt the type of image, now only support tow types.
166      * @return {Boolean} true if load correctly
167      */
168     initWithImageFile:function (strPath, eImgFmt) {
169         var data = new cc.FileData(cc.FileUtils.getInstance().fullPathFromRelativePath(strPath), "rb");
170         return this.initWithImageData(data.getBuffer(), data.getSize(), eImgFmt);
171     },
172 
173     /**
174      * The same meaning as initWithImageFile, but it is thread safe. It is casued by loadImage() in cc.TextureCache.
175      * @param {String} fullpath full path of the file
176      * @param {Number} imageType the type of image, now only support tow types.
177      * @return {Boolean} true if load correctly
178      */
179     initWithImageFileThreadSafe:function (fullpath, imageType) {
180         var data = new cc.FileData(fullpath, "rb");
181         return this.initWithImageData(data.getBuffer(), data.getSize(), imageType);
182     },
183 
184     /**
185      * Load image from stream buffer.
186      * @warning FMT_RAWDATA only support RGBA8888
187      * @param {Array} pData stream buffer that hold the image data
188      * @param {Number} nDataLen the length of data(managed in byte)
189      * @param {Number} eFmt
190      * @param {Number} width
191      * @param {Number} height
192      * @param {Number} nBitsPerComponent
193      * @return {Boolean} true if load correctly
194      */
195     initWithImageData:function (pData, nDataLen, eFmt, width, height, nBitsPerComponent) {
196         var ret = false;
197         do
198         {
199             if (!pData || nDataLen <= 0) break;
200 
201             if (cc.FMT_PNG == eFmt) {
202                 ret = this._initWithPngData(pData, nDataLen);
203                 break;
204             }
205             else if (cc.FMT_JPG == eFmt) {
206                 ret = this._initWithJpgData(pData, nDataLen);
207                 break;
208             }
209             else if (cc.FMT_RAWDATA == eFmt) {
210                 ret = this._initWithRawData(pData, nDataLen, width, height, nBitsPerComponent);
211                 break;
212             }
213         } while (0);
214         return ret;
215     },
216     getData:function () {
217         return this._data;
218     },
219     getDataLen:function () {
220         return this._width * this._height;
221     },
222     hasAlpha:function () {
223         return this._hasAlpha;
224     },
225     isPremultipliedAlpha:function () {
226         return this._preMulti;
227     },
228     getWidth:function () {
229         return this._width;
230     },
231     getHeight:function () {
232         return this._height;
233     },
234     getBitsPerComponent:function () {
235         return this._bitsPerComponent;
236     },
237 
238     /**
239      * Save the CCImage data to specified file with specified format.
240      * @param {String} filePath the file's absolute path, including file subfix
241      * @param {Boolean} isToRGB  if the image is saved as RGB format
242      * @return {Boolean}
243      */
244     saveToFile:function (filePath, isToRGB) {
245         var ret = false;
246         do
247         {
248             if (null == filePath) break;
249 
250             var strFilePath = filePath;
251             if (strFilePath.size() <= 4) break;
252             var strLowerCasePath = strFilePath;
253             for (var i = 0; i < strLowerCasePath.length; ++i) {
254                 strLowerCasePath[i] = cc.tolower(strFilePath[i]);
255             }
256 
257             if (std.string.npos != strLowerCasePath.find(".png")) {
258                 if (!this._saveImageToPNG(filePath, isToRGB)) break;
259             }
260             else if (std.string.npos != strLowerCasePath.find(".jpg")) {
261                 if (!this._saveImageToJPG(filePath)) break;
262             }
263             else {
264                 break;
265             }
266 
267             ret = true;
268         } while (0);
269 
270         return ret;
271     },
272 
273     /*protected:*/
274     _initWithJpgData:function (data, size) {
275         /* these are standard libjpeg structures for reading(decompression) */
276         var cinfo = new cc.jpeg_decompress_struct();
277         var jerr = new cc.jpeg_error_mgr();
278         /* libjpeg data structure for storing one row, that is, scanline of an image */
279         var row_pointer = [0];
280         var location = 0;
281         var i = 0;
282 
283         var ret = false;
284         do
285         {
286             /* here we set up the standard libjpeg error handler */
287             cinfo.err = cc.jpeg_std_error(jerr);
288 
289             /* setup decompression process and source, then read JPEG header */
290             cc.jpeg_create_decompress(cinfo);
291 
292             cc.jpeg_mem_src(cinfo, data, size);
293 
294             /* reading the image header which contains image information */
295             cc.jpeg_read_header(cinfo, true);
296 
297             // we only support RGB or grayscale
298             if (cinfo.jpeg_color_space != cc.JCS_RGB) {
299                 if (cinfo.jpeg_color_space == cc.JCS_GRAYSCALE || cinfo.jpeg_color_space == cc.JCS_YCbCr) {
300                     cinfo.out_color_space = cc.JCS_RGB;
301                 }
302             }
303             else {
304                 break;
305             }
306 
307             /* Start decompression jpeg here */
308             cc.jpeg_start_decompress(cinfo);
309 
310             /* init image info */
311             this._width = cinfo.image_width;
312             this._height = cinfo.image_height;
313             this._hasAlpha = false;
314             this._preMulti = false;
315             this._bitsPerComponent = 8;
316             row_pointer[0] = new [cinfo.output_width * cinfo.output_components];
317             if (!row_pointer[0]) break;
318             this._data = new [cinfo.output_width * cinfo.output_height * cinfo.output_components];
319             if (!this._data) break;
320 
321             /* now actually read the jpeg into the raw buffer */
322             /* read one scan line at a time */
323             while (cinfo.output_scanline < cinfo.image_height) {
324                 cc.jpeg_read_scanlines(cinfo, row_pointer, 1);
325                 for (i = 0; i < cinfo.image_width * cinfo.num_components; i++)
326                     this._data[location++] = row_pointer[0][i];
327             }
328 
329             cc.jpeg_finish_decompress(cinfo);
330             cc.jpeg_destroy_decompress(cinfo);
331             /* wrap up decompression, destroy objects, free pointers and close open files */
332             ret = true;
333         } while (0);
334 
335         return ret;
336     },
337 
338     _initWithPngData:function (pData, nDatalen) {
339         var ret = false, header = [0], png_ptr = 0, info_ptr = 0, imateData = 0;
340 
341         do
342         {
343             // png header len is 8 bytes
344             if (nDatalen < 8) break;
345             // check the data is png or not
346             cc.memcpy(header, pData, 8);
347             if (cc.png_sig_cmp(header, 0, 8)) break;
348 
349             // init png_struct
350             png_ptr = cc.png_create_read_struct(cc.PNG_LIBPNG_VER_STRING, 0, 0, 0);
351             if (!png_ptr) break;
352             // init png_info
353             info_ptr = cc.png_create_info_struct(png_ptr);
354             if (!info_ptr) break;
355 
356             // set the read call back function
357             var imageSource = new tImageSource();
358             imageSource.data = pData;
359             imageSource.size = nDatalen;
360             imageSource.offset = 0;
361             cc.png_set_read_fn(png_ptr, imageSource, cc.pngReadCallback);
362 
363             // read png
364             // PNG_TRANSFORM_EXPAND: perform set_expand()
365             // PNG_TRANSFORM_PACKING: expand 1, 2 and 4-bit samples to bytes
366             // PNG_TRANSFORM_STRIP_16: strip 16-bit samples to 8 bits
367             // PNG_TRANSFORM_GRAY_TO_RGB: expand grayscale samples to RGB (or GA to RGBA)
368             cc.png_read_png(png_ptr, info_ptr, cc.PNG_TRANSFORM_EXPAND | cc.PNG_TRANSFORM_PACKING
369                 | cc.PNG_TRANSFORM_STRIP_16 | cc.PNG_TRANSFORM_GRAY_TO_RGB, 0);
370 
371             var color_type = 0;
372             var width = 0;
373             var height = 0;
374             var nBitsPerComponent = 0;
375             cc.png_get_IHDR(png_ptr, info_ptr, width, height, nBitsPerComponent, color_type, 0, 0, 0);
376 
377             // init image info
378             this._preMulti = true;
379             this._hasAlpha = ( info_ptr.color_type & cc.PNG_COLOR_MASK_ALPHA ) ? true : false;
380 
381             // allocate memory and read data
382             var bytesPerComponent = 3;
383             if (this._hasAlpha) {
384                 bytesPerComponent = 4;
385             }
386             imateData = new [height * width * bytesPerComponent];
387             if (!imateData) break;
388             var rowPointers = new cc.png_bytep();
389             rowPointers = cc.png_get_rows(png_ptr, info_ptr);
390 
391             // copy data to image info
392             var bytesPerRow = width * bytesPerComponent;
393             if (this._hasAlpha) {
394                 var tmp = imateData;
395                 for (var i = 0; i < height; i++) {
396                     for (var j = 0; j < bytesPerRow; j += 4) {
397                         tmp++;
398                         tmp = cc.RGB_PREMULTIPLY_APLHA(rowPointers[i][j], rowPointers[i][j + 1],
399                             rowPointers[i][j + 2], rowPointers[i][j + 3]);
400                     }
401                 }
402             }
403             else {
404                 for (var j = 0; j < height; ++j) {
405                     cc.memcpy(imateData + j * bytesPerRow, rowPointers[j], bytesPerRow);
406                 }
407             }
408 
409             this._bitsPerComponent = nBitsPerComponent;
410             this._height = height;
411             this._width = width;
412             this._data = imateData;
413             imateData = 0;
414             ret = true;
415         } while (0);
416 
417         if (png_ptr) {
418             cc.png_destroy_read_struct(png_ptr, info_ptr ? info_ptr : 0, 0);
419         }
420         return ret;
421     },
422 
423     // @warning FMT_RAWDATA only support RGBA8888
424     _initWithRawData:function (data, datalen, width, height, bitsPerComponent) {
425         var ret = false;
426         do
427         {
428             if (0 == width || 0 == height) break;
429 
430             this._bitsPerComponent = bitsPerComponent;
431             this._height = height;
432             this._width = width;
433             this._hasAlpha = true;
434 
435             // only RGBA8888 surported
436             var nBytesPerComponent = 4;
437             var nSize = height * width * nBytesPerComponent;
438             this._data = new [nSize];
439             if (!this._data) break;
440             cc.memcpy(this._data, data, nSize);
441 
442             ret = true;
443         } while (0);
444         return ret;
445     },
446 
447     _saveImageToPNG:function (filePath, isToRGB) {
448         var ret = false;
449         do
450         {
451             if (null == filePath) break;
452 
453             var fp = new cc.FILE(), png_ptr = new cc.png_structp(), info_ptr = new cc.png_infop(), palette = new cc.png_colorp(), row_pointers = new cc.png_bytep();
454 
455             fp = cc.fopen(filePath, "wb");
456             if (null == fp) break;
457 
458             png_ptr = cc.png_create_write_struct(cc.PNG_LIBPNG_VER_STRING, null, null, null);
459 
460             if (null == png_ptr) {
461                 cc.fclose(fp);
462                 break;
463             }
464 
465             info_ptr = cc.png_create_info_struct(png_ptr);
466             if (null == info_ptr) {
467                 cc.fclose(fp);
468                 cc.png_destroy_write_struct(png_ptr, null);
469                 break;
470             }
471             if (cc.TARGET_PLATFORM != cc.PLATFORM_BADA) {
472                 if (cc.setjmp(cc.png_jmpbuf(png_ptr))) {
473                     cc.fclose(fp);
474                     cc.png_destroy_write_struct(png_ptr, info_ptr);
475                     break;
476                 }
477             }
478             cc.png_init_io(png_ptr, fp);
479 
480             if (!isToRGB && this._hasAlpha) {
481                 cc.png_set_IHDR(png_ptr, info_ptr, this._width, this._height, 8, cc.PNG_COLOR_TYPE_RGB_ALPHA,
482                     cc.PNG_INTERLACE_NONE, cc.PNG_COMPRESSION_TYPE_BASE, cc.PNG_FILTER_TYPE_BASE);
483             }
484             else {
485                 cc.png_set_IHDR(png_ptr, info_ptr, this._width, this._height, 8, cc.PNG_COLOR_TYPE_RGB,
486                     cc.PNG_INTERLACE_NONE, cc.PNG_COMPRESSION_TYPE_BASE, cc.PNG_FILTER_TYPE_BASE);
487             }
488 
489             palette = cc.png_malloc(png_ptr, cc.PNG_MAX_PALETTE_LENGTH * sizeof(cc.png_color));
490             cc.png_set_PLTE(png_ptr, info_ptr, palette, cc.PNG_MAX_PALETTE_LENGTH);
491 
492             cc.png_write_info(png_ptr, info_ptr);
493 
494             cc.png_set_packing(png_ptr);
495 
496             row_pointers = cc.malloc(this._height * sizeof(cc.png_bytep));
497             if (row_pointers == null) {
498                 cc.fclose(fp);
499                 cc.png_destroy_write_struct(png_ptr, info_ptr);
500                 break;
501             }
502 
503             if (!this._hasAlpha) {
504                 for (var i = 0; i < this._height; i++) {
505                     row_pointers[i] = this._data + i * this._width * 3;
506                 }
507 
508                 cc.png_write_image(png_ptr, row_pointers);
509 
510                 cc.free(row_pointers);
511                 row_pointers = null;
512             }
513             else {
514                 if (isToRGB) {
515                     var tempData = new [this._width * this._height * 3];
516                     if (null == tempData) {
517                         cc.fclose(fp);
518                         cc.png_destroy_write_struct(png_ptr, info_ptr);
519                         break;
520                     }
521 
522                     for (var i = 0; i < this._height; ++i) {
523                         for (var j = 0; j < this._width; ++j) {
524                             tempData[(i * this._width + j) * 3] = this._data[(i * __width + j) * 4];
525                             tempData[(i * this._width + j) * 3 + 1] = this._data[(i * __width + j) * 4 + 1];
526                             tempData[(i * this._width + j) * 3 + 2] = this._data[(i * __width + j) * 4 + 2];
527                         }
528                     }
529 
530                     for (var i = 0; i < this._height; i++) {
531                         row_pointers[i] = tempData + i * this._width * 3;
532                     }
533 
534                     cc.png_write_image(png_ptr, row_pointers);
535 
536                     cc.free(row_pointers);
537                     row_pointers = null;
538 
539                 }
540                 else {
541                     for (var i = 0; i < this._height; i++) {
542                         row_pointers[i] = this._data + i * this._width * 4;
543                     }
544 
545                     cc.png_write_image(png_ptr, row_pointers);
546 
547                     cc, free(row_pointers);
548                     row_pointers = null;
549                 }
550             }
551 
552             cc.png_write_end(png_ptr, info_ptr);
553 
554             cc.png_free(png_ptr, palette);
555             palette = null;
556 
557             cc.png_destroy_write_struct(png_ptr, info_ptr);
558 
559             cc.fclose(fp);
560 
561             ret = true;
562         } while (0);
563         return ret;
564     },
565 
566     _saveImageToJPG:function (pszFilePath) {
567         var ret = false;
568         do
569         {
570             if (null == pszFilePath) break;
571             var cinfo = new cc.jpeg_compress_struct(),
572                 jerr = new cc.jpeg_error_mgr(),
573                 outfile = new cc.FILE(), /* target file */
574                 row_pointer = [], /* pointer to JSAMPLE row[s] */
575                 row_stride;
576             /* physical row width in image buffer */
577 
578             cinfo.err = jpeg_std_error(jerr);
579             /* Now we can initialize the JPEG compression object. */
580             cc.jpeg_create_compress(cinfo);
581 
582             if ((outfile = fopen(pszFilePath, "wb")) == null) break;
583 
584             cc.jpeg_stdio_dest(cinfo, outfile);
585 
586             cinfo.image_width = this._width;
587             /* image width and height, in pixels */
588             cinfo.image_height = this._height;
589             cinfo.input_components = 3;
590             /* # of color components per pixel */
591             cinfo.in_color_space = cc.JCS_RGB;
592             /* colorspace of input image */
593 
594             cc.jpeg_set_defaults(cinfo);
595 
596             cc.jpeg_start_compress(cinfo, true);
597 
598             row_stride = this._width * 3;
599             /* JSAMPLEs per row in image_buffer */
600 
601             if (this._hasAlpha) {
602                 var tempData = new [this._width * this._height * 3];
603                 if (null == tempData) {
604                     cc.jpeg_finish_compress(cinfo);
605                     cc.jpeg_destroy_compress(cinfo);
606                     cc.fclose(outfile);
607                     break;
608                 }
609 
610                 for (var i = 0; i < this._height; ++i) {
611                     for (var j = 0; j < this._width; ++j) {
612                         tempData[(i * this._width + j) * 3] = this._data[(i * this._width + j) * 4];
613                         tempData[(i * this._width + j) * 3 + 1] = this._data[(i * this._width + j) * 4 + 1];
614                         tempData[(i * this._width + j) * 3 + 2] = this._data[(i * this._width + j) * 4 + 2];
615                     }
616                 }
617 
618                 while (cinfo.next_scanline < cinfo.image_height) {
619                     row_pointer[0] = tempData[cinfo.next_scanline * row_stride];
620                     cc.jpeg_write_scanlines(cinfo, row_pointer, 1);
621                 }
622 
623             }
624             else {
625                 while (cinfo.next_scanline < cinfo.image_height) {
626                     row_pointer[0] = this._data[cinfo.next_scanline * row_stride];
627                     cc.jpeg_write_scanlines(cinfo, row_pointer, 1);
628                 }
629             }
630 
631             cc.jpeg_finish_compress(cinfo);
632             cc.fclose(outfile);
633             cc.jpeg_destroy_compress(cinfo);
634 
635             ret = true;
636         } while (0);
637         return ret;
638     },
639 
640     /**
641      * Create image with specified string.
642      * @param {cc.Texture2D} text the text which the image show, nil cause init fail
643      * @param {Number} width the image width, if 0, the width match the text's width
644      * @param {Number} height the image height, if 0, the height match the text's height
645      * @param {Number} eAlignMask the test Alignment
646      * @param {String} pFontName the name of the font which use to draw the text. If nil, use the default system font.
647      * @param {Number} nSize the font size, if 0, use the system default size.
648      */
649     initWithString:function (text, width, height, eAlignMask, pFontName, nSize) {
650     }
651 });
652