| // Copyright 2013 The Flutter Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "flutter/testing/mock_canvas.h" | |
| #include "flutter/fml/logging.h" | |
| #include "flutter/testing/display_list_testing.h" | |
| #include "third_party/skia/include/core/SkImageInfo.h" | |
| #include "third_party/skia/include/core/SkSerialProcs.h" | |
| #include "third_party/skia/include/core/SkSize.h" | |
| #include "third_party/skia/include/core/SkTextBlob.h" | |
| namespace flutter { | |
| namespace testing { | |
| constexpr SkISize kSize = SkISize::Make(64, 64); | |
| MockCanvas::MockCanvas() : MockCanvas(kSize.fWidth, kSize.fHeight) {} | |
| MockCanvas::MockCanvas(int width, int height) | |
| : base_layer_size_({width, height}), current_layer_(0) { | |
| state_stack_.emplace_back(DlRect::MakeXYWH(0, 0, width, height), DlMatrix()); | |
| } | |
| MockCanvas::~MockCanvas() { | |
| EXPECT_EQ(current_layer_, 0); | |
| } | |
| SkISize MockCanvas::GetBaseLayerSize() const { | |
| return base_layer_size_; | |
| } | |
| SkImageInfo MockCanvas::GetImageInfo() const { | |
| SkISize size = GetBaseLayerSize(); | |
| return SkImageInfo::MakeUnknown(size.width(), size.height()); | |
| } | |
| void MockCanvas::Save() { | |
| draw_calls_.emplace_back( | |
| DrawCall{current_layer_, SaveData{current_layer_ + 1}}); | |
| state_stack_.emplace_back(state_stack_.back()); | |
| current_layer_++; // Must go here; func params order of eval is undefined | |
| } | |
| void MockCanvas::SaveLayer(const SkRect* bounds, | |
| const DlPaint* paint, | |
| const DlImageFilter* backdrop) { | |
| // saveLayer calls this prior to running, so we use it to track saveLayer | |
| // calls | |
| draw_calls_.emplace_back(DrawCall{ | |
| current_layer_, | |
| SaveLayerData{bounds ? *bounds : SkRect(), paint ? *paint : DlPaint(), | |
| backdrop ? backdrop->shared() : nullptr, | |
| current_layer_ + 1}}); | |
| state_stack_.emplace_back(state_stack_.back()); | |
| current_layer_++; // Must go here; func params order of eval is undefined | |
| } | |
| void MockCanvas::Restore() { | |
| FML_DCHECK(current_layer_ > 0); | |
| draw_calls_.emplace_back( | |
| DrawCall{current_layer_, RestoreData{current_layer_ - 1}}); | |
| state_stack_.pop_back(); | |
| current_layer_--; // Must go here; func params order of eval is undefined | |
| } | |
| // clang-format off | |
| // 2x3 2D affine subset of a 4x4 transform in row major order | |
| void MockCanvas::Transform2DAffine(SkScalar mxx, SkScalar mxy, SkScalar mxt, | |
| SkScalar myx, SkScalar myy, SkScalar myt) { | |
| Transform(SkMatrix::MakeAll(mxx, mxy, mxt, myx, myy, myt, 0, 0, 1)); | |
| } | |
| // full 4x4 transform in row major order | |
| void MockCanvas::TransformFullPerspective( | |
| SkScalar mxx, SkScalar mxy, SkScalar mxz, SkScalar mxt, | |
| SkScalar myx, SkScalar myy, SkScalar myz, SkScalar myt, | |
| SkScalar mzx, SkScalar mzy, SkScalar mzz, SkScalar mzt, | |
| SkScalar mwx, SkScalar mwy, SkScalar mwz, SkScalar mwt) { | |
| Transform(SkM44(mxx, mxy, mxz, mxt, | |
| myx, myy, myz, myt, | |
| mzx, mzy, mzz, mzt, | |
| mwx, mwy, mwz, mwt)); | |
| } | |
| // clang-format on | |
| void MockCanvas::Transform(const SkMatrix* matrix) { | |
| draw_calls_.emplace_back( | |
| DrawCall{current_layer_, ConcatMatrixData{SkM44(*matrix)}}); | |
| state_stack_.back().transform(*matrix); | |
| } | |
| void MockCanvas::Transform(const SkM44* matrix) { | |
| draw_calls_.emplace_back(DrawCall{current_layer_, ConcatMatrixData{*matrix}}); | |
| state_stack_.back().transform(*matrix); | |
| } | |
| void MockCanvas::SetTransform(const SkMatrix* matrix) { | |
| draw_calls_.emplace_back( | |
| DrawCall{current_layer_, SetMatrixData{SkM44(*matrix)}}); | |
| state_stack_.back().setTransform(*matrix); | |
| } | |
| void MockCanvas::SetTransform(const SkM44* matrix) { | |
| draw_calls_.emplace_back(DrawCall{current_layer_, SetMatrixData{*matrix}}); | |
| state_stack_.back().setTransform(*matrix); | |
| } | |
| void MockCanvas::TransformReset() { | |
| draw_calls_.emplace_back(DrawCall{current_layer_, SetMatrixData{SkM44()}}); | |
| state_stack_.back().setIdentity(); | |
| } | |
| void MockCanvas::Translate(SkScalar x, SkScalar y) { | |
| this->Transform(SkM44::Translate(x, y)); | |
| } | |
| void MockCanvas::Scale(SkScalar x, SkScalar y) { | |
| this->Transform(SkM44::Scale(x, y)); | |
| } | |
| void MockCanvas::Rotate(SkScalar degrees) { | |
| this->Transform(SkMatrix::RotateDeg(degrees)); | |
| } | |
| void MockCanvas::Skew(SkScalar sx, SkScalar sy) { | |
| this->Transform(SkMatrix::Skew(sx, sy)); | |
| } | |
| SkM44 MockCanvas::GetTransformFullPerspective() const { | |
| return state_stack_.back().matrix_4x4(); | |
| } | |
| SkMatrix MockCanvas::GetTransform() const { | |
| return state_stack_.back().matrix_3x3(); | |
| } | |
| void MockCanvas::DrawTextBlob(const sk_sp<SkTextBlob>& text, | |
| SkScalar x, | |
| SkScalar y, | |
| const DlPaint& paint) { | |
| // This duplicates existing logic in SkCanvas::onDrawPicture | |
| // that should probably be split out so it doesn't need to be here as well. | |
| // SkRect storage; | |
| // if (paint.canComputeFastBounds()) { | |
| // storage = text->bounds().makeOffset(x, y); | |
| // SkRect tmp; | |
| // if (this->quickReject(paint.computeFastBounds(storage, &tmp))) { | |
| // return; | |
| // } | |
| // } | |
| draw_calls_.emplace_back(DrawCall{ | |
| current_layer_, DrawTextData{text ? text->serialize(SkSerialProcs{}) | |
| : SkData::MakeUninitialized(0), | |
| paint, SkPoint::Make(x, y)}}); | |
| } | |
| void MockCanvas::DrawTextFrame( | |
| const std::shared_ptr<impeller::TextFrame>& text_frame, | |
| SkScalar x, | |
| SkScalar y, | |
| const DlPaint& paint) { | |
| FML_DCHECK(false); | |
| } | |
| void MockCanvas::DrawRect(const SkRect& rect, const DlPaint& paint) { | |
| draw_calls_.emplace_back(DrawCall{current_layer_, DrawRectData{rect, paint}}); | |
| } | |
| void MockCanvas::DrawPath(const SkPath& path, const DlPaint& paint) { | |
| draw_calls_.emplace_back(DrawCall{current_layer_, DrawPathData{path, paint}}); | |
| } | |
| void MockCanvas::DrawShadow(const SkPath& path, | |
| const DlColor color, | |
| const SkScalar elevation, | |
| bool transparent_occluder, | |
| SkScalar dpr) { | |
| draw_calls_.emplace_back(DrawCall{ | |
| current_layer_, | |
| DrawShadowData{path, color, elevation, transparent_occluder, dpr}}); | |
| } | |
| void MockCanvas::DrawImage(const sk_sp<DlImage>& image, | |
| SkPoint point, | |
| const DlImageSampling options, | |
| const DlPaint* paint) { | |
| if (paint) { | |
| draw_calls_.emplace_back( | |
| DrawCall{current_layer_, | |
| DrawImageData{image, point.fX, point.fY, options, *paint}}); | |
| } else { | |
| draw_calls_.emplace_back( | |
| DrawCall{current_layer_, | |
| DrawImageDataNoPaint{image, point.fX, point.fY, options}}); | |
| } | |
| } | |
| void MockCanvas::DrawDisplayList(const sk_sp<DisplayList> display_list, | |
| SkScalar opacity) { | |
| draw_calls_.emplace_back( | |
| DrawCall{current_layer_, DrawDisplayListData{display_list, opacity}}); | |
| } | |
| void MockCanvas::ClipRect(const SkRect& rect, ClipOp op, bool is_aa) { | |
| ClipEdgeStyle style = is_aa ? kSoftClipEdgeStyle : kHardClipEdgeStyle; | |
| draw_calls_.emplace_back( | |
| DrawCall{current_layer_, ClipRectData{rect, op, style}}); | |
| state_stack_.back().clipRect(rect, op, is_aa); | |
| } | |
| void MockCanvas::ClipRRect(const SkRRect& rrect, ClipOp op, bool is_aa) { | |
| ClipEdgeStyle style = is_aa ? kSoftClipEdgeStyle : kHardClipEdgeStyle; | |
| draw_calls_.emplace_back( | |
| DrawCall{current_layer_, ClipRRectData{rrect, op, style}}); | |
| state_stack_.back().clipRRect(rrect, op, is_aa); | |
| } | |
| void MockCanvas::ClipPath(const SkPath& path, ClipOp op, bool is_aa) { | |
| ClipEdgeStyle style = is_aa ? kSoftClipEdgeStyle : kHardClipEdgeStyle; | |
| draw_calls_.emplace_back( | |
| DrawCall{current_layer_, ClipPathData{path, op, style}}); | |
| state_stack_.back().clipPath(path, op, is_aa); | |
| } | |
| SkRect MockCanvas::GetDestinationClipBounds() const { | |
| return state_stack_.back().device_cull_rect(); | |
| } | |
| SkRect MockCanvas::GetLocalClipBounds() const { | |
| return state_stack_.back().local_cull_rect(); | |
| } | |
| bool MockCanvas::QuickReject(const SkRect& bounds) const { | |
| return state_stack_.back().content_culled(bounds); | |
| } | |
| void MockCanvas::DrawDRRect(const SkRRect&, const SkRRect&, const DlPaint&) { | |
| FML_DCHECK(false); | |
| } | |
| void MockCanvas::DrawPaint(const DlPaint& paint) { | |
| draw_calls_.emplace_back(DrawCall{current_layer_, DrawPaintData{paint}}); | |
| } | |
| void MockCanvas::DrawColor(DlColor color, DlBlendMode mode) { | |
| DrawPaint(DlPaint(color).setBlendMode(mode)); | |
| } | |
| void MockCanvas::DrawLine(const SkPoint& p0, | |
| const SkPoint& p1, | |
| const DlPaint& paint) { | |
| FML_DCHECK(false); | |
| } | |
| void MockCanvas::DrawDashedLine(const DlPoint& p0, | |
| const DlPoint& p1, | |
| DlScalar on_length, | |
| DlScalar off_length, | |
| const DlPaint& paint) { | |
| FML_DCHECK(false); | |
| } | |
| void MockCanvas::DrawPoints(PointMode, | |
| uint32_t, | |
| const SkPoint[], | |
| const DlPaint&) { | |
| FML_DCHECK(false); | |
| } | |
| void MockCanvas::DrawOval(const SkRect&, const DlPaint&) { | |
| FML_DCHECK(false); | |
| } | |
| void MockCanvas::DrawCircle(const SkPoint& center, | |
| SkScalar radius, | |
| const DlPaint& paint) { | |
| FML_DCHECK(false); | |
| } | |
| void MockCanvas::DrawArc(const SkRect&, | |
| SkScalar, | |
| SkScalar, | |
| bool, | |
| const DlPaint&) { | |
| FML_DCHECK(false); | |
| } | |
| void MockCanvas::DrawRRect(const SkRRect&, const DlPaint&) { | |
| FML_DCHECK(false); | |
| } | |
| void MockCanvas::DrawImageRect(const sk_sp<DlImage>&, | |
| const SkRect&, | |
| const SkRect&, | |
| const DlImageSampling, | |
| const DlPaint*, | |
| SrcRectConstraint constraint) { | |
| FML_DCHECK(false); | |
| } | |
| void MockCanvas::DrawImageNine(const sk_sp<DlImage>& image, | |
| const SkIRect& center, | |
| const SkRect& dst, | |
| DlFilterMode filter, | |
| const DlPaint* paint) { | |
| FML_DCHECK(false); | |
| } | |
| void MockCanvas::DrawVertices(const DlVertices*, DlBlendMode, const DlPaint&) { | |
| FML_DCHECK(false); | |
| } | |
| void MockCanvas::DrawAtlas(const sk_sp<DlImage>&, | |
| const SkRSXform[], | |
| const SkRect[], | |
| const DlColor[], | |
| int, | |
| DlBlendMode, | |
| const DlImageSampling, | |
| const SkRect*, | |
| const DlPaint*) { | |
| FML_DCHECK(false); | |
| } | |
| void MockCanvas::Flush() { | |
| FML_DCHECK(false); | |
| } | |
| // -------------------------------------------------------- | |
| // A few ostream operators duplicated from assertions_skia.cc | |
| // In the short term, there are issues trying to include that file | |
| // here because it appears in a skia-targeted testing source set | |
| // and in the long term, DlCanvas, and therefore this file will | |
| // eventually be cleaned of these SkObject dependencies and these | |
| // ostream operators will be converted to their DL equivalents. | |
| static std::ostream& operator<<(std::ostream& os, const SkPoint& r) { | |
| return os << "XY: " << r.fX << ", " << r.fY; | |
| } | |
| static std::ostream& operator<<(std::ostream& os, const SkRect& r) { | |
| return os << "LTRB: " << r.fLeft << ", " << r.fTop << ", " << r.fRight << ", " | |
| << r.fBottom; | |
| } | |
| static std::ostream& operator<<(std::ostream& os, const SkRRect& r) { | |
| return os << "LTRB: " << r.rect().fLeft << ", " << r.rect().fTop << ", " | |
| << r.rect().fRight << ", " << r.rect().fBottom; | |
| } | |
| static std::ostream& operator<<(std::ostream& os, const SkPath& r) { | |
| return os << "Valid: " << r.isValid() | |
| << ", FillType: " << static_cast<int>(r.getFillType()) | |
| << ", Bounds: " << r.getBounds(); | |
| } | |
| // -------------------------------------------------------- | |
| static std::ostream& operator<<(std::ostream& os, const SkM44& m) { | |
| os << m.rc(0, 0) << ", " << m.rc(0, 1) << ", " << m.rc(0, 2) << ", " | |
| << m.rc(0, 3) << std::endl; | |
| os << m.rc(1, 0) << ", " << m.rc(1, 1) << ", " << m.rc(1, 2) << ", " | |
| << m.rc(1, 3) << std::endl; | |
| os << m.rc(2, 0) << ", " << m.rc(2, 1) << ", " << m.rc(2, 2) << ", " | |
| << m.rc(2, 3) << std::endl; | |
| os << m.rc(3, 0) << ", " << m.rc(3, 1) << ", " << m.rc(3, 2) << ", " | |
| << m.rc(3, 3); | |
| return os; | |
| } | |
| bool operator==(const MockCanvas::SaveData& a, const MockCanvas::SaveData& b) { | |
| return a.save_to_layer == b.save_to_layer; | |
| } | |
| std::ostream& operator<<(std::ostream& os, const MockCanvas::SaveData& data) { | |
| return os << data.save_to_layer; | |
| } | |
| bool operator==(const MockCanvas::SaveLayerData& a, | |
| const MockCanvas::SaveLayerData& b) { | |
| return a.save_bounds == b.save_bounds && a.restore_paint == b.restore_paint && | |
| Equals(a.backdrop_filter, b.backdrop_filter) && | |
| a.save_to_layer == b.save_to_layer; | |
| } | |
| std::ostream& operator<<(std::ostream& os, | |
| const MockCanvas::SaveLayerData& data) { | |
| return os << data.save_bounds << " " << data.restore_paint << " " | |
| << data.backdrop_filter << " " << data.save_to_layer; | |
| } | |
| bool operator==(const MockCanvas::RestoreData& a, | |
| const MockCanvas::RestoreData& b) { | |
| return a.restore_to_layer == b.restore_to_layer; | |
| } | |
| std::ostream& operator<<(std::ostream& os, | |
| const MockCanvas::RestoreData& data) { | |
| return os << data.restore_to_layer; | |
| } | |
| bool operator==(const MockCanvas::ConcatMatrixData& a, | |
| const MockCanvas::ConcatMatrixData& b) { | |
| return a.matrix == b.matrix; | |
| } | |
| std::ostream& operator<<(std::ostream& os, | |
| const MockCanvas::ConcatMatrixData& data) { | |
| return os << data.matrix; | |
| } | |
| bool operator==(const MockCanvas::SetMatrixData& a, | |
| const MockCanvas::SetMatrixData& b) { | |
| return a.matrix == b.matrix; | |
| } | |
| std::ostream& operator<<(std::ostream& os, | |
| const MockCanvas::SetMatrixData& data) { | |
| return os << data.matrix; | |
| } | |
| bool operator==(const MockCanvas::DrawRectData& a, | |
| const MockCanvas::DrawRectData& b) { | |
| return a.rect == b.rect && a.paint == b.paint; | |
| } | |
| std::ostream& operator<<(std::ostream& os, | |
| const MockCanvas::DrawRectData& data) { | |
| return os << data.rect << " " << data.paint; | |
| } | |
| bool operator==(const MockCanvas::DrawPathData& a, | |
| const MockCanvas::DrawPathData& b) { | |
| return a.path == b.path && a.paint == b.paint; | |
| } | |
| std::ostream& operator<<(std::ostream& os, | |
| const MockCanvas::DrawPathData& data) { | |
| return os << data.path << " " << data.paint; | |
| } | |
| bool operator==(const MockCanvas::DrawTextData& a, | |
| const MockCanvas::DrawTextData& b) { | |
| return a.serialized_text->equals(b.serialized_text.get()) && | |
| a.paint == b.paint && a.offset == b.offset; | |
| } | |
| std::ostream& operator<<(std::ostream& os, | |
| const MockCanvas::DrawTextData& data) { | |
| return os << data.serialized_text << " " << data.paint << " " << data.offset; | |
| } | |
| bool operator==(const MockCanvas::DrawImageData& a, | |
| const MockCanvas::DrawImageData& b) { | |
| return a.image == b.image && a.x == b.x && a.y == b.y && | |
| a.options == b.options && a.paint == b.paint; | |
| } | |
| std::ostream& operator<<(std::ostream& os, | |
| const MockCanvas::DrawImageData& data) { | |
| return os << data.image << " " << data.x << " " << data.y << " " | |
| << data.options << " " << data.paint; | |
| } | |
| bool operator==(const MockCanvas::DrawImageDataNoPaint& a, | |
| const MockCanvas::DrawImageDataNoPaint& b) { | |
| return a.image == b.image && a.x == b.x && a.y == b.y && | |
| a.options == b.options; | |
| } | |
| std::ostream& operator<<(std::ostream& os, | |
| const MockCanvas::DrawImageDataNoPaint& data) { | |
| return os << data.image << " " << data.x << " " << data.y << " " | |
| << data.options; | |
| } | |
| bool operator==(const MockCanvas::DrawDisplayListData& a, | |
| const MockCanvas::DrawDisplayListData& b) { | |
| return a.display_list->Equals(b.display_list) && a.opacity == b.opacity; | |
| } | |
| std::ostream& operator<<(std::ostream& os, | |
| const MockCanvas::DrawDisplayListData& data) { | |
| auto dl = data.display_list; | |
| return os << "[" << dl->unique_id() << " " << dl->op_count() << " " | |
| << dl->bytes() << "] " << data.opacity; | |
| } | |
| bool operator==(const MockCanvas::DrawShadowData& a, | |
| const MockCanvas::DrawShadowData& b) { | |
| return a.path == b.path && a.color == b.color && a.elevation == b.elevation && | |
| a.transparent_occluder == b.transparent_occluder && a.dpr == b.dpr; | |
| } | |
| std::ostream& operator<<(std::ostream& os, | |
| const MockCanvas::DrawShadowData& data) { | |
| return os << data.path << " " << data.color << " " << data.elevation << " " | |
| << data.transparent_occluder << " " << data.dpr; | |
| } | |
| bool operator==(const MockCanvas::ClipRectData& a, | |
| const MockCanvas::ClipRectData& b) { | |
| return a.rect == b.rect && a.clip_op == b.clip_op && a.style == b.style; | |
| } | |
| static std::ostream& operator<<(std::ostream& os, | |
| const MockCanvas::ClipEdgeStyle& style) { | |
| return os << (style == MockCanvas::kSoftClipEdgeStyle ? "kSoftEdges" | |
| : "kHardEdges"); | |
| } | |
| std::ostream& operator<<(std::ostream& os, | |
| const MockCanvas::ClipRectData& data) { | |
| return os << data.rect << " " << data.clip_op << " " << data.style; | |
| } | |
| bool operator==(const MockCanvas::ClipRRectData& a, | |
| const MockCanvas::ClipRRectData& b) { | |
| return a.rrect == b.rrect && a.clip_op == b.clip_op && a.style == b.style; | |
| } | |
| std::ostream& operator<<(std::ostream& os, | |
| const MockCanvas::ClipRRectData& data) { | |
| return os << data.rrect << " " << data.clip_op << " " << data.style; | |
| } | |
| bool operator==(const MockCanvas::ClipPathData& a, | |
| const MockCanvas::ClipPathData& b) { | |
| return a.path == b.path && a.clip_op == b.clip_op && a.style == b.style; | |
| } | |
| std::ostream& operator<<(std::ostream& os, | |
| const MockCanvas::ClipPathData& data) { | |
| return os << data.path << " " << data.clip_op << " " << data.style; | |
| } | |
| std::ostream& operator<<(std::ostream& os, | |
| const MockCanvas::DrawCallData& data) { | |
| std::visit([&os](auto& d) { os << d; }, data); | |
| return os; | |
| } | |
| bool operator==(const MockCanvas::DrawCall& a, const MockCanvas::DrawCall& b) { | |
| return a.layer == b.layer && a.data == b.data; | |
| } | |
| std::ostream& operator<<(std::ostream& os, const MockCanvas::DrawCall& draw) { | |
| return os << "[Layer: " << draw.layer << ", Data: " << draw.data << "]"; | |
| } | |
| bool operator==(const MockCanvas::DrawPaintData& a, | |
| const MockCanvas::DrawPaintData& b) { | |
| return a.paint == b.paint; | |
| } | |
| std::ostream& operator<<(std::ostream& os, | |
| const MockCanvas::DrawPaintData& data) { | |
| return os << data.paint; | |
| } | |
| } // namespace testing | |
| } // namespace flutter |