/**************************************************************************** Copyright (c) 2010 cocos2d-x.org http://www.cocos2d-x.org Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ****************************************************************************/ #include "CCImage.h" #include #include #ifdef EMSCRIPTEN #include #include #endif // EMSCRIPTEN extern "C" { #include "png.h" #include "tiffio.h" #include "etc1.h" #include "jpeglib.h" } #include "third_party/common/s3tc/s3tc.h" #if defined(__native_client__) || defined(EMSCRIPTEN) // TODO(sbc): I'm pretty sure all platforms should be including // webph headers in this way. #include "webp/decode.h" #else #include "decode.h" #endif #include "ccMacros.h" #include "CCCommon.h" #include "CCStdC.h" #include "CCFileUtils.h" #include "CCConfiguration.h" #include "support/ccUtils.h" #include "support/zip_support/ZipUtils.h" #if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) #include "platform/android/CCFileUtilsAndroid.h" #endif NS_CC_BEGIN ////////////////////////////////////////////////////////////////////////// //struct and data for pvr structure namespace { static const int PVR_TEXTURE_FLAG_TYPE_MASK = 0xff; // Values taken from PVRTexture.h from http://www.imgtec.com enum class PVR2TextureFlag{ Mipmap = (1<<8), // has mip map levels Twiddle = (1<<9), // is twiddled Bumpmap = (1<<10), // has normals encoded for a bump map Tiling = (1<<11), // is bordered for tiled pvr Cubemap = (1<<12), // is a cubemap/skybox FalseMipCol = (1<<13), // are there false colored MIP levels Volume = (1<<14), // is this a volume texture Alpha = (1<<15), // v2.1 is there transparency info in the texture VerticalFlip = (1<<16), // v2.1 is the texture vertically flipped }; enum class PVR3TextureFlag { PremultipliedAlpha = (1<<1) // has premultiplied alpha }; static const char gPVRTexIdentifier[5] = "PVR!"; // v2 enum class PVR2TexturePixelFormat : unsigned char { RGBA4444 = 0x10, RGBA5551, RGBA8888, RGB565, RGB555, // unsupported RGB888, I8, AI88, PVRTC2BPP_RGBA, PVRTC4BPP_RGBA, BGRA8888, A8, }; // v3 enum class PVR3TexturePixelFormat : uint64_t { PVRTC2BPP_RGB = 0ULL, PVRTC2BPP_RGBA = 1ULL, PVRTC4BPP_RGB = 2ULL, PVRTC4BPP_RGBA = 3ULL, BGRA8888 = 0x0808080861726762ULL, RGBA8888 = 0x0808080861626772ULL, RGBA4444 = 0x0404040461626772ULL, RGBA5551 = 0x0105050561626772ULL, RGB565 = 0x0005060500626772ULL, RGB888 = 0x0008080800626772ULL, A8 = 0x0000000800000061ULL, L8 = 0x000000080000006cULL, LA88 = 0x000008080000616cULL, }; // v2 typedef const std::map _pixel2_formathash; static const _pixel2_formathash::value_type v2_pixel_formathash_value[] = { _pixel2_formathash::value_type(PVR2TexturePixelFormat::BGRA8888, Texture2D::PixelFormat::BGRA8888), _pixel2_formathash::value_type(PVR2TexturePixelFormat::RGBA8888, Texture2D::PixelFormat::RGBA8888), _pixel2_formathash::value_type(PVR2TexturePixelFormat::RGBA4444, Texture2D::PixelFormat::RGBA4444), _pixel2_formathash::value_type(PVR2TexturePixelFormat::RGBA5551, Texture2D::PixelFormat::RGB5A1), _pixel2_formathash::value_type(PVR2TexturePixelFormat::RGB565, Texture2D::PixelFormat::RGB565), _pixel2_formathash::value_type(PVR2TexturePixelFormat::RGB888, Texture2D::PixelFormat::RGB888), _pixel2_formathash::value_type(PVR2TexturePixelFormat::A8, Texture2D::PixelFormat::A8), _pixel2_formathash::value_type(PVR2TexturePixelFormat::I8, Texture2D::PixelFormat::I8), _pixel2_formathash::value_type(PVR2TexturePixelFormat::AI88, Texture2D::PixelFormat::AI88), #ifdef GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG _pixel2_formathash::value_type(PVR2TexturePixelFormat::PVRTC2BPP_RGBA, Texture2D::PixelFormat::PVRTC2A), _pixel2_formathash::value_type(PVR2TexturePixelFormat::PVRTC4BPP_RGBA, Texture2D::PixelFormat::PVRTC4A), #endif }; static const int PVR2_MAX_TABLE_ELEMENTS = sizeof(v2_pixel_formathash_value) / sizeof(v2_pixel_formathash_value[0]); static const _pixel2_formathash v2_pixel_formathash(v2_pixel_formathash_value, v2_pixel_formathash_value + PVR2_MAX_TABLE_ELEMENTS); // v3 typedef const std::map _pixel3_formathash; static _pixel3_formathash::value_type v3_pixel_formathash_value[] = { _pixel3_formathash::value_type(PVR3TexturePixelFormat::BGRA8888, Texture2D::PixelFormat::BGRA8888), _pixel3_formathash::value_type(PVR3TexturePixelFormat::RGBA8888, Texture2D::PixelFormat::RGBA8888), _pixel3_formathash::value_type(PVR3TexturePixelFormat::RGBA4444, Texture2D::PixelFormat::RGBA4444), _pixel3_formathash::value_type(PVR3TexturePixelFormat::RGBA5551, Texture2D::PixelFormat::RGB5A1), _pixel3_formathash::value_type(PVR3TexturePixelFormat::RGB565, Texture2D::PixelFormat::RGB565), _pixel3_formathash::value_type(PVR3TexturePixelFormat::RGB888, Texture2D::PixelFormat::RGB888), _pixel3_formathash::value_type(PVR3TexturePixelFormat::A8, Texture2D::PixelFormat::A8), _pixel3_formathash::value_type(PVR3TexturePixelFormat::L8, Texture2D::PixelFormat::I8), _pixel3_formathash::value_type(PVR3TexturePixelFormat::LA88, Texture2D::PixelFormat::AI88), #ifdef GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG _pixel3_formathash::value_type(PVR3TexturePixelFormat::PVRTC2BPP_RGB, Texture2D::PixelFormat::PVRTC2), _pixel3_formathash::value_type(PVR3TexturePixelFormat::PVRTC2BPP_RGBA, Texture2D::PixelFormat::PVRTC2A), _pixel3_formathash::value_type(PVR3TexturePixelFormat::PVRTC4BPP_RGB, Texture2D::PixelFormat::PVRTC4), _pixel3_formathash::value_type(PVR3TexturePixelFormat::PVRTC4BPP_RGBA, Texture2D::PixelFormat::PVRTC4A), #endif }; static const int PVR3_MAX_TABLE_ELEMENTS = sizeof(v3_pixel_formathash_value) / sizeof(v3_pixel_formathash_value[0]); static const _pixel3_formathash v3_pixel_formathash(v3_pixel_formathash_value, v3_pixel_formathash_value + PVR3_MAX_TABLE_ELEMENTS); typedef struct _PVRTexHeader { unsigned int headerLength; unsigned int height; unsigned int width; unsigned int numMipmaps; unsigned int flags; unsigned int dataLength; unsigned int bpp; unsigned int bitmaskRed; unsigned int bitmaskGreen; unsigned int bitmaskBlue; unsigned int bitmaskAlpha; unsigned int pvrTag; unsigned int numSurfs; } PVRv2TexHeader; #ifdef _MSC_VER #pragma pack(push,1) #endif typedef struct { uint32_t version; uint32_t flags; uint64_t pixelFormat; uint32_t colorSpace; uint32_t channelType; uint32_t height; uint32_t width; uint32_t depth; uint32_t numberOfSurfaces; uint32_t numberOfFaces; uint32_t numberOfMipmaps; uint32_t metadataLength; #ifdef _MSC_VER } PVRv3TexHeader; #pragma pack(pop) #else } __attribute__((packed)) PVRv3TexHeader; #endif } //pvr structure end ////////////////////////////////////////////////////////////////////////// //struct and data for s3tc(dds) struct namespace { typedef struct { uint32_t dwColorSpaceLowValue; uint32_t dwColorSpaceHighValue; } DDCOLORKEY; typedef struct { uint32_t dwCaps; uint32_t dwCaps2; uint32_t dwCaps3; uint32_t dwCaps4; } DDSCAPS2; typedef struct { uint32_t dwSize; uint32_t dwFlags; uint32_t dwFourCC; uint32_t dwRGBBitCount; uint32_t dwRBitMask; uint32_t dwGBitMask; uint32_t dwBBitMask; uint32_t dwABitMask; }DDPIXELFORMAT; typedef struct { uint32_t dwSize; uint32_t dwFlags; uint32_t dwHeight; uint32_t dwWidth; union { uint32_t lPitch; uint32_t dwLinearSize; } DUMMYUNIONNAMEN1; union { uint32_t dwBackBufferCount; uint32_t dwDepth; } DUMMYUNIONNAMEN5; union { uint32_t dwMipMapCount; uint32_t dwRefreshRate; uint32_t dwSrcVBHandle; } DUMMYUNIONNAMEN2; uint32_t dwAlphaBitDepth; uint32_t dwReserved; uint32_t lpSurface; union { DDCOLORKEY ddckCKDestOverlay; uint32_t dwEmptyFaceColor; } DUMMYUNIONNAMEN3; DDCOLORKEY ddckCKDestBlt; DDCOLORKEY ddckCKSrcOverlay; DDCOLORKEY ddckCKSrcBlt; union { DDPIXELFORMAT ddpfPixelFormat; uint32_t dwFVF; } DUMMYUNIONNAMEN4; DDSCAPS2 ddsCaps; uint32_t dwTextureStage; } DDSURFACEDESC2, *LPDDSURFACEDESC2; #pragma pack(push,1) typedef struct { char fileCode[4]; DDSURFACEDESC2 ddsd; }ccS3TCTexHeader; #pragma pack(pop) } //s3tc struct end ///////////////////////////////////////////////////////////////////// namespace { typedef struct { unsigned char* data; int size; int offset; }tImageSource; static void pngReadCallback(png_structp png_ptr, png_bytep data, png_size_t length) { tImageSource* isource = (tImageSource*)png_get_io_ptr(png_ptr); if((int)(isource->offset + length) <= isource->size) { memcpy(data, isource->data+isource->offset, length); isource->offset += length; } else { png_error(png_ptr, "pngReaderCallback failed"); } } } ////////////////////////////////////////////////////////////////////////// // Implement Image ////////////////////////////////////////////////////////////////////////// Image::Image() : _data(0) , _dataLen(0) , _width(0) , _height(0) , _fileType(Format::UNKOWN) , _renderFormat(Texture2D::PixelFormat::NONE) , _preMulti(false) , _hasPremultipliedAlpha(true) , _numberOfMipmaps(0) { } Image::~Image() { CC_SAFE_DELETE_ARRAY(_data); } bool Image::initWithImageFile(const char * strPath) { bool bRet = false; std::string fullPath = FileUtils::getInstance()->fullPathForFilename(strPath); #ifdef EMSCRIPTEN // Emscripten includes a re-implementation of SDL that uses HTML5 canvas // operations underneath. Consequently, loading images via IMG_Load (an SDL // API) will be a lot faster than running libpng et al as compiled with // Emscripten. SDL_Surface *iSurf = IMG_Load(fullPath.c_str()); int size = 4 * (iSurf->w * iSurf->h); bRet = initWithRawData((void*)iSurf->pixels, size, iSurf->w, iSurf->h, 8, true); unsigned int *tmp = (unsigned int *)_data; int nrPixels = iSurf->w * iSurf->h; for(int i = 0; i < nrPixels; i++) { unsigned char *p = _data + i * 4; tmp[i] = CC_RGB_PREMULTIPLY_ALPHA( p[0], p[1], p[2], p[3] ); } SDL_FreeSurface(iSurf); #else unsigned long bufferLen = 0; unsigned char* buffer = nullptr; //detecgt and unzip the compress file if (ZipUtils::ccIsCCZFile(fullPath.c_str())) { bufferLen = ZipUtils::ccInflateCCZFile(fullPath.c_str(), &buffer); }else if (ZipUtils::ccIsGZipFile(fullPath.c_str())) { bufferLen = ZipUtils::ccInflateGZipFile(fullPath.c_str(), &buffer); } else { buffer = FileUtils::getInstance()->getFileData(fullPath.c_str(), "rb", &bufferLen); } if (buffer != NULL && bufferLen > 0) { bRet = initWithImageData(buffer, bufferLen); } CC_SAFE_DELETE_ARRAY(buffer); #endif // EMSCRIPTEN return bRet; } bool Image::initWithImageFileThreadSafe(const char *fullpath) { bool bRet = false; unsigned long dataLen = 0; #if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) FileUtilsAndroid *fileUitls = (FileUtilsAndroid*)FileUtils::getInstance(); unsigned char *pBuffer = fileUitls->getFileDataForAsync(fullpath, "rb", &dataLen); #else unsigned char *pBuffer = FileUtils::getInstance()->getFileData(fullpath, "rb", &dataLen); #endif if (pBuffer != NULL && dataLen > 0) { bRet = initWithImageData(pBuffer, dataLen); } CC_SAFE_DELETE_ARRAY(pBuffer); return bRet; } bool Image::initWithImageData(const void * data, int dataLen) { do { CC_BREAK_IF(! data || dataLen <= 0); _fileType = detectFormat(data, dataLen); switch (_fileType) { case Format::PNG: return initWithPngData(data, dataLen); case Format::JPG: return initWithJpgData(data, dataLen); case Format::TIFF: return initWithTiffData(data, dataLen); case Format::WEBP: return initWithWebpData(data, dataLen); case Format::PVR: return initWithPVRData(data, dataLen); case Format::ETC: return initWithETCData(data, dataLen); case Format::S3TC: return initWithS3TCData(data, dataLen); default: CCAssert(false, "unsupport image format!"); return false; } } while (0); return false; } bool Image::isPng(const void *data, int dataLen) { if (dataLen <= 8) { return false; } static const unsigned char PNG_SIGNATURE[] = {0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a}; return memcmp(PNG_SIGNATURE, data, sizeof(PNG_SIGNATURE)) == 0; } bool Image::isEtc(const void *data, int dataLen) { return etc1_pkm_is_valid((etc1_byte*)data) ? true : false; } bool Image::isS3TC(const void *data, int dataLen) { ccS3TCTexHeader *header = (ccS3TCTexHeader *)data; if (strncmp(header->fileCode, "DDS", 3)!= 0) { CCLOG("cocos2d: the file is not a dds file!"); return false; } return true; } bool Image::isJpg(const void *data, int dataLen) { if (dataLen <= 4) { return false; } static const unsigned char JPG_SOI[] = {0xFF, 0xD8}; return memcmp(data, JPG_SOI, 2) == 0; } bool Image::isTiff(const void *data, int dataLen) { if (dataLen <= 4) { return false; } static const char* TIFF_II = "II"; static const char* TIFF_MM = "MM"; return (memcmp(data, TIFF_II, 2) == 0 && *(static_cast(data) + 2) == 42 && *(static_cast(data) + 3) == 0) || (memcmp(data, TIFF_MM, 2) == 0 && *(static_cast(data) + 2) == 0 && *(static_cast(data) + 3) == 42); } bool Image::isWebp(const void *data, int dataLen) { if (dataLen <= 12) { return false; } static const char* WEBP_RIFF = "RIFF"; static const char* WEBP_WEBP = "WEBP"; return memcmp(data, WEBP_RIFF, 4) == 0 && memcmp(static_cast(data) + 8, WEBP_WEBP, 4) == 0; } bool Image::isPvr(const void *data, int dataLen) { if (dataLen < sizeof(PVRv2TexHeader) || dataLen < sizeof(PVRv3TexHeader)) { return false; } const PVRv2TexHeader* headerv2 = static_cast(data); const PVRv3TexHeader* headerv3 = static_cast(data); return memcmp(&headerv2->pvrTag, gPVRTexIdentifier, strlen(gPVRTexIdentifier)) == 0 || CC_SWAP_INT32_BIG_TO_HOST(headerv3->version) == 0x50565203; } Image::Format Image::detectFormat(const void* data, int dataLen) { if (isPng(data, dataLen)) { return Format::PNG; }else if (isJpg(data, dataLen)) { return Format::JPG; }else if (isTiff(data, dataLen)) { return Format::TIFF; }else if (isWebp(data, dataLen)) { return Format::WEBP; }else if (isPvr(data, dataLen)) { return Format::PVR; }else if (isEtc(data, dataLen)) { return Format::ETC; }else if (isS3TC(data, dataLen)) { return Format::S3TC; } else { return Format::UNKOWN; } } int Image::getBitPerPixel() { return Texture2D::getPixelFormatInfoMap().at(_renderFormat).bpp; } bool Image::hasAlpha() { return Texture2D::getPixelFormatInfoMap().at(_renderFormat).alpha; } bool Image::isCompressed() { return Texture2D::getPixelFormatInfoMap().at(_renderFormat).compressed; } namespace { /* * ERROR HANDLING: * * The JPEG library's standard error handler (jerror.c) is divided into * several "methods" which you can override individually. This lets you * adjust the behavior without duplicating a lot of code, which you might * have to update with each future release. * * We override the "error_exit" method so that control is returned to the * library's caller when a fatal error occurs, rather than calling exit() * as the standard error_exit method does. * * We use C's setjmp/longjmp facility to return control. This means that the * routine which calls the JPEG library must first execute a setjmp() call to * establish the return point. We want the replacement error_exit to do a * longjmp(). But we need to make the setjmp buffer accessible to the * error_exit routine. To do this, we make a private extension of the * standard JPEG error handler object. (If we were using C++, we'd say we * were making a subclass of the regular error handler.) * * Here's the extended error handler struct: */ struct MyErrorMgr { struct jpeg_error_mgr pub; /* "public" fields */ jmp_buf setjmp_buffer; /* for return to caller */ }; typedef struct MyErrorMgr * MyErrorPtr; /* * Here's the routine that will replace the standard error_exit method: */ METHODDEF(void) myErrorExit(j_common_ptr cinfo) { /* cinfo->err really points to a MyErrorMgr struct, so coerce pointer */ MyErrorPtr myerr = (MyErrorPtr) cinfo->err; /* Always display the message. */ /* We could postpone this until after returning, if we chose. */ /* internal message function cann't show error message in some platforms, so we rewrite it here. * edit it if has version confilict. */ //(*cinfo->err->output_message) (cinfo); char buffer[JMSG_LENGTH_MAX]; (*cinfo->err->format_message) (cinfo, buffer); CCLOG("jpeg error: %s", buffer); /* Return control to the setjmp point */ longjmp(myerr->setjmp_buffer, 1); } } bool Image::initWithJpgData(const void * data, int dataLen) { /* these are standard libjpeg structures for reading(decompression) */ struct jpeg_decompress_struct cinfo; /* We use our private extension JPEG error handler. * Note that this struct must live as long as the main JPEG parameter * struct, to avoid dangling-pointer problems. */ struct MyErrorMgr jerr; /* libjpeg data structure for storing one row, that is, scanline of an image */ JSAMPROW row_pointer[1] = {0}; unsigned long location = 0; unsigned int i = 0; bool bRet = false; do { /* We set up the normal JPEG error routines, then override error_exit. */ cinfo.err = jpeg_std_error(&jerr.pub); jerr.pub.error_exit = myErrorExit; /* Establish the setjmp return context for MyErrorExit to use. */ if (setjmp(jerr.setjmp_buffer)) { /* If we get here, the JPEG code has signaled an error. * We need to clean up the JPEG object, close the input file, and return. */ jpeg_destroy_decompress(&cinfo); break; } /* setup decompression process and source, then read JPEG header */ jpeg_create_decompress( &cinfo ); #ifndef CC_TARGET_QT5 jpeg_mem_src( &cinfo, (unsigned char *) data, dataLen ); #endif /* CC_TARGET_QT5 */ /* reading the image header which contains image information */ #if (JPEG_LIB_VERSION >= 90) // libjpeg 0.9 adds stricter types. jpeg_read_header( &cinfo, TRUE ); #else jpeg_read_header( &cinfo, true ); #endif // we only support RGB or grayscale if (cinfo.jpeg_color_space == JCS_GRAYSCALE) { _renderFormat = Texture2D::PixelFormat::I8; }else { cinfo.out_color_space = JCS_RGB; _renderFormat = Texture2D::PixelFormat::RGB888; } /* Start decompression jpeg here */ jpeg_start_decompress( &cinfo ); /* init image info */ _width = (short)(cinfo.output_width); _height = (short)(cinfo.output_height); _preMulti = false; row_pointer[0] = new unsigned char[cinfo.output_width*cinfo.output_components]; CC_BREAK_IF(! row_pointer[0]); _dataLen = cinfo.output_width*cinfo.output_height*cinfo.output_components; _data = new unsigned char[_dataLen]; CC_BREAK_IF(! _data); /* now actually read the jpeg into the raw buffer */ /* read one scan line at a time */ while( cinfo.output_scanline < cinfo.output_height ) { jpeg_read_scanlines( &cinfo, row_pointer, 1 ); for( i=0; i0) { n=0x80000000UL; if ((uint64)n>mb) n=(unsigned long)mb; if((int)(isource->offset + n) <= isource->size) { memcpy(ma, isource->data+isource->offset, n); isource->offset += n; o = n; } else { return 0; } ma+=o; mb-=o; p+=o; if (o!=n) { break; } } return p; } static tmsize_t tiffWriteProc(thandle_t fd, void* buf, tmsize_t size) { CC_UNUSED_PARAM(fd); CC_UNUSED_PARAM(buf); CC_UNUSED_PARAM(size); return 0; } static uint64 tiffSeekProc(thandle_t fd, uint64 off, int whence) { tImageSource* isource = (tImageSource*)fd; uint64 ret = -1; do { if (whence == SEEK_SET) { CC_BREAK_IF(off >= (uint64)isource->size); ret = isource->offset = (uint32)off; } else if (whence == SEEK_CUR) { CC_BREAK_IF(isource->offset + off >= (uint64)isource->size); ret = isource->offset += (uint32)off; } else if (whence == SEEK_END) { CC_BREAK_IF(off >= (uint64)isource->size); ret = isource->offset = (uint32)(isource->size-1 - off); } else { CC_BREAK_IF(off >= (uint64)isource->size); ret = isource->offset = (uint32)off; } } while (0); return ret; } static uint64 tiffSizeProc(thandle_t fd) { tImageSource* pImageSrc = (tImageSource*)fd; return pImageSrc->size; } static int tiffCloseProc(thandle_t fd) { CC_UNUSED_PARAM(fd); return 0; } static int tiffMapProc(thandle_t fd, void** pbase, toff_t* psize) { CC_UNUSED_PARAM(fd); CC_UNUSED_PARAM(pbase); CC_UNUSED_PARAM(psize); return 0; } static void tiffUnmapProc(thandle_t fd, void* base, toff_t size) { CC_UNUSED_PARAM(fd); CC_UNUSED_PARAM(base); CC_UNUSED_PARAM(size); } } bool Image::initWithTiffData(const void* data, int dataLen) { bool bRet = false; do { // set the read call back function tImageSource imageSource; imageSource.data = (unsigned char*)data; imageSource.size = dataLen; imageSource.offset = 0; TIFF* tif = TIFFClientOpen("file.tif", "r", (thandle_t)&imageSource, tiffReadProc, tiffWriteProc, tiffSeekProc, tiffCloseProc, tiffSizeProc, tiffMapProc, tiffUnmapProc); CC_BREAK_IF(NULL == tif); uint32 w = 0, h = 0; uint16 bitsPerSample = 0, samplePerPixel = 0, planarConfig = 0; size_t npixels = 0; TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &w); TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &h); TIFFGetField(tif, TIFFTAG_BITSPERSAMPLE, &bitsPerSample); TIFFGetField(tif, TIFFTAG_SAMPLESPERPIXEL, &samplePerPixel); TIFFGetField(tif, TIFFTAG_PLANARCONFIG, &planarConfig); npixels = w * h; _renderFormat = Texture2D::PixelFormat::RGBA8888; _width = w; _height = h; _dataLen = npixels * sizeof (uint32); _data = new unsigned char[_dataLen]; uint32* raster = (uint32*) _TIFFmalloc(npixels * sizeof (uint32)); if (raster != NULL) { if (TIFFReadRGBAImageOriented(tif, w, h, raster, ORIENTATION_TOPLEFT, 0)) { /* the raster data is pre-multiplied by the alpha component after invoking TIFFReadRGBAImageOriented*/ _preMulti = true; memcpy(_data, raster, npixels*sizeof (uint32)); } _TIFFfree(raster); } TIFFClose(tif); bRet = true; } while (0); return bRet; } namespace { bool testFormatForPvr2TCSupport(PVR2TexturePixelFormat format) { if (!Configuration::getInstance()->supportsPVRTC()) { if (format == PVR2TexturePixelFormat::PVRTC2BPP_RGBA || format == PVR2TexturePixelFormat::PVRTC4BPP_RGBA) { return false; } } return true; } bool testFormatForPvr3TCSupport(PVR3TexturePixelFormat format) { if (!Configuration::getInstance()->supportsPVRTC()) { if (format == PVR3TexturePixelFormat::PVRTC2BPP_RGB || format == PVR3TexturePixelFormat::PVRTC2BPP_RGBA || format == PVR3TexturePixelFormat::PVRTC4BPP_RGB || format == PVR3TexturePixelFormat::PVRTC4BPP_RGBA) { return false; } } return true; } } bool Image::initWithPVRv2Data(const void *data, int dataLen) { int dataLength = 0, dataOffset = 0, dataSize = 0; int blockSize = 0, widthBlocks = 0, heightBlocks = 0; int width = 0, height = 0; //Cast first sizeof(PVRTexHeader) bytes of data stream as PVRTexHeader const PVRv2TexHeader *header = static_cast(data); //Make sure that tag is in correct formatting if (memcmp(&header->pvrTag, gPVRTexIdentifier, strlen(gPVRTexIdentifier)) != 0) { return false; } Configuration *configuration = Configuration::getInstance(); _hasPremultipliedAlpha = false; unsigned int flags = CC_SWAP_INT32_LITTLE_TO_HOST(header->flags); PVR2TexturePixelFormat formatFlags = static_cast(flags & PVR_TEXTURE_FLAG_TYPE_MASK); bool flipped = (flags & (unsigned int)PVR2TextureFlag::VerticalFlip) ? true : false; if (flipped) { CCLOG("cocos2d: WARNING: Image is flipped. Regenerate it using PVRTexTool"); } if (! configuration->supportsNPOT() && (header->width != ccNextPOT(header->width) || header->height != ccNextPOT(header->height))) { CCLOG("cocos2d: ERROR: Loading an NPOT texture (%dx%d) but is not supported on this device", header->width, header->height); return false; } if (!testFormatForPvr2TCSupport(formatFlags)) { CCLOG("cocos2d: WARNING: Unsupported PVR Pixel Format: 0x%02X. Re-encode it with a OpenGL pixel format variant", formatFlags); return false; } if (v2_pixel_formathash.find(formatFlags) == v2_pixel_formathash.end()) { CCLOG("cocos2d: WARNING: Unsupported PVR Pixel Format: 0x%02X. Re-encode it with a OpenGL pixel format variant", formatFlags); return false; } auto it = Texture2D::getPixelFormatInfoMap().find(v2_pixel_formathash.at(formatFlags)); if (it == Texture2D::getPixelFormatInfoMap().end()) { CCLOG("cocos2d: WARNING: Unsupported PVR Pixel Format: 0x%02X. Re-encode it with a OpenGL pixel format variant", formatFlags); return false; } _renderFormat = it->first; //Reset num of mipmaps _numberOfMipmaps = 0; //Get size of mipmap _width = width = CC_SWAP_INT32_LITTLE_TO_HOST(header->width); _height = height = CC_SWAP_INT32_LITTLE_TO_HOST(header->height); //Get ptr to where data starts.. dataLength = CC_SWAP_INT32_LITTLE_TO_HOST(header->dataLength); //Move by size of header _dataLen = dataLen - sizeof(PVRv2TexHeader); _data = new unsigned char[_dataLen]; memcpy(_data, (unsigned char*)data + sizeof(PVRv2TexHeader), _dataLen); // Calculate the data size for each texture level and respect the minimum number of blocks while (dataOffset < dataLength) { switch (formatFlags) { case PVR2TexturePixelFormat::PVRTC2BPP_RGBA: blockSize = 8 * 4; // Pixel by pixel block size for 2bpp widthBlocks = width / 8; heightBlocks = height / 4; break; case PVR2TexturePixelFormat::PVRTC4BPP_RGBA: blockSize = 4 * 4; // Pixel by pixel block size for 4bpp widthBlocks = width / 4; heightBlocks = height / 4; break; case PVR2TexturePixelFormat::BGRA8888: if (Configuration::getInstance()->supportsBGRA8888() == false) { CCLOG("cocos2d: Image. BGRA8888 not supported on this device"); return false; } default: blockSize = 1; widthBlocks = width; heightBlocks = height; break; } // Clamp to minimum number of blocks if (widthBlocks < 2) { widthBlocks = 2; } if (heightBlocks < 2) { heightBlocks = 2; } dataSize = widthBlocks * heightBlocks * ((blockSize * it->second.bpp) / 8); int packetLength = (dataLength - dataOffset); packetLength = packetLength > dataSize ? dataSize : packetLength; //Make record to the mipmaps array and increment counter _mipmaps[_numberOfMipmaps].address = _data + dataOffset; _mipmaps[_numberOfMipmaps].len = packetLength; _numberOfMipmaps++; dataOffset += packetLength; //Update width and height to the next lower power of two width = MAX(width >> 1, 1); height = MAX(height >> 1, 1); } return true; } bool Image::initWithPVRv3Data(const void *data, int dataLen) { if (dataLen < sizeof(PVRv3TexHeader)) { return false; } const PVRv3TexHeader *header = static_cast(data); // validate version if (CC_SWAP_INT32_BIG_TO_HOST(header->version) != 0x50565203) { CCLOG("cocos2d: WARNING: pvr file version mismatch"); return false; } // parse pixel format PVR3TexturePixelFormat pixelFormat = static_cast(header->pixelFormat); if (!testFormatForPvr3TCSupport(pixelFormat)) { CCLOG("cocos2d: WARNING: Unsupported PVR Pixel Format: 0x%016llX. Re-encode it with a OpenGL pixel format variant", static_cast(pixelFormat)); return false; } if (v3_pixel_formathash.find(pixelFormat) == v3_pixel_formathash.end()) { CCLOG("cocos2d: WARNING: Unsupported PVR Pixel Format: 0x%016llX. Re-encode it with a OpenGL pixel format variant", static_cast(pixelFormat)); return false; } auto it = Texture2D::getPixelFormatInfoMap().find(v3_pixel_formathash.at(pixelFormat)); if (it == Texture2D::getPixelFormatInfoMap().end()) { CCLOG("cocos2d: WARNING: Unsupported PVR Pixel Format: 0x%016llX. Re-encode it with a OpenGL pixel format variant", static_cast(pixelFormat)); return false; } _renderFormat = it->first; // flags int flags = CC_SWAP_INT32_LITTLE_TO_HOST(header->flags); // PVRv3 specifies premultiply alpha in a flag -- should always respect this in PVRv3 files if (flags & (unsigned int)PVR3TextureFlag::PremultipliedAlpha) { _preMulti = true; } // sizing int width = CC_SWAP_INT32_LITTLE_TO_HOST(header->width); int height = CC_SWAP_INT32_LITTLE_TO_HOST(header->height); _width = width; _height = height; int dataOffset = 0, dataSize = 0; int blockSize = 0, widthBlocks = 0, heightBlocks = 0; _dataLen = dataLen - (sizeof(PVRv3TexHeader) + header->metadataLength); _data = new unsigned char[_dataLen]; memcpy(_data, static_cast(data) + sizeof(PVRv3TexHeader) + header->metadataLength, _dataLen); _numberOfMipmaps = header->numberOfMipmaps; CCAssert(_numberOfMipmaps < MIPMAP_MAX, "Image: Maximum number of mimpaps reached. Increate the CC_MIPMAP_MAX value"); for (int i = 0; i < _numberOfMipmaps; i++) { switch ((PVR3TexturePixelFormat)pixelFormat) { case PVR3TexturePixelFormat::PVRTC2BPP_RGB : case PVR3TexturePixelFormat::PVRTC2BPP_RGBA : blockSize = 8 * 4; // Pixel by pixel block size for 2bpp widthBlocks = width / 8; heightBlocks = height / 4; break; case PVR3TexturePixelFormat::PVRTC4BPP_RGB : case PVR3TexturePixelFormat::PVRTC4BPP_RGBA : blockSize = 4 * 4; // Pixel by pixel block size for 4bpp widthBlocks = width / 4; heightBlocks = height / 4; break; case PVR3TexturePixelFormat::BGRA8888: if( ! Configuration::getInstance()->supportsBGRA8888()) { CCLOG("cocos2d: Image. BGRA8888 not supported on this device"); return false; } default: blockSize = 1; widthBlocks = width; heightBlocks = height; break; } // Clamp to minimum number of blocks if (widthBlocks < 2) { widthBlocks = 2; } if (heightBlocks < 2) { heightBlocks = 2; } dataSize = widthBlocks * heightBlocks * ((blockSize * it->second.bpp) / 8); int packetLength = _dataLen - dataOffset; packetLength = packetLength > dataSize ? dataSize : packetLength; _mipmaps[i].address = _data + dataOffset; _mipmaps[i].len = packetLength; dataOffset += packetLength; CCAssert(dataOffset <= _dataLen, "CCTexurePVR: Invalid lenght"); width = MAX(width >> 1, 1); height = MAX(height >> 1, 1); } return true; } bool Image::initWithETCData(const void *data, int dataLen) { const etc1_byte* header = static_cast(data); //check the data if(!etc1_pkm_is_valid(header)) { return false; } _width = etc1_pkm_get_width(header); _height = etc1_pkm_get_height(header); if( 0 == _width || 0 == _height ) { return false; } if(Configuration::getInstance()->supportsETC()) { //old opengl version has no define for GL_ETC1_RGB8_OES, add macro to make compiler happy. #ifdef GL_ETC1_RGB8_OES _renderFormat = Texture2D::PixelFormat::ETC; _dataLen = dataLen - ETC_PKM_HEADER_SIZE; _data = new unsigned char[_dataLen]; memcpy(_data, static_cast(data) + ETC_PKM_HEADER_SIZE, _dataLen); return true; #endif } else { //if it is not gles or device do not support ETC, decode texture by software int bytePerPixel = 3; unsigned int stride = _width * bytePerPixel; _renderFormat = Texture2D::PixelFormat::RGB888; _dataLen = _width * _height * bytePerPixel; _data = new unsigned char[_dataLen]; if (etc1_decode_image(static_cast(data) + ETC_PKM_HEADER_SIZE, static_cast(_data), _width, _height, bytePerPixel, stride) != 0) { _dataLen = 0; CC_SAFE_DELETE_ARRAY(_data); return false; } return true; } return false; } const uint32_t makeFourCC(char ch0, char ch1, char ch2, char ch3) { const uint32_t fourCC=((uint32_t)(char)(ch0) | ((uint32_t)(char)(ch1) << 8) |((uint32_t)(char)(ch2) << 16) | ((uint32_t)(char)(ch3) << 24 )); return fourCC; } bool Image::initWithS3TCData(const void *data, int dataLen) { const uint32_t FOURCC_DXT1 = makeFourCC('D', 'X', 'T', '1'); const uint32_t FOURCC_DXT3 = makeFourCC('D', 'X', 'T', '3'); const uint32_t FOURCC_DXT5 = makeFourCC('D', 'X', 'T', '5'); /* load the .dds file */ ccS3TCTexHeader *header = (ccS3TCTexHeader *)data;; unsigned char *pixel_data = (unsigned char*)malloc(dataLen-sizeof(ccS3TCTexHeader)); memcpy((void *)pixel_data, (unsigned char*)data+sizeof(ccS3TCTexHeader), dataLen-sizeof(ccS3TCTexHeader)); _width = header->ddsd.dwWidth; _height = header->ddsd.dwHeight; _numberOfMipmaps = header->ddsd.DUMMYUNIONNAMEN2.dwMipMapCount; _dataLen = 0; int blockSize = (FOURCC_DXT1==header->ddsd.DUMMYUNIONNAMEN4.ddpfPixelFormat.dwFourCC) ? 8 : 16; /* caculate the dataLen */ int width = _width; int height = _height; if(Configuration::getInstance()->supportsS3TC()) //compressed data length { _dataLen = dataLen -sizeof(ccS3TCTexHeader); _data = new unsigned char [_dataLen]; memcpy((void *)_data,(void *)pixel_data , _dataLen); }else //decompressed data length { for(unsigned int i = 0; i< _numberOfMipmaps && (width || height); ++i) { if (width == 0) width = 1; if (height == 0) height = 1; _dataLen += (height * width *4); width >>= 1; height >>= 1; } _data = new unsigned char [_dataLen]; } /* load the mipmaps */ int encode_offset = 0; int decode_offset = 0; width = _width; height =_height; for (unsigned int i = 0; i < _numberOfMipmaps && (width || height); ++i) // { if (width == 0) width = 1; if (height == 0) height = 1; int size = ((width+3)/4)*((height+3)/4)*blockSize; if (Configuration::getInstance()->supportsS3TC()) { //decode texture throught hardware CCLOG("this is s3tc H decode"); if(FOURCC_DXT1==header->ddsd.DUMMYUNIONNAMEN4.ddpfPixelFormat.dwFourCC) { _renderFormat = Texture2D::PixelFormat::S3TC_Dxt1; } else if(FOURCC_DXT3==header->ddsd.DUMMYUNIONNAMEN4.ddpfPixelFormat.dwFourCC) { _renderFormat = Texture2D::PixelFormat::S3TC_Dxt3; } else if(FOURCC_DXT5==header->ddsd.DUMMYUNIONNAMEN4.ddpfPixelFormat.dwFourCC) { _renderFormat = Texture2D::PixelFormat::S3TC_Dxt5; } _mipmaps[i].address = (unsigned char *)_data + encode_offset; _mipmaps[i].len = size; } else { //if it is not gles or device do not support S3TC, decode texture by software int bytePerPixel = 4; unsigned int stride = width * bytePerPixel; _renderFormat = Texture2D::PixelFormat::RGBA8888; // std::vector decodeImageData(stride * height); if(FOURCC_DXT1==header->ddsd.DUMMYUNIONNAMEN4.ddpfPixelFormat.dwFourCC) { s3tc_decode(pixel_data+encode_offset, &decodeImageData[0], width, height, S3TCDecodeFlag::dxt1); } else if(FOURCC_DXT3==header->ddsd.DUMMYUNIONNAMEN4.ddpfPixelFormat.dwFourCC) { s3tc_decode(pixel_data+encode_offset, &decodeImageData[0], width, height, S3TCDecodeFlag::dxt3); } else if(FOURCC_DXT5==header->ddsd.DUMMYUNIONNAMEN4.ddpfPixelFormat.dwFourCC) { s3tc_decode(pixel_data+encode_offset, &decodeImageData[0], width, height, S3TCDecodeFlag::dxt5); } _mipmaps[i].address = (unsigned char *)_data + decode_offset; _mipmaps[i].len = (stride * height); memcpy((void *)_mipmaps[i].address, (void *)&decodeImageData[0], _mipmaps[i].len); decode_offset += stride * height; } encode_offset += size; width >>= 1; height >>= 1; } /* end load the mipmaps */ CC_SAFE_DELETE(pixel_data); return true; } bool Image::initWithPVRData(const void *data, int dataLen) { return initWithPVRv2Data(data, dataLen) || initWithPVRv3Data(data, dataLen); } bool Image::initWithWebpData(const void *data, int dataLen) { bool bRet = false; do { WebPDecoderConfig config; if (WebPInitDecoderConfig(&config) == 0) break; if (WebPGetFeatures(static_cast(data), dataLen, &config.input) != VP8_STATUS_OK) break; if (config.input.width == 0 || config.input.height == 0) break; config.output.colorspace = MODE_RGBA; _renderFormat = Texture2D::PixelFormat::RGBA8888; _width = config.input.width; _height = config.input.height; int bufferSize = _width * _height * 4; _data = new unsigned char[bufferSize]; config.output.u.RGBA.rgba = static_cast(_data); config.output.u.RGBA.stride = _width * 4; config.output.u.RGBA.size = bufferSize; config.output.is_external_memory = 1; if (WebPDecode(static_cast(data), dataLen, &config) != VP8_STATUS_OK) { delete []_data; _data = NULL; break; } bRet = true; } while (0); return bRet; } bool Image::initWithRawData(const void * data, int dataLen, int width, int height, int bitsPerComponent, bool preMulti) { bool bRet = false; do { CC_BREAK_IF(0 == width || 0 == height); _height = height; _width = width; _preMulti = preMulti; _renderFormat = Texture2D::PixelFormat::RGBA8888; // only RGBA8888 supported int bytesPerComponent = 4; _dataLen = height * width * bytesPerComponent; _data = new unsigned char[_dataLen]; CC_BREAK_IF(! _data); memcpy(_data, data, _dataLen); bRet = true; } while (0); return bRet; } #if (CC_TARGET_PLATFORM != CC_PLATFORM_IOS) bool Image::saveToFile(const char *pszFilePath, bool bIsToRGB) { //only support for Texture2D::PixelFormat::RGB888 or Texture2D::PixelFormat::RGBA8888 uncompressed data if (isCompressed() || (_renderFormat != Texture2D::PixelFormat::RGB888 && _renderFormat != Texture2D::PixelFormat::RGBA8888)) { CCLOG("cocos2d: Image: saveToFile is only support for Texture2D::PixelFormat::RGB888 or Texture2D::PixelFormat::RGBA8888 uncompressed data for now"); return false; } #if (CC_TARGET_PLATFORM == CC_PLATFORM_MAC) assert(false); return false; #else bool bRet = false; do { CC_BREAK_IF(NULL == pszFilePath); std::string strFilePath(pszFilePath); CC_BREAK_IF(strFilePath.size() <= 4); std::string strLowerCasePath(strFilePath); for (unsigned int i = 0; i < strLowerCasePath.length(); ++i) { strLowerCasePath[i] = tolower(strFilePath[i]); } if (std::string::npos != strLowerCasePath.find(".png")) { CC_BREAK_IF(!saveImageToPNG(pszFilePath, bIsToRGB)); } else if (std::string::npos != strLowerCasePath.find(".jpg")) { CC_BREAK_IF(!saveImageToJPG(pszFilePath)); } else { break; } bRet = true; } while (0); return bRet; #endif } #endif bool Image::saveImageToPNG(const char * filePath, bool isToRGB) { bool bRet = false; do { CC_BREAK_IF(NULL == filePath); FILE *fp; png_structp png_ptr; png_infop info_ptr; png_colorp palette; png_bytep *row_pointers; fp = fopen(filePath, "wb"); CC_BREAK_IF(NULL == fp); png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (NULL == png_ptr) { fclose(fp); break; } info_ptr = png_create_info_struct(png_ptr); if (NULL == info_ptr) { fclose(fp); png_destroy_write_struct(&png_ptr, NULL); break; } #if (CC_TARGET_PLATFORM != CC_PLATFORM_BADA && CC_TARGET_PLATFORM != CC_PLATFORM_NACL) if (setjmp(png_jmpbuf(png_ptr))) { fclose(fp); png_destroy_write_struct(&png_ptr, &info_ptr); break; } #endif png_init_io(png_ptr, fp); if (!isToRGB && hasAlpha()) { png_set_IHDR(png_ptr, info_ptr, _width, _height, 8, PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); } else { png_set_IHDR(png_ptr, info_ptr, _width, _height, 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); } palette = (png_colorp)png_malloc(png_ptr, PNG_MAX_PALETTE_LENGTH * sizeof (png_color)); png_set_PLTE(png_ptr, info_ptr, palette, PNG_MAX_PALETTE_LENGTH); png_write_info(png_ptr, info_ptr); png_set_packing(png_ptr); row_pointers = (png_bytep *)malloc(_height * sizeof(png_bytep)); if(row_pointers == NULL) { fclose(fp); png_destroy_write_struct(&png_ptr, &info_ptr); break; } if (hasAlpha()) { for (int i = 0; i < (int)_height; i++) { row_pointers[i] = (png_bytep)_data + i * _width * 3; } png_write_image(png_ptr, row_pointers); free(row_pointers); row_pointers = NULL; } else { if (isToRGB) { unsigned char *pTempData = new unsigned char[_width * _height * 3]; if (NULL == pTempData) { fclose(fp); png_destroy_write_struct(&png_ptr, &info_ptr); break; } for (int i = 0; i < _height; ++i) { for (int j = 0; j < _width; ++j) { pTempData[(i * _width + j) * 3] = _data[(i * _width + j) * 4]; pTempData[(i * _width + j) * 3 + 1] = _data[(i * _width + j) * 4 + 1]; pTempData[(i * _width + j) * 3 + 2] = _data[(i * _width + j) * 4 + 2]; } } for (int i = 0; i < (int)_height; i++) { row_pointers[i] = (png_bytep)pTempData + i * _width * 3; } png_write_image(png_ptr, row_pointers); free(row_pointers); row_pointers = NULL; CC_SAFE_DELETE_ARRAY(pTempData); } else { for (int i = 0; i < (int)_height; i++) { row_pointers[i] = (png_bytep)_data + i * _width * 4; } png_write_image(png_ptr, row_pointers); free(row_pointers); row_pointers = NULL; } } png_write_end(png_ptr, info_ptr); png_free(png_ptr, palette); palette = NULL; png_destroy_write_struct(&png_ptr, &info_ptr); fclose(fp); bRet = true; } while (0); return bRet; } bool Image::saveImageToJPG(const char * filePath) { bool bRet = false; do { CC_BREAK_IF(NULL == filePath); struct jpeg_compress_struct cinfo; struct jpeg_error_mgr jerr; FILE * outfile; /* target file */ JSAMPROW row_pointer[1]; /* pointer to JSAMPLE row[s] */ int row_stride; /* physical row width in image buffer */ cinfo.err = jpeg_std_error(&jerr); /* Now we can initialize the JPEG compression object. */ jpeg_create_compress(&cinfo); CC_BREAK_IF((outfile = fopen(filePath, "wb")) == NULL); jpeg_stdio_dest(&cinfo, outfile); cinfo.image_width = _width; /* image width and height, in pixels */ cinfo.image_height = _height; cinfo.input_components = 3; /* # of color components per pixel */ cinfo.in_color_space = JCS_RGB; /* colorspace of input image */ jpeg_set_defaults(&cinfo); jpeg_start_compress(&cinfo, TRUE); row_stride = _width * 3; /* JSAMPLEs per row in image_buffer */ if (hasAlpha()) { unsigned char *pTempData = new unsigned char[_width * _height * 3]; if (NULL == pTempData) { jpeg_finish_compress(&cinfo); jpeg_destroy_compress(&cinfo); fclose(outfile); break; } for (int i = 0; i < _height; ++i) { for (int j = 0; j < _width; ++j) { pTempData[(i * _width + j) * 3] = _data[(i * _width + j) * 4]; pTempData[(i * _width + j) * 3 + 1] = _data[(i * _width + j) * 4 + 1]; pTempData[(i * _width + j) * 3 + 2] = _data[(i * _width + j) * 4 + 2]; } } while (cinfo.next_scanline < cinfo.image_height) { row_pointer[0] = & pTempData[cinfo.next_scanline * row_stride]; (void) jpeg_write_scanlines(&cinfo, row_pointer, 1); } CC_SAFE_DELETE_ARRAY(pTempData); } else { while (cinfo.next_scanline < cinfo.image_height) { row_pointer[0] = & _data[cinfo.next_scanline * row_stride]; (void) jpeg_write_scanlines(&cinfo, row_pointer, 1); } } jpeg_finish_compress(&cinfo); fclose(outfile); jpeg_destroy_compress(&cinfo); bRet = true; } while (0); return bRet; } NS_CC_END