1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "flutter/testing/mock_canvas.h"
6
7#include "flutter/fml/logging.h"
8#include "flutter/testing/display_list_testing.h"
9#include "third_party/skia/include/core/SkImageInfo.h"
10#include "third_party/skia/include/core/SkSerialProcs.h"
11#include "third_party/skia/include/core/SkSize.h"
12#include "third_party/skia/include/core/SkTextBlob.h"
13
14namespace flutter {
15namespace testing {
16
17constexpr SkISize kSize = SkISize::Make(w: 64, h: 64);
18
19MockCanvas::MockCanvas()
20 : tracker_(SkRect::Make(size: kSize), SkMatrix::I()), current_layer_(0) {}
21
22MockCanvas::MockCanvas(int width, int height)
23 : tracker_(SkRect::MakeIWH(w: width, h: height), SkMatrix::I()),
24 current_layer_(0) {}
25
26MockCanvas::~MockCanvas() {
27 EXPECT_EQ(current_layer_, 0);
28}
29
30SkISize MockCanvas::GetBaseLayerSize() const {
31 return tracker_.base_device_cull_rect().roundOut().size();
32}
33
34SkImageInfo MockCanvas::GetImageInfo() const {
35 SkISize size = GetBaseLayerSize();
36 return SkImageInfo::MakeUnknown(width: size.width(), height: size.height());
37}
38
39void MockCanvas::Save() {
40 draw_calls_.emplace_back(
41 args: DrawCall{.layer: current_layer_, .data: SaveData{.save_to_layer: current_layer_ + 1}});
42 tracker_.save();
43 current_layer_++; // Must go here; func params order of eval is undefined
44}
45
46void MockCanvas::SaveLayer(const SkRect* bounds,
47 const DlPaint* paint,
48 const DlImageFilter* backdrop) {
49 // saveLayer calls this prior to running, so we use it to track saveLayer
50 // calls
51 draw_calls_.emplace_back(args: DrawCall{
52 .layer: current_layer_,
53 .data: SaveLayerData{.save_bounds: bounds ? *bounds : SkRect(), .restore_paint: paint ? *paint : DlPaint(),
54 .backdrop_filter: backdrop ? backdrop->shared() : nullptr,
55 .save_to_layer: current_layer_ + 1}});
56 tracker_.save();
57 current_layer_++; // Must go here; func params order of eval is undefined
58}
59
60void MockCanvas::Restore() {
61 FML_DCHECK(current_layer_ > 0);
62
63 draw_calls_.emplace_back(
64 args: DrawCall{.layer: current_layer_, .data: RestoreData{.restore_to_layer: current_layer_ - 1}});
65 tracker_.restore();
66 current_layer_--; // Must go here; func params order of eval is undefined
67}
68
69// clang-format off
70
71// 2x3 2D affine subset of a 4x4 transform in row major order
72void MockCanvas::Transform2DAffine(SkScalar mxx, SkScalar mxy, SkScalar mxt,
73 SkScalar myx, SkScalar myy, SkScalar myt) {
74 Transform(matrix: SkMatrix::MakeAll(scaleX: mxx, skewX: mxy, transX: mxt, skewY: myx, scaleY: myy, transY: myt, pers0: 0, pers1: 0, pers2: 1));
75}
76
77// full 4x4 transform in row major order
78void MockCanvas::TransformFullPerspective(
79 SkScalar mxx, SkScalar mxy, SkScalar mxz, SkScalar mxt,
80 SkScalar myx, SkScalar myy, SkScalar myz, SkScalar myt,
81 SkScalar mzx, SkScalar mzy, SkScalar mzz, SkScalar mzt,
82 SkScalar mwx, SkScalar mwy, SkScalar mwz, SkScalar mwt) {
83 Transform(matrix44: SkM44(mxx, mxy, mxz, mxt,
84 myx, myy, myz, myt,
85 mzx, mzy, mzz, mzt,
86 mwx, mwy, mwz, mwt));
87}
88
89// clang-format on
90
91void MockCanvas::Transform(const SkMatrix* matrix) {
92 draw_calls_.emplace_back(
93 args: DrawCall{.layer: current_layer_, .data: ConcatMatrixData{.matrix: SkM44(*matrix)}});
94 tracker_.transform(matrix: *matrix);
95}
96
97void MockCanvas::Transform(const SkM44* matrix) {
98 draw_calls_.emplace_back(args: DrawCall{.layer: current_layer_, .data: ConcatMatrixData{.matrix: *matrix}});
99 tracker_.transform(m44: *matrix);
100}
101
102void MockCanvas::SetTransform(const SkMatrix* matrix) {
103 draw_calls_.emplace_back(
104 args: DrawCall{.layer: current_layer_, .data: SetMatrixData{.matrix: SkM44(*matrix)}});
105 tracker_.setTransform(*matrix);
106}
107
108void MockCanvas::SetTransform(const SkM44* matrix) {
109 draw_calls_.emplace_back(args: DrawCall{.layer: current_layer_, .data: SetMatrixData{.matrix: *matrix}});
110 tracker_.setTransform(*matrix);
111}
112
113void MockCanvas::TransformReset() {
114 draw_calls_.emplace_back(args: DrawCall{.layer: current_layer_, .data: SetMatrixData{.matrix: SkM44()}});
115 tracker_.setIdentity();
116}
117
118void MockCanvas::Translate(SkScalar x, SkScalar y) {
119 this->Transform(matrix44: SkM44::Translate(x, y));
120}
121
122void MockCanvas::Scale(SkScalar x, SkScalar y) {
123 this->Transform(matrix44: SkM44::Scale(x, y));
124}
125
126void MockCanvas::Rotate(SkScalar degrees) {
127 this->Transform(matrix: SkMatrix::RotateDeg(deg: degrees));
128}
129
130void MockCanvas::Skew(SkScalar sx, SkScalar sy) {
131 this->Transform(matrix: SkMatrix::Skew(kx: sx, ky: sy));
132}
133
134SkM44 MockCanvas::GetTransformFullPerspective() const {
135 return tracker_.matrix_4x4();
136}
137
138SkMatrix MockCanvas::GetTransform() const {
139 return tracker_.matrix_3x3();
140}
141
142void MockCanvas::DrawTextBlob(const sk_sp<SkTextBlob>& text,
143 SkScalar x,
144 SkScalar y,
145 const DlPaint& paint) {
146 // This duplicates existing logic in SkCanvas::onDrawPicture
147 // that should probably be split out so it doesn't need to be here as well.
148 // SkRect storage;
149 // if (paint.canComputeFastBounds()) {
150 // storage = text->bounds().makeOffset(x, y);
151 // SkRect tmp;
152 // if (this->quickReject(paint.computeFastBounds(storage, &tmp))) {
153 // return;
154 // }
155 // }
156
157 draw_calls_.emplace_back(args: DrawCall{
158 .layer: current_layer_, .data: DrawTextData{.serialized_text: text ? text->serialize(procs: SkSerialProcs{})
159 : SkData::MakeUninitialized(length: 0),
160 .paint: paint, .offset: SkPoint::Make(x, y)}});
161}
162
163void MockCanvas::DrawRect(const SkRect& rect, const DlPaint& paint) {
164 draw_calls_.emplace_back(args: DrawCall{.layer: current_layer_, .data: DrawRectData{.rect: rect, .paint: paint}});
165}
166
167void MockCanvas::DrawPath(const SkPath& path, const DlPaint& paint) {
168 draw_calls_.emplace_back(args: DrawCall{.layer: current_layer_, .data: DrawPathData{.path: path, .paint: paint}});
169}
170
171void MockCanvas::DrawShadow(const SkPath& path,
172 const DlColor color,
173 const SkScalar elevation,
174 bool transparent_occluder,
175 SkScalar dpr) {
176 draw_calls_.emplace_back(args: DrawCall{
177 .layer: current_layer_,
178 .data: DrawShadowData{.path: path, .color: color, .elevation: elevation, .transparent_occluder: transparent_occluder, .dpr: dpr}});
179}
180
181void MockCanvas::DrawImage(const sk_sp<DlImage>& image,
182 SkPoint point,
183 const DlImageSampling options,
184 const DlPaint* paint) {
185 if (paint) {
186 draw_calls_.emplace_back(
187 args: DrawCall{.layer: current_layer_,
188 .data: DrawImageData{.image: image, .x: point.fX, .y: point.fY, .options: options, .paint: *paint}});
189 } else {
190 draw_calls_.emplace_back(
191 args: DrawCall{.layer: current_layer_,
192 .data: DrawImageDataNoPaint{.image: image, .x: point.fX, .y: point.fY, .options: options}});
193 }
194}
195
196void MockCanvas::DrawDisplayList(const sk_sp<DisplayList> display_list,
197 SkScalar opacity) {
198 draw_calls_.emplace_back(
199 args: DrawCall{.layer: current_layer_, .data: DrawDisplayListData{.display_list: display_list, .opacity: opacity}});
200}
201
202void MockCanvas::ClipRect(const SkRect& rect, ClipOp op, bool is_aa) {
203 ClipEdgeStyle style = is_aa ? kSoft_ClipEdgeStyle : kHard_ClipEdgeStyle;
204 draw_calls_.emplace_back(
205 args: DrawCall{.layer: current_layer_, .data: ClipRectData{.rect: rect, .clip_op: op, .style: style}});
206 tracker_.clipRect(rect, op, is_aa);
207}
208
209void MockCanvas::ClipRRect(const SkRRect& rrect, ClipOp op, bool is_aa) {
210 ClipEdgeStyle style = is_aa ? kSoft_ClipEdgeStyle : kHard_ClipEdgeStyle;
211 draw_calls_.emplace_back(
212 args: DrawCall{.layer: current_layer_, .data: ClipRRectData{.rrect: rrect, .clip_op: op, .style: style}});
213 tracker_.clipRRect(rrect, op, is_aa);
214}
215
216void MockCanvas::ClipPath(const SkPath& path, ClipOp op, bool is_aa) {
217 ClipEdgeStyle style = is_aa ? kSoft_ClipEdgeStyle : kHard_ClipEdgeStyle;
218 draw_calls_.emplace_back(
219 args: DrawCall{.layer: current_layer_, .data: ClipPathData{.path: path, .clip_op: op, .style: style}});
220 tracker_.clipPath(path, op, is_aa);
221}
222
223SkRect MockCanvas::GetDestinationClipBounds() const {
224 return tracker_.device_cull_rect();
225}
226
227SkRect MockCanvas::GetLocalClipBounds() const {
228 return tracker_.local_cull_rect();
229}
230
231bool MockCanvas::QuickReject(const SkRect& bounds) const {
232 return tracker_.content_culled(content_bounds: bounds);
233}
234
235void MockCanvas::DrawDRRect(const SkRRect&, const SkRRect&, const DlPaint&) {
236 FML_DCHECK(false);
237}
238
239void MockCanvas::DrawPaint(const DlPaint& paint) {
240 draw_calls_.emplace_back(args: DrawCall{.layer: current_layer_, .data: DrawPaintData{.paint: paint}});
241}
242
243void MockCanvas::DrawColor(DlColor color, DlBlendMode mode) {
244 DrawPaint(paint: DlPaint(color).setBlendMode(mode));
245}
246
247void MockCanvas::DrawLine(const SkPoint& p0,
248 const SkPoint& p1,
249 const DlPaint& paint) {
250 FML_DCHECK(false);
251}
252
253void MockCanvas::DrawPoints(PointMode,
254 uint32_t,
255 const SkPoint[],
256 const DlPaint&) {
257 FML_DCHECK(false);
258}
259
260void MockCanvas::DrawOval(const SkRect&, const DlPaint&) {
261 FML_DCHECK(false);
262}
263
264void MockCanvas::DrawCircle(const SkPoint& center,
265 SkScalar radius,
266 const DlPaint& paint) {
267 FML_DCHECK(false);
268}
269
270void MockCanvas::DrawArc(const SkRect&,
271 SkScalar,
272 SkScalar,
273 bool,
274 const DlPaint&) {
275 FML_DCHECK(false);
276}
277
278void MockCanvas::DrawRRect(const SkRRect&, const DlPaint&) {
279 FML_DCHECK(false);
280}
281
282void MockCanvas::DrawImageRect(const sk_sp<DlImage>&,
283 const SkRect&,
284 const SkRect&,
285 const DlImageSampling,
286 const DlPaint*,
287 SrcRectConstraint constraint) {
288 FML_DCHECK(false);
289}
290
291void MockCanvas::DrawImageNine(const sk_sp<DlImage>& image,
292 const SkIRect& center,
293 const SkRect& dst,
294 DlFilterMode filter,
295 const DlPaint* paint) {
296 FML_DCHECK(false);
297}
298
299void MockCanvas::DrawVertices(const DlVertices*, DlBlendMode, const DlPaint&) {
300 FML_DCHECK(false);
301}
302
303void MockCanvas::DrawAtlas(const sk_sp<DlImage>&,
304 const SkRSXform[],
305 const SkRect[],
306 const DlColor[],
307 int,
308 DlBlendMode,
309 const DlImageSampling,
310 const SkRect*,
311 const DlPaint*) {
312 FML_DCHECK(false);
313}
314
315void MockCanvas::Flush() {
316 FML_DCHECK(false);
317}
318
319// --------------------------------------------------------
320// A few ostream operators duplicated from assertions_skia.cc
321// In the short term, there are issues trying to include that file
322// here because it appears in a skia-targeted testing source set
323// and in the long term, DlCanvas, and therefore this file will
324// eventually be cleaned of these SkObject dependencies and these
325// ostream operators will be converted to their DL equivalents.
326static std::ostream& operator<<(std::ostream& os, const SkPoint& r) {
327 return os << "XY: " << r.fX << ", " << r.fY;
328}
329
330static std::ostream& operator<<(std::ostream& os, const SkRect& r) {
331 return os << "LTRB: " << r.fLeft << ", " << r.fTop << ", " << r.fRight << ", "
332 << r.fBottom;
333}
334
335static std::ostream& operator<<(std::ostream& os, const SkRRect& r) {
336 return os << "LTRB: " << r.rect().fLeft << ", " << r.rect().fTop << ", "
337 << r.rect().fRight << ", " << r.rect().fBottom;
338}
339
340static std::ostream& operator<<(std::ostream& os, const SkPath& r) {
341 return os << "Valid: " << r.isValid()
342 << ", FillType: " << static_cast<int>(r.getFillType())
343 << ", Bounds: " << r.getBounds();
344}
345// --------------------------------------------------------
346
347static std::ostream& operator<<(std::ostream& os, const SkM44& m) {
348 os << m.rc(r: 0, c: 0) << ", " << m.rc(r: 0, c: 1) << ", " << m.rc(r: 0, c: 2) << ", "
349 << m.rc(r: 0, c: 3) << std::endl;
350 os << m.rc(r: 1, c: 0) << ", " << m.rc(r: 1, c: 1) << ", " << m.rc(r: 1, c: 2) << ", "
351 << m.rc(r: 1, c: 3) << std::endl;
352 os << m.rc(r: 2, c: 0) << ", " << m.rc(r: 2, c: 1) << ", " << m.rc(r: 2, c: 2) << ", "
353 << m.rc(r: 2, c: 3) << std::endl;
354 os << m.rc(r: 3, c: 0) << ", " << m.rc(r: 3, c: 1) << ", " << m.rc(r: 3, c: 2) << ", "
355 << m.rc(r: 3, c: 3);
356 return os;
357}
358
359bool operator==(const MockCanvas::SaveData& a, const MockCanvas::SaveData& b) {
360 return a.save_to_layer == b.save_to_layer;
361}
362
363std::ostream& operator<<(std::ostream& os, const MockCanvas::SaveData& data) {
364 return os << data.save_to_layer;
365}
366
367bool operator==(const MockCanvas::SaveLayerData& a,
368 const MockCanvas::SaveLayerData& b) {
369 return a.save_bounds == b.save_bounds && a.restore_paint == b.restore_paint &&
370 Equals(a: a.backdrop_filter, b: b.backdrop_filter) &&
371 a.save_to_layer == b.save_to_layer;
372}
373
374std::ostream& operator<<(std::ostream& os,
375 const MockCanvas::SaveLayerData& data) {
376 return os << data.save_bounds << " " << data.restore_paint << " "
377 << data.backdrop_filter << " " << data.save_to_layer;
378}
379
380bool operator==(const MockCanvas::RestoreData& a,
381 const MockCanvas::RestoreData& b) {
382 return a.restore_to_layer == b.restore_to_layer;
383}
384
385std::ostream& operator<<(std::ostream& os,
386 const MockCanvas::RestoreData& data) {
387 return os << data.restore_to_layer;
388}
389
390bool operator==(const MockCanvas::ConcatMatrixData& a,
391 const MockCanvas::ConcatMatrixData& b) {
392 return a.matrix == b.matrix;
393}
394
395std::ostream& operator<<(std::ostream& os,
396 const MockCanvas::ConcatMatrixData& data) {
397 return os << data.matrix;
398}
399
400bool operator==(const MockCanvas::SetMatrixData& a,
401 const MockCanvas::SetMatrixData& b) {
402 return a.matrix == b.matrix;
403}
404
405std::ostream& operator<<(std::ostream& os,
406 const MockCanvas::SetMatrixData& data) {
407 return os << data.matrix;
408}
409
410bool operator==(const MockCanvas::DrawRectData& a,
411 const MockCanvas::DrawRectData& b) {
412 return a.rect == b.rect && a.paint == b.paint;
413}
414
415std::ostream& operator<<(std::ostream& os,
416 const MockCanvas::DrawRectData& data) {
417 return os << data.rect << " " << data.paint;
418}
419
420bool operator==(const MockCanvas::DrawPathData& a,
421 const MockCanvas::DrawPathData& b) {
422 return a.path == b.path && a.paint == b.paint;
423}
424
425std::ostream& operator<<(std::ostream& os,
426 const MockCanvas::DrawPathData& data) {
427 return os << data.path << " " << data.paint;
428}
429
430bool operator==(const MockCanvas::DrawTextData& a,
431 const MockCanvas::DrawTextData& b) {
432 return a.serialized_text->equals(other: b.serialized_text.get()) &&
433 a.paint == b.paint && a.offset == b.offset;
434}
435
436std::ostream& operator<<(std::ostream& os,
437 const MockCanvas::DrawTextData& data) {
438 return os << data.serialized_text << " " << data.paint << " " << data.offset;
439}
440
441bool operator==(const MockCanvas::DrawImageData& a,
442 const MockCanvas::DrawImageData& b) {
443 return a.image == b.image && a.x == b.x && a.y == b.y &&
444 a.options == b.options && a.paint == b.paint;
445}
446
447std::ostream& operator<<(std::ostream& os,
448 const MockCanvas::DrawImageData& data) {
449 return os << data.image << " " << data.x << " " << data.y << " "
450 << data.options << " " << data.paint;
451}
452
453bool operator==(const MockCanvas::DrawImageDataNoPaint& a,
454 const MockCanvas::DrawImageDataNoPaint& b) {
455 return a.image == b.image && a.x == b.x && a.y == b.y &&
456 a.options == b.options;
457}
458
459std::ostream& operator<<(std::ostream& os,
460 const MockCanvas::DrawImageDataNoPaint& data) {
461 return os << data.image << " " << data.x << " " << data.y << " "
462 << data.options;
463}
464
465bool operator==(const MockCanvas::DrawDisplayListData& a,
466 const MockCanvas::DrawDisplayListData& b) {
467 return a.display_list->Equals(other: b.display_list) && a.opacity == b.opacity;
468}
469
470std::ostream& operator<<(std::ostream& os,
471 const MockCanvas::DrawDisplayListData& data) {
472 auto dl = data.display_list;
473 return os << "[" << dl->unique_id() << " " << dl->op_count() << " "
474 << dl->bytes() << "] " << data.opacity;
475}
476
477bool operator==(const MockCanvas::DrawShadowData& a,
478 const MockCanvas::DrawShadowData& b) {
479 return a.path == b.path && a.color == b.color && a.elevation == b.elevation &&
480 a.transparent_occluder == b.transparent_occluder && a.dpr == b.dpr;
481}
482
483std::ostream& operator<<(std::ostream& os,
484 const MockCanvas::DrawShadowData& data) {
485 return os << data.path << " " << data.color << " " << data.elevation << " "
486 << data.transparent_occluder << " " << data.dpr;
487}
488
489bool operator==(const MockCanvas::ClipRectData& a,
490 const MockCanvas::ClipRectData& b) {
491 return a.rect == b.rect && a.clip_op == b.clip_op && a.style == b.style;
492}
493
494static std::ostream& operator<<(std::ostream& os,
495 const MockCanvas::ClipEdgeStyle& style) {
496 return os << (style == MockCanvas::kSoft_ClipEdgeStyle ? "kSoftEdges"
497 : "kHardEdges");
498}
499
500std::ostream& operator<<(std::ostream& os,
501 const MockCanvas::ClipRectData& data) {
502 return os << data.rect << " " << data.clip_op << " " << data.style;
503}
504
505bool operator==(const MockCanvas::ClipRRectData& a,
506 const MockCanvas::ClipRRectData& b) {
507 return a.rrect == b.rrect && a.clip_op == b.clip_op && a.style == b.style;
508}
509
510std::ostream& operator<<(std::ostream& os,
511 const MockCanvas::ClipRRectData& data) {
512 return os << data.rrect << " " << data.clip_op << " " << data.style;
513}
514
515bool operator==(const MockCanvas::ClipPathData& a,
516 const MockCanvas::ClipPathData& b) {
517 return a.path == b.path && a.clip_op == b.clip_op && a.style == b.style;
518}
519
520std::ostream& operator<<(std::ostream& os,
521 const MockCanvas::ClipPathData& data) {
522 return os << data.path << " " << data.clip_op << " " << data.style;
523}
524
525std::ostream& operator<<(std::ostream& os,
526 const MockCanvas::DrawCallData& data) {
527 std::visit(visitor: [&os](auto& d) { os << d; }, vs: data);
528 return os;
529}
530
531bool operator==(const MockCanvas::DrawCall& a, const MockCanvas::DrawCall& b) {
532 return a.layer == b.layer && a.data == b.data;
533}
534
535std::ostream& operator<<(std::ostream& os, const MockCanvas::DrawCall& draw) {
536 return os << "[Layer: " << draw.layer << ", Data: " << draw.data << "]";
537}
538
539bool operator==(const MockCanvas::DrawPaintData& a,
540 const MockCanvas::DrawPaintData& b) {
541 return a.paint == b.paint;
542}
543
544std::ostream& operator<<(std::ostream& os,
545 const MockCanvas::DrawPaintData& data) {
546 return os << data.paint;
547}
548
549} // namespace testing
550} // namespace flutter
551

source code of flutter_engine/flutter/testing/mock_canvas.cc