• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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