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