diff --git a/display_list/display_list_builder.cc b/display_list/display_list_builder.cc index fc7a8586f38ee..970e019ff56bf 100644 --- a/display_list/display_list_builder.cc +++ b/display_list/display_list_builder.cc @@ -602,9 +602,7 @@ void DisplayListBuilder::clipRect(const SkRect& rect, switch (clip_op) { case SkClipOp::kIntersect: Push(0, 1, rect, is_aa); - if (!current_layer_->clip_bounds.intersect(rect)) { - current_layer_->clip_bounds.setEmpty(); - } + intersect(rect); break; case SkClipOp::kDifference: Push(0, 1, rect, is_aa); @@ -620,9 +618,7 @@ void DisplayListBuilder::clipRRect(const SkRRect& rrect, switch (clip_op) { case SkClipOp::kIntersect: Push(0, 1, rrect, is_aa); - if (!current_layer_->clip_bounds.intersect(rrect.getBounds())) { - current_layer_->clip_bounds.setEmpty(); - } + intersect(rrect.getBounds()); break; case SkClipOp::kDifference: Push(0, 1, rrect, is_aa); @@ -653,15 +649,25 @@ void DisplayListBuilder::clipPath(const SkPath& path, switch (clip_op) { case SkClipOp::kIntersect: Push(0, 1, path, is_aa); - if (!current_layer_->clip_bounds.intersect(path.getBounds())) { - current_layer_->clip_bounds.setEmpty(); + if (!path.isInverseFillType()) { + intersect(path.getBounds()); } break; case SkClipOp::kDifference: Push(0, 1, path, is_aa); + // Map "kDifference of inverse path" to "kIntersect of the original path". + if (path.isInverseFillType()) { + intersect(path.getBounds()); + } break; } } +void DisplayListBuilder::intersect(const SkRect& rect) { + SkRect devClipBounds = getTransform().mapRect(rect); + if (!current_layer_->clip_bounds.intersect(devClipBounds)) { + current_layer_->clip_bounds.setEmpty(); + } +} SkRect DisplayListBuilder::getLocalClipBounds() { SkM44 inverse; if (current_layer_->matrix.invert(&inverse)) { diff --git a/display_list/display_list_builder.h b/display_list/display_list_builder.h index 152c0eb64af3f..ce785c594b7ad 100644 --- a/display_list/display_list_builder.h +++ b/display_list/display_list_builder.h @@ -361,6 +361,7 @@ class DisplayListBuilder final : public virtual Dispatcher, void setAttributesFromDlPaint(const DlPaint& paint, const DisplayListAttributeFlags flags); + void intersect(const SkRect& rect); // kInvalidSigma is used to indicate that no MaskBlur is currently set. static constexpr SkScalar kInvalidSigma = 0.0; diff --git a/display_list/display_list_unittests.cc b/display_list/display_list_unittests.cc index 2a6b01d396b6a..a3db273606562 100644 --- a/display_list/display_list_unittests.cc +++ b/display_list/display_list_unittests.cc @@ -2116,6 +2116,25 @@ TEST(DisplayList, ClipRectAffectsClipBounds) { ASSERT_EQ(builder.getDestinationClipBounds(), initialDestinationBounds); } +TEST(DisplayList, ClipRectAffectsClipBoundsWithMatrix) { + DisplayListBuilder builder; + SkRect clipBounds1 = SkRect::MakeLTRB(0, 0, 10, 10); + SkRect clipBounds2 = SkRect::MakeLTRB(10, 10, 20, 20); + builder.save(); + builder.clipRect(clipBounds1, SkClipOp::kIntersect, false); + builder.translate(10, 0); + builder.clipRect(clipBounds1, SkClipOp::kIntersect, false); + ASSERT_TRUE(builder.getDestinationClipBounds().isEmpty()); + builder.restore(); + + builder.save(); + builder.clipRect(clipBounds1, SkClipOp::kIntersect, false); + builder.translate(-10, -10); + builder.clipRect(clipBounds2, SkClipOp::kIntersect, false); + ASSERT_EQ(builder.getDestinationClipBounds(), clipBounds1); + builder.restore(); +} + TEST(DisplayList, ClipRRectAffectsClipBounds) { DisplayListBuilder builder; SkRect clipBounds = SkRect::MakeLTRB(10.2, 11.3, 20.4, 25.7); @@ -2156,6 +2175,28 @@ TEST(DisplayList, ClipRRectAffectsClipBounds) { ASSERT_EQ(builder.getDestinationClipBounds(), initialDestinationBounds); } +TEST(DisplayList, ClipRRectAffectsClipBoundsWithMatrix) { + DisplayListBuilder builder; + SkRect clipBounds1 = SkRect::MakeLTRB(0, 0, 10, 10); + SkRect clipBounds2 = SkRect::MakeLTRB(10, 10, 20, 20); + SkRRect clip1 = SkRRect::MakeRectXY(clipBounds1, 3, 2); + SkRRect clip2 = SkRRect::MakeRectXY(clipBounds2, 3, 2); + + builder.save(); + builder.clipRRect(clip1, SkClipOp::kIntersect, false); + builder.translate(10, 0); + builder.clipRRect(clip1, SkClipOp::kIntersect, false); + ASSERT_TRUE(builder.getDestinationClipBounds().isEmpty()); + builder.restore(); + + builder.save(); + builder.clipRRect(clip1, SkClipOp::kIntersect, false); + builder.translate(-10, -10); + builder.clipRRect(clip2, SkClipOp::kIntersect, false); + ASSERT_EQ(builder.getDestinationClipBounds(), clipBounds1); + builder.restore(); +} + TEST(DisplayList, ClipPathAffectsClipBounds) { DisplayListBuilder builder; SkPath clip = SkPath().addCircle(10.2, 11.3, 2).addCircle(20.4, 25.7, 2); @@ -2196,6 +2237,27 @@ TEST(DisplayList, ClipPathAffectsClipBounds) { ASSERT_EQ(builder.getDestinationClipBounds(), initialDestinationBounds); } +TEST(DisplayList, ClipPathAffectsClipBoundsWithMatrix) { + DisplayListBuilder builder; + SkRect clipBounds = SkRect::MakeLTRB(0, 0, 10, 10); + SkPath clip1 = SkPath().addCircle(2.5, 2.5, 2.5).addCircle(7.5, 7.5, 2.5); + SkPath clip2 = SkPath().addCircle(12.5, 12.5, 2.5).addCircle(17.5, 17.5, 2.5); + + builder.save(); + builder.clipPath(clip1, SkClipOp::kIntersect, false); + builder.translate(10, 0); + builder.clipPath(clip1, SkClipOp::kIntersect, false); + ASSERT_TRUE(builder.getDestinationClipBounds().isEmpty()); + builder.restore(); + + builder.save(); + builder.clipPath(clip1, SkClipOp::kIntersect, false); + builder.translate(-10, -10); + builder.clipPath(clip2, SkClipOp::kIntersect, false); + ASSERT_EQ(builder.getDestinationClipBounds(), clipBounds); + builder.restore(); +} + TEST(DisplayList, DiffClipRectDoesNotAffectClipBounds) { DisplayListBuilder builder; SkRect diff_clip = SkRect::MakeLTRB(0, 0, 15, 15); @@ -2252,6 +2314,30 @@ TEST(DisplayList, DiffClipPathDoesNotAffectClipBounds) { ASSERT_EQ(builder.getDestinationClipBounds(), initialDestinationBounds); } +TEST(DisplayList, ClipPathWithInvertFillTypeDoesNotAffectClipBounds) { + SkRect cull_rect = SkRect::MakeLTRB(0, 0, 100.0, 100.0); + DisplayListBuilder builder(cull_rect); + SkPath clip = SkPath().addCircle(10.2, 11.3, 2).addCircle(20.4, 25.7, 2); + clip.setFillType(SkPathFillType::kInverseWinding); + builder.clipPath(clip, SkClipOp::kIntersect, false); + + ASSERT_EQ(builder.getLocalClipBounds(), cull_rect); + ASSERT_EQ(builder.getDestinationClipBounds(), cull_rect); +} + +TEST(DisplayList, DiffClipPathWithInvertFillTypeAffectsClipBounds) { + SkRect cull_rect = SkRect::MakeLTRB(0, 0, 100.0, 100.0); + DisplayListBuilder builder(cull_rect); + SkPath clip = SkPath().addCircle(10.2, 11.3, 2).addCircle(20.4, 25.7, 2); + clip.setFillType(SkPathFillType::kInverseWinding); + SkRect clip_bounds = SkRect::MakeLTRB(8.2, 9.3, 22.4, 27.7); + SkRect clip_expanded_bounds = SkRect::MakeLTRB(8, 9, 23, 28); + builder.clipPath(clip, SkClipOp::kDifference, false); + + ASSERT_EQ(builder.getLocalClipBounds(), clip_expanded_bounds); + ASSERT_EQ(builder.getDestinationClipBounds(), clip_bounds); +} + TEST(DisplayList, FlatDrawPointsProducesBounds) { SkPoint horizontal_points[2] = {{10, 10}, {20, 10}}; SkPoint vertical_points[2] = {{10, 10}, {10, 20}}; diff --git a/testing/dart/canvas_test.dart b/testing/dart/canvas_test.dart index 4de620f9750cd..0e3b880c277bb 100644 --- a/testing/dart/canvas_test.dart +++ b/testing/dart/canvas_test.dart @@ -731,6 +731,27 @@ void main() { expect(canvas.getDestinationClipBounds(), initialDestinationBounds); }); + test('Canvas.clipRect with matrix affects canvas.getClipBounds', () async { + final PictureRecorder recorder = PictureRecorder(); + final Canvas canvas = Canvas(recorder); + const Rect clipBounds1 = Rect.fromLTRB(0.0, 0.0, 10.0, 10.0); + const Rect clipBounds2 = Rect.fromLTRB(10.0, 10.0, 20.0, 20.0); + + canvas.save(); + canvas.clipRect(clipBounds1); + canvas.translate(0, 10.0); + canvas.clipRect(clipBounds1); + expect(canvas.getDestinationClipBounds().isEmpty, isTrue); + canvas.restore(); + + canvas.save(); + canvas.clipRect(clipBounds1); + canvas.translate(-10.0, -10.0); + canvas.clipRect(clipBounds2); + expect(canvas.getDestinationClipBounds(), clipBounds1); + canvas.restore(); + }); + test('Canvas.clipRRect affects canvas.getClipBounds', () async { final PictureRecorder recorder = PictureRecorder(); final Canvas canvas = Canvas(recorder); @@ -772,6 +793,29 @@ void main() { expect(canvas.getDestinationClipBounds(), initialDestinationBounds); }); + test('Canvas.clipRRect with matrix affects canvas.getClipBounds', () async { + final PictureRecorder recorder = PictureRecorder(); + final Canvas canvas = Canvas(recorder); + const Rect clipBounds1 = Rect.fromLTRB(0.0, 0.0, 10.0, 10.0); + const Rect clipBounds2 = Rect.fromLTRB(10.0, 10.0, 20.0, 20.0); + final RRect clip1 = RRect.fromRectAndRadius(clipBounds1, const Radius.circular(3)); + final RRect clip2 = RRect.fromRectAndRadius(clipBounds2, const Radius.circular(3)); + + canvas.save(); + canvas.clipRRect(clip1); + canvas.translate(0, 10.0); + canvas.clipRRect(clip1); + expect(canvas.getDestinationClipBounds().isEmpty, isTrue); + canvas.restore(); + + canvas.save(); + canvas.clipRRect(clip1); + canvas.translate(-10.0, -10.0); + canvas.clipRRect(clip2); + expect(canvas.getDestinationClipBounds(), clipBounds1); + canvas.restore(); + }); + test('Canvas.clipPath affects canvas.getClipBounds', () async { final PictureRecorder recorder = PictureRecorder(); final Canvas canvas = Canvas(recorder); @@ -813,6 +857,29 @@ void main() { expect(canvas.getDestinationClipBounds(), initialDestinationBounds); }); + test('Canvas.clipPath with matrix affects canvas.getClipBounds', () async { + final PictureRecorder recorder = PictureRecorder(); + final Canvas canvas = Canvas(recorder); + const Rect clipBounds1 = Rect.fromLTRB(0.0, 0.0, 10.0, 10.0); + const Rect clipBounds2 = Rect.fromLTRB(10.0, 10.0, 20.0, 20.0); + final Path clip1 = Path()..addRect(clipBounds1)..addOval(clipBounds1); + final Path clip2 = Path()..addRect(clipBounds2)..addOval(clipBounds2); + + canvas.save(); + canvas.clipPath(clip1); + canvas.translate(0, 10.0); + canvas.clipPath(clip1); + expect(canvas.getDestinationClipBounds().isEmpty, isTrue); + canvas.restore(); + + canvas.save(); + canvas.clipPath(clip1); + canvas.translate(-10.0, -10.0); + canvas.clipPath(clip2); + expect(canvas.getDestinationClipBounds(), clipBounds1); + canvas.restore(); + }); + test('Canvas.clipRect(diff) does not affect canvas.getClipBounds', () async { final PictureRecorder recorder = PictureRecorder(); final Canvas canvas = Canvas(recorder);