• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 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 "skia_framework.h"
17 
18 #include <chrono>
19 #include <cmath>
20 #include <cstring>
21 #include <functional>
22 #include <iomanip>
23 #include <iostream>
24 #include <sstream>
25 
26 #include <include/core/SkBitmap.h>
27 #include <include/core/SkCanvas.h>
28 #include <include/core/SkFontMetrics.h>
29 #include <include/core/SkFontMgr.h>
30 #include <include/core/SkPath.h>
31 #include <include/core/SkSurface.h>
32 #include <include/core/SkTextBlob.h>
33 
34 #include <hitrace_meter.h>
35 #include <input_manager.h>
36 #include <securec.h>
37 #include <transaction/rs_interfaces.h>
38 #include <ui/rs_display_node.h>
39 #include <ui/rs_surface_node.h>
40 #include <utils/log.h>
41 
42 using namespace OHOS;
43 using namespace OHOS::Rosen;
44 using namespace std::chrono_literals;
45 using MMIPE = MMI::PointerEvent;
46 
47 #define TRACE_BEGIN(str) StartTrace(HITRACE_TAG_GRAPHIC_AGP, str)
48 #define TRACE_END() FinishTrace(HITRACE_TAG_GRAPHIC_AGP)
49 #define TRACE_COUNT(str, val) CountTrace(HITRACE_TAG_GRAPHIC_AGP, str, val)
50 #define TRACE_SCOPE(str) HitraceScoped trace(HITRACE_TAG_GRAPHIC_AGP, str)
51 
52 struct RSData {
53     std::shared_ptr<AppExecFwk::EventRunner> runner = nullptr;
54     std::shared_ptr<VSyncReceiver> receiver = nullptr;
55     std::shared_ptr<RSSurfaceNode> sNode = nullptr;
56     std::shared_ptr<RSDisplayNode> dNode = nullptr;
57     std::function<void(void)> onVsync = nullptr;
58     int64_t lastTime = 0;
59 
60     void RequestVsync();
61 };
62 
RequestVsync()63 void RSData::RequestVsync()
64 {
65     TRACE_COUNT("RequestVsync", 1);
66     auto func = [this](int64_t ts, void *data) {
67         TRACE_COUNT("RequestVsync", 0);
68         (void) ts;
69         (void) data;
70         if (this->onVsync != nullptr) {
71             this->onVsync();
72         }
73     };
74     receiver->RequestNextVSync({.callback_ = func});
75 }
76 
77 class PointerFilter : public OHOS::MMI::IInputEventFilter {
78 public:
PointerFilter(SkiaFramework * skiaFramework)79     PointerFilter(SkiaFramework* skiaFramework) : sf(skiaFramework) {}
80 
OnInputEvent(std::shared_ptr<OHOS::MMI::KeyEvent> keyEvent) const81     bool OnInputEvent(std::shared_ptr<OHOS::MMI::KeyEvent> keyEvent) const override {return false;}
82     bool OnInputEvent(std::shared_ptr<OHOS::MMI::PointerEvent> pointerEvent) const override;
83 
84 private:
85     void ProcessSinglePointerEvent(const std::shared_ptr<OHOS::MMI::PointerEvent> pointerEvent) const;
86     void ProcessPointerEvents(const std::shared_ptr<OHOS::MMI::PointerEvent> pointerEvent) const;
87     void ProcessSingleAction(const uint32_t action, int x, int y) const;
88 
89     SkiaFramework* sf = nullptr;
90 };
91 
OnInputEvent(std::shared_ptr<OHOS::MMI::PointerEvent> pointerEvent) const92 bool PointerFilter::OnInputEvent(std::shared_ptr<OHOS::MMI::PointerEvent> pointerEvent) const
93 {
94     TRACE_SCOPE("HandleInput");
95     std::lock_guard<std::mutex> lock(sf->propsMutex_);
96 
97     TRACE_BEGIN("HandleInputLocked");
98     auto &rsdata = *reinterpret_cast<struct RSData *>(sf->data_);
99     const auto &ids = pointerEvent->GetPointerIds();
100 
101     if (ids.size() == 1) {
102         ProcessSinglePointerEvent(pointerEvent);
103     }
104 
105     if (ids.size() >= 2) {  // 2 means not single pointer event
106         ProcessPointerEvents(pointerEvent);
107     }
108 
109     rsdata.RequestVsync();
110     pointerEvent->MarkProcessed();
111     TRACE_END();
112     return true;
113 }
114 
ProcessSinglePointerEvent(const std::shared_ptr<OHOS::MMI::PointerEvent> pointerEvent) const115 void PointerFilter::ProcessSinglePointerEvent(const std::shared_ptr<OHOS::MMI::PointerEvent> pointerEvent) const
116 {
117     const auto &ids = pointerEvent->GetPointerIds();
118     const auto &action = pointerEvent->GetPointerAction();
119     MMI::PointerEvent::PointerItem firstPointer = {};
120     pointerEvent->GetPointerItem(ids[0], firstPointer);
121     const auto &x = firstPointer.GetDisplayX();
122     const auto &y = firstPointer.GetDisplayY();
123 
124     if (action == MMIPE::POINTER_ACTION_DOWN) {
125         auto &rsdata = *reinterpret_cast<struct RSData*>(sf->data_);
126         // 400000 means 400 * 1000 is the discriminant time for consecutive clicks
127         if (pointerEvent->GetActionTime() - rsdata.lastTime <= 400000) {
128             sf->right_ = false;
129             sf->left_ = true;
130         }
131 
132         if (sf->right_ == false && sf->left_ == false) {
133             sf->right_ = true;
134         }
135         rsdata.lastTime = pointerEvent->GetActionTime();
136     }
137     ProcessSingleAction(action, x, y);
138 }
139 
ProcessSingleAction(const uint32_t action,int x,int y) const140 void PointerFilter::ProcessSingleAction(const uint32_t action, int x, int y) const
141 {
142     if (sf->left_ && action == MMIPE::POINTER_ACTION_DOWN) {
143         sf->dirty_ = true;
144         sf->clickx_ = x;
145         sf->clicky_ = y;
146     }
147     if (sf->left_ && action == MMIPE::POINTER_ACTION_MOVE) {
148         sf->dirty_ = true;
149         sf->x_ = x;
150         sf->y_ = y;
151     }
152     if (sf->left_ && action == MMIPE::POINTER_ACTION_UP) {
153         sf->dirty_ = true;
154         sf->left_ = false;
155     }
156 
157     if (sf->right_ && action == MMIPE::POINTER_ACTION_DOWN) {
158         sf->dirty_ = true;
159         sf->downRX_ = x;
160         sf->downRY_ = y;
161     }
162     if (sf->right_ && action == MMIPE::POINTER_ACTION_MOVE) {
163         sf->dirty_ = true;
164         sf->x_ = x;
165         sf->y_ = y;
166 
167         sf->mat_ = SkMatrix::Translate(-sf->diffx_, -sf->diffy_).preConcat(sf->mat_);
168         sf->diffx_ = sf->x_ - sf->downRX_;
169         sf->diffy_ = sf->y_ - sf->downRY_;
170         sf->mat_ = SkMatrix::Translate(sf->diffx_, sf->diffy_).preConcat(sf->mat_);
171         sf->UpdateInvertMatrix();
172     }
173     if (sf->right_ && action == MMIPE::POINTER_ACTION_UP) {
174         sf->dirty_ = true;
175         sf->right_ = false;
176         sf->mat_ = SkMatrix::Translate(-sf->diffx_, -sf->diffy_).preConcat(sf->mat_);
177         sf->mat_ = SkMatrix::Translate(x - sf->downRX_, y - sf->downRY_).preConcat(sf->mat_);
178         sf->UpdateInvertMatrix();
179         sf->diffx_ = sf->diffy_ = 0;
180     }
181 }
182 
ProcessPointerEvents(const std::shared_ptr<OHOS::MMI::PointerEvent> pointerEvent) const183 void PointerFilter::ProcessPointerEvents(const std::shared_ptr<OHOS::MMI::PointerEvent> pointerEvent) const
184 {
185     const auto &ids = pointerEvent->GetPointerIds();
186     const auto &action = pointerEvent->GetPointerAction();
187     MMI::PointerEvent::PointerItem firstPointer = {};
188     MMI::PointerEvent::PointerItem secondPointer = {};
189     sf->right_ = false;
190     sf->left_ = false;
191 
192     pointerEvent->GetPointerItem(ids[0], firstPointer);
193     pointerEvent->GetPointerItem(ids[1], secondPointer);
194     const auto &x1 = firstPointer.GetDisplayX();
195     const auto &y1 = firstPointer.GetDisplayY();
196     const auto &x2 = secondPointer.GetDisplayX();
197     const auto &y2 = secondPointer.GetDisplayY();
198 
199     if (action == MMIPE::POINTER_ACTION_DOWN) {
200         // 2 is to compute the middle position
201         sf->scalex_ = (x1 + x2) / 2;
202         sf->scaley_ = (y1 + y2) / 2;    // 2 means divisor, get the middle position of Y axle
203         sf->scalediff_ = sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
204         sf->scaleMat_ = sf->mat_;
205     }
206     if (action == MMIPE::POINTER_ACTION_MOVE || action == MMIPE::POINTER_ACTION_UP) {
207         sf->dirty_ = true;
208         auto scalediff = sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
209         auto point = sf->invmat_.mapXY(sf->scalex_, sf->scaley_);
210         sf->mat_ = sf->scaleMat_;
211         sf->mat_ = sf->mat_.preConcat(SkMatrix::Translate(+point.x(), +point.y()));
212         auto scale = (scalediff / sf->scalediff_.load()) == 0 ?
213             1 : scalediff / sf->scalediff_.load();
214         sf->mat_ = sf->mat_.preConcat(SkMatrix::Scale(scale, scale));
215         sf->mat_ = sf->mat_.preConcat(SkMatrix::Translate(-point.x(), -point.y()));
216         sf->UpdateInvertMatrix();
217     }
218 }
219 
SkiaFramework()220 SkiaFramework::SkiaFramework()
221 {
222     UpdateTraceLabel();
223     data_ = new struct RSData();
224 }
225 
~SkiaFramework()226 SkiaFramework::~SkiaFramework()
227 {
228     auto &rsdata = *reinterpret_cast<struct RSData *>(data_);
229     rsdata.dNode->RemoveChild(rsdata.sNode);
230     rsdata.sNode = nullptr;
231     rsdata.dNode = nullptr;
232 
233     if (auto tp = RSTransactionProxy::GetInstance(); tp != nullptr) {
234         tp->FlushImplicitTransaction();
235     }
236     delete reinterpret_cast<struct RSData *>(data_);
237 }
238 
SetDrawFunc(const DrawFunc & onDraw)239 void SkiaFramework::SetDrawFunc(const DrawFunc &onDraw)
240 {
241     onDraw_ = onDraw;
242 }
243 
SetResizeFunc(const ResizeFunc & onResize)244 void SkiaFramework::SetResizeFunc(const ResizeFunc &onResize)
245 {
246     onResize_ = onResize;
247 }
248 
SetGPUAllowance(bool allow)249 void SkiaFramework::SetGPUAllowance(bool allow)
250 {
251     allowGPU_ = allow;
252 }
253 
SetWindowWidth(int width)254 void SkiaFramework::SetWindowWidth(int width)
255 {
256     windowWidth_ = width;
257 }
258 
SetWindowHeight(int height)259 void SkiaFramework::SetWindowHeight(int height)
260 {
261     windowHeight_ = height;
262 }
263 
SetWindowScale(double scale)264 void SkiaFramework::SetWindowScale(double scale)
265 {
266     windowScale_ = scale;
267 }
268 
SetWindowTitle(const std::string & title)269 void SkiaFramework::SetWindowTitle(const std::string &title)
270 {
271     windowTitle_ = title;
272 }
273 
GetWindowWidth() const274 int SkiaFramework::GetWindowWidth() const
275 {
276     return windowWidth_;
277 }
278 
GetWindowHeight() const279 int SkiaFramework::GetWindowHeight() const
280 {
281     return windowHeight_;
282 }
283 
GetWindowScale() const284 double SkiaFramework::GetWindowScale() const
285 {
286     return windowScale_;
287 }
288 
Run()289 void SkiaFramework::Run()
290 {
291     TRACE_BEGIN("Run");
292     auto &rsdata = *reinterpret_cast<struct RSData *>(data_);
293     // vsync
294     rsdata.runner = AppExecFwk::EventRunner::Create(false);
295     auto handler = std::make_shared<AppExecFwk::EventHandler>(rsdata.runner);
296     rsdata.receiver = RSInterfaces::GetInstance().CreateVSyncReceiver("vsync", handler);
297     rsdata.receiver->Init();
298 
299     // input
300     auto mmi = MMI::InputManager::GetInstance();
301     if (mmi == nullptr) {
302        return;
303     }
304 
305     auto filter = std::make_shared<PointerFilter>(this);
306     uint32_t touchTags = CapabilityToTags(OHOS::MMI::InputDeviceCapability::INPUT_DEV_CAP_MAX);
307     mmi->AddInputEventFilter(filter, 220, touchTags); // the priority is 220
308     rsdata.sNode = RSSurfaceNode::Create({});
309     rsdata.dNode = RSDisplayNode::Create({});
310     if (rsdata.sNode == nullptr || rsdata.dNode == nullptr) {
311         return;
312     }
313     rsdata.sNode->SetBounds(0, 0, windowWidth_, windowHeight_);
314     rsdata.sNode->SetAlpha(1); // 1 is opaque
315     rsdata.dNode->AddChild(rsdata.sNode, -1); //child index is -1
316     if (auto tp = RSTransactionProxy::GetInstance(); tp != nullptr) {
317         tp->FlushImplicitTransaction();
318     } else {
319         return;
320     }
321 
322     PrepareVsyncFunc();
323     mat_ = mat_.preConcat(SkMatrix::Scale(windowScale_, windowScale_));
324     rsdata.RequestVsync();
325     TRACE_END();
326     rsdata.runner->Run();
327 }
328 
PrepareVsyncFunc()329 void SkiaFramework::PrepareVsyncFunc()
330 {
331     auto &rsdata = *reinterpret_cast<struct RSData *>(data_);
332     rsdata.onVsync = [this]() {
333         if (dirty_ == false) {
334             return;
335         }
336         dirty_ = false;
337         TRACE_SCOPE("OnVsync");
338         auto &rsdata = *reinterpret_cast<struct RSData *>(data_);
339         sptr<Surface> surface = rsdata.sNode->GetSurface();
340         if (surface == nullptr) {
341             return;
342         }
343 
344         sptr<SurfaceBuffer> buffer;
345         int32_t releaseFence;
346         BufferRequestConfig config = {
347             .width = windowWidth_,
348             .height = windowHeight_,
349             .strideAlignment = 0x8,
350             .format = GRAPHIC_PIXEL_FMT_RGBA_8888,
351             .usage = BUFFER_USAGE_CPU_READ | BUFFER_USAGE_CPU_WRITE | BUFFER_USAGE_MEM_DMA,
352         };
353 
354         SurfaceError ret = surface->RequestBuffer(buffer, releaseFence, config);
355         LOGI("request buffer ret is: %{public}s", SurfaceErrorStr(ret).c_str());
356 
357         if (buffer == nullptr) {
358             LOGE("request buffer failed: buffer is nullptr");
359             return;
360         }
361         if (buffer->GetVirAddr() == nullptr) {
362             LOGE("get virAddr failed: virAddr is nullptr");
363             return;
364         }
365 
366         auto addr = static_cast<uint8_t *>(buffer->GetVirAddr());
367         LOGI("buffer width:%{public}d, height:%{public}d", buffer->GetWidth(), buffer->GetHeight());
368         SkBitmap bitmap;
369         ProcessBitmap(bitmap, buffer);
370         constexpr uint32_t stride = 4;
371         uint32_t addrSize = buffer->GetWidth() * buffer->GetHeight() * stride;
372         void* bitmapAddr = bitmap.getPixels();
373         if (auto res = memcpy_s(addr, addrSize, bitmapAddr, addrSize); res != EOK) {
374             LOGI("memcpy_s failed");
375         }
376 
377         BufferFlushConfig flushConfig = { .damage = { .w = buffer->GetWidth(), .h = buffer->GetHeight(), }, };
378         ret = surface->FlushBuffer(buffer, -1, flushConfig);
379         LOGI("flushBuffer ret is: %{public}s", SurfaceErrorStr(ret).c_str());
380     };
381 }
382 
ProcessBitmap(SkBitmap & bitmap,const sptr<SurfaceBuffer> buffer)383 void SkiaFramework::ProcessBitmap(SkBitmap &bitmap, const sptr<SurfaceBuffer> buffer)
384 {
385     auto imageInfo = SkImageInfo::Make(buffer->GetWidth(), buffer->GetHeight(), kRGBA_8888_SkColorType,
386         kOpaque_SkAlphaType);
387     bitmap.setInfo(imageInfo);
388     bitmap.allocPixels();
389 
390     SkCanvas canvas(bitmap);
391     canvas.resetMatrix();
392     canvas.save();
393 
394     canvas.clear(SK_ColorWHITE);
395     canvas.setMatrix(mat_);
396     DrawBefore(canvas);
397     if (onDraw_) {
398         TRACE_SCOPE("OnDraw");
399         onDraw_(canvas);
400     }
401 
402     canvas.restore();
403     DrawColorPicker(canvas, bitmap);
404     DrawAfter(canvas);
405 }
406 
UpdateInvertMatrix()407 void SkiaFramework::UpdateInvertMatrix()
408 {
409     if (auto ret = mat_.invert(&invmat_); ret == false) {
410         invmat_ = SkMatrix::I();
411     }
412 }
413 
DrawString(SkCanvas & canvas,const std::string & str,double x,double y)414 void SkiaFramework::DrawString(SkCanvas &canvas, const std::string &str, double x, double y)
415 {
416     SkPaint textPaint;
417     textPaint.setAntiAlias(true);
418     textPaint.setColor(0xff0066ff); // color is 0xff0066ff
419     textPaint.setStyle(SkPaint::kFill_Style);
420 
421     SkFont font;
422     font.setTypeface(SkTypeface::MakeFromFile("/system/fonts/HarmonyOS_Sans_SC_Black.ttf"));
423     font.setSize(16); // font size is 16
424     canvas.drawString(str.c_str(), x, y, font, textPaint);
425 }
426 
MeasureString(const std::string & str)427 SkPoint3 SkiaFramework::MeasureString(const std::string &str)
428 {
429     SkFont font;
430     font.setTypeface(SkTypeface::MakeFromFile("/system/fonts/HarmonyOS_Sans_SC_Black.ttf"));
431     font.setSize(16);   // font size is 16
432     auto width = font.measureText(str.data(), str.length(), SkTextEncoding::kUTF8);
433     SkFontMetrics metrics;
434     font.getMetrics(&metrics);
435     return {width, -metrics.fAscent + metrics.fDescent, -metrics.fAscent};
436 }
437 
DrawBefore(SkCanvas & canvas)438 void SkiaFramework::DrawBefore(SkCanvas &canvas)
439 {
440     TRACE_SCOPE("DrawBefore");
441     std::lock_guard<std::mutex> lock(propsMutex_);
442     SkPaint paint;
443     paint.setAntiAlias(true);
444     paint.setColor(0x09000000);
445     paint.setStyle(SkPaint::kStroke_Style);
446     paint.setStrokeWidth(1);
447     SkPaint paint2 = paint;
448     paint2.setColor(0x22000000);
449 
450     SkPaint textPaint;
451     textPaint.setAntiAlias(true);
452     textPaint.setColor(0xff00007f);
453 
454     SkFont font;
455     font.setTypeface(SkTypeface::MakeFromFile("/system/fonts/HarmonyOS_Sans_SC_Black.ttf"));
456     font.setSize(16);   // font size is 16
457     DrawPathAndString(canvas, font, paint, textPaint);
458 }
459 
DrawPathAndString(SkCanvas & canvas,SkFont & font,SkPaint & paint1,SkPaint & paint2)460 void SkiaFramework::DrawPathAndString(SkCanvas &canvas, SkFont &font, SkPaint &paint1, SkPaint &paint2)
461 {
462     auto rect = invmat_.mapRect(SkRect::MakeXYWH(0, 0, windowWidth_, windowHeight_));
463     auto left = static_cast<int>(rect.left()) / 100 * 100; // Make it an integer multiple of 100
464     auto right = static_cast<int>(rect.right());
465     auto top = static_cast<int>(rect.top()) / 100 * 100; // Make it an integer multiple of 100
466     auto bottom = static_cast<int>(rect.bottom());
467     SkPaint paint3 = paint1;
468     paint3.setColor(0x22000000);
469 
470     SkPath path;
471     // Draw grids, 20 * 20 grids and 100 * 100 grids
472     for (int i = left; i <= right; i += 20) {
473         path.moveTo(i, -1e9);
474         path.lineTo(i, 1e9);
475     }
476 
477     for (int i = top; i <= bottom; i += 20) {   // 20 means draw 20 * 20 grids
478         path.moveTo(-1e9, i);
479         path.lineTo(1e9, i);
480     }
481     canvas.drawPath(path, paint1);
482 
483     SkPath path2;
484     for (int i = left; i <= right; i += 100) {  // 100 means draw 100 * 100 grids
485         path2.moveTo(i, -1e9);
486         path2.lineTo(i, 1e9);
487 
488         std::stringstream ss;
489         ss << i;
490         canvas.drawString(ss.str().c_str(), i, font.getSize() + 0, font, paint2);
491     }
492 
493     for (int i = top; i <= bottom; i += 100) {  // 100 means draw 100 * 100 grids
494         path2.moveTo(-1e9, i);
495         path2.lineTo(1e9, i);
496 
497         std::stringstream ss;
498         ss << i;
499         canvas.drawString(ss.str().c_str(), 0, font.getSize() + i, font, paint2);
500     }
501     canvas.drawPath(path2, paint3);
502 }
503 
DrawColorPicker(SkCanvas & canvas,SkBitmap & bitmap)504 void SkiaFramework::DrawColorPicker(SkCanvas &canvas, SkBitmap &bitmap)
505 {
506     if (left_ == false) {
507         return;
508     }
509 
510     TRACE_SCOPE("DrawColorPicker");
511     std::lock_guard<std::mutex> lock(propsMutex_);
512     SkFont font;
513     font.setTypeface(SkTypeface::MakeFromFile("/system/fonts/HarmonyOS_Sans_SC_Black.ttf"));
514     font.setSize(24);   // font size is 24
515 
516     SkPaint paint;
517     paint.setAntiAlias(true);
518     paint.setStyle(SkPaint::kFill_Style);
519     SkPaint paint2;
520     paint2.setAntiAlias(true);
521     paint2.setColor(SK_ColorBLACK);
522     paint2.setStyle(paint2.kStroke_Style);
523 
524     auto color = bitmap.getColor(x_, y_);
525     paint.setColor(color);
526     std::stringstream ss;
527     // 6 is the output width
528     ss << std::hex << std::setfill('0') << std::setw(6) << color;
529     canvas.drawString(ss.str().c_str(), x_, y_, font, paint2);
530     canvas.drawString(ss.str().c_str(), x_, y_, font, paint);
531 }
532 
DrawAfter(SkCanvas & canvas)533 void SkiaFramework::DrawAfter(SkCanvas &canvas)
534 {
535     if (left_ == false) {
536         return;
537     }
538 
539     TRACE_SCOPE("DrawAfter");
540     std::lock_guard<std::mutex> lock(propsMutex_);
541     SkPaint paint;
542     paint.setAntiAlias(true);
543     paint.setColor(0x33000000);
544     paint.setStyle(SkPaint::kStroke_Style);
545     paint.setStrokeWidth(1);
546 
547     SkPaint textPaint;
548     textPaint.setAntiAlias(true);
549     textPaint.setColor(0xff0000ff);
550     textPaint.setStyle(SkPaint::kStroke_Style);
551     textPaint.setStrokeWidth(1);
552 
553     SkFont font;
554     font.setTypeface(SkTypeface::MakeFromFile("/system/fonts/HarmonyOS_Sans_SC_Black.ttf"));
555     font.setSize(16);   // font size is 16
556 
557     SkPath path;
558     path.moveTo(x_, 0);
559     path.lineTo(x_, 1e9);
560     path.moveTo(0, y_);
561     path.lineTo(1e9, y_);
562 
563     auto point = invmat_.mapXY(x_, y_);
564     std::stringstream ss;
565     ss << "(" << point.x() << ", " << point.y() << ")";
566     // 10 is the offset to to start drawing strings
567     canvas.drawString(ss.str().c_str(),
568                       x_ + 10,  // 10 is the offset to to start drawing strings
569                       font.getSize() + y_ + 10, // 10 is the offset to to start drawing strings
570                       font, textPaint);
571 
572     canvas.drawPath(path, paint);
573 }
574