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/js_offscreen_canvas.h"
17
18 #include "base/utils/utils.h"
19 #include "bridge/declarative_frontend/engine/bindings.h"
20 #include "bridge/declarative_frontend/jsview/js_canvas_gradient.h"
21 #include "bridge/declarative_frontend/jsview/js_canvas_pattern.h"
22 #include "bridge/declarative_frontend/jsview/js_matrix2d.h"
23 #include "bridge/declarative_frontend/jsview/js_render_image.h"
24 #include "napi/native_api.h"
25 #include "napi/native_node_api.h"
26
27 namespace OHOS::Ace::Framework {
28 constexpr int32_t ARGS_COUNT_ONE = 1;
29 constexpr int32_t ARGS_COUNT_TWO = 2;
30
DetachOffscreenCanvas(napi_env env,void * value,void * hint)31 void* DetachOffscreenCanvas(napi_env env, void* value, void* hint)
32 {
33 if (value == nullptr) {
34 LOGW("Invalid parameter.");
35 return nullptr;
36 }
37 JSOffscreenCanvas* workCanvas = (JSOffscreenCanvas*)value;
38 if (workCanvas->IsGetContext()) {
39 JSException::Throw(ERROR_CODE_PARAM_INVALID, "%s",
40 "An OffscreenCanvas could not be transferred because it had a rendering context.");
41 return nullptr;
42 }
43 if (workCanvas->IsDetached()) {
44 JSException::Throw(ERROR_CODE_PARAM_INVALID, "%s",
45 "An OffscreenCanvas could not be transferred because the object is detached.");
46 return nullptr;
47 }
48 workCanvas->SetDetachStatus(true);
49
50 auto result = new (std::nothrow) JSOffscreenCanvas();
51 result->SetWidth(workCanvas->GetWidth());
52 result->SetHeight(workCanvas->GetHeight());
53 return result;
54 }
55
AttachOffscreenCanvas(napi_env env,void * value,void *)56 napi_value AttachOffscreenCanvas(napi_env env, void* value, void*)
57 {
58 if (value == nullptr) {
59 LOGW("Invalid parameter.");
60 return nullptr;
61 }
62 JSOffscreenCanvas* workCanvas = (JSOffscreenCanvas*)value;
63 if (workCanvas == nullptr) {
64 LOGW("Invalid context.");
65 return nullptr;
66 }
67
68 napi_value offscreenCanvas = nullptr;
69 napi_create_object(env, &offscreenCanvas);
70 napi_property_descriptor desc[] = {
71 DECLARE_NAPI_GETTER_SETTER("width", JSOffscreenCanvas::JsGetWidth, JSOffscreenCanvas::JsSetWidth),
72 DECLARE_NAPI_GETTER_SETTER("height", JSOffscreenCanvas::JsGetHeight, JSOffscreenCanvas::JsSetHeight),
73 DECLARE_NAPI_FUNCTION("transferToImageBitmap", JSOffscreenCanvas::JsTransferToImageBitmap),
74 DECLARE_NAPI_FUNCTION("getContext", JSOffscreenCanvas::JsGetContext),
75 };
76 napi_define_properties(env, offscreenCanvas, sizeof(desc) / sizeof(*desc), desc);
77 napi_coerce_to_native_binding_object(
78 env, offscreenCanvas, DetachOffscreenCanvas, AttachOffscreenCanvas, value, nullptr);
79 napi_wrap(
80 env, offscreenCanvas, value,
81 [](napi_env env, void* data, void* hint) {
82 LOGD("Finalizer for offscreen canvas is called");
83 auto wrapper = reinterpret_cast<JSOffscreenCanvas*>(data);
84 delete wrapper;
85 wrapper = nullptr;
86 },
87 nullptr, nullptr);
88 return offscreenCanvas;
89 }
90
InitOffscreenCanvas(napi_env env)91 napi_value JSOffscreenCanvas::InitOffscreenCanvas(napi_env env)
92 {
93 napi_value object = nullptr;
94 napi_create_object(env, &object);
95
96 napi_property_descriptor desc[] = {
97 DECLARE_NAPI_GETTER_SETTER("width", JsGetWidth, JsSetWidth),
98 DECLARE_NAPI_GETTER_SETTER("height", JsGetHeight, JsSetHeight),
99 DECLARE_NAPI_FUNCTION("transferToImageBitmap", JsTransferToImageBitmap),
100 DECLARE_NAPI_FUNCTION("getContext", JsGetContext),
101 };
102 napi_status status = napi_define_class(
103 env, "OffscreenCanvas", NAPI_AUTO_LENGTH, Constructor, nullptr, sizeof(desc) / sizeof(*desc), desc, &object);
104 if (status != napi_ok) {
105 LOGW("Initialize offscreen canvas failed");
106 return nullptr;
107 }
108 return object;
109 }
110
JSBind(BindingTarget globalObj,void * nativeEngine)111 void JSOffscreenCanvas::JSBind(BindingTarget globalObj, void* nativeEngine)
112 {
113 if (!nativeEngine) {
114 return;
115 }
116 napi_env env = reinterpret_cast<napi_env>(nativeEngine);
117
118 napi_value jsGlobalObj = nullptr;
119 napi_get_global(env, &jsGlobalObj);
120
121 napi_value result = InitOffscreenCanvas(env);
122 napi_set_named_property(env, jsGlobalObj, "OffscreenCanvas", result);
123 }
124
Constructor(napi_env env,napi_callback_info info)125 napi_value JSOffscreenCanvas::Constructor(napi_env env, napi_callback_info info)
126 {
127 ContainerScope scope(Container::CurrentIdSafely());
128 size_t argc = 2;
129 napi_value thisVar = nullptr;
130 napi_value argv[2] = { nullptr };
131 NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, &thisVar, nullptr));
132 if (argc < ARGS_COUNT_TWO || argv[0] == nullptr || argv[1] == nullptr) {
133 LOGW("Invalid args.");
134 return nullptr;
135 }
136 double fWidth = 0.0;
137 double fHeight = 0.0;
138 auto workCanvas = new (std::nothrow) JSOffscreenCanvas();
139 auto context = PipelineBase::GetCurrentContext();
140 if (context != nullptr) {
141 workCanvas->instanceId_ = context->GetInstanceId();
142 }
143 if (napi_get_value_double(env, argv[0], &fWidth) == napi_ok) {
144 fWidth = PipelineBase::Vp2PxWithCurrentDensity(fWidth);
145 workCanvas->SetWidth(fWidth);
146 }
147 if (napi_get_value_double(env, argv[1], &fHeight) == napi_ok) {
148 fHeight = PipelineBase::Vp2PxWithCurrentDensity(fHeight);
149 workCanvas->SetHeight(fHeight);
150 }
151
152 napi_coerce_to_native_binding_object(
153 env, thisVar, DetachOffscreenCanvas, AttachOffscreenCanvas, workCanvas, nullptr);
154 napi_wrap(
155 env, thisVar, workCanvas,
156 [](napi_env env, void* data, void* hint) {
157 LOGD("Finalizer for offscreen canvas is called");
158 auto workCanvas = reinterpret_cast<JSOffscreenCanvas*>(data);
159 delete workCanvas;
160 workCanvas = nullptr;
161 },
162 nullptr, nullptr);
163 return thisVar;
164 }
165
JsGetWidth(napi_env env,napi_callback_info info)166 napi_value JSOffscreenCanvas::JsGetWidth(napi_env env, napi_callback_info info)
167 {
168 ContainerScope scope(Container::CurrentIdSafely());
169 JSOffscreenCanvas* me = static_cast<JSOffscreenCanvas*>(GetNapiCallbackInfoAndThis(env, info));
170 napi_value defaultWidth = nullptr;
171 napi_create_double(env, 0.0, &defaultWidth);
172 return (me != nullptr && !me->isDetached_) ? me->OnGetWidth(env) : defaultWidth;
173 }
174
JsGetHeight(napi_env env,napi_callback_info info)175 napi_value JSOffscreenCanvas::JsGetHeight(napi_env env, napi_callback_info info)
176 {
177 ContainerScope scope(Container::CurrentIdSafely());
178 JSOffscreenCanvas* me = static_cast<JSOffscreenCanvas*>(GetNapiCallbackInfoAndThis(env, info));
179 napi_value defaultHeight = nullptr;
180 napi_create_double(env, 0.0, &defaultHeight);
181 return (me != nullptr && !me->isDetached_) ? me->OnGetHeight(env) : defaultHeight;
182 }
183
JsSetWidth(napi_env env,napi_callback_info info)184 napi_value JSOffscreenCanvas::JsSetWidth(napi_env env, napi_callback_info info)
185 {
186 ContainerScope scope(Container::CurrentIdSafely());
187 JSOffscreenCanvas* me = static_cast<JSOffscreenCanvas*>(GetNapiCallbackInfoAndThis(env, info));
188 return (me != nullptr && !me->isDetached_) ? me->OnSetWidth(env, info) : nullptr;
189 }
190
JsSetHeight(napi_env env,napi_callback_info info)191 napi_value JSOffscreenCanvas::JsSetHeight(napi_env env, napi_callback_info info)
192 {
193 ContainerScope scope(Container::CurrentIdSafely());
194 JSOffscreenCanvas* me = static_cast<JSOffscreenCanvas*>(GetNapiCallbackInfoAndThis(env, info));
195 return (me != nullptr && !me->isDetached_) ? me->OnSetHeight(env, info) : nullptr;
196 }
JsTransferToImageBitmap(napi_env env,napi_callback_info info)197 napi_value JSOffscreenCanvas::JsTransferToImageBitmap(napi_env env, napi_callback_info info)
198 {
199 ContainerScope scope(Container::CurrentIdSafely());
200 JSOffscreenCanvas* me = static_cast<JSOffscreenCanvas*>(GetNapiCallbackInfoAndThis(env, info));
201 if (me->isDetached_) {
202 JSException::Throw("%s", "Failed to execute 'transferToImageBitmap' on 'OffscreenCanvas': Cannot transfer an "
203 "ImageBitmap from a detached OffscreenCanvas");
204 return nullptr;
205 }
206 napi_value defaultImage = nullptr;
207 napi_create_object(env, &defaultImage);
208 return (me != nullptr) ? me->onTransferToImageBitmap(env) : defaultImage;
209 }
210
JsGetContext(napi_env env,napi_callback_info info)211 napi_value JSOffscreenCanvas::JsGetContext(napi_env env, napi_callback_info info)
212 {
213 ContainerScope scope(Container::CurrentIdSafely());
214 JSOffscreenCanvas* me = static_cast<JSOffscreenCanvas*>(GetNapiCallbackInfoAndThis(env, info));
215 if (me->isDetached_) {
216 JSException::Throw(
217 "%s", "Failed to execute 'getContext' on 'OffscreenCanvas': OffscreenCanvas object is detached");
218 return nullptr;
219 }
220 napi_value defaultContext = nullptr;
221 napi_create_object(env, &defaultContext);
222 return (me != nullptr) ? me->onGetContext(env, info) : defaultContext;
223 }
224
OnGetWidth(napi_env env)225 napi_value JSOffscreenCanvas::OnGetWidth(napi_env env)
226 {
227 double fWidth = PipelineBase::Px2VpWithCurrentDensity(GetWidth());
228 napi_value width = nullptr;
229 napi_create_double(env, fWidth, &width);
230 return width;
231 }
232
OnGetHeight(napi_env env)233 napi_value JSOffscreenCanvas::OnGetHeight(napi_env env)
234 {
235 double fHeight = PipelineBase::Px2VpWithCurrentDensity(GetHeight());
236 napi_value height = nullptr;
237 napi_create_double(env, fHeight, &height);
238 return height;
239 }
240
OnSetWidth(napi_env env,napi_callback_info info)241 napi_value JSOffscreenCanvas::OnSetWidth(napi_env env, napi_callback_info info)
242 {
243 CHECK_NULL_RETURN(offscreenCanvasPattern_, nullptr);
244 size_t argc = 0;
245 napi_value argv = nullptr;
246 napi_get_cb_info(env, info, &argc, nullptr, nullptr, nullptr);
247 if (argc != ARGS_COUNT_ONE) {
248 LOGD("Invalid args.");
249 return nullptr;
250 }
251 napi_get_cb_info(env, info, &argc, &argv, nullptr, nullptr);
252 if (argv == nullptr) {
253 return nullptr;
254 }
255 double width = 0.0;
256 if (napi_get_value_double(env, argv, &width) == napi_ok) {
257 width = PipelineBase::Vp2PxWithCurrentDensity(width);
258 } else {
259 return nullptr;
260 }
261
262 if (width_ != width) {
263 width_ = width;
264 offscreenCanvasPattern_->UpdateSize(width_, height_);
265 }
266 return nullptr;
267 }
268
OnSetHeight(napi_env env,napi_callback_info info)269 napi_value JSOffscreenCanvas::OnSetHeight(napi_env env, napi_callback_info info)
270 {
271 CHECK_NULL_RETURN(offscreenCanvasPattern_, nullptr);
272 size_t argc = 0;
273 napi_value argv = nullptr;
274 napi_get_cb_info(env, info, &argc, nullptr, nullptr, nullptr);
275 if (argc != ARGS_COUNT_ONE) {
276 LOGD("Invalid args.");
277 return nullptr;
278 }
279 napi_get_cb_info(env, info, &argc, &argv, nullptr, nullptr);
280 if (argv == nullptr) {
281 return nullptr;
282 }
283 double height = 0.0;
284 if (napi_get_value_double(env, argv, &height) == napi_ok) {
285 height = PipelineBase::Vp2PxWithCurrentDensity(height);
286 } else {
287 return nullptr;
288 }
289
290 if (height_ != height) {
291 height_ = height;
292 offscreenCanvasPattern_->UpdateSize(width_, height_);
293 }
294 return nullptr;
295 }
296
onTransferToImageBitmap(napi_env env)297 napi_value JSOffscreenCanvas::onTransferToImageBitmap(napi_env env)
298 {
299 std::string type = "ImageBitmap";
300 if (offscreenCanvasContext_ == nullptr) {
301 return nullptr;
302 }
303 uint32_t id = offscreenCanvasContext_->GetId();
304 auto final_height = static_cast<uint32_t>(GetHeight());
305 auto final_width = static_cast<uint32_t>(GetWidth());
306 napi_value renderImage = nullptr;
307 napi_value jsType = nullptr;
308 napi_value jsId = nullptr;
309 napi_value jsHeight = nullptr;
310 napi_value jsWidth = nullptr;
311 napi_create_object(env, &renderImage);
312 napi_create_string_utf8(env, type.c_str(), type.length(), &jsType);
313 napi_create_uint32(env, id, &jsId);
314 napi_create_double(env, final_height, &jsHeight);
315 napi_create_double(env, final_width, &jsWidth);
316 napi_set_named_property(env, renderImage, "__type", jsType);
317 napi_set_named_property(env, renderImage, "__id", jsId);
318 napi_set_named_property(env, renderImage, "height", jsHeight);
319 napi_set_named_property(env, renderImage, "width", jsWidth);
320 return renderImage;
321 }
322
onGetContext(napi_env env,napi_callback_info info)323 napi_value JSOffscreenCanvas::onGetContext(napi_env env, napi_callback_info info)
324 {
325 isGetContext_ = true;
326 size_t argc = 2;
327 napi_value argv[2] = { nullptr };
328 napi_value offscreenCanvas = nullptr;
329 NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, &offscreenCanvas, nullptr));
330 if (argc < ARGS_COUNT_ONE || argc > ARGS_COUNT_TWO) {
331 LOGD("Invalid args.");
332 return nullptr;
333 }
334 if (argv[0] == nullptr) {
335 return nullptr;
336 }
337 if (!Container::IsCurrentUseNewPipeline()) {
338 return nullptr;
339 }
340
341 size_t textLen = 0;
342 std::string contextType = "";
343 napi_get_value_string_utf8(env, argv[0], nullptr, 0, &textLen);
344 std::unique_ptr<char[]> text = std::make_unique<char[]>(textLen + 1);
345 napi_get_value_string_utf8(env, argv[0], text.get(), textLen + 1, &textLen);
346 contextType = text.get();
347 if (contextType == "2d") {
348 contextType_ = ContextType::CONTEXT_2D;
349 napi_value contextObj = CreateContext2d(env, GetWidth(), GetHeight());
350 if (contextObj == nullptr) {
351 return nullptr;
352 }
353 napi_value isSucceed = nullptr;
354 if (napi_get_named_property(env, contextObj, "__isSucceed", &isSucceed) == napi_ok) {
355 bool value = true;
356 napi_get_value_bool(env, isSucceed, &value);
357 if (!value) {
358 return nullptr;
359 }
360 } else {
361 return nullptr;
362 }
363 if (argv[1] != nullptr) {
364 panda::Local<panda::ObjectRef> localValue = NapiValueToLocalValue(argv[1]);
365 JSObject jsObject(localValue);
366 offscreenCanvasSettings_ = jsObject.Unwrap<JSRenderingContextSettings>();
367 if (offscreenCanvasSettings_ != nullptr && offscreenCanvasContext_ != nullptr) {
368 bool anti = offscreenCanvasSettings_->GetAntialias();
369 offscreenCanvasContext_->SetAnti(anti);
370 offscreenCanvasContext_->SetAntiAlias();
371 }
372 }
373 return contextObj;
374 }
375 return nullptr;
376 }
377
CreateContext2d(napi_env env,double width,double height)378 napi_value JSOffscreenCanvas::CreateContext2d(napi_env env, double width, double height)
379 {
380 napi_value global = nullptr;
381 napi_status status = napi_get_global(env, &global);
382 if (status != napi_ok) {
383 return nullptr;
384 }
385 napi_value constructor = nullptr;
386 status = napi_get_named_property(env, global, "OffscreenCanvasRenderingContext2D", &constructor);
387 if (status != napi_ok) {
388 return nullptr;
389 }
390
391 napi_value thisVal = nullptr;
392 napi_create_object(env, &thisVal);
393 status = napi_new_instance(env, constructor, 0, nullptr, &thisVal);
394 if (status != napi_ok) {
395 return nullptr;
396 }
397 if (instanceId_ != -1) {
398 offscreenCanvasPattern_ = AceType::MakeRefPtr<NG::OffscreenCanvasPattern>(
399 GetContext(), static_cast<int32_t>(width), static_cast<int32_t>(height));
400 } else {
401 offscreenCanvasPattern_ = AceType::MakeRefPtr<NG::OffscreenCanvasPattern>(
402 static_cast<int32_t>(width), static_cast<int32_t>(height));
403 }
404 if (offscreenCanvasPattern_ == nullptr) {
405 return thisVal;
406 }
407 bool isSucceed = true;
408 napi_value value = nullptr;
409 if (!offscreenCanvasPattern_->IsSucceed()) {
410 isSucceed = false;
411 napi_create_int32(env, isSucceed, &value);
412 napi_set_named_property(env, thisVal, "__isSucceed", value);
413 return thisVal;
414 }
415 napi_create_int32(env, isSucceed, &value);
416 napi_set_named_property(env, thisVal, "__isSucceed", value);
417
418 panda::Local<panda::ObjectRef> localValue = NapiValueToLocalValue(thisVal);
419 JSObject jsObject(localValue);
420 offscreenCanvasContext_ = Referenced::Claim(jsObject.Unwrap<JSOffscreenRenderingContext>());
421 offscreenCanvasContext_->SetInstanceId(Container::CurrentId());
422 offscreenCanvasContext_->SetOffscreenPattern(offscreenCanvasPattern_);
423 offscreenCanvasContext_->AddOffscreenCanvasPattern(offscreenCanvasPattern_);
424 return thisVal;
425 }
426 } // namespace OHOS::Ace::Framework
427