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 (std::nothrow) 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 (std::nothrow) 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 = 0;
259 napi_value argv = nullptr;
260 napi_get_cb_info(env, info, &argc, nullptr, nullptr, nullptr);
261 if (argc != ARGS_COUNT_ONE) {
262 LOGD("Invalid args.");
263 return nullptr;
264 }
265 napi_get_cb_info(env, info, &argc, &argv, nullptr, nullptr);
266 if (argv == nullptr) {
267 return nullptr;
268 }
269 double width = 0.0;
270 if (napi_get_value_double(env, argv, &width) == napi_ok) {
271 double density = GetDensity();
272 width *= density;
273 } else {
274 return nullptr;
275 }
276
277 if (width_ != width) {
278 width_ = width;
279 offscreenCanvasPattern_->UpdateSize(width_, height_);
280 if (offscreenCanvasContext_ != nullptr) {
281 offscreenCanvasContext_->SetWidth(width_);
282 }
283 }
284 return nullptr;
285 }
286
OnSetHeight(napi_env env,napi_callback_info info)287 napi_value JSOffscreenCanvas::OnSetHeight(napi_env env, napi_callback_info info)
288 {
289 CHECK_NULL_RETURN(offscreenCanvasPattern_, nullptr);
290 size_t argc = 0;
291 napi_value argv = nullptr;
292 napi_get_cb_info(env, info, &argc, nullptr, nullptr, nullptr);
293 if (argc != ARGS_COUNT_ONE) {
294 LOGD("Invalid args.");
295 return nullptr;
296 }
297 napi_get_cb_info(env, info, &argc, &argv, nullptr, nullptr);
298 if (argv == nullptr) {
299 return nullptr;
300 }
301 double height = 0.0;
302 if (napi_get_value_double(env, argv, &height) == napi_ok) {
303 double density = GetDensity();
304 height *= density;
305 } else {
306 return nullptr;
307 }
308
309 if (height_ != height) {
310 height_ = height;
311 offscreenCanvasPattern_->UpdateSize(width_, height_);
312 if (offscreenCanvasContext_ != nullptr) {
313 offscreenCanvasContext_->SetHeight(height_);
314 }
315 }
316 return nullptr;
317 }
318
onTransferToImageBitmap(napi_env env)319 napi_value JSOffscreenCanvas::onTransferToImageBitmap(napi_env env)
320 {
321 if (offscreenCanvasPattern_ == nullptr || offscreenCanvasContext_ == nullptr) {
322 return nullptr;
323 }
324 napi_value renderImage = nullptr;
325 napi_create_object(env, &renderImage);
326 auto pixelMap = offscreenCanvasPattern_->TransferToImageBitmap();
327 if (!JSRenderImage::CreateJSRenderImage(env, pixelMap, renderImage)) {
328 return nullptr;
329 }
330 void* nativeObj = nullptr;
331 napi_status status = napi_unwrap(env, renderImage, &nativeObj);
332 if (status != napi_ok) {
333 return nullptr;
334 }
335 auto jsImage = (JSRenderImage*)nativeObj;
336 CHECK_NULL_RETURN(jsImage, nullptr);
337 #ifndef PIXEL_MAP_SUPPORTED
338 auto imageData = offscreenCanvasPattern_->GetImageData(0, 0, width_, height_);
339 if (imageData == nullptr) {
340 return nullptr;
341 }
342 jsImage->SetImageData(std::make_shared<Ace::ImageData>(*imageData));
343 #endif
344 jsImage->SetUnit(GetUnit());
345 jsImage->SetWidth(GetWidth());
346 jsImage->SetHeight(GetHeight());
347 return renderImage;
348 }
349
onGetContext(napi_env env,napi_callback_info info)350 napi_value JSOffscreenCanvas::onGetContext(napi_env env, napi_callback_info info)
351 {
352 isGetContext_ = true;
353 size_t argc = 2;
354 napi_value argv[2] = { nullptr };
355 napi_value offscreenCanvas = nullptr;
356 NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, &offscreenCanvas, nullptr));
357 if (argc < ARGS_COUNT_ONE || argc > ARGS_COUNT_TWO) {
358 LOGD("Invalid args.");
359 return nullptr;
360 }
361 if (argv[0] == nullptr) {
362 return nullptr;
363 }
364 if (!Container::IsCurrentUseNewPipeline()) {
365 return nullptr;
366 }
367
368 size_t textLen = 0;
369 std::string contextType = "";
370 napi_get_value_string_utf8(env, argv[0], nullptr, 0, &textLen);
371 std::unique_ptr<char[]> text = std::make_unique<char[]>(textLen + 1);
372 napi_get_value_string_utf8(env, argv[0], text.get(), textLen + 1, &textLen);
373 contextType = text.get();
374 if (contextType == "2d") {
375 contextType_ = ContextType::CONTEXT_2D;
376 auto jsInfo = reinterpret_cast<panda::JsiRuntimeCallInfo*>(info);
377 auto* vm = jsInfo->GetVM();
378 CHECK_NULL_RETURN(vm, nullptr);
379 napi_value contextObj = CreateContext2d(env, GetWidth(), GetHeight(), vm);
380 if (contextObj == nullptr) {
381 return nullptr;
382 }
383 SetAntiAlias(argv[1]);
384 offscreenCanvasContext_->SetUnit(GetUnit());
385 offscreenCanvasContext_->SetDensity();
386 return contextObj;
387 }
388 return nullptr;
389 }
390
SetAntiAlias(napi_value argv)391 void JSOffscreenCanvas::SetAntiAlias(napi_value argv)
392 {
393 if (argv != nullptr) {
394 panda::Local<panda::ObjectRef> localValue = NapiValueToLocalValue(argv);
395 JSObject jsObject(localValue);
396 offscreenCanvasSettings_ = jsObject.Unwrap<JSRenderingContextSettings>();
397 if (offscreenCanvasSettings_ != nullptr && offscreenCanvasContext_ != nullptr) {
398 bool anti = offscreenCanvasSettings_->GetAntialias();
399 offscreenCanvasContext_->SetAnti(anti);
400 offscreenCanvasContext_->SetAntiAlias();
401 }
402 }
403 }
404
CreateContext2d(napi_env env,double width,double height,const EcmaVM * vm)405 napi_value JSOffscreenCanvas::CreateContext2d(napi_env env, double width, double height, const EcmaVM* vm)
406 {
407 napi_value global = nullptr;
408 napi_status status = napi_get_global(env, &global);
409 if (status != napi_ok) {
410 return nullptr;
411 }
412 napi_value constructor = nullptr;
413 status = napi_get_named_property(env, global, "OffscreenCanvasRenderingContext2D", &constructor);
414 if (status != napi_ok) {
415 return nullptr;
416 }
417
418 napi_value thisVal = nullptr;
419 napi_create_object(env, &thisVal);
420 status = napi_new_instance(env, constructor, 0, nullptr, &thisVal);
421 if (status != napi_ok) {
422 return nullptr;
423 }
424 if (offscreenCanvasPattern_ == nullptr) {
425 return thisVal;
426 }
427 JSObject jsObject(vm, NapiValueToLocalValue(thisVal)->ToEcmaObject(vm));
428 offscreenCanvasContext_ = Referenced::Claim(jsObject.Unwrap<JSOffscreenRenderingContext>());
429 offscreenCanvasContext_->SetOffscreenPattern(offscreenCanvasPattern_);
430 offscreenCanvasContext_->AddOffscreenCanvasPattern(offscreenCanvasPattern_);
431 offscreenCanvasContext_->SetWidth(width_);
432 offscreenCanvasContext_->SetHeight(height_);
433 return thisVal;
434 }
435 } // namespace OHOS::Ace::Framework
436