* Copyright (c) 2026 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "napi/native_api.h"
#include <unordered_map>
#include <mutex>
#include <cstring>
#include "Log.h"
#include "ApngDecoder.h"
#include "Error.h"
#include "StreamSourceHarmony.h"
#include "ApngImage.h"
#ifdef BUILD_DEBUG
#include <chrono>
#endif
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __cplusplus
}
#endif
namespace apng_drawable {
constexpr size_t ARG_COUNT_CREATE_PIXEL_MAP = 5;
constexpr int64_t NS_PER_MS = 1000000;
static void RgbaToBgraInPlace(uint8_t *pixels, size_t byteCount)
{
for (size_t i = 0; i + CHANNEL_4_BYTE_SIZE <= byteCount; i += CHANNEL_4_BYTE_SIZE) {
uint8_t t = pixels[i + CHANNEL_INDEX_R];
pixels[i + CHANNEL_INDEX_R] = pixels[i + CHANNEL_INDEX_B];
pixels[i + CHANNEL_INDEX_B] = t;
}
}
static std::unordered_map<int32_t, std::shared_ptr<ApngImage>> gImageMap;
static std::mutex g_lock;
static uint32_t g_idCounter = 0;
static int32_t StoreImage(std::shared_ptr<ApngImage> image)
{
int32_t imageId;
std::lock_guard<std::mutex> lock(g_lock);
g_idCounter++;
imageId = static_cast<int32_t>(g_idCounter);
gImageMap.emplace(imageId, std::move(image));
OH_LOG_Print(LOG_APP, LOG_DEBUG, APNG_LOG_DOMAIN, "ApngDecoderNapi",
"Decode: Stored image with id=%{public}d, total images=%{public}zu", imageId, gImageMap.size());
LOGV(" | total images: %ld", gImageMap.size());
return imageId;
}
struct DecodeParams {
void* data = nullptr;
size_t length = 0;
bool valid = false;
};
static DecodeParams ParseDecodeParams(napi_env env, napi_callback_info info)
{
DecodeParams params;
size_t argc = 1;
napi_value args[1];
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
OH_LOG_Print(LOG_APP, LOG_DEBUG, APNG_LOG_DOMAIN, "ApngDecoderNapi", "Decode: argc=%{public}zu", argc);
if (argc < 1) {
OH_LOG_Print(LOG_APP, LOG_ERROR, APNG_LOG_DOMAIN, "ApngDecoderNapi",
"Decode: Expected ArrayBuffer argument but argc=%{public}zu", argc);
napi_throw_error(env, nullptr, "Expected ArrayBuffer argument");
return params;
}
bool isArrayBuffer = false;
napi_is_arraybuffer(env, args[0], &isArrayBuffer);
if (!isArrayBuffer) {
OH_LOG_Print(LOG_APP, LOG_ERROR, APNG_LOG_DOMAIN, "ApngDecoderNapi", "Decode: Argument is not an ArrayBuffer");
napi_throw_type_error(env, nullptr, "Argument must be an ArrayBuffer");
return params;
}
napi_get_arraybuffer_info(env, args[0], ¶ms.data, ¶ms.length);
OH_LOG_Print(LOG_APP, LOG_DEBUG, APNG_LOG_DOMAIN, "ApngDecoderNapi", "Decode: ArrayBuffer size=%{public}zu bytes",
params.length);
params.valid = true;
return params;
}
static napi_value CreateDecodeErrorResult(napi_env env, int32_t errorCode)
{
napi_value result;
napi_create_object(env, &result);
napi_value idValue;
napi_create_int32(env, errorCode, &idValue);
napi_set_named_property(env, result, "id", idValue);
napi_value zeroValue;
napi_create_int32(env, 0, &zeroValue);
napi_set_named_property(env, result, "width", zeroValue);
napi_set_named_property(env, result, "height", zeroValue);
napi_set_named_property(env, result, "frameCount", zeroValue);
napi_set_named_property(env, result, "loopCount", zeroValue);
napi_set_named_property(env, result, "allFrameByteCount", zeroValue);
napi_value emptyArray;
napi_create_array_with_length(env, 0, &emptyArray);
napi_set_named_property(env, result, "frameDurations", emptyArray);
return result;
}
static napi_value CreateDecodeResult(napi_env env, const std::shared_ptr<ApngImage> &image, int32_t id)
{
napi_value result;
napi_create_object(env, &result);
napi_value idValue;
napi_create_int32(env, id, &idValue);
napi_set_named_property(env, result, "id", idValue);
napi_value widthValue;
napi_create_int32(env, image->GetWidth(), &widthValue);
napi_set_named_property(env, result, "width", widthValue);
napi_value heightValue;
napi_create_int32(env, image-> GetHeight(), &heightValue);
napi_set_named_property(env, result, "height", heightValue);
napi_value frameCountValue;
napi_create_int32(env, image->GetFrameCount(), &frameCountValue);
napi_set_named_property(env, result, "frameCount", frameCountValue);
napi_value loopCountValue;
napi_create_int32(env, image->GetRepeatCount(), &loopCountValue);
napi_set_named_property(env, result, "loopCount", loopCountValue);
napi_value allFrameByteCountValue;
napi_create_int64(env, image->GetAllFrameByteCount(), &allFrameByteCountValue);
napi_set_named_property(env, result, "allFrameByteCount", allFrameByteCountValue);
uint32_t frameCount = image->GetFrameCount();
napi_value frameDurationsArray;
napi_create_array_with_length(env, frameCount, &frameDurationsArray);
for (uint32_t i = 0; i < frameCount; ++i) {
std::shared_ptr<ApngFrame> frame = image->GetFrame(i);
if (!frame) {
break;
}
napi_value durationValue;
napi_create_int32(env, static_cast<int32_t>(frame->GetDuration()), &durationValue);
napi_set_element(env, frameDurationsArray, i, durationValue);
}
napi_set_named_property(env, result, "frameDurations", frameDurationsArray);
return result;
}
static napi_value Decode(napi_env env, napi_callback_info info)
{
OH_LOG_Print(LOG_APP, LOG_INFO, APNG_LOG_DOMAIN, "ApngDecoderNapi", "Decode function called");
LOGV("Decode start");
#ifdef BUILD_DEBUG
std::chrono::system_clock::time_point start = std::chrono::system_clock::now();
#endif
DecodeParams decodeParams = ParseDecodeParams(env, info);
if (!decodeParams.valid) {
return nullptr;
}
std::unique_ptr<StreamSourceBase> source =
std::make_unique<StreamSourceHarmony>(static_cast<const uint8_t *>(decodeParams.data), decodeParams.length);
OH_LOG_Print(LOG_APP, LOG_DEBUG, APNG_LOG_DOMAIN, "ApngDecoderNapi", "Decode: Calling ApngDecoder::Decode");
int32_t resultCode;
std::shared_ptr<ApngImage> image = std::move(ApngDecoder::Decode(std::move(source), resultCode));
OH_LOG_Print(LOG_APP, LOG_DEBUG, APNG_LOG_DOMAIN, "ApngDecoderNapi", "Decode: Decode result code=%{public}d",
resultCode);
LOGV(" | Decode result: %d", resultCode);
if (resultCode != SUCCESS || !image) {
OH_LOG_Print(LOG_APP, LOG_ERROR, APNG_LOG_DOMAIN, "ApngDecoderNapi",
"Decode: Failed with code=%{public}d, image=%{public}p", resultCode, image.get());
OH_LOG_Print(LOG_APP, LOG_ERROR, APNG_LOG_DOMAIN, "ApngDecoderNapi",
"Decode: Returning error result with id=%{public}d", resultCode);
return CreateDecodeErrorResult(env, resultCode);
}
OH_LOG_Print(LOG_APP, LOG_DEBUG, APNG_LOG_DOMAIN, "ApngDecoderNapi",
"Decode: Success, width=%{public}d, height=%{public}d, frames=%{public}u", image->GetWidth(),
image-> GetHeight(), image->GetFrameCount());
#ifdef BUILD_DEBUG
std::chrono::system_clock::time_point end = std::chrono::system_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count();
LOGV(" | time: %lld ns (%lld ms)", elapsed, elapsed / NS_PER_MS);
#endif
int32_t imageId = StoreImage(std::move(image));
napi_value result = CreateDecodeResult(env, gImageMap[imageId], imageId);
OH_LOG_Print(LOG_APP, LOG_INFO, APNG_LOG_DOMAIN, "ApngDecoderNapi", "Decode: Completed successfully, id=%{public}d",
imageId);
LOGV("Decode end");
return result;
}
static napi_value IsApng(napi_env env, napi_callback_info info)
{
OH_LOG_Print(LOG_APP, LOG_DEBUG, APNG_LOG_DOMAIN, "ApngDecoderNapi", "IsApng function called");
size_t argc = 1;
napi_value args[1];
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
if (argc < 1) {
OH_LOG_Print(LOG_APP, LOG_ERROR, APNG_LOG_DOMAIN, "ApngDecoderNapi",
"IsApng: Expected ArrayBuffer argument but argc=%{public}zu", argc);
napi_throw_error(env, nullptr, "Expected ArrayBuffer argument");
return nullptr;
}
bool isArrayBuffer = false;
napi_is_arraybuffer(env, args[0], &isArrayBuffer);
if (!isArrayBuffer) {
OH_LOG_Print(LOG_APP, LOG_ERROR, APNG_LOG_DOMAIN, "ApngDecoderNapi", "IsApng: Argument is not an ArrayBuffer");
napi_throw_type_error(env, nullptr, "Argument must be an ArrayBuffer");
return nullptr;
}
void *data = nullptr;
size_t length = 0;
napi_get_arraybuffer_info(env, args[0], &data, &length);
OH_LOG_Print(LOG_APP, LOG_DEBUG, APNG_LOG_DOMAIN, "ApngDecoderNapi", "IsApng: ArrayBuffer size=%{public}zu bytes",
length);
std::unique_ptr<StreamSourceBase> source =
std::make_unique<StreamSourceHarmony>(static_cast<const uint8_t *>(data), length);
bool result = ApngDecoder::IsApng(std::move(source));
OH_LOG_Print(LOG_APP, LOG_DEBUG, APNG_LOG_DOMAIN, "ApngDecoderNapi", "IsApng: result=%{public}s",
result ? "true" : "false");
napi_value resultValue;
napi_get_boolean(env, result, &resultValue);
return resultValue;
}
struct DrawParams {
int32_t id = -1;
int32_t index = -1;
bool valid = false;
napi_value args[3];
size_t argc = 0;
};
static DrawParams ParseDrawParams(napi_env env, napi_callback_info info)
{
DrawParams params;
#define EXPECTED_ARG_COUNT 3
params.argc = EXPECTED_ARG_COUNT;
napi_status status = napi_get_cb_info(env, info, ¶ms.argc, params.args, nullptr, nullptr);
if (status != napi_ok) {
OH_LOG_Print(LOG_APP, LOG_ERROR, APNG_LOG_DOMAIN, "ApngDecoderNapi",
"Draw: napi_get_cb_info failed, status=%{public}d", status);
napi_throw_error(env, nullptr, "napi_get_cb_info failed");
return params;
}
OH_LOG_Print(LOG_APP, LOG_DEBUG, APNG_LOG_DOMAIN, "ApngDecoderNapi", "Draw: argc=%{public}zu", params.argc);
if (params.argc < EXPECTED_ARG_COUNT) {
OH_LOG_Print(LOG_APP, LOG_ERROR, APNG_LOG_DOMAIN, "ApngDecoderNapi",
"Draw: Expected (id, index, outputBuffer) arguments but argc=%{public}zu", params.argc);
napi_throw_error(env, nullptr, "Expected (id, index, outputBuffer) arguments");
return params;
}
status = napi_get_value_int32(env, params.args[0], ¶ms.id);
if (status != napi_ok || params.id < 0) {
OH_LOG_Print(LOG_APP, LOG_ERROR, APNG_LOG_DOMAIN, "ApngDecoderNapi", "Draw: Invalid id=%{public}d", params.id);
napi_throw_error(env, nullptr, "Invalid id");
return params;
}
status = napi_get_value_int32(env, params.args[1], ¶ms.index);
if (status != napi_ok || params.index < 0) {
napi_throw_error(env, nullptr, "Invalid index");
return params;
}
params.valid = true;
return params;
}
static std::shared_ptr<ApngImage> GetImageById(int32_t id)
{
std::lock_guard<std::mutex> lock(g_lock);
auto it = gImageMap.find(id);
if (it != gImageMap.end()) {
return it->second;
}
return nullptr;
}
static bool ValidateOutputBuffer(napi_env env, napi_value bufferArg, void*& outData, size_t& outLength)
{
bool isArrayBuffer = false;
napi_status status = napi_is_arraybuffer(env, bufferArg, &isArrayBuffer);
if (status != napi_ok || !isArrayBuffer) {
OH_LOG_Print(LOG_APP, LOG_ERROR, APNG_LOG_DOMAIN, "ApngDecoderNapi",
"Draw: Third argument is not an ArrayBuffer");
napi_throw_type_error(env, nullptr, "Third argument must be an ArrayBuffer");
return false;
}
status = napi_get_arraybuffer_info(env, bufferArg, &outData, &outLength);
if (status != napi_ok) {
OH_LOG_Print(LOG_APP, LOG_ERROR, APNG_LOG_DOMAIN, "ApngDecoderNapi",
"Draw: napi_get_arraybuffer_info failed, status=%{public}d", status);
napi_throw_error(env, nullptr, "napi_get_arraybuffer_info failed");
return false;
}
return true;
}
static napi_value Draw(napi_env env, napi_callback_info info)
{
OH_LOG_Print(LOG_APP, LOG_DEBUG, APNG_LOG_DOMAIN, "ApngDecoderNapi", "Draw function called");
DrawParams params = ParseDrawParams(env, info);
if (!params.valid) {
return nullptr;
}
OH_LOG_Print(LOG_APP, LOG_DEBUG, APNG_LOG_DOMAIN, "ApngDecoderNapi", "Draw: id=%{public}d, index=%{public}d",
params.id, params.index);
std::shared_ptr<ApngImage> image = GetImageById(params.id);
if (!image) {
napi_throw_error(env, nullptr, "Image not found");
return nullptr;
}
std::shared_ptr<ApngFrame> frame = image->GetFrame(static_cast<uint32_t>(params.index));
if (!frame) {
OH_LOG_Print(LOG_APP, LOG_ERROR, APNG_LOG_DOMAIN, "ApngDecoderNapi",
"Draw: Frame not found, id=%{public}d, index=%{public}d", params.id, params.index);
napi_throw_error(env, nullptr, "Frame not found");
return nullptr;
}
void* outputData = nullptr;
size_t outputLength = 0;
const int outputIndex = 2;
if (!ValidateOutputBuffer(env, params.args[outputIndex], outputData, outputLength)) {
return nullptr;
}
uint32_t frameByteCount = image->GetFrameByteCount();
if (outputLength < frameByteCount) {
OH_LOG_Print(LOG_APP, LOG_ERROR, APNG_LOG_DOMAIN, "ApngDecoderNapi",
"Draw: Output buffer too small, required=%{public}u, provided=%{public}zu", frameByteCount, outputLength);
napi_throw_error(env, nullptr, "Output buffer too small");
return nullptr;
}
std::copy_n(reinterpret_cast<uint8_t*>(frame->GetRawPixels()), frameByteCount, static_cast<uint8_t*>(outputData));
OH_LOG_Print(LOG_APP, LOG_DEBUG, APNG_LOG_DOMAIN, "ApngDecoderNapi",
"Draw: Successfully copied frame data, size=%{public}u bytes", frameByteCount);
napi_value success;
napi_get_boolean(env, true, &success);
return success;
}
static napi_value Recycle(napi_env env, napi_callback_info info)
{
OH_LOG_Print(LOG_APP, LOG_DEBUG, APNG_LOG_DOMAIN, "ApngDecoderNapi", "Recycle function called");
size_t argc = 1;
napi_value args[1];
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
if (argc < 1) {
OH_LOG_Print(LOG_APP, LOG_ERROR, APNG_LOG_DOMAIN, "ApngDecoderNapi",
"Recycle: Expected id argument but argc=%{public}zu", argc);
napi_throw_error(env, nullptr, "Expected id argument");
return nullptr;
}
int32_t id;
napi_get_value_int32(env, args[0], &id);
OH_LOG_Print(LOG_APP, LOG_INFO, APNG_LOG_DOMAIN, "ApngDecoderNapi", "Recycle: id=%{public}d", id);
LOGV("recycle start. id : %d", id);
if (id < 0) {
OH_LOG_Print(LOG_APP, LOG_ERROR, APNG_LOG_DOMAIN, "ApngDecoderNapi", "Recycle: Invalid id=%{public}d", id);
napi_value errorCode;
napi_create_int32(env, ERR_NOT_EXIST_IMAGE, &errorCode);
return errorCode;
}
std::lock_guard<std::mutex> lock(g_lock);
auto const & it = gImageMap.find(id);
if (it == gImageMap.end()) {
OH_LOG_Print(LOG_APP, LOG_ERROR, APNG_LOG_DOMAIN, "ApngDecoderNapi",
"Recycle: Image not found for id=%{public}d", id);
napi_value errorCode;
napi_create_int32(env, ERR_NOT_EXIST_IMAGE, &errorCode);
return errorCode;
}
gImageMap.erase(it);
OH_LOG_Print(LOG_APP, LOG_INFO, APNG_LOG_DOMAIN, "ApngDecoderNapi",
"Recycle: Successfully removed image id=%{public}d, remaining images=%{public}zu", id, gImageMap.size());
LOGV(" | removed from map. remaining: %ld", gImageMap.size());
LOGV("recycle end");
napi_value success;
napi_create_int32(env, SUCCESS, &success);
return success;
}
static napi_value Copy(napi_env env, napi_callback_info info)
{
size_t argc = 1;
napi_value args[1];
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
OH_LOG_Print(LOG_APP, LOG_DEBUG, APNG_LOG_DOMAIN, "ApngDecoderNapi", "Copy: argc=%{public}zu", argc);
if (argc < 1) {
OH_LOG_Print(LOG_APP, LOG_ERROR, APNG_LOG_DOMAIN, "ApngDecoderNapi",
"Copy: Expected id argument but argc=%{public}zu", argc);
napi_throw_error(env, nullptr, "Expected id argument");
return nullptr;
}
int32_t id;
napi_get_value_int32(env, args[0], &id);
OH_LOG_Print(LOG_APP, LOG_INFO, APNG_LOG_DOMAIN, "ApngDecoderNapi", "Copy: id=%{public}d", id);
LOGV("copy start. id : %d", id);
if (id < 0) {
OH_LOG_Print(LOG_APP, LOG_ERROR, APNG_LOG_DOMAIN, "ApngDecoderNapi", "Copy: Invalid id=%{public}d", id);
napi_value errorCode;
napi_create_int32(env, ERR_NOT_EXIST_IMAGE, &errorCode);
return errorCode;
}
std::shared_ptr<ApngImage> copyPtr = nullptr;
{
std::lock_guard<std::mutex> lock(g_lock);
auto const & it = gImageMap.find(id);
if (it == gImageMap.end()) {
OH_LOG_Print(LOG_APP, LOG_ERROR, APNG_LOG_DOMAIN, "ApngDecoderNapi",
"Copy: Image not found for id=%{public}d", id);
napi_value errorCode;
napi_create_int32(env, ERR_NOT_EXIST_IMAGE, &errorCode);
return errorCode;
}
copyPtr = it->second;
}
int32_t resultId;
{
std::lock_guard<std::mutex> lock(g_lock);
resultId = static_cast<int32_t>(++g_idCounter);
gImageMap.emplace(resultId, copyPtr);
OH_LOG_Print(LOG_APP, LOG_INFO, APNG_LOG_DOMAIN, "ApngDecoderNapi",
"Copy: Created copy with id=%{public}d from source id=%{public}d, total images=%{public}zu", resultId, id,
gImageMap.size());
}
napi_value result = CreateDecodeResult(env, copyPtr, resultId);
OH_LOG_Print(LOG_APP, LOG_INFO, APNG_LOG_DOMAIN, "ApngDecoderNapi",
"Copy: Completed successfully, new id=%{public}d", resultId);
LOGV("copy end");
return result;
}
struct ParsedParams {
void* data = nullptr;
size_t length = 0;
int32_t width = 0;
int32_t height = 0;
int32_t targetWidth = 0;
int32_t targetHeight = 0;
bool valid = false;
};
static ParsedParams ParseAndValidateParams(napi_env env, napi_callback_info info)
{
ParsedParams params;
size_t argc = ARG_COUNT_CREATE_PIXEL_MAP;
napi_value args[ARG_COUNT_CREATE_PIXEL_MAP];
napi_status status = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
if (status != napi_ok) {
napi_throw_error(env, nullptr, "napi_get_cb_info failed");
return params;
}
if (argc < ARG_COUNT_CREATE_PIXEL_MAP) {
napi_throw_error(env, nullptr, "Expected (buffer, width, height, targetWidth, targetHeight) arguments");
return params;
}
bool isArrayBuffer = false;
status = napi_is_arraybuffer(env, args[0], &isArrayBuffer);
if (!isArrayBuffer) {
napi_throw_type_error(env, nullptr, "First argument must be an ArrayBuffer");
return params;
}
status = napi_get_arraybuffer_info(env, args[0], ¶ms.data, ¶ms.length);
if (status != napi_ok) {
napi_throw_error(env, nullptr, "napi_get_arraybuffer_info failed");
return params;
}
status = napi_get_value_int32(env, args[ARG_INDEX_WIDTH], ¶ms.width);
status = (status == napi_ok) ? napi_get_value_int32(env, args[ARG_INDEX_HEIGHT], ¶ms.height)
: status;
status = (status == napi_ok) ? napi_get_value_int32(env, args[ARG_INDEX_TARGET_WIDTH], ¶ms.targetWidth)
: status;
status = (status == napi_ok) ? napi_get_value_int32(env, args[ARG_INDEX_TARGET_HEIGHT], ¶ms.targetHeight)
: status;
if (status != napi_ok) {
napi_throw_error(env, nullptr, "napi_get_value_int32 failed");
return params;
}
size_t expectedSize = static_cast<size_t>(params.width) * static_cast<size_t>(params.height) * CHANNEL_4_BYTE_SIZE;
if (params.length < expectedSize) {
napi_throw_error(env, nullptr, "Buffer size is too small");
return params;
}
params.valid = true;
return params;
}
static napi_value CreatePixelBuffer(napi_env env, const ParsedParams& params, size_t& outDataSize)
{
napi_value pixelBuffer = nullptr;
void* pixelData = nullptr;
size_t expectedSize = static_cast<size_t>(params.width) * static_cast<size_t>(params.height) * CHANNEL_4_BYTE_SIZE;
if (params.targetWidth == params.width && params.targetHeight == params.height) {
outDataSize = expectedSize;
napi_status status = napi_create_arraybuffer(env, outDataSize, &pixelData, &pixelBuffer);
if (status != napi_ok) {
OH_LOG_Print(LOG_APP, LOG_ERROR, APNG_LOG_DOMAIN, "ApngDecoderNapi",
"CreatePixelMapFromBuffer: napi_create_arraybuffer failed, status=%{public}d", status);
napi_throw_error(env, nullptr, "napi_create_arraybuffer failed");
return nullptr;
}
std::copy_n((unsigned char*)params.data, outDataSize, (unsigned char*)pixelData);
RgbaToBgraInPlace(static_cast<uint8_t*>(pixelData), outDataSize);
} else {
size_t targetDataSize = static_cast<size_t>(params.targetWidth) *
static_cast<size_t>(params.targetHeight) *
CHANNEL_4_BYTE_SIZE;
outDataSize = targetDataSize;
napi_status status = napi_create_arraybuffer(env, targetDataSize, &pixelData, &pixelBuffer);
if (status != napi_ok) {
OH_LOG_Print(LOG_APP, LOG_ERROR, APNG_LOG_DOMAIN, "ApngDecoderNapi",
"CreatePixelMapFromBuffer: napi_create_arraybuffer failed for scaled image, status=%{public}d", status);
napi_throw_error(env, nullptr, "napi_create_arraybuffer failed");
return nullptr;
}
uint8_t* src = static_cast<uint8_t*>(params.data);
uint8_t* dst = static_cast<uint8_t*>(pixelData);
for (int32_t y = 0; y < params.targetHeight; ++y) {
int32_t srcY = (y * params.height) / params.targetHeight;
for (int32_t x = 0; x < params.targetWidth; ++x) {
int32_t srcX = (x * params.width) / params.targetWidth;
size_t srcOffset = (srcY * params.width + srcX) * CHANNEL_4_BYTE_SIZE;
size_t dstOffset = (y * params.targetWidth + x) * CHANNEL_4_BYTE_SIZE;
dst[dstOffset + CHANNEL_INDEX_R] = src[srcOffset + CHANNEL_INDEX_R];
dst[dstOffset + CHANNEL_INDEX_G] = src[srcOffset + CHANNEL_INDEX_G];
dst[dstOffset + CHANNEL_INDEX_B] = src[srcOffset + CHANNEL_INDEX_B];
dst[dstOffset + CHANNEL_INDEX_A] = src[srcOffset + CHANNEL_INDEX_A];
}
}
RgbaToBgraInPlace(static_cast<uint8_t*>(pixelData), targetDataSize);
OH_LOG_Print(LOG_APP, LOG_DEBUG, APNG_LOG_DOMAIN, "ApngDecoderNapi",
"CreatePixelMapFromBuffer: Scaling completed, output size=%{public}zu bytes", targetDataSize);
}
return pixelBuffer;
}
static bool SetPixelMapProperties(napi_env env,
napi_value pixelMapObj,
napi_value pixelBuffer,
const ParsedParams& params)
{
napi_value widthValue;
napi_value heightValue;
napi_value targetWidthValue;
napi_value targetHeightValue;
napi_status status = napi_create_int32(env, params.targetWidth, &widthValue);
status = (status == napi_ok) ? napi_create_int32(env, params.targetHeight, &heightValue)
: status;
status = (status == napi_ok) ? napi_create_int32(env, params.targetWidth, &targetWidthValue)
: status;
status = (status == napi_ok) ? napi_create_int32(env, params.targetHeight, &targetHeightValue)
: status;
if (status != napi_ok) {
OH_LOG_Print(LOG_APP, LOG_ERROR, APNG_LOG_DOMAIN, "ApngDecoderNapi",
"CreatePixelMapFromBuffer: napi_create_int32 failed, status=%{public}d", status);
napi_throw_error(env, nullptr, "napi_create_int32 failed");
return false;
}
status = napi_set_named_property(env, pixelMapObj, "pixelBuffer", pixelBuffer);
status = (status == napi_ok) ? napi_set_named_property(env, pixelMapObj, "width", widthValue)
: status;
status = (status == napi_ok) ? napi_set_named_property(env, pixelMapObj, "height", heightValue)
: status;
status = (status == napi_ok) ? napi_set_named_property(env, pixelMapObj, "targetWidth", targetWidthValue)
: status;
status = (status == napi_ok) ? napi_set_named_property(env, pixelMapObj, "targetHeight", targetHeightValue)
: status;
if (status != napi_ok) {
OH_LOG_Print(LOG_APP, LOG_ERROR, APNG_LOG_DOMAIN, "ApngDecoderNapi",
"CreatePixelMapFromBuffer: napi_set_named_property failed, status=%{public}d", status);
napi_throw_error(env, nullptr, "napi_set_named_property failed");
return false;
}
return true;
}
static napi_value CreatePixelMapFromBuffer(napi_env env, napi_callback_info info)
{
OH_LOG_Print(LOG_APP, LOG_DEBUG, APNG_LOG_DOMAIN, "ApngDecoderNapi", "CreatePixelMapFromBuffer function called");
ParsedParams params = ParseAndValidateParams(env, info);
if (!params.valid) {
return nullptr;
}
napi_value pixelMapObj;
napi_status status = napi_create_object(env, &pixelMapObj);
if (status != napi_ok) {
OH_LOG_Print(LOG_APP, LOG_ERROR, APNG_LOG_DOMAIN, "ApngDecoderNapi",
"CreatePixelMapFromBuffer: napi_create_object failed, status=%{public}d", status);
napi_throw_error(env, nullptr, "napi_create_object failed");
return nullptr;
}
size_t pixelDataSize = 0;
napi_value pixelBuffer = CreatePixelBuffer(env, params, pixelDataSize);
if (pixelBuffer == nullptr) {
return nullptr;
}
if (!SetPixelMapProperties(env, pixelMapObj, pixelBuffer, params)) {
return nullptr;
}
OH_LOG_Print(LOG_APP, LOG_DEBUG, APNG_LOG_DOMAIN, "ApngDecoderNapi",
"CreatePixelMapFromBuffer: Created PixelMap wrapper object");
return pixelMapObj;
}
extern "C" napi_value InitApngDrawable(napi_env env, napi_value exports)
{
OH_LOG_Print(LOG_APP, LOG_INFO, APNG_LOG_DOMAIN, "ApngDecoderNapi",
"========== APNG MODULE INIT CALLED ==========");
OH_LOG_Print(LOG_APP, LOG_INFO, APNG_LOG_DOMAIN, "ApngDecoderNapi", "Initializing apng_drawable module");
napi_property_descriptor desc[] = {
{ "decode", nullptr, Decode, nullptr, nullptr, nullptr, napi_default, nullptr },
{ "isApng", nullptr, IsApng, nullptr, nullptr, nullptr, napi_default, nullptr },
{ "draw", nullptr, Draw, nullptr, nullptr, nullptr, napi_default, nullptr },
{ "recycle", nullptr, Recycle, nullptr, nullptr, nullptr, napi_default, nullptr },
{ "copy", nullptr, Copy, nullptr, nullptr, nullptr, napi_default, nullptr },
{ "createPixelMapFromBuffer", nullptr, CreatePixelMapFromBuffer, nullptr, nullptr, nullptr, napi_default, nullptr }
};
napi_status status = napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
if (status != napi_ok) {
OH_LOG_Print(LOG_APP, LOG_ERROR, APNG_LOG_DOMAIN, "ApngDecoderNapi",
"InitApngDrawable: napi_define_properties failed, status=%{public}d", status);
return exports;
}
OH_LOG_Print(LOG_APP, LOG_INFO, APNG_LOG_DOMAIN, "ApngDecoderNapi",
"apng_drawable module initialized successfully with %{public}zu properties", sizeof(desc) / sizeof(desc[0]));
return exports;
}
}