diff --git a/engine/src/flutter/flow/raster_cache.cc b/engine/src/flutter/flow/raster_cache.cc index 8a4e46d58d6..9b6e72d43c2 100644 --- a/engine/src/flutter/flow/raster_cache.cc +++ b/engine/src/flutter/flow/raster_cache.cc @@ -47,8 +47,23 @@ void RasterCacheResult::draw(SkCanvas& canvas, const SkPaint* paint) const { #endif canvas.resetMatrix(); flow_.Step(); + + bool exceeds_bounds = bounds.fLeft + image_->dimensions().width() > + SkScalarCeilToScalar(bounds.fRight) || + bounds.fTop + image_->dimensions().height() > + SkScalarCeilToScalar(bounds.fBottom); + + // Make sure raster cache doesn't bleed to physical pixels outside of + // original bounds. https://github.com/flutter/flutter/issues/110002 + if (exceeds_bounds) { + canvas.save(); + canvas.clipRect(SkRect::Make(bounds.roundOut())); + } canvas.drawImage(image_, bounds.fLeft, bounds.fTop, SkSamplingOptions(), paint); + if (exceeds_bounds) { + canvas.restore(); + } } RasterCache::RasterCache(size_t access_threshold, diff --git a/engine/src/flutter/flow/raster_cache_unittests.cc b/engine/src/flutter/flow/raster_cache_unittests.cc index afd00392afa..9a72da8e698 100644 --- a/engine/src/flutter/flow/raster_cache_unittests.cc +++ b/engine/src/flutter/flow/raster_cache_unittests.cc @@ -787,5 +787,67 @@ TEST_F(RasterCacheTest, RasterCacheKeyID_LayerChildrenIds) { ASSERT_EQ(ids, expected_ids); } +TEST_F(RasterCacheTest, RasterCacheBleedingNoClipNeeded) { + SkImageInfo info = + SkImageInfo::MakeN32(40, 40, SkAlphaType::kOpaque_SkAlphaType); + + auto image = SkImage::MakeRasterData( + info, SkData::MakeUninitialized(40 * 40 * 4), 40 * 4); + auto canvas = MockCanvas(); + canvas.setMatrix(SkMatrix::Scale(2, 2)); + // Drawing cached image does not exceeds physical pixels of the original + // bounds and does not need to be clipped. + auto cache_result = + RasterCacheResult(image, SkRect::MakeXYWH(100.3, 100.3, 20, 20), ""); + auto paint = SkPaint(); + cache_result.draw(canvas, &paint); + + EXPECT_EQ(canvas.draw_calls(), + std::vector({ + MockCanvas::DrawCall{ + 0, MockCanvas::SetMatrixData{SkM44::Scale(2, 2)}}, + MockCanvas::DrawCall{0, MockCanvas::SaveData{1}}, + MockCanvas::DrawCall{1, MockCanvas::SetMatrixData{SkM44()}}, + MockCanvas::DrawCall{ + 1, MockCanvas::DrawImageData{image, 200.6, 200.6, + SkSamplingOptions(), paint}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}, + })); +} + +TEST_F(RasterCacheTest, RasterCacheBleedingClipNeeded) { + SkImageInfo info = + SkImageInfo::MakeN32(40, 40, SkAlphaType::kOpaque_SkAlphaType); + + auto image = SkImage::MakeRasterData( + info, SkData::MakeUninitialized(40 * 40 * 4), 40 * 4); + auto canvas = MockCanvas(); + canvas.setMatrix(SkMatrix::Scale(2, 2)); + + auto cache_result = + RasterCacheResult(image, SkRect::MakeXYWH(100.3, 100.3, 19.6, 19.6), ""); + auto paint = SkPaint(); + cache_result.draw(canvas, &paint); + + EXPECT_EQ( + canvas.draw_calls(), + std::vector({ + MockCanvas::DrawCall{0, + MockCanvas::SetMatrixData{SkM44::Scale(2, 2)}}, + MockCanvas::DrawCall{0, MockCanvas::SaveData{1}}, + MockCanvas::DrawCall{1, MockCanvas::SetMatrixData{SkM44()}}, + MockCanvas::DrawCall{1, MockCanvas::SaveData{2}}, + MockCanvas::DrawCall{ + 2, MockCanvas::ClipRectData{SkRect::MakeLTRB(200, 200, 240, 240), + SkClipOp::kIntersect, + MockCanvas::kHard_ClipEdgeStyle}}, + MockCanvas::DrawCall{ + 2, MockCanvas::DrawImageData{image, 200.6, 200.6, + SkSamplingOptions(), paint}}, + MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}, + })); +} + } // namespace testing } // namespace flutter