1 /*
2 * Copyright (c) 2022-2023 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "bridge/declarative_frontend/jsview/canvas/js_offscreen_canvas.h"
17
18 #include "napi/native_api.h"
19 #include "napi/native_node_api.h"
20
21 #include "base/utils/utils.h"
22 #include "bridge/declarative_frontend/engine/bindings.h"
23 #include "bridge/declarative_frontend/jsview/canvas/js_canvas_gradient.h"
24 #include "bridge/declarative_frontend/jsview/canvas/js_canvas_pattern.h"
25 #include "bridge/declarative_frontend/jsview/canvas/js_matrix2d.h"
26 #include "bridge/declarative_frontend/jsview/canvas/js_render_image.h"
27
28 namespace OHOS::Ace::Framework {
29 constexpr int32_t ARGS_COUNT_ONE = 1;
30 constexpr int32_t ARGS_COUNT_TWO = 2;
31
DetachOffscreenCanvas(napi_env env,void * value,void * hint)32 void* DetachOffscreenCanvas(napi_env env, void* value, void* hint)
33 {
34 if (value == nullptr) {
35 LOGW("Invalid parameter.");
36 return nullptr;
37 }
38 JSOffscreenCanvas* workCanvas = (JSOffscreenCanvas*)value;
39 if (workCanvas->IsGetContext()) {
40 JSException::Throw(ERROR_CODE_PARAM_INVALID, "%s",
41 "An OffscreenCanvas could not be transferred because it had a rendering context.");
42 return nullptr;
43 }
44 if (workCanvas->IsDetached()) {
45 JSException::Throw(ERROR_CODE_PARAM_INVALID, "%s",
46 "An OffscreenCanvas could not be transferred because the object is detached.");
47 return nullptr;
48 }
49 workCanvas->SetDetachStatus(true);
50
51 auto result = new JSOffscreenCanvas();
52 result->SetWidth(workCanvas->GetWidth());
53 result->SetHeight(workCanvas->GetHeight());
54 result->SetUnit(workCanvas->GetUnit());
55 return result;
56 }
57
AttachOffscreenCanvas(napi_env env,void * value,void *)58 napi_value AttachOffscreenCanvas(napi_env env, void* value, void*)
59 {
60 if (value == nullptr) {
61 LOGW("Invalid parameter.");
62 return nullptr;
63 }
64 JSOffscreenCanvas* workCanvas = (JSOffscreenCanvas*)value;
65 if (workCanvas == nullptr) {
66 LOGW("Invalid context.");
67 return nullptr;
68 }
69 auto offscreenCanvasPattern = AceType::MakeRefPtr<NG::OffscreenCanvasPattern>(
70 workCanvas->GetWidth(), workCanvas->GetHeight());
71 workCanvas->SetOffscreenPattern(offscreenCanvasPattern);
72 auto bitmapSize = offscreenCanvasPattern->GetBitmapSize();
73
74 napi_value offscreenCanvas = nullptr;
75 napi_create_object(env, &offscreenCanvas);
76
77 napi_property_descriptor desc[] = {
78 DECLARE_NAPI_GETTER_SETTER("width", JSOffscreenCanvas::JsGetWidth, JSOffscreenCanvas::JsSetWidth),
79 DECLARE_NAPI_GETTER_SETTER("height", JSOffscreenCanvas::JsGetHeight, JSOffscreenCanvas::JsSetHeight),
80 DECLARE_NAPI_FUNCTION("transferToImageBitmap", JSOffscreenCanvas::JsTransferToImageBitmap),
81 DECLARE_NAPI_FUNCTION("getContext", JSOffscreenCanvas::JsGetContext),
82 };
83 napi_define_properties(env, offscreenCanvas, sizeof(desc) / sizeof(*desc), desc);
84
85 napi_coerce_to_native_binding_object(
86 env, offscreenCanvas, DetachOffscreenCanvas, AttachOffscreenCanvas, value, nullptr);
87 napi_wrap_with_size(
88 env, offscreenCanvas, value,
89 [](napi_env env, void* data, void* hint) {
90 LOGD("Finalizer for offscreen canvas is called");
91 auto wrapper = reinterpret_cast<JSOffscreenCanvas*>(data);
92 delete wrapper;
93 wrapper = nullptr;
94 },
95 nullptr, nullptr, bitmapSize);
96 return offscreenCanvas;
97 }
98
InitOffscreenCanvas(napi_env env)99 napi_value JSOffscreenCanvas::InitOffscreenCanvas(napi_env env)
100 {
101 napi_value object = nullptr;
102 napi_create_object(env, &object);
103
104 napi_property_descriptor desc[] = {
105 DECLARE_NAPI_GETTER_SETTER("width", JsGetWidth, JsSetWidth),
106 DECLARE_NAPI_GETTER_SETTER("height", JsGetHeight, JsSetHeight),
107 DECLARE_NAPI_FUNCTION("transferToImageBitmap", JsTransferToImageBitmap),
108 DECLARE_NAPI_FUNCTION("getContext", JsGetContext),
109 };
110 napi_status status = napi_define_class(
111 env, "OffscreenCanvas", NAPI_AUTO_LENGTH, Constructor, nullptr, sizeof(desc) / sizeof(*desc), desc, &object);
112 if (status != napi_ok) {
113 LOGW("Initialize offscreen canvas failed");
114 return nullptr;
115 }
116 return object;
117 }
118
JSBind(BindingTarget globalObj,void * nativeEngine)119 void JSOffscreenCanvas::JSBind(BindingTarget globalObj, void* nativeEngine)
120 {
121 if (!nativeEngine) {
122 return;
123 }
124 napi_env env = reinterpret_cast<napi_env>(nativeEngine);
125
126 napi_value jsGlobalObj = nullptr;
127 napi_get_global(env, &jsGlobalObj);
128
129 napi_value result = InitOffscreenCanvas(env);
130 napi_set_named_property(env, jsGlobalObj, "OffscreenCanvas", result);
131 }
132
Constructor(napi_env env,napi_callback_info info)133 napi_value JSOffscreenCanvas::Constructor(napi_env env, napi_callback_info info)
134 {
135 ContainerScope scope(Container::CurrentIdSafely());
136 size_t argc = 3;
137 napi_value thisVar = nullptr;
138 napi_value argv[3] = { nullptr };
139 NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, &thisVar, nullptr));
140 if (argc < ARGS_COUNT_TWO || argv[0] == nullptr || argv[1] == nullptr) {
141 LOGW("Invalid args.");
142 return nullptr;
143 }
144 double fWidth = 0.0;
145 double fHeight = 0.0;
146 auto workCanvas = new JSOffscreenCanvas();
147 if (argv[2] != nullptr) {
148 int32_t unit = 0;
149 napi_get_value_int32(env, argv[2], &unit);
150 if (static_cast<CanvasUnit>(unit) == CanvasUnit::PX) {
151 workCanvas->SetUnit(CanvasUnit::PX);
152 }
153 }
154 double density = workCanvas->GetDensity();
155 if (napi_get_value_double(env, argv[0], &fWidth) == napi_ok) {
156 fWidth *= density;
157 workCanvas->SetWidth(fWidth);
158 }
159 if (napi_get_value_double(env, argv[1], &fHeight) == napi_ok) {
160 fHeight *= density;
161 workCanvas->SetHeight(fHeight);
162 }
163 workCanvas->offscreenCanvasPattern_ = AceType::MakeRefPtr<NG::OffscreenCanvasPattern>(
164 static_cast<int32_t>(fWidth), static_cast<int32_t>(fHeight));
165 auto bitmapSize = workCanvas->offscreenCanvasPattern_->GetBitmapSize();
166 napi_coerce_to_native_binding_object(
167 env, thisVar, DetachOffscreenCanvas, AttachOffscreenCanvas, workCanvas, nullptr);
168 napi_wrap_with_size(
169 env, thisVar, workCanvas,
170 [](napi_env env, void* data, void* hint) {
171 LOGD("Finalizer for offscreen canvas is called");
172 auto workCanvas = reinterpret_cast<JSOffscreenCanvas*>(data);
173 delete workCanvas;
174 workCanvas = nullptr;
175 },
176 nullptr, nullptr, bitmapSize);
177 return thisVar;
178 }
179
JsGetWidth(napi_env env,napi_callback_info info)180 napi_value JSOffscreenCanvas::JsGetWidth(napi_env env, napi_callback_info info)
181 {
182 ContainerScope scope(Container::CurrentIdSafely());
183 JSOffscreenCanvas* me = static_cast<JSOffscreenCanvas*>(GetNapiCallbackInfoAndThis(env, info));
184 napi_value defaultWidth = nullptr;
185 napi_create_double(env, 0.0, &defaultWidth);
186 return (me != nullptr && !me->isDetached_) ? me->OnGetWidth(env) : defaultWidth;
187 }
188
JsGetHeight(napi_env env,napi_callback_info info)189 napi_value JSOffscreenCanvas::JsGetHeight(napi_env env, napi_callback_info info)
190 {
191 ContainerScope scope(Container::CurrentIdSafely());
192 JSOffscreenCanvas* me = static_cast<JSOffscreenCanvas*>(GetNapiCallbackInfoAndThis(env, info));
193 napi_value defaultHeight = nullptr;
194 napi_create_double(env, 0.0, &defaultHeight);
195 return (me != nullptr && !me->isDetached_) ? me->OnGetHeight(env) : defaultHeight;
196 }
197
JsSetWidth(napi_env env,napi_callback_info info)198 napi_value JSOffscreenCanvas::JsSetWidth(napi_env env, napi_callback_info info)
199 {
200 JSOffscreenCanvas* me = static_cast<JSOffscreenCanvas*>(GetNapiCallbackInfoAndThis(env, info));
201 return (me != nullptr && !me->isDetached_) ? me->OnSetWidth(env, info) : nullptr;
202 }
203
JsSetHeight(napi_env env,napi_callback_info info)204 napi_value JSOffscreenCanvas::JsSetHeight(napi_env env, napi_callback_info info)
205 {
206 JSOffscreenCanvas* me = static_cast<JSOffscreenCanvas*>(GetNapiCallbackInfoAndThis(env, info));
207 return (me != nullptr && !me->isDetached_) ? me->OnSetHeight(env, info) : nullptr;
208 }
JsTransferToImageBitmap(napi_env env,napi_callback_info info)209 napi_value JSOffscreenCanvas::JsTransferToImageBitmap(napi_env env, napi_callback_info info)
210 {
211 JSOffscreenCanvas* me = static_cast<JSOffscreenCanvas*>(GetNapiCallbackInfoAndThis(env, info));
212 if (me != nullptr && me->isDetached_) {
213 JSException::Throw("%s", "Failed to execute 'transferToImageBitmap' on 'OffscreenCanvas': Cannot transfer an "
214 "ImageBitmap from a detached OffscreenCanvas");
215 return nullptr;
216 }
217 napi_value defaultImage = nullptr;
218 napi_create_object(env, &defaultImage);
219 return (me != nullptr) ? me->onTransferToImageBitmap(env) : defaultImage;
220 }
221
JsGetContext(napi_env env,napi_callback_info info)222 napi_value JSOffscreenCanvas::JsGetContext(napi_env env, napi_callback_info info)
223 {
224 JSOffscreenCanvas* me = static_cast<JSOffscreenCanvas*>(GetNapiCallbackInfoAndThis(env, info));
225 if (me != nullptr && me->isDetached_) {
226 JSException::Throw(
227 "%s", "Failed to execute 'getContext' on 'OffscreenCanvas': OffscreenCanvas object is detached");
228 return nullptr;
229 }
230 napi_value defaultContext = nullptr;
231 napi_create_object(env, &defaultContext);
232 return (me != nullptr) ? me->onGetContext(env, info) : defaultContext;
233 }
234
OnGetWidth(napi_env env)235 napi_value JSOffscreenCanvas::OnGetWidth(napi_env env)
236 {
237 double fWidth = GetWidth();
238 double density = GetDensity();
239 fWidth /= density;
240 napi_value width = nullptr;
241 napi_create_double(env, fWidth, &width);
242 return width;
243 }
244
OnGetHeight(napi_env env)245 napi_value JSOffscreenCanvas::OnGetHeight(napi_env env)
246 {
247 double fHeight = GetHeight();
248 double density = GetDensity();
249 fHeight /= density;
250 napi_value height = nullptr;
251 napi_create_double(env, fHeight, &height);
252 return height;
253 }
254
OnSetWidth(napi_env env,napi_callback_info info)255 napi_value JSOffscreenCanvas::OnSetWidth(napi_env env, napi_callback_info info)
256 {
257 CHECK_NULL_RETURN(offscreenCanvasPattern_, nullptr);
258 size_t argc = 1;
259 napi_value argv[1] = { nullptr };
260 NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr));
261 if (argc != ARGS_COUNT_ONE) {
262 LOGD("Invalid args.");
263 return nullptr;
264 }
265 double width = 0.0;
266 NAPI_CALL(env, napi_get_value_double(env, argv[0], &width));
267 double density = GetDensity();
268 width *= density;
269
270 if (width_ != width) {
271 width_ = width;
272 offscreenCanvasPattern_->UpdateSize(width_, height_);
273 if (offscreenCanvasContext_ != nullptr) {
274 offscreenCanvasContext_->SetWidth(width_);
275 }
276 }
277 return nullptr;
278 }
279
OnSetHeight(napi_env env,napi_callback_info info)280 napi_value JSOffscreenCanvas::OnSetHeight(napi_env env, napi_callback_info info)
281 {
282 CHECK_NULL_RETURN(offscreenCanvasPattern_, nullptr);
283 size_t argc = 1;
284 napi_value argv[1] = { nullptr };
285 NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr));
286 if (argc != ARGS_COUNT_ONE) {
287 LOGD("Invalid args.");
288 return nullptr;
289 }
290 double height = 0.0;
291 NAPI_CALL(env, napi_get_value_double(env, argv[0], &height));
292 double density = GetDensity();
293 height *= density;
294
295 if (height_ != height) {
296 height_ = height;
297 offscreenCanvasPattern_->UpdateSize(width_, height_);
298 if (offscreenCanvasContext_ != nullptr) {
299 offscreenCanvasContext_->SetHeight(height_);
300 }
301 }
302 return nullptr;
303 }
304
onTransferToImageBitmap(napi_env env)305 napi_value JSOffscreenCanvas::onTransferToImageBitmap(napi_env env)
306 {
307 if (offscreenCanvasPattern_ == nullptr || offscreenCanvasContext_ == nullptr) {
308 return nullptr;
309 }
310 napi_value renderImage = nullptr;
311 napi_create_object(env, &renderImage);
312 auto pixelMap = offscreenCanvasPattern_->TransferToImageBitmap();
313 if (!JSRenderImage::CreateJSRenderImage(env, pixelMap, renderImage)) {
314 return nullptr;
315 }
316 void* nativeObj = nullptr;
317 NAPI_CALL(env, napi_unwrap(env, renderImage, &nativeObj));
318 auto jsImage = (JSRenderImage*)nativeObj;
319 CHECK_NULL_RETURN(jsImage, nullptr);
320 #ifndef PIXEL_MAP_SUPPORTED
321 auto imageData = offscreenCanvasPattern_->GetImageData(0, 0, width_, height_);
322 if (imageData == nullptr) {
323 return nullptr;
324 }
325 jsImage->SetImageData(std::make_shared<Ace::ImageData>(*imageData));
326 #endif
327 jsImage->SetUnit(GetUnit());
328 jsImage->SetWidth(GetWidth());
329 jsImage->SetHeight(GetHeight());
330 return renderImage;
331 }
332
onGetContext(napi_env env,napi_callback_info info)333 napi_value JSOffscreenCanvas::onGetContext(napi_env env, napi_callback_info info)
334 {
335 isGetContext_ = true;
336 size_t argc = 2;
337 napi_value argv[2] = { nullptr };
338 napi_value offscreenCanvas = nullptr;
339 NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, &offscreenCanvas, nullptr));
340 if (argc < ARGS_COUNT_ONE || argc > ARGS_COUNT_TWO) {
341 LOGD("Invalid args.");
342 return nullptr;
343 }
344 if (argv[0] == nullptr) {
345 return nullptr;
346 }
347 if (!Container::IsCurrentUseNewPipeline()) {
348 return nullptr;
349 }
350
351 size_t textLen = 0;
352 std::string contextType = "";
353 napi_get_value_string_utf8(env, argv[0], nullptr, 0, &textLen);
354 std::unique_ptr<char[]> text = std::make_unique<char[]>(textLen + 1);
355 napi_get_value_string_utf8(env, argv[0], text.get(), textLen + 1, &textLen);
356 contextType = text.get();
357 if (contextType == "2d") {
358 contextType_ = ContextType::CONTEXT_2D;
359 auto jsInfo = reinterpret_cast<panda::JsiRuntimeCallInfo*>(info);
360 auto* vm = jsInfo->GetVM();
361 CHECK_NULL_RETURN(vm, nullptr);
362 napi_value contextObj = CreateContext2d(env, GetWidth(), GetHeight(), vm);
363 if (contextObj == nullptr) {
364 return nullptr;
365 }
366 SetAntiAlias(argv[1]);
367 offscreenCanvasContext_->SetUnit(GetUnit());
368 offscreenCanvasContext_->SetDensity();
369 return contextObj;
370 }
371 return nullptr;
372 }
373
SetAntiAlias(napi_value argv)374 void JSOffscreenCanvas::SetAntiAlias(napi_value argv)
375 {
376 if (argv != nullptr) {
377 panda::Local<panda::ObjectRef> localValue = NapiValueToLocalValue(argv);
378 JSObject jsObject(localValue);
379 offscreenCanvasSettings_ = jsObject.Unwrap<JSRenderingContextSettings>();
380 if (offscreenCanvasSettings_ != nullptr && offscreenCanvasContext_ != nullptr) {
381 bool anti = offscreenCanvasSettings_->GetAntialias();
382 offscreenCanvasContext_->SetAnti(anti);
383 offscreenCanvasContext_->SetAntiAlias();
384 }
385 }
386 }
387
CreateContext2d(napi_env env,double width,double height,const EcmaVM * vm)388 napi_value JSOffscreenCanvas::CreateContext2d(napi_env env, double width, double height, const EcmaVM* vm)
389 {
390 napi_value global = nullptr;
391 NAPI_CALL(env, napi_get_global(env, &global));
392 napi_value constructor = nullptr;
393 NAPI_CALL(env, napi_get_named_property(env, global, "OffscreenCanvasRenderingContext2D", &constructor));
394
395 napi_value thisVal = nullptr;
396 napi_create_object(env, &thisVal);
397 NAPI_CALL(env, napi_new_instance(env, constructor, 0, nullptr, &thisVal));
398 if (offscreenCanvasPattern_ == nullptr) {
399 return thisVal;
400 }
401 JSObject jsObject(vm, NapiValueToLocalValue(thisVal)->ToEcmaObject(vm));
402 offscreenCanvasContext_ = Referenced::Claim(jsObject.Unwrap<JSOffscreenRenderingContext>());
403 offscreenCanvasContext_->SetOffscreenPattern(offscreenCanvasPattern_);
404 offscreenCanvasContext_->AddOffscreenCanvasPattern(offscreenCanvasPattern_);
405 offscreenCanvasContext_->SetWidth(width_);
406 offscreenCanvasContext_->SetHeight(height_);
407 return thisVal;
408 }
409 } // namespace OHOS::Ace::Framework
410