You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1182 lines
40 KiB
1182 lines
40 KiB
/****************************************************************************
|
|
Copyright (c) 2010-2012 cocos2d-x.org
|
|
Copyright (c) 2013-2016 Chukong Technologies Inc.
|
|
Copyright (c) 2016-2023 Xiamen Yaji Software Co., Ltd.
|
|
|
|
http://www.cocos.com
|
|
|
|
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 "Image.h"
|
|
#include <cctype>
|
|
#include <cstring>
|
|
#include "base/Config.h" // CC_USE_JPEG, CC_USE_WEBP
|
|
#include "base/std/container/string.h"
|
|
#include "gfx-base/GFXDef-common.h"
|
|
|
|
#if CC_USE_JPEG
|
|
#include "jpeg/jpeglib.h"
|
|
#endif // CC_USE_JPEG
|
|
|
|
#include "base/Data.h"
|
|
#include "base/Log.h"
|
|
#include "base/Utils.h"
|
|
#include "gfx-base/GFXDef.h"
|
|
|
|
extern "C" {
|
|
#if CC_USE_PNG
|
|
#if __OHOS__ || __LINUX__ || __QNX__
|
|
#include "png.h"
|
|
#else
|
|
#include "png/png.h"
|
|
#endif
|
|
#endif //CC_USE_PNG
|
|
|
|
#include "base/etc1.h"
|
|
#include "base/etc2.h"
|
|
}
|
|
|
|
#include "base/Compressed.h"
|
|
#include "base/astc.h"
|
|
|
|
#if CC_USE_WEBP
|
|
#include "webp/decode.h"
|
|
#endif // CC_USE_WEBP
|
|
|
|
#include "base/ZipUtils.h"
|
|
#include "platform/FileUtils.h"
|
|
#if (CC_PLATFORM == CC_PLATFORM_ANDROID)
|
|
#include "platform/android/FileUtils-android.h"
|
|
#endif
|
|
|
|
#include "base/std/container/unordered_map.h"
|
|
|
|
namespace cc {
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
//struct and data for pvr structure
|
|
|
|
namespace {
|
|
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
|
|
FALSE_MIP_COL = (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
|
|
VERTICAL_FLIP = (1 << 16), // v2.1 is the texture vertically flipped
|
|
};
|
|
|
|
const char G_PVR_TEX_IDENTIFIER[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,
|
|
PVRTC2_2BPP_RGBA = 4ULL,
|
|
PVRTC2_4BPP_RGBA = 5ULL,
|
|
ETC1 = 6ULL,
|
|
DXT1 = 7ULL,
|
|
DXT2 = 8ULL,
|
|
DXT3 = 9ULL,
|
|
DXT4 = 10ULL,
|
|
DXT5 = 11ULL,
|
|
BC1 = 7ULL,
|
|
BC2 = 9ULL,
|
|
BC3 = 11ULL,
|
|
BC4 = 12ULL,
|
|
BC5 = 13ULL,
|
|
BC6 = 14ULL,
|
|
BC7 = 15ULL,
|
|
UYVY = 16ULL,
|
|
YUY2 = 17ULL,
|
|
B_W1BPP = 18ULL,
|
|
R9G9B9E5 = 19ULL,
|
|
RGBG8888 = 20ULL,
|
|
GRGB8888 = 21ULL,
|
|
ETC2_RGB = 22ULL,
|
|
ETC2_RGBA = 23ULL,
|
|
ETC2_RGBA1 = 24ULL,
|
|
EAC_R11_UNSIGNED = 25ULL,
|
|
EAC_R11_SIGNED = 26ULL,
|
|
EAC_R_G11_UNSIGNED = 27ULL,
|
|
EAC_R_G11_SIGNED = 28ULL,
|
|
|
|
BGRA8888 = 0x0808080861726762ULL,
|
|
RGBA8888 = 0x0808080861626772ULL,
|
|
RGBA4444 = 0x0404040461626772ULL,
|
|
RGBA5551 = 0x0105050561626772ULL,
|
|
RGB565 = 0x0005060500626772ULL,
|
|
RGB888 = 0x0008080800626772ULL,
|
|
A8 = 0x0000000800000061ULL,
|
|
L8 = 0x000000080000006cULL,
|
|
LA88 = 0x000008080000616cULL,
|
|
};
|
|
|
|
// v2
|
|
using _pixel2_formathash = const ccstd::unordered_map<PVR2TexturePixelFormat, gfx::Format>;
|
|
|
|
const _pixel2_formathash::value_type V2_PIXEL_FORMATHASH_VALUE[] = {
|
|
_pixel2_formathash::value_type(PVR2TexturePixelFormat::BGRA8888, gfx::Format::BGRA8),
|
|
_pixel2_formathash::value_type(PVR2TexturePixelFormat::RGBA8888, gfx::Format::RGBA8),
|
|
_pixel2_formathash::value_type(PVR2TexturePixelFormat::RGBA4444, gfx::Format::RGBA4),
|
|
_pixel2_formathash::value_type(PVR2TexturePixelFormat::RGBA5551, gfx::Format::RGB5A1),
|
|
_pixel2_formathash::value_type(PVR2TexturePixelFormat::RGB565, gfx::Format::R5G6B5),
|
|
_pixel2_formathash::value_type(PVR2TexturePixelFormat::RGB888, gfx::Format::RGB8),
|
|
_pixel2_formathash::value_type(PVR2TexturePixelFormat::A8, gfx::Format::A8),
|
|
_pixel2_formathash::value_type(PVR2TexturePixelFormat::I8, gfx::Format::L8),
|
|
_pixel2_formathash::value_type(PVR2TexturePixelFormat::AI88, gfx::Format::LA8),
|
|
|
|
_pixel2_formathash::value_type(PVR2TexturePixelFormat::PVRTC2BPP_RGBA, gfx::Format::PVRTC_RGBA2),
|
|
_pixel2_formathash::value_type(PVR2TexturePixelFormat::PVRTC4BPP_RGBA, gfx::Format::PVRTC_RGBA4),
|
|
};
|
|
|
|
const int PVR2_MAX_TABLE_ELEMENTS = sizeof(V2_PIXEL_FORMATHASH_VALUE) / sizeof(V2_PIXEL_FORMATHASH_VALUE[0]);
|
|
const _pixel2_formathash V2_PIXEL_FORMATHASH(V2_PIXEL_FORMATHASH_VALUE, V2_PIXEL_FORMATHASH_VALUE + PVR2_MAX_TABLE_ELEMENTS);
|
|
|
|
// v3
|
|
using _pixel3_formathash = const ccstd::unordered_map<PVR3TexturePixelFormat, gfx::Format>;
|
|
_pixel3_formathash::value_type v3PixelFormathashValue[] = {
|
|
_pixel3_formathash::value_type(PVR3TexturePixelFormat::BGRA8888, gfx::Format::BGRA8),
|
|
_pixel3_formathash::value_type(PVR3TexturePixelFormat::RGBA8888, gfx::Format::RGBA8),
|
|
_pixel3_formathash::value_type(PVR3TexturePixelFormat::RGBA4444, gfx::Format::RGBA4),
|
|
_pixel3_formathash::value_type(PVR3TexturePixelFormat::RGBA5551, gfx::Format::RGB5A1),
|
|
_pixel3_formathash::value_type(PVR3TexturePixelFormat::RGB565, gfx::Format::R5G6B5),
|
|
_pixel3_formathash::value_type(PVR3TexturePixelFormat::RGB888, gfx::Format::RGB8),
|
|
_pixel3_formathash::value_type(PVR3TexturePixelFormat::A8, gfx::Format::A8),
|
|
_pixel3_formathash::value_type(PVR3TexturePixelFormat::L8, gfx::Format::L8),
|
|
_pixel3_formathash::value_type(PVR3TexturePixelFormat::LA88, gfx::Format::LA8),
|
|
|
|
_pixel3_formathash::value_type(PVR3TexturePixelFormat::PVRTC2BPP_RGB, gfx::Format::PVRTC_RGB2),
|
|
_pixel3_formathash::value_type(PVR3TexturePixelFormat::PVRTC2BPP_RGBA, gfx::Format::PVRTC_RGBA2),
|
|
_pixel3_formathash::value_type(PVR3TexturePixelFormat::PVRTC4BPP_RGB, gfx::Format::PVRTC_RGB4),
|
|
_pixel3_formathash::value_type(PVR3TexturePixelFormat::PVRTC4BPP_RGBA, gfx::Format::PVRTC_RGBA4),
|
|
|
|
_pixel3_formathash::value_type(PVR3TexturePixelFormat::ETC1, gfx::Format::ETC_RGB8),
|
|
};
|
|
|
|
const int PVR3_MAX_TABLE_ELEMENTS = sizeof(v3PixelFormathashValue) / sizeof(v3PixelFormathashValue[0]);
|
|
|
|
const _pixel3_formathash V3_PIXEL_FORMATHASH(v3PixelFormathashValue, v3PixelFormathashValue + PVR3_MAX_TABLE_ELEMENTS);
|
|
|
|
using PVRv2TexHeader = 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;
|
|
};
|
|
|
|
#ifdef _MSC_VER
|
|
#pragma pack(push, 1)
|
|
#endif
|
|
using PVRv3TexHeader = 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
|
|
};
|
|
#pragma pack(pop)
|
|
#else
|
|
} __attribute__((packed));
|
|
#endif
|
|
} // namespace
|
|
//pvr structure end
|
|
|
|
namespace {
|
|
using tImageSource = struct {
|
|
const unsigned char *data;
|
|
uint32_t size;
|
|
int offset;
|
|
};
|
|
|
|
#ifdef CC_USE_PNG
|
|
void pngReadCallback(png_structp pngPtr, png_bytep data, png_size_t length) {
|
|
auto *isource = static_cast<tImageSource *>(png_get_io_ptr(pngPtr));
|
|
|
|
if (static_cast<int>(isource->offset + length) <= isource->size) {
|
|
memcpy(data, isource->data + isource->offset, length);
|
|
isource->offset += static_cast<int>(length);
|
|
} else {
|
|
png_error(pngPtr, "pngReaderCallback failed");
|
|
}
|
|
}
|
|
#endif //CC_USE_PNG
|
|
} // namespace
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Implement Image
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
Image::Image() : _renderFormat(gfx::Format::UNKNOWN) {
|
|
}
|
|
|
|
Image::~Image() {
|
|
CC_SAFE_FREE(_data);
|
|
}
|
|
|
|
bool Image::initWithImageFile(const ccstd::string &path) {
|
|
bool ret = false;
|
|
//NOTE: fullPathForFilename isn't threadsafe. we should make sure the parameter is a full path.
|
|
// _filePath = FileUtils::getInstance()->fullPathForFilename(path);
|
|
_filePath = path;
|
|
|
|
const Data data = FileUtils::getInstance()->getDataFromFile(_filePath);
|
|
|
|
if (!data.isNull()) {
|
|
ret = initWithImageData(data.getBytes(), data.getSize());
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool Image::initWithImageData(const unsigned char *data, uint32_t dataLen) { //NOLINT(misc-no-recursion)
|
|
bool ret = false;
|
|
do {
|
|
CC_BREAK_IF(!data || dataLen <= 0);
|
|
|
|
unsigned char *unpackedData = nullptr;
|
|
uint32_t unpackedLen = 0;
|
|
|
|
//detect and unzip the compress file
|
|
if (ZipUtils::isCCZBuffer(data, dataLen)) {
|
|
unpackedLen = ZipUtils::inflateCCZBuffer(data, dataLen, &unpackedData);
|
|
} else if (ZipUtils::isGZipBuffer(data, dataLen)) {
|
|
unpackedLen = ZipUtils::inflateMemory(const_cast<unsigned char *>(data), dataLen, &unpackedData);
|
|
} else {
|
|
unpackedData = const_cast<unsigned char *>(data);
|
|
unpackedLen = dataLen;
|
|
}
|
|
|
|
_fileType = detectFormat(unpackedData, unpackedLen);
|
|
|
|
switch (_fileType) {
|
|
case Format::PNG:
|
|
ret = initWithPngData(unpackedData, unpackedLen);
|
|
break;
|
|
case Format::JPG:
|
|
ret = initWithJpgData(unpackedData, unpackedLen);
|
|
break;
|
|
#if CC_USE_WEBP
|
|
case Format::WEBP:
|
|
ret = initWithWebpData(unpackedData, unpackedLen);
|
|
break;
|
|
#endif
|
|
case Format::PVR:
|
|
ret = initWithPVRData(unpackedData, unpackedLen);
|
|
break;
|
|
case Format::ETC:
|
|
ret = initWithETCData(unpackedData, unpackedLen);
|
|
break;
|
|
case Format::ETC2:
|
|
ret = initWithETC2Data(unpackedData, unpackedLen);
|
|
break;
|
|
case Format::ASTC:
|
|
ret = initWithASTCData(unpackedData, unpackedLen);
|
|
break;
|
|
case Format::COMPRESSED:
|
|
ret = initWithCompressedMipsData(unpackedData, unpackedLen);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (unpackedData != data) {
|
|
free(unpackedData);
|
|
}
|
|
} while (false);
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool Image::isPng(const unsigned char *data, uint32_t 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 unsigned char *data, uint32_t /*dataLen*/) {
|
|
return etc1_pkm_is_valid(const_cast<etc1_byte *>(data)) != 0;
|
|
}
|
|
|
|
bool Image::isEtc2(const unsigned char *data, uint32_t /*dataLen*/) {
|
|
return etc2_pkm_is_valid(const_cast<etc2_byte *>(data)) != 0;
|
|
}
|
|
|
|
bool Image::isASTC(const unsigned char *data, uint32_t /*dataLen*/) {
|
|
return astcIsValid(const_cast<astc_byte *>(data));
|
|
}
|
|
|
|
bool Image::isCompressed(const unsigned char *data, uint32_t /*dataLen*/) {
|
|
return compressedIsValid(data);
|
|
}
|
|
|
|
bool Image::isJpg(const unsigned char *data, uint32_t dataLen) {
|
|
if (dataLen <= 4) {
|
|
return false;
|
|
}
|
|
|
|
static const unsigned char JPG_SOI[] = {0xFF, 0xD8};
|
|
|
|
return memcmp(data, JPG_SOI, 2) == 0;
|
|
}
|
|
|
|
bool Image::isWebp(const unsigned char *data, uint32_t dataLen) {
|
|
if (dataLen <= 12) {
|
|
return false;
|
|
}
|
|
|
|
static const char *webpRiff = "RIFF";
|
|
static const char *webpWebp = "WEBP";
|
|
|
|
return memcmp(data, webpRiff, 4) == 0 && memcmp(static_cast<const unsigned char *>(data) + 8, webpWebp, 4) == 0;
|
|
}
|
|
|
|
bool Image::isPvr(const unsigned char *data, uint32_t dataLen) {
|
|
if (static_cast<size_t>(dataLen) < sizeof(PVRv2TexHeader) || static_cast<size_t>(dataLen) < sizeof(PVRv3TexHeader)) {
|
|
return false;
|
|
}
|
|
|
|
const auto *headerv2 = static_cast<const PVRv2TexHeader *>(static_cast<const void *>(data));
|
|
const auto *headerv3 = static_cast<const PVRv3TexHeader *>(static_cast<const void *>(data));
|
|
|
|
return memcmp(&headerv2->pvrTag, G_PVR_TEX_IDENTIFIER, strlen(G_PVR_TEX_IDENTIFIER)) == 0 || CC_SWAP_INT32_BIG_TO_HOST(headerv3->version) == 0x50565203;
|
|
}
|
|
|
|
Image::Format Image::detectFormat(const unsigned char *data, uint32_t dataLen) {
|
|
if (isPng(data, dataLen)) {
|
|
return Format::PNG;
|
|
}
|
|
if (isJpg(data, dataLen)) {
|
|
return Format::JPG;
|
|
}
|
|
if (isWebp(data, dataLen)) {
|
|
return Format::WEBP;
|
|
}
|
|
if (isPvr(data, dataLen)) {
|
|
return Format::PVR;
|
|
}
|
|
if (isEtc(data, dataLen)) {
|
|
return Format::ETC;
|
|
}
|
|
if (isEtc2(data, dataLen)) {
|
|
return Format::ETC2;
|
|
}
|
|
if (isASTC(data, dataLen)) {
|
|
return Format::ASTC;
|
|
}
|
|
if (isCompressed(data, dataLen)) {
|
|
return Format::COMPRESSED;
|
|
}
|
|
return Format::UNKNOWN;
|
|
}
|
|
|
|
gfx::Format Image::getASTCFormat(const unsigned char *pHeader) {
|
|
int xdim = pHeader[ASTC_HEADER_MAGIC];
|
|
int ydim = pHeader[ASTC_HEADER_MAGIC + 1];
|
|
|
|
if (xdim == 4) return gfx::Format::ASTC_RGBA_4X4;
|
|
if (xdim == 5) {
|
|
if (ydim == 4) return gfx::Format::ASTC_RGBA_5X4;
|
|
return gfx::Format::ASTC_RGBA_5X5;
|
|
}
|
|
if (xdim == 6) {
|
|
if (ydim == 5) return gfx::Format::ASTC_RGBA_6X5;
|
|
return gfx::Format::ASTC_RGBA_6X6;
|
|
}
|
|
if (xdim == 8) {
|
|
if (ydim == 5) return gfx::Format::ASTC_RGBA_8X5;
|
|
if (ydim == 6) return gfx::Format::ASTC_RGBA_8X6;
|
|
return gfx::Format::ASTC_RGBA_8X8;
|
|
}
|
|
if (xdim == 10) {
|
|
if (ydim == 5) return gfx::Format::ASTC_RGBA_10X5;
|
|
if (ydim == 6) return gfx::Format::ASTC_RGBA_10X6;
|
|
if (ydim == 8) return gfx::Format::ASTC_RGBA_10X8;
|
|
return gfx::Format::ASTC_RGBA_10X10;
|
|
}
|
|
if (ydim == 10) return gfx::Format::ASTC_RGBA_12X10;
|
|
return gfx::Format::ASTC_RGBA_12X12;
|
|
}
|
|
|
|
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:
|
|
*/
|
|
#if CC_USE_JPEG
|
|
struct MyErrorMgr {
|
|
struct jpeg_error_mgr pub; /* "public" fields */
|
|
jmp_buf setjmp_buffer; /* for return to caller */
|
|
};
|
|
|
|
using MyErrorPtr = struct MyErrorMgr *;
|
|
|
|
/*
|
|
* Here's the routine that will replace the standard error_exit method:
|
|
*/
|
|
|
|
void myErrorExit(j_common_ptr cinfo) {
|
|
/* cinfo->err really points to a MyErrorMgr struct, so coerce pointer */
|
|
auto *myerr = reinterpret_cast<MyErrorPtr>(cinfo->err);
|
|
|
|
/* Always display the message. */
|
|
/* We could postpone this until after returning, if we chose. */
|
|
/* internal message function can't show error message in some platforms, so we rewrite it here.
|
|
* edit it if has version conflict.
|
|
*/
|
|
//(*cinfo->err->output_message) (cinfo);
|
|
char buffer[JMSG_LENGTH_MAX];
|
|
(*cinfo->err->format_message)(cinfo, buffer);
|
|
CC_LOG_DEBUG("jpeg error: %s", buffer);
|
|
|
|
/* Return control to the setjmp point */
|
|
longjmp(myerr->setjmp_buffer, 1);
|
|
}
|
|
#endif // CC_USE_JPEG
|
|
} // namespace
|
|
|
|
bool Image::initWithJpgData(const unsigned char *data, uint32_t dataLen) {
|
|
#if CC_USE_JPEG
|
|
/* 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 rowPointer[1] = {nullptr};
|
|
uint32_t location = 0;
|
|
|
|
bool ret = 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);
|
|
|
|
jpeg_mem_src(&cinfo, const_cast<unsigned char *>(data), dataLen);
|
|
|
|
/* 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 = gfx::Format::L8;
|
|
} else {
|
|
cinfo.out_color_space = JCS_RGB;
|
|
_renderFormat = gfx::Format::RGB8;
|
|
}
|
|
|
|
/* Start decompression jpeg here */
|
|
jpeg_start_decompress(&cinfo);
|
|
|
|
/* init image info */
|
|
_isCompressed = false;
|
|
_width = cinfo.output_width;
|
|
_height = cinfo.output_height;
|
|
_dataLen = cinfo.output_width * cinfo.output_height * cinfo.output_components;
|
|
_data = static_cast<unsigned char *>(malloc(_dataLen * sizeof(unsigned char)));
|
|
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) {
|
|
rowPointer[0] = _data + location;
|
|
location += cinfo.output_width * cinfo.output_components;
|
|
jpeg_read_scanlines(&cinfo, rowPointer, 1);
|
|
}
|
|
|
|
/* When read image file with broken data, jpeg_finish_decompress() may cause error.
|
|
* Besides, jpeg_destroy_decompress() shall deallocate and release all memory associated
|
|
* with the decompression object.
|
|
* So it doesn't need to call jpeg_finish_decompress().
|
|
*/
|
|
//jpeg_finish_decompress( &cinfo );
|
|
jpeg_destroy_decompress(&cinfo);
|
|
/* wrap up decompression, destroy objects, free pointers and close open files */
|
|
ret = true;
|
|
} while (false);
|
|
|
|
return ret;
|
|
#endif // CC_USE_JPEG
|
|
}
|
|
|
|
bool Image::initWithPngData(const unsigned char *data, uint32_t dataLen) {
|
|
#if CC_USE_PNG
|
|
// length of bytes to check if it is a valid png file
|
|
#define PNGSIGSIZE 8
|
|
bool ret = false;
|
|
png_byte header[PNGSIGSIZE] = {0};
|
|
png_structp pngPtr = nullptr;
|
|
png_infop infoPtr = nullptr;
|
|
|
|
do {
|
|
// png header len is 8 bytes
|
|
CC_BREAK_IF(dataLen < PNGSIGSIZE);
|
|
|
|
// check the data is png or not
|
|
memcpy(header, data, PNGSIGSIZE);
|
|
CC_BREAK_IF(png_sig_cmp(header, 0, PNGSIGSIZE));
|
|
|
|
// init png_struct
|
|
pngPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
|
|
CC_BREAK_IF(!pngPtr);
|
|
|
|
// init png_info
|
|
infoPtr = png_create_info_struct(pngPtr);
|
|
CC_BREAK_IF(!infoPtr);
|
|
|
|
CC_BREAK_IF(setjmp(png_jmpbuf(pngPtr)));
|
|
|
|
// set the read call back function
|
|
tImageSource imageSource;
|
|
imageSource.data = const_cast<unsigned char *>(data);
|
|
imageSource.size = dataLen;
|
|
imageSource.offset = 0;
|
|
png_set_read_fn(pngPtr, &imageSource, pngReadCallback);
|
|
|
|
// read png header info
|
|
|
|
// read png file info
|
|
png_read_info(pngPtr, infoPtr);
|
|
|
|
_isCompressed = false;
|
|
_width = png_get_image_width(pngPtr, infoPtr);
|
|
_height = png_get_image_height(pngPtr, infoPtr);
|
|
png_byte bitDepth = png_get_bit_depth(pngPtr, infoPtr);
|
|
png_uint_32 colorType = png_get_color_type(pngPtr, infoPtr);
|
|
|
|
//CC_LOG_DEBUG("color type %u", color_type);
|
|
|
|
// force palette images to be expanded to 24-bit RGB
|
|
// it may include alpha channel
|
|
if (colorType == PNG_COLOR_TYPE_PALETTE) {
|
|
png_set_palette_to_rgb(pngPtr);
|
|
}
|
|
// low-bit-depth grayscale images are to be expanded to 8 bits
|
|
if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8) {
|
|
bitDepth = 8;
|
|
png_set_expand_gray_1_2_4_to_8(pngPtr);
|
|
}
|
|
// expand any tRNS chunk data into a full alpha channel
|
|
if (png_get_valid(pngPtr, infoPtr, PNG_INFO_tRNS)) {
|
|
png_set_tRNS_to_alpha(pngPtr);
|
|
}
|
|
// reduce images with 16-bit samples to 8 bits
|
|
if (bitDepth == 16) {
|
|
png_set_strip_16(pngPtr);
|
|
}
|
|
|
|
// Expanded earlier for grayscale, now take care of palette and rgb
|
|
if (bitDepth < 8) {
|
|
png_set_packing(pngPtr);
|
|
}
|
|
// update info
|
|
png_read_update_info(pngPtr, infoPtr);
|
|
colorType = png_get_color_type(pngPtr, infoPtr);
|
|
|
|
switch (colorType) {
|
|
case PNG_COLOR_TYPE_GRAY:
|
|
_renderFormat = gfx::Format::L8;
|
|
break;
|
|
case PNG_COLOR_TYPE_GRAY_ALPHA:
|
|
_renderFormat = gfx::Format::LA8;
|
|
break;
|
|
case PNG_COLOR_TYPE_RGB:
|
|
_renderFormat = gfx::Format::RGB8;
|
|
break;
|
|
case PNG_COLOR_TYPE_RGB_ALPHA:
|
|
_renderFormat = gfx::Format::RGBA8;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
auto *rowPointers = static_cast<png_bytep *>(malloc(sizeof(png_bytep) * _height));
|
|
|
|
const png_size_t rowBytes = png_get_rowbytes(pngPtr, infoPtr);
|
|
|
|
_dataLen = static_cast<uint32_t>(rowBytes * _height);
|
|
_data = static_cast<unsigned char *>(malloc(_dataLen * sizeof(unsigned char)));
|
|
if (!_data) {
|
|
if (rowPointers != nullptr) {
|
|
free(rowPointers);
|
|
}
|
|
break;
|
|
}
|
|
|
|
for (int i = 0; i < _height; ++i) {
|
|
rowPointers[i] = _data + i * rowBytes;
|
|
}
|
|
png_read_image(pngPtr, rowPointers);
|
|
png_read_end(pngPtr, nullptr);
|
|
|
|
if (rowPointers != nullptr) {
|
|
free(rowPointers);
|
|
}
|
|
|
|
ret = true;
|
|
} while (false);
|
|
|
|
if (pngPtr) {
|
|
png_destroy_read_struct(&pngPtr, (infoPtr) ? &infoPtr : nullptr, nullptr);
|
|
}
|
|
return ret;
|
|
#endif //CC_USE_PNG
|
|
}
|
|
|
|
bool Image::initWithPVRv2Data(const unsigned char *data, uint32_t dataLen) {
|
|
//Cast first sizeof(PVRTexHeader) bytes of data stream as PVRTexHeader
|
|
const auto *header = static_cast<const PVRv2TexHeader *>(static_cast<const void *>(data));
|
|
|
|
//Make sure that tag is in correct formatting
|
|
if (memcmp(&header->pvrTag, G_PVR_TEX_IDENTIFIER, strlen(G_PVR_TEX_IDENTIFIER)) != 0) {
|
|
return false;
|
|
}
|
|
|
|
unsigned int flags = CC_SWAP_INT32_LITTLE_TO_HOST(header->flags);
|
|
auto formatFlags = static_cast<PVR2TexturePixelFormat>(flags & PVR_TEXTURE_FLAG_TYPE_MASK);
|
|
const bool flipped = (flags & static_cast<unsigned int>(PVR2TextureFlag::VERTICAL_FLIP)) != 0;
|
|
if (flipped) {
|
|
CC_LOG_DEBUG("initWithPVRv2Data: WARNING: Image is flipped. Regenerate it using PVRTexTool");
|
|
}
|
|
|
|
if (V2_PIXEL_FORMATHASH.find(formatFlags) == V2_PIXEL_FORMATHASH.end()) {
|
|
CC_LOG_DEBUG("initWithPVRv2Data: WARNING: Unsupported PVR Pixel Format: 0x%02X. Re-encode it with a OpenGL pixel format variant", (int)formatFlags);
|
|
return false;
|
|
}
|
|
|
|
const auto it = V2_PIXEL_FORMATHASH.find(formatFlags);
|
|
if (it == V2_PIXEL_FORMATHASH.end()) {
|
|
CC_LOG_DEBUG("initWithPVRv2Data: WARNING: Unsupported PVR Pixel Format: 0x%02X. Re-encode it with a OpenGL pixel format variant", (int)formatFlags);
|
|
return false;
|
|
}
|
|
|
|
_renderFormat = it->second;
|
|
|
|
//Get size of mipmap
|
|
_width = static_cast<int>(CC_SWAP_INT32_LITTLE_TO_HOST(header->width));
|
|
_height = static_cast<int>(CC_SWAP_INT32_LITTLE_TO_HOST(header->height));
|
|
_isCompressed = true;
|
|
|
|
//Move by size of header
|
|
_dataLen = dataLen - sizeof(PVRv2TexHeader);
|
|
_data = static_cast<unsigned char *>(malloc(_dataLen * sizeof(unsigned char)));
|
|
memcpy(_data, data + sizeof(PVRv2TexHeader), _dataLen);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Image::initWithPVRv3Data(const unsigned char *data, uint32_t dataLen) {
|
|
if (static_cast<size_t>(dataLen) < sizeof(PVRv3TexHeader)) {
|
|
return false;
|
|
}
|
|
|
|
const auto *header = static_cast<const PVRv3TexHeader *>(static_cast<const void *>(data));
|
|
|
|
// validate version
|
|
if (CC_SWAP_INT32_BIG_TO_HOST(header->version) != 0x50565203) {
|
|
CC_LOG_DEBUG("initWithPVRv3Data: WARNING: pvr file version mismatch");
|
|
return false;
|
|
}
|
|
|
|
// parse pixel format
|
|
auto pixelFormat = static_cast<PVR3TexturePixelFormat>(header->pixelFormat);
|
|
|
|
if (V3_PIXEL_FORMATHASH.find(pixelFormat) == V3_PIXEL_FORMATHASH.end()) {
|
|
CC_LOG_DEBUG("initWithPVRv3Data: WARNING: Unsupported PVR Pixel Format: 0x%016llX. Re-encode it with a OpenGL pixel format variant",
|
|
static_cast<unsigned long long>(pixelFormat));
|
|
return false;
|
|
}
|
|
|
|
const auto it = V3_PIXEL_FORMATHASH.find(pixelFormat);
|
|
if (it == V3_PIXEL_FORMATHASH.end()) {
|
|
CC_LOG_DEBUG("initWithPVRv3Data: WARNING: Unsupported PVR Pixel Format: 0x%016llX. Re-encode it with a OpenGL pixel format variant",
|
|
static_cast<unsigned long long>(pixelFormat));
|
|
return false;
|
|
}
|
|
|
|
_renderFormat = it->second;
|
|
|
|
// sizing
|
|
_width = static_cast<int>(CC_SWAP_INT32_LITTLE_TO_HOST(header->width));
|
|
_height = static_cast<int>(CC_SWAP_INT32_LITTLE_TO_HOST(header->height));
|
|
_isCompressed = true;
|
|
|
|
_dataLen = dataLen - (sizeof(PVRv3TexHeader) + header->metadataLength);
|
|
_data = static_cast<unsigned char *>(malloc(_dataLen * sizeof(unsigned char)));
|
|
memcpy(_data, data + sizeof(PVRv3TexHeader) + header->metadataLength, _dataLen);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Image::initWithETCData(const unsigned char *data, uint32_t dataLen) {
|
|
const auto *header = static_cast<const etc1_byte *>(data);
|
|
|
|
//check the data
|
|
if (!etc1_pkm_is_valid(header)) {
|
|
return false;
|
|
}
|
|
|
|
_width = etc1_pkm_get_width(header);
|
|
_height = etc1_pkm_get_height(header);
|
|
_isCompressed = true;
|
|
|
|
if (0 == _width || 0 == _height) {
|
|
return false;
|
|
}
|
|
|
|
_renderFormat = gfx::Format::ETC_RGB8;
|
|
_dataLen = dataLen - ETC_PKM_HEADER_SIZE;
|
|
_data = static_cast<unsigned char *>(malloc(_dataLen * sizeof(unsigned char)));
|
|
memcpy(_data, static_cast<const unsigned char *>(data) + ETC_PKM_HEADER_SIZE, _dataLen);
|
|
return true;
|
|
}
|
|
|
|
bool Image::initWithETC2Data(const unsigned char *data, uint32_t dataLen) {
|
|
const auto *header = static_cast<const etc2_byte *>(data);
|
|
|
|
//check the data
|
|
if (!etc2_pkm_is_valid(header)) {
|
|
return false;
|
|
}
|
|
|
|
_width = etc2_pkm_get_width(header);
|
|
_height = etc2_pkm_get_height(header);
|
|
_isCompressed = true;
|
|
|
|
if (0 == _width || 0 == _height) {
|
|
return false;
|
|
}
|
|
|
|
etc2_uint32 format = etc2_pkm_get_format(header);
|
|
if (format == ETC2_RGB_NO_MIPMAPS) {
|
|
_renderFormat = gfx::Format::ETC2_RGB8;
|
|
} else {
|
|
_renderFormat = gfx::Format::ETC2_RGBA8;
|
|
}
|
|
|
|
_dataLen = dataLen - ETC2_PKM_HEADER_SIZE;
|
|
_data = static_cast<unsigned char *>(malloc(_dataLen * sizeof(unsigned char)));
|
|
memcpy(_data, static_cast<const unsigned char *>(data) + ETC2_PKM_HEADER_SIZE, _dataLen);
|
|
return true;
|
|
}
|
|
|
|
bool Image::initWithASTCData(const unsigned char *data, uint32_t dataLen) {
|
|
const auto *header = static_cast<const astc_byte *>(data);
|
|
|
|
//check the data
|
|
if (!astcIsValid(header)) {
|
|
return false;
|
|
}
|
|
|
|
_width = astcGetWidth(header);
|
|
_height = astcGetHeight(header);
|
|
_isCompressed = true;
|
|
|
|
if (0 == _width || 0 == _height) {
|
|
return false;
|
|
}
|
|
|
|
_renderFormat = getASTCFormat(header);
|
|
|
|
_dataLen = dataLen - ASTC_HEADER_SIZE;
|
|
_data = static_cast<unsigned char *>(malloc(_dataLen * sizeof(unsigned char)));
|
|
memcpy(_data, data + ASTC_HEADER_SIZE, _dataLen);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Image::initWithCompressedMipsData(const unsigned char *data, uint32_t /*dataLen*/) { //NOLINT(misc-no-recursion)
|
|
//check the data
|
|
if (!compressedIsValid(data)) {
|
|
return false;
|
|
}
|
|
|
|
// unpack compressed chunks
|
|
int width = 0;
|
|
int height = 0;
|
|
bool ret = false;
|
|
const auto chunkNumbers = getChunkNumbers(data);
|
|
ccstd::vector<unsigned char *> dataBuffers;
|
|
dataBuffers.resize(chunkNumbers);
|
|
_mipmapLevelDataSize.resize(chunkNumbers);
|
|
uint32_t dstDataLen = 0;
|
|
for (uint32_t i = 0; i < chunkNumbers; ++i) {
|
|
const auto *chunk = getChunk(data, i);
|
|
const auto dataLength = getChunkSizes(data, i);
|
|
if (_data) free(_data);
|
|
ret = initWithImageData(chunk, dataLength);
|
|
|
|
if (i == 0) {
|
|
width = _width;
|
|
height = _height;
|
|
}
|
|
|
|
dstDataLen += _dataLen;
|
|
_mipmapLevelDataSize[i] = _dataLen;
|
|
dataBuffers[i] = static_cast<unsigned char *>(malloc(_dataLen * sizeof(unsigned char)));
|
|
memcpy(dataBuffers[i], _data, _dataLen);
|
|
|
|
if (!ret) break;
|
|
}
|
|
|
|
auto *dstData = static_cast<unsigned char *>(malloc(dstDataLen * sizeof(unsigned char)));
|
|
uint32_t byteOffset = 0;
|
|
for (uint32_t i = 0; i < chunkNumbers; ++i) {
|
|
memcpy(dstData + byteOffset, dataBuffers[i], _mipmapLevelDataSize[i]);
|
|
byteOffset += _mipmapLevelDataSize[i];
|
|
free(dataBuffers[i]);
|
|
}
|
|
|
|
_width = width;
|
|
_height = height;
|
|
if (_data) free(_data);
|
|
_data = dstData;
|
|
_dataLen = dstDataLen;
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool Image::initWithPVRData(const unsigned char *data, uint32_t dataLen) {
|
|
return initWithPVRv2Data(data, dataLen) || initWithPVRv3Data(data, dataLen);
|
|
}
|
|
|
|
#if CC_USE_WEBP
|
|
bool Image::initWithWebpData(const unsigned char *data, uint32_t dataLen) {
|
|
bool ret = false;
|
|
do {
|
|
WebPDecoderConfig config;
|
|
if (WebPInitDecoderConfig(&config) == 0) break;
|
|
if (WebPGetFeatures(static_cast<const uint8_t *>(data), dataLen, &config.input) != VP8_STATUS_OK) break;
|
|
if (config.input.width == 0 || config.input.height == 0) break;
|
|
|
|
config.output.colorspace = config.input.has_alpha ? MODE_rgbA : MODE_RGB;
|
|
_renderFormat = config.input.has_alpha ? gfx::Format::RGBA8 : gfx::Format::RGB8;
|
|
_width = config.input.width;
|
|
_height = config.input.height;
|
|
_isCompressed = false;
|
|
|
|
_dataLen = _width * _height * (config.input.has_alpha ? 4 : 3);
|
|
_data = static_cast<unsigned char *>(malloc(_dataLen * sizeof(unsigned char)));
|
|
|
|
config.output.u.RGBA.rgba = static_cast<uint8_t *>(_data);
|
|
config.output.u.RGBA.stride = _width * (config.input.has_alpha ? 4 : 3);
|
|
config.output.u.RGBA.size = _dataLen;
|
|
config.output.is_external_memory = 1;
|
|
|
|
if (WebPDecode(static_cast<const uint8_t *>(data), dataLen, &config) != VP8_STATUS_OK) {
|
|
free(_data);
|
|
_data = nullptr;
|
|
break;
|
|
}
|
|
|
|
ret = true;
|
|
} while (false);
|
|
return ret;
|
|
}
|
|
#endif // CC_USE_WEBP
|
|
|
|
bool Image::initWithRawData(const unsigned char *data, uint32_t /*dataLen*/, int width, int height, int /*bitsPerComponent*/, bool /*preMulti*/) {
|
|
bool ret = false;
|
|
do {
|
|
CC_BREAK_IF(0 == width || 0 == height);
|
|
|
|
_height = height;
|
|
_width = width;
|
|
_renderFormat = gfx::Format::RGBA8;
|
|
_isCompressed = false;
|
|
|
|
// only RGBA8888 supported
|
|
int bytesPerComponent = 4;
|
|
_dataLen = height * width * bytesPerComponent;
|
|
_data = static_cast<unsigned char *>(malloc(_dataLen * sizeof(unsigned char)));
|
|
CC_BREAK_IF(!_data);
|
|
memcpy(_data, data, _dataLen);
|
|
|
|
ret = true;
|
|
} while (false);
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool Image::saveToFile(const std::string &filename, bool isToRGB) {
|
|
//only support for Image::PixelFormat::RGB888 or Image::PixelFormat::RGBA8888 uncompressed data
|
|
if (isCompressed() || (_renderFormat != gfx::Format::RGB8 && _renderFormat != gfx::Format::RGBA8)) {
|
|
CC_LOG_DEBUG("saveToFile: Image: saveToFile is only support for gfx::Format::RGB8 or gfx::Format::RGBA8 uncompressed data for now");
|
|
return false;
|
|
}
|
|
|
|
std::string fileExtension = FileUtils::getInstance()->getFileExtension(filename);
|
|
|
|
if (fileExtension == ".png") {
|
|
return saveImageToPNG(filename, isToRGB);
|
|
}
|
|
if (fileExtension == ".jpg") {
|
|
return saveImageToJPG(filename);
|
|
}
|
|
CC_LOG_DEBUG("saveToFile: Image: saveToFile no support file extension(only .png or .jpg) for file: %s", filename.c_str());
|
|
return false;
|
|
}
|
|
|
|
bool Image::saveImageToPNG(const std::string &filePath, bool isToRGB) {
|
|
bool ret = false;
|
|
|
|
FILE *fp{nullptr};
|
|
png_structp pngPtr{nullptr};
|
|
png_infop infoPtr{nullptr};
|
|
png_colorp palette{nullptr};
|
|
png_bytep *rowPointers{nullptr};
|
|
const bool hasAlpha = gfx::GFX_FORMAT_INFOS[static_cast<int>(_renderFormat)].hasAlpha;
|
|
do {
|
|
// Init png structure and png ptr
|
|
pngPtr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
|
|
CC_BREAK_IF(!pngPtr);
|
|
if (setjmp(png_jmpbuf(pngPtr))) {
|
|
break;
|
|
}
|
|
infoPtr = png_create_info_struct(pngPtr);
|
|
CC_BREAK_IF(!infoPtr);
|
|
// Start open file
|
|
fp = fopen(FileUtils::getInstance()->getSuitableFOpen(filePath).c_str(), "wb");
|
|
CC_BREAK_IF(!fp);
|
|
png_init_io(pngPtr, fp);
|
|
const auto mask = (!isToRGB && hasAlpha) ? PNG_COLOR_TYPE_RGB_ALPHA : PNG_COLOR_TYPE_RGB;
|
|
png_set_IHDR(pngPtr, infoPtr, _width, _height, 8, mask,
|
|
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
|
|
|
|
palette = static_cast<png_colorp>(png_malloc(pngPtr, PNG_MAX_PALETTE_LENGTH * sizeof(png_color)));
|
|
CC_BREAK_IF(!palette);
|
|
png_set_PLTE(pngPtr, infoPtr, palette, PNG_MAX_PALETTE_LENGTH);
|
|
|
|
png_write_info(pngPtr, infoPtr);
|
|
|
|
png_set_packing(pngPtr);
|
|
|
|
//row_pointers = (png_bytep *)png_malloc(png_ptr, _height * sizeof(png_bytep));
|
|
rowPointers = static_cast<png_bytep *>(CC_MALLOC(_height * sizeof(png_bytep)));
|
|
CC_BREAK_IF(!rowPointers);
|
|
|
|
if (!hasAlpha) {
|
|
for (int i = 0; i < _height; i++) {
|
|
rowPointers[i] = static_cast<png_bytep>(_data) + i * _width * 3;
|
|
}
|
|
png_write_image(pngPtr, rowPointers);
|
|
} else {
|
|
if (isToRGB) {
|
|
auto *tempData = static_cast<unsigned char *>(CC_MALLOC(_width * _height * 3 * sizeof(unsigned char)));
|
|
if (nullptr == tempData) {
|
|
break;
|
|
}
|
|
auto *dst = tempData;
|
|
auto *src = _data;
|
|
for (int t = 0; t < _width * _height; t++) {
|
|
memcpy(dst, src, 3);
|
|
dst += 3;
|
|
src += 4;
|
|
}
|
|
|
|
for (int i = 0; i < _height; i++) {
|
|
rowPointers[i] = static_cast<png_bytep>(tempData) + i * _width * 3;
|
|
}
|
|
if (tempData != nullptr) {
|
|
CC_FREE(tempData);
|
|
}
|
|
png_write_image(pngPtr, rowPointers);
|
|
} else {
|
|
for (int i = 0; i < _height; i++) {
|
|
rowPointers[i] = static_cast<png_bytep>(_data) + i * _width * 4 /*Bytes per pixel*/;
|
|
}
|
|
png_write_image(pngPtr, rowPointers);
|
|
}
|
|
}
|
|
|
|
png_write_end(pngPtr, infoPtr);
|
|
ret = true;
|
|
} while (false);
|
|
|
|
/*Later free for all functions*/
|
|
if (rowPointers) {
|
|
free(rowPointers);
|
|
rowPointers = nullptr;
|
|
}
|
|
if (palette) {
|
|
png_free(pngPtr, palette);
|
|
}
|
|
if (infoPtr) {
|
|
png_destroy_write_struct(&pngPtr, &infoPtr);
|
|
}
|
|
if (fp) {
|
|
fclose(fp);
|
|
}
|
|
if (pngPtr) {
|
|
png_destroy_write_struct(&pngPtr, nullptr);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
bool Image::saveImageToJPG(const std::string &filePath) {
|
|
bool ret = false;
|
|
do {
|
|
struct jpeg_compress_struct cinfo;
|
|
struct jpeg_error_mgr jerr;
|
|
FILE *outfile; /* target file */
|
|
JSAMPROW rowPointer[1]; /* pointer to JSAMPLE row[s] */
|
|
int rowStride; /* 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(FileUtils::getInstance()->getSuitableFOpen(filePath).c_str(), "wb")) == nullptr);
|
|
|
|
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_set_quality(&cinfo, 90, TRUE);
|
|
|
|
jpeg_start_compress(&cinfo, TRUE);
|
|
|
|
rowStride = _width * 3; /* JSAMPLEs per row in image_buffer */
|
|
const bool hasAlpha = gfx::GFX_FORMAT_INFOS[static_cast<int>(_renderFormat)].hasAlpha;
|
|
|
|
if (hasAlpha) {
|
|
auto *tempData = static_cast<unsigned char *>(CC_MALLOC(_width * _height * 3 * sizeof(unsigned char)));
|
|
if (nullptr == tempData) {
|
|
jpeg_finish_compress(&cinfo);
|
|
jpeg_destroy_compress(&cinfo);
|
|
fclose(outfile);
|
|
break;
|
|
}
|
|
auto *dst = tempData;
|
|
auto *src = _data;
|
|
for (int t = 0; t < _width * _height; t++) {
|
|
memcpy(dst, src, 3);
|
|
dst += 3;
|
|
src += 4;
|
|
}
|
|
while (cinfo.next_scanline < cinfo.image_height) {
|
|
rowPointer[0] = &tempData[cinfo.next_scanline * rowStride];
|
|
(void)jpeg_write_scanlines(&cinfo, rowPointer, 1);
|
|
}
|
|
if (tempData != nullptr) {
|
|
CC_FREE(tempData);
|
|
}
|
|
} else {
|
|
while (cinfo.next_scanline < cinfo.image_height) {
|
|
rowPointer[0] = &_data[cinfo.next_scanline * rowStride];
|
|
(void)jpeg_write_scanlines(&cinfo, rowPointer, 1);
|
|
}
|
|
}
|
|
|
|
jpeg_finish_compress(&cinfo);
|
|
fclose(outfile);
|
|
jpeg_destroy_compress(&cinfo);
|
|
|
|
ret = true;
|
|
} while (false);
|
|
return ret;
|
|
}
|
|
|
|
} // namespace cc
|
|
|