| // 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 "third_party/skia/include/core/SkImageInfo.h" |
| #include "third_party/skia/include/core/SkPicture.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() |
| : SkCanvasVirtualEnforcer<SkCanvas>(kSize.fWidth, kSize.fHeight), |
| internal_canvas_(imageInfo().width(), imageInfo().height()), |
| current_layer_(0) { |
| internal_canvas_.addCanvas(this); |
| } |
| |
| MockCanvas::~MockCanvas() { |
| EXPECT_EQ(current_layer_, 0); |
| } |
| |
| void MockCanvas::willSave() { |
| draw_calls_.emplace_back( |
| DrawCall{current_layer_, SaveData{current_layer_ + 1}}); |
| current_layer_++; // Must go here; func params order of eval is undefined |
| } |
| |
| SkCanvas::SaveLayerStrategy MockCanvas::getSaveLayerStrategy( |
| const SaveLayerRec& rec) { |
| // saveLayer calls this prior to running, so we use it to track saveLayer |
| // calls |
| draw_calls_.emplace_back(DrawCall{ |
| current_layer_, |
| SaveLayerData{rec.fBounds ? *rec.fBounds : SkRect(), |
| rec.fPaint ? *rec.fPaint : SkPaint(), |
| rec.fBackdrop ? sk_ref_sp<SkImageFilter>(rec.fBackdrop) |
| : sk_sp<SkImageFilter>(), |
| current_layer_ + 1}}); |
| current_layer_++; // Must go here; func params order of eval is undefined |
| return kNoLayer_SaveLayerStrategy; |
| } |
| |
| void MockCanvas::willRestore() { |
| FML_DCHECK(current_layer_ > 0); |
| |
| draw_calls_.emplace_back( |
| DrawCall{current_layer_, RestoreData{current_layer_ - 1}}); |
| current_layer_--; // Must go here; func params order of eval is undefined |
| } |
| |
| void MockCanvas::didConcat44(const SkM44& matrix) { |
| draw_calls_.emplace_back(DrawCall{current_layer_, ConcatMatrixData{matrix}}); |
| } |
| |
| void MockCanvas::didSetM44(const SkM44& matrix) { |
| draw_calls_.emplace_back(DrawCall{current_layer_, SetMatrixData{matrix}}); |
| } |
| |
| void MockCanvas::didScale(SkScalar x, SkScalar y) { |
| this->didConcat44(SkM44::Scale(x, y)); |
| } |
| |
| void MockCanvas::didTranslate(SkScalar x, SkScalar y) { |
| this->didConcat44(SkM44::Translate(x, y)); |
| } |
| |
| void MockCanvas::onDrawTextBlob(const SkTextBlob* text, |
| SkScalar x, |
| SkScalar y, |
| const SkPaint& 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; |
| const SkRect* bounds = nullptr; |
| if (paint.canComputeFastBounds()) { |
| storage = text->bounds().makeOffset(x, y); |
| SkRect tmp; |
| if (this->quickReject(paint.computeFastBounds(storage, &tmp))) { |
| return; |
| } |
| bounds = &storage; |
| } |
| |
| draw_calls_.emplace_back(DrawCall{ |
| current_layer_, DrawTextData{text ? text->serialize(SkSerialProcs{}) |
| : SkData::MakeUninitialized(0), |
| paint, SkPoint::Make(x, y)}}); |
| } |
| |
| void MockCanvas::onDrawRect(const SkRect& rect, const SkPaint& paint) { |
| draw_calls_.emplace_back(DrawCall{current_layer_, DrawRectData{rect, paint}}); |
| } |
| |
| void MockCanvas::onDrawPath(const SkPath& path, const SkPaint& paint) { |
| draw_calls_.emplace_back(DrawCall{current_layer_, DrawPathData{path, paint}}); |
| } |
| |
| void MockCanvas::onDrawShadowRec(const SkPath& path, |
| const SkDrawShadowRec& rec) { |
| (void)rec; // Can't use b/c Skia keeps this type anonymous. |
| draw_calls_.emplace_back(DrawCall{current_layer_, DrawShadowData{path}}); |
| } |
| |
| void MockCanvas::onDrawPicture(const SkPicture* picture, |
| const SkMatrix* matrix, |
| const SkPaint* paint) { |
| // This duplicates existing logic in SkCanvas::onDrawPicture |
| // that should probably be split out so it doesn't need to be here as well. |
| if (!paint || paint->canComputeFastBounds()) { |
| SkRect bounds = picture->cullRect(); |
| if (paint) { |
| paint->computeFastBounds(bounds, &bounds); |
| } |
| if (matrix) { |
| matrix->mapRect(&bounds); |
| } |
| if (this->quickReject(bounds)) { |
| return; |
| } |
| } |
| |
| draw_calls_.emplace_back(DrawCall{ |
| current_layer_, |
| DrawPictureData{ |
| picture ? picture->serialize() : SkData::MakeUninitialized(0), |
| paint ? *paint : SkPaint(), matrix ? *matrix : SkMatrix()}}); |
| } |
| |
| void MockCanvas::onClipRect(const SkRect& rect, |
| SkClipOp op, |
| ClipEdgeStyle style) { |
| draw_calls_.emplace_back( |
| DrawCall{current_layer_, ClipRectData{rect, op, style}}); |
| // quickReject() is handled by base class and needs accurate clip information |
| SkCanvas::onClipRect(rect, op, style); |
| } |
| |
| void MockCanvas::onClipRRect(const SkRRect& rrect, |
| SkClipOp op, |
| ClipEdgeStyle style) { |
| draw_calls_.emplace_back( |
| DrawCall{current_layer_, ClipRRectData{rrect, op, style}}); |
| // quickReject() is handled by base class and needs accurate clip information |
| SkCanvas::onClipRRect(rrect, op, style); |
| } |
| |
| void MockCanvas::onClipPath(const SkPath& path, |
| SkClipOp op, |
| ClipEdgeStyle style) { |
| draw_calls_.emplace_back( |
| DrawCall{current_layer_, ClipPathData{path, op, style}}); |
| // quickReject() is handled by base class and needs accurate clip information |
| SkCanvas::onClipPath(path, op, style); |
| } |
| |
| bool MockCanvas::onDoSaveBehind(const SkRect*) { |
| FML_DCHECK(false); |
| return false; |
| } |
| |
| void MockCanvas::onDrawAnnotation(const SkRect&, const char[], SkData*) { |
| FML_DCHECK(false); |
| } |
| |
| void MockCanvas::onDrawDRRect(const SkRRect&, const SkRRect&, const SkPaint&) { |
| FML_DCHECK(false); |
| } |
| |
| void MockCanvas::onDrawDrawable(SkDrawable*, const SkMatrix*) { |
| FML_DCHECK(false); |
| } |
| |
| void MockCanvas::onDrawPatch(const SkPoint[12], |
| const SkColor[4], |
| const SkPoint[4], |
| SkBlendMode, |
| const SkPaint&) { |
| FML_DCHECK(false); |
| } |
| |
| void MockCanvas::onDrawPaint(const SkPaint& skPaint) { |
| draw_calls_.emplace_back(DrawCall{current_layer_, DrawPaint{skPaint}}); |
| } |
| |
| void MockCanvas::onDrawBehind(const SkPaint&) { |
| FML_DCHECK(false); |
| } |
| |
| void MockCanvas::onDrawPoints(PointMode, |
| size_t, |
| const SkPoint[], |
| const SkPaint&) { |
| FML_DCHECK(false); |
| } |
| |
| void MockCanvas::onDrawRegion(const SkRegion&, const SkPaint&) { |
| FML_DCHECK(false); |
| } |
| |
| void MockCanvas::onDrawOval(const SkRect&, const SkPaint&) { |
| FML_DCHECK(false); |
| } |
| |
| void MockCanvas::onDrawArc(const SkRect&, |
| SkScalar, |
| SkScalar, |
| bool, |
| const SkPaint&) { |
| FML_DCHECK(false); |
| } |
| |
| void MockCanvas::onDrawRRect(const SkRRect&, const SkPaint&) { |
| FML_DCHECK(false); |
| } |
| |
| void MockCanvas::onDrawImage2(const SkImage*, |
| SkScalar, |
| SkScalar, |
| const SkSamplingOptions&, |
| const SkPaint*) { |
| FML_DCHECK(false); |
| } |
| |
| void MockCanvas::onDrawImageRect2(const SkImage*, |
| const SkRect&, |
| const SkRect&, |
| const SkSamplingOptions&, |
| const SkPaint*, |
| SrcRectConstraint) { |
| FML_DCHECK(false); |
| } |
| |
| void MockCanvas::onDrawImageLattice2(const SkImage*, |
| const Lattice&, |
| const SkRect&, |
| SkFilterMode, |
| const SkPaint*) { |
| FML_DCHECK(false); |
| } |
| |
| void MockCanvas::onDrawVerticesObject(const SkVertices*, |
| SkBlendMode, |
| const SkPaint&) { |
| FML_DCHECK(false); |
| } |
| |
| void MockCanvas::onDrawAtlas2(const SkImage*, |
| const SkRSXform[], |
| const SkRect[], |
| const SkColor[], |
| int, |
| SkBlendMode, |
| const SkSamplingOptions&, |
| const SkRect*, |
| const SkPaint*) { |
| FML_DCHECK(false); |
| } |
| |
| void MockCanvas::onDrawEdgeAAQuad(const SkRect&, |
| const SkPoint[4], |
| QuadAAFlags, |
| const SkColor4f&, |
| SkBlendMode) { |
| FML_DCHECK(false); |
| } |
| |
| void MockCanvas::onClipRegion(const SkRegion&, SkClipOp) { |
| FML_DCHECK(false); |
| } |
| |
| 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 && |
| 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::DrawPictureData& a, |
| const MockCanvas::DrawPictureData& b) { |
| return a.serialized_picture->equals(b.serialized_picture.get()) && |
| a.paint == b.paint && a.matrix == b.matrix; |
| } |
| |
| std::ostream& operator<<(std::ostream& os, |
| const MockCanvas::DrawPictureData& data) { |
| return os << data.serialized_picture << " " << data.paint << " " |
| << data.matrix; |
| } |
| |
| bool operator==(const MockCanvas::DrawShadowData& a, |
| const MockCanvas::DrawShadowData& b) { |
| return a.path == b.path; |
| } |
| |
| std::ostream& operator<<(std::ostream& os, |
| const MockCanvas::DrawShadowData& data) { |
| return os << data.path; |
| } |
| |
| 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; |
| } |
| |
| 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::DrawPaint& a, |
| const MockCanvas::DrawPaint& b) { |
| return a.paint == b.paint; |
| } |
| |
| std::ostream& operator<<(std::ostream& os, const MockCanvas::DrawPaint& data) { |
| return os << data.paint; |
| } |
| |
| } // namespace testing |
| } // namespace flutter |