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