Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

V3.6.1 save image data #12503

Merged
merged 14 commits into from
Aug 24, 2022
13 changes: 13 additions & 0 deletions cocos/native-binding/impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,18 @@ if( NATIVE ){
globalJsb.__JsbBridgeWrapper = value;
},
});
const originSaveImageData = globalJsb.saveImageData;
globalJsb.saveImageData = (data: Uint8Array, width: number, height: number, filePath: string) => {
return new Promise<void>((resolve, reject) => {
originSaveImageData(data, width, height, filePath, (isSuccess) => {
if (isSuccess) {
resolve();
} else {
reject();
}
})
});
}
}

export const native = {
Expand All @@ -140,4 +152,5 @@ export const native = {
AssetsManager: globalJsb.AssetsManager,
EventAssetsManager: globalJsb.EventAssetsManager,
Manifest: globalJsb.Manifest,
saveImageData: globalJsb.saveImageData,
};
33 changes: 33 additions & 0 deletions cocos/native-binding/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1316,4 +1316,37 @@ export declare namespace native {
*/
export function removeAllListeners();
}
/**
* @en Save the image to the path indicated.
* @zh 保存图片到指定路径。
* @param data : @en the image data, should be raw data array with uint8 @zh 图片数据, 应为原始数据数组,uint8 格式。
* @param path : @en the path to save @zh 保存路径
* @param width : @en the width of the image @zh 图片宽度
* @param height : @en the height of the image @zh 图片高度
* @param filePath : @en the file path of the image @zh 图片文件路径
* @param callback : @en the callback function @zh 回调函数
* @example
* ```ts
let renderTexture = new RenderTexture();
let renderWindowInfo = {
width: this._width,
height: this._height
};
renderTexture.reset(renderWindowInfo);
cameras.forEach((camera: any) => {
camera.targetTexture = renderTexture;
});
await this.waitForNextFrame();
cameras.forEach((camera: any) => {
camera.targetTexture = null;
});
let pixelData = renderTexture.readPixels();
jsb.saveImageData(pixelData, path, width, height, filePath, (isSuccess) => {
if (isSuccess) {
console.log('save image success');
} else {
console.log('save image failed');
}));
*/
export function saveImageData(data: Uint8Array, width: number, height: number, filePath: string): Promise<void>;
}
62 changes: 62 additions & 0 deletions native/cocos/bindings/manual/jsb_global.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

#include "jsb_global.h"
#include "application/ApplicationManager.h"
#include "base/Data.h"
#include "base/DeferredReleasePool.h"
#include "base/Scheduler.h"
#include "base/ThreadPool.h"
Expand Down Expand Up @@ -659,7 +660,67 @@ static bool js_loadImage(se::State &s) { // NOLINT
return false;
}
SE_BIND_FUNC(js_loadImage)
//pixels(RGBA), width, height, fullFilePath(*.png/*.jpg)
static bool js_saveImageData(se::State& s) { // NOLINT
const auto& args = s.args();
size_t argc = args.size();
bool ok = true;// NOLINT(readability-identifier-length)
if (argc == 4 || argc == 5) {
auto *uint8ArrayObj = args[0].toObject();
uint8_t *uint8ArrayData {nullptr};
size_t length = 0;
uint8ArrayObj->root();
uint8ArrayObj->incRef();
uint8ArrayObj->getTypedArrayData(&uint8ArrayData, &length);
uint32_t width;
uint32_t height;
ok &= sevalue_to_native(args[1], &width);
ok &= sevalue_to_native(args[2], &height);

std::string filePath;
ok &= sevalue_to_native(args[3], &filePath);
SE_PRECONDITION2(ok, false, "js_saveImageData : Error processing arguments");

se::Value callbackVal = argc == 5 ? args[4] : se::Value::Null;
se::Object *callbackObj {nullptr};
if (!callbackVal.isNull()) {
CC_ASSERT(callbackVal.isObject());
CC_ASSERT(callbackVal.toObject()->isFunction());
callbackObj = callbackVal.toObject();
callbackObj->root();
callbackObj->incRef();
}

gThreadPool->pushTask([=](int /*tid*/) {
// isToRGB = false, to keep alpha channel
auto *img = ccnew Image();
// A conversion from size_t to uint32_t might lose integer precision
img->initWithRawData(uint8ArrayData, static_cast<uint32_t>(length), width, height, 32 /*Unused*/);
bool isSuccess = img->saveToFile(filePath, false/*isToRGB*/);
CC_CURRENT_ENGINE()->getScheduler()->performFunctionInCocosThread([=]() {
se::AutoHandleScope hs;
se::ValueArray seArgs;

se::Value isSuccessVal;
nativevalue_to_se(isSuccess, isSuccessVal);

seArgs.push_back(isSuccessVal);
if (callbackObj) {
callbackObj->call(seArgs, nullptr);
callbackObj->unroot();
callbackObj->decRef();
}
uint8ArrayObj->unroot();
uint8ArrayObj->decRef();
delete img;
});
});
return true;
}
SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d or %d", (int)argc, 4, 5);
return false;
}
SE_BIND_FUNC(js_saveImageData)
static bool js_destroyImage(se::State &s) { // NOLINT
const auto &args = s.args();
size_t argc = args.size();
Expand Down Expand Up @@ -1313,6 +1374,7 @@ bool jsb_register_global_variables(se::Object *global) { // NOLINT
__jsbObj->defineFunction("dumpNativePtrToSeObjectMap", _SE(jsc_dumpNativePtrToSeObjectMap));

__jsbObj->defineFunction("loadImage", _SE(js_loadImage));
__jsbObj->defineFunction("saveImageData", _SE(js_saveImageData));
__jsbObj->defineFunction("openURL", _SE(JSB_openURL));
__jsbObj->defineFunction("copyTextToClipboard", _SE(JSB_copyTextToClipboard));
__jsbObj->defineFunction("setPreferredFramesPerSecond", _SE(JSB_setPreferredFramesPerSecond));
Expand Down
186 changes: 185 additions & 1 deletion native/cocos/platform/Image.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#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"
Expand Down Expand Up @@ -288,7 +289,6 @@ bool Image::initWithImageFile(const ccstd::string &path) {

bool Image::initWithImageData(const unsigned char *data, uint32_t dataLen) {
bool ret = false;

do {
CC_BREAK_IF(!data || dataLen <= 0);

Expand Down Expand Up @@ -944,4 +944,188 @@ bool Image::initWithRawData(const unsigned char *data, uint32_t /*dataLen*/, int
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};
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);
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 */
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
12 changes: 11 additions & 1 deletion native/cocos/platform/Image.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,16 @@ class Image : public RefCounted {
inline int getWidth() const { return _width; }
inline int getHeight() const { return _height; }
inline ccstd::string getFilePath() const { return _filePath; }

inline bool isCompressed() const { return _isCompressed; }

/**
@brief Save Image data to the specified file, with specified format.
@param filename the file's absolute path, including file suffix.
@param isToRGB whether the image is saved as RGB format.
*/
bool saveToFile(const std::string &filename, bool isToRGB = true);


protected:
bool initWithJpgData(const unsigned char *data, uint32_t dataLen);
bool initWithPngData(const unsigned char *data, uint32_t dataLen);
Expand All @@ -96,6 +103,9 @@ class Image : public RefCounted {
bool initWithETC2Data(const unsigned char *data, uint32_t dataLen);
bool initWithASTCData(const unsigned char *data, uint32_t dataLen);

bool saveImageToPNG(const std::string& filePath, bool isToRGB = true);
bool saveImageToJPG(const std::string& filePath);
timlyeee marked this conversation as resolved.
Show resolved Hide resolved

unsigned char *_data = nullptr;
uint32_t _dataLen = 0;
int _width = 0;
Expand Down
6 changes: 3 additions & 3 deletions native/cocos/platform/win32/modules/System.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -112,10 +112,10 @@ ccstd::string System::getCurrentLanguageCode() const {
const LCID locale_id = MAKELCID(lid, SORT_DEFAULT);
int length = GetLocaleInfoA(locale_id, LOCALE_SISO639LANGNAME, nullptr, 0);

char *tempCode = ccnew char[length];
char *tempCode = reinterpret_cast<char*>(CC_MALLOC(length));
GetLocaleInfoA(locale_id, LOCALE_SISO639LANGNAME, tempCode, length);
ccstd::string code = tempCode;
delete tempCode;
ccstd::string code(tempCode);
CC_FREE(tempCode);

return code;
}
Expand Down