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