/* * Copyright (C) 2022 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 "image_napi.h" #include "napi/native_node_api.h" #include "image_log.h" #include "media_errors.h" #include "image_format.h" #include "image_napi_utils.h" #undef LOG_DOMAIN #define LOG_DOMAIN LOG_TAG_DOMAIN_ID_IMAGE #undef LOG_TAG #define LOG_TAG "ImageNapi" namespace { constexpr int NUM0 = 0; constexpr int NUM1 = 1; constexpr int NUM2 = 2; const std::string MY_NAME = "ImageNapi"; } namespace OHOS { namespace Media { struct ImageAsyncContext { napi_env env = nullptr; napi_async_work work = nullptr; napi_deferred deferred = nullptr; napi_ref callbackRef = nullptr; napi_ref thisRef = nullptr; ImageNapi *napi = nullptr; uint32_t status; int32_t componentType; NativeImage* image = nullptr; NativeComponent* component = nullptr; bool isTestContext = false; }; ImageHolderManager<NativeImage> ImageNapi::sNativeImageHolder_; thread_local napi_ref ImageNapi::sConstructor_ = nullptr; ImageNapi::ImageNapi() {} ImageNapi::~ImageNapi() { NativeRelease(); } void ImageNapi::NativeRelease() { if (native_ != nullptr) { sNativeImageHolder_.release(native_->GetId()); native_->release(); native_ = nullptr; } } napi_value ImageNapi::Init(napi_env env, napi_value exports) { IMAGE_FUNCTION_IN(); napi_property_descriptor props[] = { DECLARE_NAPI_GETTER("clipRect", JSGetClipRect), DECLARE_NAPI_GETTER("size", JsGetSize), DECLARE_NAPI_GETTER("format", JsGetFormat), DECLARE_NAPI_GETTER("timestamp", JsGetTimestamp), DECLARE_NAPI_FUNCTION("getComponent", JsGetComponent), DECLARE_NAPI_FUNCTION("release", JsRelease), }; size_t size = IMG_ARRAY_SIZE(props); napi_value thisVar = nullptr; auto name = MY_NAME.c_str(); if (napi_define_class(env, name, SIZE_MAX, Constructor, nullptr, size, props, &thisVar) != napi_ok) { IMAGE_ERR("Define class failed"); return exports; } sConstructor_ = nullptr; if (napi_create_reference(env, thisVar, NUM1, &sConstructor_) != napi_ok) { IMAGE_ERR("Create reference failed"); return exports; } if (napi_set_named_property(env, exports, name, thisVar) != napi_ok) { IMAGE_ERR("Define class failed"); return exports; } IMAGE_DEBUG("Init success"); return exports; } std::shared_ptr<NativeImage> ImageNapi::GetNativeImage(napi_env env, napi_value image) { ImageNapi* napi = nullptr; napi_status status = napi_unwrap(env, image, reinterpret_cast<void**>(&napi)); if (!IMG_IS_OK(status) || napi == nullptr) { IMAGE_ERR("GetImage napi unwrap failed"); return nullptr; } return napi->native_; } napi_value ImageNapi::Constructor(napi_env env, napi_callback_info info) { napi_status status; napi_value thisVar = nullptr; napi_value undefineVar; size_t argc = NUM1; napi_value argv[NUM1]; IMAGE_FUNCTION_IN(); napi_get_undefined(env, &undefineVar); status = napi_get_cb_info(env, info, &argc, argv, &thisVar, nullptr); if (status != napi_ok || thisVar == nullptr || argc != NUM1) { IMAGE_ERR("Constructor Failed to napi_get_cb_info"); return undefineVar; } std::string id; if (!ImageNapiUtils::GetUtf8String(env, argv[NUM0], id) || (id.size() == NUM0)) { IMAGE_ERR("Failed to parse native image id"); return undefineVar; } std::unique_ptr<ImageNapi> napi = std::make_unique<ImageNapi>(); napi->native_ = sNativeImageHolder_.get(id); napi->isTestImage_ = false; if (napi->native_ == nullptr) { if (MY_NAME.compare(id.c_str()) == 0) { napi->isTestImage_ = true; } else { IMAGE_ERR("Failed to get native image"); return undefineVar; } } status = napi_wrap(env, thisVar, reinterpret_cast<void *>(napi.get()), ImageNapi::Destructor, nullptr, nullptr); if (status != napi_ok) { IMAGE_ERR("Failure wrapping js to native napi"); return undefineVar; } napi.release(); IMAGE_FUNCTION_OUT(); return thisVar; } void ImageNapi::Destructor(napi_env env, void *nativeObject, void *finalize) { if (nativeObject != nullptr) { delete reinterpret_cast<ImageNapi *>(nativeObject); } } napi_value ImageNapi::Create(napi_env env) { napi_value constructor = nullptr; napi_value result = nullptr; napi_value argv[NUM1]; IMAGE_FUNCTION_IN(); if (env == nullptr) { IMAGE_ERR("Input args is invalid"); return nullptr; } if (napi_get_reference_value(env, sConstructor_, &constructor) == napi_ok && constructor != nullptr) { if (napi_create_string_utf8(env, MY_NAME.c_str(), NAPI_AUTO_LENGTH, &(argv[NUM0])) != napi_ok) { IMAGE_ERR("Create native image id Failed"); } if (napi_new_instance(env, constructor, NUM1, argv, &result) != napi_ok) { IMAGE_ERR("New instance could not be obtained"); } } IMAGE_FUNCTION_OUT(); return result; } napi_value ImageNapi::Create(napi_env env, std::shared_ptr<NativeImage> nativeImage) { napi_value constructor = nullptr; napi_value result = nullptr; napi_value argv[NUM1]; IMAGE_FUNCTION_IN(); if (env == nullptr || nativeImage == nullptr) { IMAGE_ERR("Input args is invalid"); return nullptr; } if (napi_get_reference_value(env, sConstructor_, &constructor) == napi_ok && constructor != nullptr) { auto id = sNativeImageHolder_.save(nativeImage); nativeImage->SetId(id); if (napi_create_string_utf8(env, id.c_str(), NAPI_AUTO_LENGTH, &(argv[NUM0])) != napi_ok) { IMAGE_ERR("Create native image id Failed"); } if (napi_new_instance(env, constructor, NUM1, argv, &result) != napi_ok) { IMAGE_ERR("New instance could not be obtained"); } } IMAGE_FUNCTION_OUT(); return result; } static inline bool JsCheckObjectType(napi_env env, napi_value value, napi_valuetype type) { return (ImageNapiUtils::getType(env, value) == type); } static inline bool JsGetCallbackFunc(napi_env env, napi_value value, napi_ref *result) { if (JsCheckObjectType(env, value, napi_function)) { napi_create_reference(env, value, NUM1, result); return true; } return false; } static inline bool JsGetInt32Args(napi_env env, napi_value value, int *result) { if (JsCheckObjectType(env, value, napi_number)) { napi_get_value_int32(env, value, result); return true; } return false; } using AsyncExecCallback = void (*)(napi_env env, ImageAsyncContext* ctx); using AsyncCompleteCallback = void (*)(napi_env env, napi_status status, ImageAsyncContext* ctx); static bool JsCreateWork(napi_env env, const char* name, AsyncExecCallback exec, AsyncCompleteCallback complete, ImageAsyncContext* ctx) { napi_value resource = nullptr; if (napi_create_string_utf8(env, name, NAPI_AUTO_LENGTH, &resource) != napi_ok) { IMAGE_ERR("napi_create_string_utf8 failed"); return false; } napi_status status = napi_create_async_work( env, nullptr, resource, reinterpret_cast<napi_async_execute_callback>(exec), reinterpret_cast<napi_async_complete_callback>(complete), static_cast<void *>(ctx), &(ctx->work)); if (status != napi_ok) { IMAGE_ERR("fail to create async work %{public}d", status); return false; } if (napi_queue_async_work(env, ctx->work) != napi_ok) { IMAGE_ERR("fail to queue async work"); return false; } return true; } NativeImage* ImageNapi::GetNative() { if (native_ != nullptr) { return native_.get(); } return nullptr; } static std::unique_ptr<ImageAsyncContext> UnwrapContext(napi_env env, napi_callback_info info, size_t* argc = nullptr, napi_value* argv = nullptr, bool needCreateRef = false) { napi_value thisVar = nullptr; size_t tmp = NUM0; IMAGE_FUNCTION_IN(); if (napi_get_cb_info(env, info, (argc == nullptr)?&tmp:argc, argv, &thisVar, nullptr) != napi_ok) { IMAGE_ERR("Fail to napi_get_cb_info"); return nullptr; } std::unique_ptr<ImageAsyncContext> ctx = std::make_unique<ImageAsyncContext>(); if (napi_unwrap(env, thisVar, reinterpret_cast<void**>(&ctx->napi)) != napi_ok || ctx->napi == nullptr) { IMAGE_ERR("fail to unwrap constructor_"); return nullptr; } ctx->image = ctx->napi->GetNative(); if (needCreateRef) { napi_create_reference(env, thisVar, NUM1, &(ctx->thisRef)); } return ctx; } static inline void ProcessPromise(napi_env env, napi_deferred deferred, napi_value* result, bool resolved) { napi_status status; if (resolved) { status = napi_resolve_deferred(env, deferred, result[NUM1]); } else { status = napi_reject_deferred(env, deferred, result[NUM0]); } if (status != napi_ok) { IMAGE_ERR("ProcessPromise failed"); } deferred = nullptr; } static inline void ProcessCallback(napi_env env, napi_ref ref, napi_value* result) { napi_value retVal; napi_value callback; napi_get_reference_value(env, ref, &callback); if (callback != nullptr) { napi_call_function(env, nullptr, callback, NUM2, result, &retVal); } napi_delete_reference(env, ref); } static void CommonCallbackRoutine(napi_env env, ImageAsyncContext* &context, const napi_value &valueParam) { IMAGE_FUNCTION_IN(); napi_value result[2] = {0}; if (context == nullptr) { IMAGE_ERR("context is nullptr"); return; } if (context->status == SUCCESS) { napi_create_uint32(env, context->status, &result[0]); result[1] = valueParam; } else { ImageNapiUtils::CreateErrorObj(env, result[0], context->status, "There is generic napi failure!"); napi_get_undefined(env, &result[1]); } if (context->deferred) { ProcessPromise(env, context->deferred, result, context->status == SUCCESS); } else { ProcessCallback(env, context->callbackRef, result); } napi_delete_async_work(env, context->work); delete context; context = nullptr; IMAGE_FUNCTION_OUT(); } static void BuildIntProperty(napi_env env, const std::string &name, int32_t val, napi_value result) { napi_value nVal; napi_create_int32(env, val, &nVal); napi_set_named_property(env, result, name.c_str(), nVal); } static napi_value BuildJsSize(napi_env env, int32_t width, int32_t height) { napi_value result = nullptr; napi_create_object(env, &result); BuildIntProperty(env, "width", width, result); BuildIntProperty(env, "height", height, result); return result; } static napi_value BuildJsRegion(napi_env env, int32_t width, int32_t height, int32_t x, int32_t y) { napi_value result = nullptr; napi_create_object(env, &result); napi_set_named_property(env, result, "size", BuildJsSize(env, width, height)); BuildIntProperty(env, "x", x, result); BuildIntProperty(env, "y", y, result); return result; } napi_value ImageNapi::JSGetClipRect(napi_env env, napi_callback_info info) { napi_value result = nullptr; IMAGE_FUNCTION_IN(); napi_get_undefined(env, &result); std::unique_ptr<ImageAsyncContext> context = UnwrapContext(env, info); if (context != nullptr && context->napi != nullptr && context->napi->isTestImage_) { const int32_t WIDTH = 8192; const int32_t HEIGHT = 8; return BuildJsRegion(env, WIDTH, HEIGHT, NUM0, NUM0); } if (context == nullptr || context->image == nullptr) { IMAGE_ERR("Image surface buffer is nullptr"); return result; } int32_t width = NUM0; int32_t height = NUM0; if (context->image->GetSize(width, height) != SUCCESS) { IMAGE_ERR("Image native get size failed"); return result; } return BuildJsRegion(env, width, height, NUM0, NUM0); } napi_value ImageNapi::JsGetSize(napi_env env, napi_callback_info info) { napi_value result = nullptr; IMAGE_FUNCTION_IN(); napi_get_undefined(env, &result); std::unique_ptr<ImageAsyncContext> context = UnwrapContext(env, info); if (context != nullptr && context->napi != nullptr && context->napi->isTestImage_) { const int32_t WIDTH = 8192; const int32_t HEIGHT = 8; return BuildJsSize(env, WIDTH, HEIGHT); } if (context == nullptr || context->image == nullptr) { IMAGE_ERR("Image surface buffer is nullptr"); return result; } int32_t width = NUM0; int32_t height = NUM0; if (context->image->GetSize(width, height) != SUCCESS) { IMAGE_ERR("Image native get size failed"); return result; } return BuildJsSize(env, width, height); } napi_value ImageNapi::JsGetFormat(napi_env env, napi_callback_info info) { napi_value result = nullptr; IMAGE_FUNCTION_IN(); napi_get_undefined(env, &result); std::unique_ptr<ImageAsyncContext> context = UnwrapContext(env, info); if (context != nullptr && context->napi != nullptr && context->napi->isTestImage_) { const int32_t FORMAT = 12; napi_create_int32(env, FORMAT, &result); return result; } if (context == nullptr || context->image == nullptr) { IMAGE_ERR("Image surface buffer is nullptr"); return result; } int32_t format = NUM0; if (context->image->GetFormat(format) != SUCCESS) { IMAGE_ERR("Image native get format failed"); return result; } napi_create_int32(env, format, &result); return result; } napi_value ImageNapi::JsGetTimestamp(napi_env env, napi_callback_info info) { napi_value result = nullptr; IMAGE_FUNCTION_IN(); napi_get_undefined(env, &result); std::unique_ptr<ImageAsyncContext> context = UnwrapContext(env, info); if (context == nullptr || context->image == nullptr) { IMAGE_ERR("context is nullptr or Image native is nullptr"); return result; } int64_t timestamp = 0; if (context->image->GetTimestamp(timestamp) != SUCCESS) { IMAGE_ERR("Image native get timestamp failed"); return result; } napi_create_int64(env, timestamp, &result); return result; } static void JSReleaseCallBack(napi_env env, napi_status status, ImageAsyncContext* context) { IMAGE_FUNCTION_IN(); napi_value result = nullptr; napi_get_undefined(env, &result); if (context == nullptr) { IMAGE_ERR("context is nullptr"); return; } if (context->thisRef != nullptr) { napi_value thisVar; napi_get_reference_value(env, context->thisRef, &thisVar); napi_delete_reference(env, context->thisRef); if (thisVar != nullptr) { ImageNapi *tmp = nullptr; auto status_ = napi_remove_wrap(env, thisVar, reinterpret_cast<void**>(&tmp)); if (status_ != napi_ok) { IMAGE_ERR("NAPI remove wrap failed status %{public}d", status_); } } } context->status = SUCCESS; IMAGE_FUNCTION_OUT(); CommonCallbackRoutine(env, context, result); } napi_value ImageNapi::JsRelease(napi_env env, napi_callback_info info) { IMAGE_FUNCTION_IN(); napi_value result = nullptr; size_t argc = NUM1; napi_value argv[NUM1] = {0}; napi_get_undefined(env, &result); auto context = UnwrapContext(env, info, &argc, argv, true); if (context == nullptr) { IMAGE_ERR("fail to unwrap constructor_"); return result; } if (argc == NUM1) { if (!JsGetCallbackFunc(env, argv[NUM0], &(context->callbackRef))) { IMAGE_ERR("Unsupport arg 0 type"); return result; } } else { napi_create_promise(env, &(context->deferred), &result); } if (JsCreateWork(env, "JsRelease", [](napi_env env, ImageAsyncContext* data) {}, JSReleaseCallBack, context.get())) { context.release(); } IMAGE_FUNCTION_OUT(); return result; } static bool CreateArrayBuffer(napi_env env, uint8_t* src, size_t srcLen, napi_value *res) { if (src == nullptr || srcLen == 0) { return false; } auto status = napi_create_external_arraybuffer(env, src, srcLen, [](napi_env env, void* data, void* hint) { }, nullptr, res); if (status != napi_ok) { return false; } return true; } static inline bool IsEqual(const int32_t& check, ImageFormat format) { return (check == int32_t(format)); } static inline bool IsEqual(const int32_t& check, ComponentType type) { return (check == int32_t(type)); } static inline bool IsYUVComponent(const int32_t& type) { return (IsEqual(type, ComponentType::YUV_Y) || IsEqual(type, ComponentType::YUV_U) || IsEqual(type, ComponentType::YUV_V)); } static inline bool IsYUV422SPImage(int32_t format) { return (IsEqual(format, ImageFormat::YCBCR_422_SP) || (format == int32_t(GRAPHIC_PIXEL_FMT_YCBCR_422_SP))); } static inline bool CheckComponentType(const int32_t& type, int32_t format) { return ((IsYUV422SPImage(format) && IsYUVComponent(type)) || (!IsYUV422SPImage(format) && IsEqual(type, ComponentType::JPEG))); } static bool BuildJsComponentObject(napi_env env, int32_t type, uint8_t* buffer, NativeComponent* component, napi_value* result) { napi_value array; if (!CreateArrayBuffer(env, buffer, component->size, &array)) { return false; } napi_create_object(env, result); napi_set_named_property(env, *result, "byteBuffer", array); BuildIntProperty(env, "componentType", type, *result); BuildIntProperty(env, "rowStride", component->rowStride, *result); BuildIntProperty(env, "pixelStride", component->pixelStride, *result); return true; } static void TestGetComponentCallBack(napi_env env, napi_status status, ImageAsyncContext* context) { if (context == nullptr) { IMAGE_ERR("Invalid input context"); return; } napi_value result; napi_value array; void *nativePtr = nullptr; if (napi_create_arraybuffer(env, NUM1, &nativePtr, &array) != napi_ok || nativePtr == nullptr) { return; } napi_create_object(env, &result); napi_set_named_property(env, result, "byteBuffer", array); BuildIntProperty(env, "componentType", context->componentType, result); BuildIntProperty(env, "rowStride", NUM0, result); BuildIntProperty(env, "pixelStride", NUM0, result); context->status = SUCCESS; CommonCallbackRoutine(env, context, result); } static void JsGetComponentCallBack(napi_env env, napi_status status, ImageAsyncContext* context) { IMAGE_FUNCTION_IN(); napi_value result; napi_get_undefined(env, &result); if (context != nullptr && context->napi != nullptr && context->isTestContext) { TestGetComponentCallBack(env, status, context); return; } if (context == nullptr) { IMAGE_ERR("Invalid input context"); return; } context->status = ERROR; NativeComponent* component = context->component; if (component == nullptr) { IMAGE_ERR("Invalid component"); CommonCallbackRoutine(env, context, result); return; } uint8_t *buffer = nullptr; if (component->virAddr != nullptr) { buffer = component->virAddr; } else { buffer = component->raw.data(); } if (buffer == nullptr || component->size == NUM0) { IMAGE_ERR("Invalid buffer"); CommonCallbackRoutine(env, context, result); return; } if (BuildJsComponentObject(env, context->componentType, buffer, component, &result)) { context->status = SUCCESS; } else { IMAGE_ERR("napi_create_arraybuffer failed!"); } IMAGE_FUNCTION_OUT(); CommonCallbackRoutine(env, context, result); } static void JsGetComponentExec(napi_env env, ImageAsyncContext* context) { if (context == nullptr || context->napi == nullptr) { IMAGE_ERR("Invalid input context"); return; } auto native = context->napi->GetNative(); if (native == nullptr) { IMAGE_ERR("Empty native"); return; } context->component = native->GetComponent(context->componentType); } static bool JsGetComponentArgs(napi_env env, size_t argc, napi_value* argv, ImageAsyncContext* context) { if (argv == nullptr || context == nullptr || argc < NUM1 || context->napi == nullptr) { IMAGE_ERR("argv is nullptr"); return false; } if (!JsGetInt32Args(env, argv[NUM0], &(context->componentType))) { IMAGE_ERR("Unsupport arg 0 type"); return false; } auto native = context->napi->GetNative(); if (native == nullptr && !context->isTestContext) { IMAGE_ERR("native is nullptr"); return false; } int32_t format = NUM0; if (context->isTestContext) { const int32_t TEST_FORMAT = 12; format = TEST_FORMAT; } else { native->GetFormat(format); } if (!CheckComponentType(context->componentType, format)) { IMAGE_ERR("Unsupport component type 0 value: %{public}d", context->componentType); return false; } if (argc == NUM2 && !JsGetCallbackFunc(env, argv[NUM1], &(context->callbackRef))) { IMAGE_ERR("Unsupport arg 1 type"); return false; } return true; } napi_value ImageNapi::JsGetComponent(napi_env env, napi_callback_info info) { IMAGE_FUNCTION_IN(); napi_value result = nullptr; size_t argc = NUM2; napi_value argv[NUM2] = {0}; napi_get_undefined(env, &result); auto context = UnwrapContext(env, info, &argc, argv); if (context == nullptr) { return ImageNapiUtils::ThrowExceptionError(env, static_cast<int32_t>(napi_invalid_arg), "fail to unwrap constructor_ "); } context->isTestContext = context->napi->isTestImage_; if (!JsGetComponentArgs(env, argc, argv, context.get())) { return ImageNapiUtils::ThrowExceptionError(env, static_cast<int32_t>(napi_invalid_arg), "Unsupport arg type!"); } if (context->callbackRef == nullptr) { napi_create_promise(env, &(context->deferred), &result); } if (JsCreateWork(env, "JsGetComponent", JsGetComponentExec, JsGetComponentCallBack, context.get())) { context.release(); } IMAGE_FUNCTION_OUT(); return result; } } // namespace Media } // namespace OHOS