[Engine] Add RoundSuperellipse to drawing OP (#160883)

This PR adds support for clipping round superellipse to the engine.

For what a rounded superellipse is, see [this design
doc](https://docs.google.com/document/d/1CJXULKJGQt22FOFsrlm2TKVjKBtif1yU4U50cMfL6Kc/edit?tab=t.0).
Video demos can be found at https://github.com/flutter/engine/pull/56726
and https://github.com/flutter/flutter/pull/161409.

Only impeller can actually render it. On Skia and Web, this shape falls
back to `RRect`.

Part of https://github.com/flutter/flutter/issues/139321 and
https://github.com/flutter/flutter/issues/13914, also related to
https://github.com/flutter/flutter/issues/91523.

## Pre-launch Checklist

- [ ] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [ ] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [ ] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [ ] I signed the [CLA].
- [ ] I listed at least one issue that this PR fixes in the description
above.
- [ ] I updated/added relevant documentation (doc comments with `///`).
- [ ] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [ ] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [ ] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
This commit is contained in:
Tong Mu 2025-02-21 15:58:26 -08:00 committed by GitHub
parent 7d23780c7f
commit 5b7a3c8b20
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
77 changed files with 3038 additions and 697 deletions

View File

@ -58,6 +58,7 @@
../../../flutter/flow/layers/clip_path_layer_unittests.cc ../../../flutter/flow/layers/clip_path_layer_unittests.cc
../../../flutter/flow/layers/clip_rect_layer_unittests.cc ../../../flutter/flow/layers/clip_rect_layer_unittests.cc
../../../flutter/flow/layers/clip_rrect_layer_unittests.cc ../../../flutter/flow/layers/clip_rrect_layer_unittests.cc
../../../flutter/flow/layers/clip_rsuperellipse_layer_unittests.cc
../../../flutter/flow/layers/color_filter_layer_unittests.cc ../../../flutter/flow/layers/color_filter_layer_unittests.cc
../../../flutter/flow/layers/container_layer_unittests.cc ../../../flutter/flow/layers/container_layer_unittests.cc
../../../flutter/flow/layers/display_list_layer_unittests.cc ../../../flutter/flow/layers/display_list_layer_unittests.cc

View File

@ -41379,6 +41379,8 @@ ORIGIN: ../../../flutter/flow/layers/clip_rect_layer.cc + ../../../flutter/LICEN
ORIGIN: ../../../flutter/flow/layers/clip_rect_layer.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/flow/layers/clip_rect_layer.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/flow/layers/clip_rrect_layer.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/flow/layers/clip_rrect_layer.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/flow/layers/clip_rrect_layer.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/flow/layers/clip_rrect_layer.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/flow/layers/clip_rsuperellipse_layer.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/flow/layers/clip_rsuperellipse_layer.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/flow/layers/clip_shape_layer.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/flow/layers/clip_shape_layer.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/flow/layers/color_filter_layer.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/flow/layers/color_filter_layer.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/flow/layers/color_filter_layer.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/flow/layers/color_filter_layer.h + ../../../flutter/LICENSE
@ -42490,6 +42492,8 @@ ORIGIN: ../../../flutter/lib/ui/painting/picture_recorder.cc + ../../../flutter/
ORIGIN: ../../../flutter/lib/ui/painting/picture_recorder.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/ui/painting/picture_recorder.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/ui/painting/rrect.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/ui/painting/rrect.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/ui/painting/rrect.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/ui/painting/rrect.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/ui/painting/rsuperellipse.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/ui/painting/rsuperellipse.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/ui/painting/shader.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/ui/painting/shader.cc + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/ui/painting/shader.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/ui/painting/shader.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/ui/painting/single_frame_codec.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/ui/painting/single_frame_codec.cc + ../../../flutter/LICENSE
@ -44342,6 +44346,8 @@ FILE: ../../../flutter/flow/layers/clip_rect_layer.cc
FILE: ../../../flutter/flow/layers/clip_rect_layer.h FILE: ../../../flutter/flow/layers/clip_rect_layer.h
FILE: ../../../flutter/flow/layers/clip_rrect_layer.cc FILE: ../../../flutter/flow/layers/clip_rrect_layer.cc
FILE: ../../../flutter/flow/layers/clip_rrect_layer.h FILE: ../../../flutter/flow/layers/clip_rrect_layer.h
FILE: ../../../flutter/flow/layers/clip_rsuperellipse_layer.cc
FILE: ../../../flutter/flow/layers/clip_rsuperellipse_layer.h
FILE: ../../../flutter/flow/layers/clip_shape_layer.h FILE: ../../../flutter/flow/layers/clip_shape_layer.h
FILE: ../../../flutter/flow/layers/color_filter_layer.cc FILE: ../../../flutter/flow/layers/color_filter_layer.cc
FILE: ../../../flutter/flow/layers/color_filter_layer.h FILE: ../../../flutter/flow/layers/color_filter_layer.h
@ -45457,6 +45463,8 @@ FILE: ../../../flutter/lib/ui/painting/picture_recorder.cc
FILE: ../../../flutter/lib/ui/painting/picture_recorder.h FILE: ../../../flutter/lib/ui/painting/picture_recorder.h
FILE: ../../../flutter/lib/ui/painting/rrect.cc FILE: ../../../flutter/lib/ui/painting/rrect.cc
FILE: ../../../flutter/lib/ui/painting/rrect.h FILE: ../../../flutter/lib/ui/painting/rrect.h
FILE: ../../../flutter/lib/ui/painting/rsuperellipse.cc
FILE: ../../../flutter/lib/ui/painting/rsuperellipse.h
FILE: ../../../flutter/lib/ui/painting/shader.cc FILE: ../../../flutter/lib/ui/painting/shader.cc
FILE: ../../../flutter/lib/ui/painting/shader.h FILE: ../../../flutter/lib/ui/painting/shader.h
FILE: ../../../flutter/lib/ui/painting/single_frame_codec.cc FILE: ../../../flutter/lib/ui/painting/single_frame_codec.cc

View File

@ -335,6 +335,12 @@ void DisplayListGLComplexityCalculator::GLHelper::drawDiffRoundRect(
AccumulateComplexity(complexity); AccumulateComplexity(complexity);
} }
void DisplayListGLComplexityCalculator::GLHelper::drawRoundSuperellipse(
const DlRoundSuperellipse& rse) {
// Drawing RSEs on Skia falls back to RRect.
drawRoundRect(rse.ToApproximateRoundRect());
}
void DisplayListGLComplexityCalculator::GLHelper::drawPath(const DlPath& path) { void DisplayListGLComplexityCalculator::GLHelper::drawPath(const DlPath& path) {
if (IsComplex()) { if (IsComplex()) {
return; return;

View File

@ -51,6 +51,7 @@ class DisplayListGLComplexityCalculator
void drawRoundRect(const DlRoundRect& rrect) override; void drawRoundRect(const DlRoundRect& rrect) override;
void drawDiffRoundRect(const DlRoundRect& outer, void drawDiffRoundRect(const DlRoundRect& outer,
const DlRoundRect& inner) override; const DlRoundRect& inner) override;
void drawRoundSuperellipse(const DlRoundSuperellipse& rse) override;
void drawPath(const DlPath& path) override; void drawPath(const DlPath& path) override;
void drawArc(const DlRect& oval_bounds, void drawArc(const DlRect& oval_bounds,
DlScalar start_degrees, DlScalar start_degrees,

View File

@ -329,6 +329,12 @@ void DisplayListMetalComplexityCalculator::MetalHelper::drawDiffRoundRect(
AccumulateComplexity(complexity); AccumulateComplexity(complexity);
} }
void DisplayListMetalComplexityCalculator::MetalHelper::drawRoundSuperellipse(
const DlRoundSuperellipse& rse) {
// Drawing RSEs on Skia falls back to RRect.
drawRoundRect(rse.ToApproximateRoundRect());
}
void DisplayListMetalComplexityCalculator::MetalHelper::drawPath( void DisplayListMetalComplexityCalculator::MetalHelper::drawPath(
const DlPath& path) { const DlPath& path) {
if (IsComplex()) { if (IsComplex()) {

View File

@ -51,6 +51,7 @@ class DisplayListMetalComplexityCalculator
void drawRoundRect(const DlRoundRect& rrect) override; void drawRoundRect(const DlRoundRect& rrect) override;
void drawDiffRoundRect(const DlRoundRect& outer, void drawDiffRoundRect(const DlRoundRect& outer,
const DlRoundRect& inner) override; const DlRoundRect& inner) override;
void drawRoundSuperellipse(const DlRoundSuperellipse& rse) override;
void drawPath(const DlPath& path) override; void drawPath(const DlPath& path) override;
void drawArc(const DlRect& oval_bounds, void drawArc(const DlRect& oval_bounds,
DlScalar start_degrees, DlScalar start_degrees,

View File

@ -300,10 +300,12 @@ DisplayListOpCategory DisplayList::GetOpCategory(DisplayListOpType type) {
case DisplayListOpType::kClipIntersectRect: case DisplayListOpType::kClipIntersectRect:
case DisplayListOpType::kClipIntersectOval: case DisplayListOpType::kClipIntersectOval:
case DisplayListOpType::kClipIntersectRoundRect: case DisplayListOpType::kClipIntersectRoundRect:
case DisplayListOpType::kClipIntersectRoundSuperellipse:
case DisplayListOpType::kClipIntersectPath: case DisplayListOpType::kClipIntersectPath:
case DisplayListOpType::kClipDifferenceRect: case DisplayListOpType::kClipDifferenceRect:
case DisplayListOpType::kClipDifferenceOval: case DisplayListOpType::kClipDifferenceOval:
case DisplayListOpType::kClipDifferenceRoundRect: case DisplayListOpType::kClipDifferenceRoundRect:
case DisplayListOpType::kClipDifferenceRoundSuperellipse:
case DisplayListOpType::kClipDifferencePath: case DisplayListOpType::kClipDifferencePath:
return DisplayListOpCategory::kClip; return DisplayListOpCategory::kClip;
@ -316,6 +318,7 @@ DisplayListOpCategory DisplayList::GetOpCategory(DisplayListOpType type) {
case DisplayListOpType::kDrawCircle: case DisplayListOpType::kDrawCircle:
case DisplayListOpType::kDrawRoundRect: case DisplayListOpType::kDrawRoundRect:
case DisplayListOpType::kDrawDiffRoundRect: case DisplayListOpType::kDrawDiffRoundRect:
case DisplayListOpType::kDrawRoundSuperellipse:
case DisplayListOpType::kDrawArc: case DisplayListOpType::kDrawArc:
case DisplayListOpType::kDrawPath: case DisplayListOpType::kDrawPath:
case DisplayListOpType::kDrawPoints: case DisplayListOpType::kDrawPoints:

View File

@ -52,88 +52,91 @@
namespace flutter { namespace flutter {
#define FOR_EACH_DISPLAY_LIST_OP(V) \ #define FOR_EACH_DISPLAY_LIST_OP(V) \
V(SetAntiAlias) \ V(SetAntiAlias) \
V(SetInvertColors) \ V(SetInvertColors) \
\ \
V(SetStrokeCap) \ V(SetStrokeCap) \
V(SetStrokeJoin) \ V(SetStrokeJoin) \
\ \
V(SetStyle) \ V(SetStyle) \
V(SetStrokeWidth) \ V(SetStrokeWidth) \
V(SetStrokeMiter) \ V(SetStrokeMiter) \
\ \
V(SetColor) \ V(SetColor) \
V(SetBlendMode) \ V(SetBlendMode) \
\ \
V(ClearColorFilter) \ V(ClearColorFilter) \
V(SetPodColorFilter) \ V(SetPodColorFilter) \
\ \
V(ClearColorSource) \ V(ClearColorSource) \
V(SetPodColorSource) \ V(SetPodColorSource) \
V(SetImageColorSource) \ V(SetImageColorSource) \
V(SetRuntimeEffectColorSource) \ V(SetRuntimeEffectColorSource) \
\ \
V(ClearImageFilter) \ V(ClearImageFilter) \
V(SetPodImageFilter) \ V(SetPodImageFilter) \
V(SetSharedImageFilter) \ V(SetSharedImageFilter) \
\ \
V(ClearMaskFilter) \ V(ClearMaskFilter) \
V(SetPodMaskFilter) \ V(SetPodMaskFilter) \
\ \
V(Save) \ V(Save) \
V(SaveLayer) \ V(SaveLayer) \
V(SaveLayerBackdrop) \ V(SaveLayerBackdrop) \
V(Restore) \ V(Restore) \
\ \
V(Translate) \ V(Translate) \
V(Scale) \ V(Scale) \
V(Rotate) \ V(Rotate) \
V(Skew) \ V(Skew) \
V(Transform2DAffine) \ V(Transform2DAffine) \
V(TransformFullPerspective) \ V(TransformFullPerspective) \
V(TransformReset) \ V(TransformReset) \
\ \
V(ClipIntersectRect) \ V(ClipIntersectRect) \
V(ClipIntersectOval) \ V(ClipIntersectOval) \
V(ClipIntersectRoundRect) \ V(ClipIntersectRoundRect) \
V(ClipIntersectPath) \ V(ClipIntersectRoundSuperellipse) \
V(ClipDifferenceRect) \ V(ClipIntersectPath) \
V(ClipDifferenceOval) \ V(ClipDifferenceRect) \
V(ClipDifferenceRoundRect) \ V(ClipDifferenceOval) \
V(ClipDifferencePath) \ V(ClipDifferenceRoundRect) \
\ V(ClipDifferenceRoundSuperellipse) \
V(DrawPaint) \ V(ClipDifferencePath) \
V(DrawColor) \ \
\ V(DrawPaint) \
V(DrawLine) \ V(DrawColor) \
V(DrawDashedLine) \ \
V(DrawRect) \ V(DrawLine) \
V(DrawOval) \ V(DrawDashedLine) \
V(DrawCircle) \ V(DrawRect) \
V(DrawRoundRect) \ V(DrawOval) \
V(DrawDiffRoundRect) \ V(DrawCircle) \
V(DrawArc) \ V(DrawRoundRect) \
V(DrawPath) \ V(DrawDiffRoundRect) \
\ V(DrawRoundSuperellipse) \
V(DrawPoints) \ V(DrawArc) \
V(DrawLines) \ V(DrawPath) \
V(DrawPolygon) \ \
V(DrawVertices) \ V(DrawPoints) \
\ V(DrawLines) \
V(DrawImage) \ V(DrawPolygon) \
V(DrawImageWithAttr) \ V(DrawVertices) \
V(DrawImageRect) \ \
V(DrawImageNine) \ V(DrawImage) \
V(DrawImageNineWithAttr) \ V(DrawImageWithAttr) \
V(DrawAtlas) \ V(DrawImageRect) \
V(DrawAtlasCulled) \ V(DrawImageNine) \
\ V(DrawImageNineWithAttr) \
V(DrawDisplayList) \ V(DrawAtlas) \
V(DrawTextBlob) \ V(DrawAtlasCulled) \
V(DrawTextFrame) \ \
\ V(DrawDisplayList) \
V(DrawShadow) \ V(DrawTextBlob) \
V(DrawTextFrame) \
\
V(DrawShadow) \
V(DrawShadowTransparentOccluder) V(DrawShadowTransparentOccluder)
#define DL_OP_TO_ENUM_VALUE(name) k##name, #define DL_OP_TO_ENUM_VALUE(name) k##name,

View File

@ -4512,7 +4512,7 @@ TEST_F(DisplayListTest, DrawDisplayListForwardsBackdropFlag) {
#define CLIP_EXPECTOR(name) ClipExpector name(__FILE__, __LINE__) #define CLIP_EXPECTOR(name) ClipExpector name(__FILE__, __LINE__)
struct ClipExpectation { struct ClipExpectation {
std::variant<DlRect, DlRoundRect, DlPath> shape; std::variant<DlRect, DlRoundRect, DlRoundSuperellipse, DlPath> shape;
bool is_oval; bool is_oval;
DlClipOp clip_op; DlClipOp clip_op;
bool is_aa; bool is_aa;
@ -4524,6 +4524,8 @@ struct ClipExpectation {
case 1: case 1:
return "DlRoundRect"; return "DlRoundRect";
case 2: case 2:
return "DlRoundSuperellipse";
case 3:
return "DlPath"; return "DlPath";
default: default:
return "Unknown"; return "Unknown";
@ -4632,6 +4634,11 @@ class ClipExpector : public virtual DlOpReceiver,
bool is_aa) override { bool is_aa) override {
check(rrect, clip_op, is_aa); check(rrect, clip_op, is_aa);
} }
void clipRoundSuperellipse(const DlRoundSuperellipse& rse,
DlClipOp clip_op,
bool is_aa) override {
check(rse, clip_op, is_aa);
}
void clipPath(const DlPath& path, DlClipOp clip_op, bool is_aa) override { void clipPath(const DlPath& path, DlClipOp clip_op, bool is_aa) override {
check(path, clip_op, is_aa); check(path, clip_op, is_aa);
} }

View File

@ -1025,6 +1025,42 @@ void DisplayListBuilder::ClipRoundRect(const DlRoundRect& rrect,
break; break;
} }
} }
void DisplayListBuilder::ClipRoundSuperellipse(const DlRoundSuperellipse& rse,
DlClipOp clip_op,
bool is_aa) {
if (rse.IsRect()) {
ClipRect(rse.GetBounds(), clip_op, is_aa);
return;
}
if (rse.IsOval()) {
ClipOval(rse.GetBounds(), clip_op, is_aa);
return;
}
if (current_info().is_nop) {
return;
}
if (current_info().has_valid_clip && clip_op == DlClipOp::kIntersect &&
layer_local_state().rsuperellipse_covers_cull(rse)) {
return;
}
global_state().clipRSuperellipse(rse, clip_op, is_aa);
layer_local_state().clipRSuperellipse(rse, clip_op, is_aa);
if (global_state().is_cull_rect_empty() ||
layer_local_state().is_cull_rect_empty()) {
current_info().is_nop = true;
return;
}
current_info().has_valid_clip = true;
checkForDeferredSave();
switch (clip_op) {
case DlClipOp::kIntersect:
Push<ClipIntersectRoundSuperellipseOp>(0, rse, is_aa);
break;
case DlClipOp::kDifference:
Push<ClipDifferenceRoundSuperellipseOp>(0, rse, is_aa);
break;
}
}
void DisplayListBuilder::ClipPath(const DlPath& path, void DisplayListBuilder::ClipPath(const DlPath& path,
DlClipOp clip_op, DlClipOp clip_op,
bool is_aa) { bool is_aa) {
@ -1214,6 +1250,27 @@ void DisplayListBuilder::DrawDiffRoundRect(const DlRoundRect& outer,
SetAttributesFromPaint(paint, DisplayListOpFlags::kDrawDRRectFlags); SetAttributesFromPaint(paint, DisplayListOpFlags::kDrawDRRectFlags);
drawDiffRoundRect(outer, inner); drawDiffRoundRect(outer, inner);
} }
void DisplayListBuilder::drawRoundSuperellipse(const DlRoundSuperellipse& rse) {
if (rse.IsRect()) {
drawRect(rse.GetBounds());
} else if (rse.IsOval()) {
drawOval(rse.GetBounds());
} else {
DisplayListAttributeFlags flags = kDrawRSuperellipseFlags;
OpResult result = PaintResult(current_, flags);
if (result != OpResult::kNoEffect &&
AccumulateOpBounds(rse.GetBounds(), flags)) {
Push<DrawRoundSuperellipseOp>(0, rse);
CheckLayerOpacityCompatibility();
UpdateLayerResult(result);
}
}
}
void DisplayListBuilder::DrawRoundSuperellipse(const DlRoundSuperellipse& rse,
const DlPaint& paint) {
SetAttributesFromPaint(paint, DisplayListOpFlags::kDrawRSuperellipseFlags);
drawRoundSuperellipse(rse);
}
void DisplayListBuilder::drawPath(const DlPath& path) { void DisplayListBuilder::drawPath(const DlPath& path) {
DisplayListAttributeFlags flags = kDrawPathFlags; DisplayListAttributeFlags flags = kDrawPathFlags;
OpResult result = PaintResult(current_, flags); OpResult result = PaintResult(current_, flags);

View File

@ -118,6 +118,10 @@ class DisplayListBuilder final : public virtual DlCanvas,
DlClipOp clip_op = DlClipOp::kIntersect, DlClipOp clip_op = DlClipOp::kIntersect,
bool is_aa = false) override; bool is_aa = false) override;
// |DlCanvas| // |DlCanvas|
void ClipRoundSuperellipse(const DlRoundSuperellipse& rse,
DlClipOp clip_op = DlClipOp::kIntersect,
bool is_aa = false) override;
// |DlCanvas|
void ClipPath(const DlPath& path, void ClipPath(const DlPath& path,
DlClipOp clip_op = DlClipOp::kIntersect, DlClipOp clip_op = DlClipOp::kIntersect,
bool is_aa = false) override; bool is_aa = false) override;
@ -172,6 +176,9 @@ class DisplayListBuilder final : public virtual DlCanvas,
const DlRoundRect& inner, const DlRoundRect& inner,
const DlPaint& paint) override; const DlPaint& paint) override;
// |DlCanvas| // |DlCanvas|
void DrawRoundSuperellipse(const DlRoundSuperellipse& rse,
const DlPaint& paint) override;
// |DlCanvas|
void DrawPath(const DlPath& path, const DlPaint& paint) override; void DrawPath(const DlPath& path, const DlPaint& paint) override;
// |DlCanvas| // |DlCanvas|
void DrawArc(const DlRect& bounds, void DrawArc(const DlRect& bounds,
@ -409,6 +416,12 @@ class DisplayListBuilder final : public virtual DlCanvas,
ClipRoundRect(rrect, clip_op, is_aa); ClipRoundRect(rrect, clip_op, is_aa);
} }
// |DlOpReceiver| // |DlOpReceiver|
void clipRoundSuperellipse(const DlRoundSuperellipse& rse,
DlClipOp clip_op,
bool is_aa) override {
ClipRoundSuperellipse(rse, clip_op, is_aa);
}
// |DlOpReceiver|
void clipPath(const DlPath& path, DlClipOp clip_op, bool is_aa) override { void clipPath(const DlPath& path, DlClipOp clip_op, bool is_aa) override {
ClipPath(path, clip_op, is_aa); ClipPath(path, clip_op, is_aa);
} }
@ -438,6 +451,8 @@ class DisplayListBuilder final : public virtual DlCanvas,
void drawDiffRoundRect(const DlRoundRect& outer, void drawDiffRoundRect(const DlRoundRect& outer,
const DlRoundRect& inner) override; const DlRoundRect& inner) override;
// |DlOpReceiver| // |DlOpReceiver|
void drawRoundSuperellipse(const DlRoundSuperellipse& rse) override;
// |DlOpReceiver|
void drawPath(const DlPath& path) override; void drawPath(const DlPath& path) override;
// |DlOpReceiver| // |DlOpReceiver|
void drawArc(const DlRect& bounds, void drawArc(const DlRect& bounds,

View File

@ -85,6 +85,9 @@ class DlCanvas {
virtual void ClipRoundRect(const DlRoundRect& rrect, virtual void ClipRoundRect(const DlRoundRect& rrect,
DlClipOp clip_op = DlClipOp::kIntersect, DlClipOp clip_op = DlClipOp::kIntersect,
bool is_aa = false) = 0; bool is_aa = false) = 0;
virtual void ClipRoundSuperellipse(const DlRoundSuperellipse& rse,
DlClipOp clip_op = DlClipOp::kIntersect,
bool is_aa = false) = 0;
virtual void ClipPath(const DlPath& path, virtual void ClipPath(const DlPath& path,
DlClipOp clip_op = DlClipOp::kIntersect, DlClipOp clip_op = DlClipOp::kIntersect,
bool is_aa = false) = 0; bool is_aa = false) = 0;
@ -125,6 +128,8 @@ class DlCanvas {
virtual void DrawDiffRoundRect(const DlRoundRect& outer, virtual void DrawDiffRoundRect(const DlRoundRect& outer,
const DlRoundRect& inner, const DlRoundRect& inner,
const DlPaint& paint) = 0; const DlPaint& paint) = 0;
virtual void DrawRoundSuperellipse(const DlRoundSuperellipse& rse,
const DlPaint& paint) = 0;
virtual void DrawPath(const DlPath& path, const DlPaint& paint) = 0; virtual void DrawPath(const DlPath& path, const DlPaint& paint) = 0;
virtual void DrawArc(const DlRect& bounds, virtual void DrawArc(const DlRect& bounds,
DlScalar start, DlScalar start,

View File

@ -323,6 +323,10 @@ class DisplayListOpFlags : DisplayListFlags {
kBasePaintFlags | // kBasePaintFlags | //
kBaseStrokeOrFillFlags // kBaseStrokeOrFillFlags //
}; };
static constexpr DisplayListAttributeFlags kDrawRSuperellipseFlags{
kBasePaintFlags | //
kBaseStrokeOrFillFlags //
};
static constexpr DisplayListAttributeFlags kDrawPathFlags{ static constexpr DisplayListAttributeFlags kDrawPathFlags{
kBasePaintFlags | // kBasePaintFlags | //
kBaseStrokeOrFillFlags | // kBaseStrokeOrFillFlags | //

View File

@ -291,6 +291,9 @@ class DlOpReceiver {
virtual void clipRoundRect(const DlRoundRect& rrect, virtual void clipRoundRect(const DlRoundRect& rrect,
DlClipOp clip_op, DlClipOp clip_op,
bool is_aa) = 0; bool is_aa) = 0;
virtual void clipRoundSuperellipse(const DlRoundSuperellipse& rse,
DlClipOp clip_op,
bool is_aa) = 0;
virtual void clipPath(const DlPath& path, DlClipOp clip_op, bool is_aa) = 0; virtual void clipPath(const DlPath& path, DlClipOp clip_op, bool is_aa) = 0;
// The following rendering methods all take their rendering attributes // The following rendering methods all take their rendering attributes
@ -313,6 +316,7 @@ class DlOpReceiver {
virtual void drawRoundRect(const DlRoundRect& rrect) = 0; virtual void drawRoundRect(const DlRoundRect& rrect) = 0;
virtual void drawDiffRoundRect(const DlRoundRect& outer, virtual void drawDiffRoundRect(const DlRoundRect& outer,
const DlRoundRect& inner) = 0; const DlRoundRect& inner) = 0;
virtual void drawRoundSuperellipse(const DlRoundSuperellipse& rse) = 0;
virtual void drawPath(const DlPath& path) = 0; virtual void drawPath(const DlPath& path) = 0;
virtual void drawArc(const DlRect& oval_bounds, virtual void drawArc(const DlRect& oval_bounds,
DlScalar start_degrees, DlScalar start_degrees,

View File

@ -486,6 +486,7 @@ struct TransformResetOp final : TransformClipOpBase {
// DlRect is 16 more bytes, which packs efficiently into 24 bytes total // DlRect is 16 more bytes, which packs efficiently into 24 bytes total
// DlRoundRect is 48 more bytes, which rounds up to 48 bytes // DlRoundRect is 48 more bytes, which rounds up to 48 bytes
// which packs into 56 bytes total // which packs into 56 bytes total
// DlRoundSuperellipse is the same as DlRoundRect
// CacheablePath is 128 more bytes, which packs efficiently into 136 bytes total // CacheablePath is 128 more bytes, which packs efficiently into 136 bytes total
// //
// We could pack the clip_op and the bool both into the free 4 bytes after // We could pack the clip_op and the bool both into the free 4 bytes after
@ -509,9 +510,11 @@ struct TransformResetOp final : TransformClipOpBase {
DEFINE_CLIP_SHAPE_OP(Rect, DlRect, Intersect) DEFINE_CLIP_SHAPE_OP(Rect, DlRect, Intersect)
DEFINE_CLIP_SHAPE_OP(Oval, DlRect, Intersect) DEFINE_CLIP_SHAPE_OP(Oval, DlRect, Intersect)
DEFINE_CLIP_SHAPE_OP(RoundRect, DlRoundRect, Intersect) DEFINE_CLIP_SHAPE_OP(RoundRect, DlRoundRect, Intersect)
DEFINE_CLIP_SHAPE_OP(RoundSuperellipse, DlRoundSuperellipse, Intersect)
DEFINE_CLIP_SHAPE_OP(Rect, DlRect, Difference) DEFINE_CLIP_SHAPE_OP(Rect, DlRect, Difference)
DEFINE_CLIP_SHAPE_OP(Oval, DlRect, Difference) DEFINE_CLIP_SHAPE_OP(Oval, DlRect, Difference)
DEFINE_CLIP_SHAPE_OP(RoundRect, DlRoundRect, Difference) DEFINE_CLIP_SHAPE_OP(RoundRect, DlRoundRect, Difference)
DEFINE_CLIP_SHAPE_OP(RoundSuperellipse, DlRoundSuperellipse, Difference)
#undef DEFINE_CLIP_SHAPE_OP #undef DEFINE_CLIP_SHAPE_OP
// 4 byte header + 20 byte payload packs evenly into 24 bytes // 4 byte header + 20 byte payload packs evenly into 24 bytes
@ -578,6 +581,7 @@ struct DrawColorOp final : DrawOpBase {
// SkOval is same as DlRect // SkOval is same as DlRect
// DlRoundRect is 48 more bytes, using 52 bytes which rounds up to 56 bytes // DlRoundRect is 48 more bytes, using 52 bytes which rounds up to 56 bytes
// total (4 bytes unused) // total (4 bytes unused)
// DlRoundSuperellipse is the same as DlRoundRect
#define DEFINE_DRAW_1ARG_OP(op_name, arg_type, arg_name) \ #define DEFINE_DRAW_1ARG_OP(op_name, arg_type, arg_name) \
struct Draw##op_name##Op final : DrawOpBase { \ struct Draw##op_name##Op final : DrawOpBase { \
static constexpr auto kType = DisplayListOpType::kDraw##op_name; \ static constexpr auto kType = DisplayListOpType::kDraw##op_name; \
@ -594,6 +598,7 @@ struct DrawColorOp final : DrawOpBase {
DEFINE_DRAW_1ARG_OP(Rect, DlRect, rect) DEFINE_DRAW_1ARG_OP(Rect, DlRect, rect)
DEFINE_DRAW_1ARG_OP(Oval, DlRect, oval) DEFINE_DRAW_1ARG_OP(Oval, DlRect, oval)
DEFINE_DRAW_1ARG_OP(RoundRect, DlRoundRect, rrect) DEFINE_DRAW_1ARG_OP(RoundRect, DlRoundRect, rrect)
DEFINE_DRAW_1ARG_OP(RoundSuperellipse, DlRoundSuperellipse, rse)
#undef DEFINE_DRAW_1ARG_OP #undef DEFINE_DRAW_1ARG_OP
// 4 byte header + 16 byte payload uses 20 bytes but is rounded // 4 byte header + 16 byte payload uses 20 bytes but is rounded

View File

@ -9,6 +9,7 @@
#include "flutter/impeller/geometry/path.h" #include "flutter/impeller/geometry/path.h"
#include "flutter/impeller/geometry/rect.h" #include "flutter/impeller/geometry/rect.h"
#include "flutter/impeller/geometry/round_rect.h" #include "flutter/impeller/geometry/round_rect.h"
#include "flutter/impeller/geometry/round_superellipse.h"
#include "flutter/impeller/geometry/rstransform.h" #include "flutter/impeller/geometry/rstransform.h"
#include "flutter/impeller/geometry/scalar.h" #include "flutter/impeller/geometry/scalar.h"
@ -32,6 +33,7 @@ using DlISize = impeller::ISize32;
using DlRect = impeller::Rect; using DlRect = impeller::Rect;
using DlIRect = impeller::IRect32; using DlIRect = impeller::IRect32;
using DlRoundRect = impeller::RoundRect; using DlRoundRect = impeller::RoundRect;
using DlRoundSuperellipse = impeller::RoundSuperellipse;
using DlRoundingRadii = impeller::RoundingRadii; using DlRoundingRadii = impeller::RoundingRadii;
using DlMatrix = impeller::Matrix; using DlMatrix = impeller::Matrix;
using DlQuad = impeller::Quad; using DlQuad = impeller::Quad;
@ -203,6 +205,16 @@ inline const SkRRect ToSkRRect(const DlRoundRect& round_rect) {
return rrect; return rrect;
}; };
// Approximates a rounded superellipse with a round rectangle to the
// best practical accuracy.
//
// Skia does not support rounded superellipses directly, so rendering
// `DlRoundSuperellipses` on Skia requires falling back to RRect.
inline constexpr const SkRRect ToApproximateSkRRect(
const DlRoundSuperellipse& rse) {
return ToSkRRect(rse.ToApproximateRoundRect());
};
inline constexpr SkMatrix ToSkMatrix(const DlMatrix& matrix) { inline constexpr SkMatrix ToSkMatrix(const DlMatrix& matrix) {
return SkMatrix::MakeAll(matrix.m[0], matrix.m[4], matrix.m[12], // return SkMatrix::MakeAll(matrix.m[0], matrix.m[4], matrix.m[12], //
matrix.m[1], matrix.m[5], matrix.m[13], // matrix.m[1], matrix.m[5], matrix.m[13], //

View File

@ -68,5 +68,14 @@ TEST(DisplayListGeometryTypes, VectorToSizeConversion) {
EXPECT_NE(ToDlSize(sk_v), dl_s); EXPECT_NE(ToDlSize(sk_v), dl_s);
} }
TEST(DisplayListGeometryTypes, RSEToRRectConversion) {
DlRoundSuperellipse dl_rse = DlRoundSuperellipse::MakeRectRadius(
DlRect::MakeLTRB(10, 20, 30, 40), 1.0f);
SkRRect sk_rrect =
SkRRect::MakeRectXY(SkRect::MakeLTRB(10, 20, 30, 40), 1.0f, 1.0f);
EXPECT_EQ(sk_rrect, ToApproximateSkRRect(dl_rse));
}
} // namespace testing } // namespace testing
} // namespace flutter } // namespace flutter

View File

@ -157,6 +157,13 @@ void DlSkCanvasAdapter::ClipRoundRect(const DlRoundRect& rrect,
delegate_->clipRRect(ToSkRRect(rrect), ToSk(clip_op), is_aa); delegate_->clipRRect(ToSkRRect(rrect), ToSk(clip_op), is_aa);
} }
void DlSkCanvasAdapter::ClipRoundSuperellipse(const DlRoundSuperellipse& rse,
DlClipOp clip_op,
bool is_aa) {
// Skia doesn't support round superellipse, thus fall back to round rectangle.
delegate_->clipRRect(ToApproximateSkRRect(rse), ToSk(clip_op), is_aa);
}
void DlSkCanvasAdapter::ClipPath(const DlPath& path, void DlSkCanvasAdapter::ClipPath(const DlPath& path,
DlClipOp clip_op, DlClipOp clip_op,
bool is_aa) { bool is_aa) {
@ -235,6 +242,12 @@ void DlSkCanvasAdapter::DrawDiffRoundRect(const DlRoundRect& outer,
delegate_->drawDRRect(ToSkRRect(outer), ToSkRRect(inner), ToSk(paint)); delegate_->drawDRRect(ToSkRRect(outer), ToSkRRect(inner), ToSk(paint));
} }
void DlSkCanvasAdapter::DrawRoundSuperellipse(const DlRoundSuperellipse& rse,
const DlPaint& paint) {
// Skia doesn't support round superellipse, thus fall back to round rectangle.
delegate_->drawRRect(ToApproximateSkRRect(rse), ToSk(paint));
}
void DlSkCanvasAdapter::DrawPath(const DlPath& path, const DlPaint& paint) { void DlSkCanvasAdapter::DrawPath(const DlPath& path, const DlPaint& paint) {
path.WillRenderSkPath(); path.WillRenderSkPath();
delegate_->drawPath(path.GetSkPath(), ToSk(paint)); delegate_->drawPath(path.GetSkPath(), ToSk(paint));

View File

@ -69,6 +69,9 @@ class DlSkCanvasAdapter final : public virtual DlCanvas {
void ClipRoundRect(const DlRoundRect& rrect, void ClipRoundRect(const DlRoundRect& rrect,
DlClipOp clip_op, DlClipOp clip_op,
bool is_aa) override; bool is_aa) override;
void ClipRoundSuperellipse(const DlRoundSuperellipse& rse,
DlClipOp clip_op,
bool is_aa) override;
void ClipPath(const DlPath& path, DlClipOp clip_op, bool is_aa) override; void ClipPath(const DlPath& path, DlClipOp clip_op, bool is_aa) override;
/// Conservative estimate of the bounds of all outstanding clip operations /// Conservative estimate of the bounds of all outstanding clip operations
@ -104,6 +107,8 @@ class DlSkCanvasAdapter final : public virtual DlCanvas {
void DrawDiffRoundRect(const DlRoundRect& outer, void DrawDiffRoundRect(const DlRoundRect& outer,
const DlRoundRect& inner, const DlRoundRect& inner,
const DlPaint& paint) override; const DlPaint& paint) override;
void DrawRoundSuperellipse(const DlRoundSuperellipse& rse,
const DlPaint& paint) override;
void DrawPath(const DlPath& path, const DlPaint& paint) override; void DrawPath(const DlPath& path, const DlPaint& paint) override;
void DrawArc(const DlRect& bounds, void DrawArc(const DlRect& bounds,
DlScalar start, DlScalar start,

View File

@ -140,6 +140,12 @@ void DlSkCanvasDispatcher::clipRoundRect(const DlRoundRect& rrect,
bool is_aa) { bool is_aa) {
canvas_->clipRRect(ToSkRRect(rrect), ToSk(clip_op), is_aa); canvas_->clipRRect(ToSkRRect(rrect), ToSk(clip_op), is_aa);
} }
void DlSkCanvasDispatcher::clipRoundSuperellipse(const DlRoundSuperellipse& rse,
DlClipOp clip_op,
bool is_aa) {
// Skia doesn't support round superellipse, thus fall back to round rectangle.
canvas_->clipRRect(ToApproximateSkRRect(rse), ToSk(clip_op), is_aa);
}
void DlSkCanvasDispatcher::clipPath(const DlPath& path, void DlSkCanvasDispatcher::clipPath(const DlPath& path,
DlClipOp clip_op, DlClipOp clip_op,
bool is_aa) { bool is_aa) {
@ -192,6 +198,11 @@ void DlSkCanvasDispatcher::drawDiffRoundRect(const DlRoundRect& outer,
const DlRoundRect& inner) { const DlRoundRect& inner) {
canvas_->drawDRRect(ToSkRRect(outer), ToSkRRect(inner), paint()); canvas_->drawDRRect(ToSkRRect(outer), ToSkRRect(inner), paint());
} }
void DlSkCanvasDispatcher::drawRoundSuperellipse(
const DlRoundSuperellipse& rse) {
// Skia doesn't support round superellipse, thus fall back to round rectangle.
canvas_->drawRRect(ToApproximateSkRRect(rse), paint());
}
void DlSkCanvasDispatcher::drawPath(const DlPath& path) { void DlSkCanvasDispatcher::drawPath(const DlPath& path) {
path.WillRenderSkPath(); path.WillRenderSkPath();
canvas_->drawPath(path.GetSkPath(), paint()); canvas_->drawPath(path.GetSkPath(), paint());

View File

@ -56,6 +56,9 @@ class DlSkCanvasDispatcher : public virtual DlOpReceiver,
void clipRoundRect(const DlRoundRect& rrect, void clipRoundRect(const DlRoundRect& rrect,
DlClipOp clip_op, DlClipOp clip_op,
bool is_aa) override; bool is_aa) override;
void clipRoundSuperellipse(const DlRoundSuperellipse& rse,
DlClipOp clip_op,
bool is_aa) override;
void clipPath(const DlPath& path, DlClipOp clip_op, bool is_aa) override; void clipPath(const DlPath& path, DlClipOp clip_op, bool is_aa) override;
void drawPaint() override; void drawPaint() override;
@ -71,6 +74,7 @@ class DlSkCanvasDispatcher : public virtual DlOpReceiver,
void drawRoundRect(const DlRoundRect& rrect) override; void drawRoundRect(const DlRoundRect& rrect) override;
void drawDiffRoundRect(const DlRoundRect& outer, void drawDiffRoundRect(const DlRoundRect& outer,
const DlRoundRect& inner) override; const DlRoundRect& inner) override;
void drawRoundSuperellipse(const DlRoundSuperellipse& rse) override;
void drawPath(const DlPath& path) override; void drawPath(const DlPath& path) override;
void drawArc(const DlRect& bounds, void drawArc(const DlRect& bounds,
DlScalar start, DlScalar start,

View File

@ -546,6 +546,34 @@ std::vector<DisplayListInvocationGroup> CreateAllClipOps() {
r.clipRoundRect(kTestRRect, DlClipOp::kDifference, false); r.clipRoundRect(kTestRRect, DlClipOp::kDifference, false);
}}, }},
}}, }},
{"ClipRSuperellipse",
{
{1, 56, 0,
[](DlOpReceiver& r) {
r.clipRoundSuperellipse(kTestRSuperellipse, DlClipOp::kIntersect,
true);
}},
{1, 56, 0,
[](DlOpReceiver& r) {
r.clipRoundSuperellipse(kTestRSuperellipse.Shift(1, 1),
DlClipOp::kIntersect, true);
}},
{1, 56, 0,
[](DlOpReceiver& r) {
r.clipRoundSuperellipse(kTestRSuperellipse, DlClipOp::kIntersect,
false);
}},
{1, 56, 0,
[](DlOpReceiver& r) {
r.clipRoundSuperellipse(kTestRSuperellipse, DlClipOp::kDifference,
true);
}},
{1, 56, 0,
[](DlOpReceiver& r) {
r.clipRoundSuperellipse(kTestRSuperellipse, DlClipOp::kDifference,
false);
}},
}},
{"ClipPath", {"ClipPath",
{ {
{1, 24, 0, {1, 24, 0,
@ -725,6 +753,17 @@ std::vector<DisplayListInvocationGroup> CreateAllRenderingOps() {
{1, 56, 1, {1, 56, 1,
[](DlOpReceiver& r) { r.drawRoundRect(kTestRRect.Shift(5, 5)); }}, [](DlOpReceiver& r) { r.drawRoundRect(kTestRRect.Shift(5, 5)); }},
}}, }},
{"DrawRSuperellipse",
{
{1, 56, 1,
[](DlOpReceiver& r) {
r.drawRoundSuperellipse(kTestRSuperellipse);
}},
{1, 56, 1,
[](DlOpReceiver& r) {
r.drawRoundSuperellipse(kTestRSuperellipse.Shift(5, 5));
}},
}},
{"DrawDRRect", {"DrawDRRect",
{ {
{1, 104, 1, {1, 104, 1,

View File

@ -170,6 +170,8 @@ constexpr DlRect kTestBounds = DlRect::MakeLTRB(10, 10, 50, 60);
constexpr SkRect kTestSkBounds = SkRect::MakeLTRB(10, 10, 50, 60); constexpr SkRect kTestSkBounds = SkRect::MakeLTRB(10, 10, 50, 60);
static const DlRoundRect kTestRRect = static const DlRoundRect kTestRRect =
DlRoundRect::MakeRectXY(kTestBounds, 5, 5); DlRoundRect::MakeRectXY(kTestBounds, 5, 5);
static const DlRoundSuperellipse kTestRSuperellipse =
DlRoundSuperellipse::MakeRectXY(kTestBounds, 3, 3);
static const SkRRect kTestSkRRect = SkRRect::MakeRectXY(kTestSkBounds, 5, 5); static const SkRRect kTestSkRRect = SkRRect::MakeRectXY(kTestSkBounds, 5, 5);
static const SkRRect kTestRRectRect = SkRRect::MakeRect(kTestSkBounds); static const SkRRect kTestRRectRect = SkRRect::MakeRect(kTestSkBounds);
static const DlRoundRect kTestInnerRRect = static const DlRoundRect kTestInnerRRect =

View File

@ -6,6 +6,7 @@
#include "flutter/display_list/dl_builder.h" #include "flutter/display_list/dl_builder.h"
#include "flutter/fml/logging.h" #include "flutter/fml/logging.h"
#include "flutter/impeller/geometry/round_superellipse_param.h"
namespace flutter { namespace flutter {
@ -67,6 +68,20 @@ void DisplayListMatrixClipState::clipOval(const DlRect& bounds,
} }
} }
namespace {
inline std::array<DlRect, 2> RoundingRadiiSafeRects(
const DlRect& bounds,
const impeller::RoundingRadii& radii) {
return {
bounds.Expand( //
-std::max(radii.top_left.width, radii.bottom_left.width), 0,
-std::max(radii.top_right.width, radii.bottom_right.width), 0),
bounds.Expand(
0, -std::max(radii.top_left.height, radii.top_right.height), //
0, -std::max(radii.bottom_left.height, radii.bottom_right.height))};
}
} // namespace
void DisplayListMatrixClipState::clipRRect(const DlRoundRect& rrect, void DisplayListMatrixClipState::clipRRect(const DlRoundRect& rrect,
DlClipOp op, DlClipOp op,
bool is_aa) { bool is_aa) {
@ -83,15 +98,34 @@ void DisplayListMatrixClipState::clipRRect(const DlRoundRect& rrect,
cull_rect_ = DlRect(); cull_rect_ = DlRect();
return; return;
} }
auto radii = rrect.GetRadii(); auto safe_rects = RoundingRadiiSafeRects(bounds, rrect.GetRadii());
DlRect safe = bounds.Expand( adjustCullRect(safe_rects[0], op, is_aa);
-std::max(radii.top_left.width, radii.bottom_left.width), 0, adjustCullRect(safe_rects[1], op, is_aa);
-std::max(radii.top_right.width, radii.bottom_right.width), 0); break;
adjustCullRect(safe, op, is_aa); }
safe = bounds.Expand( }
0, -std::max(radii.top_left.height, radii.top_right.height), // }
0, -std::max(radii.bottom_left.height, radii.bottom_right.height));
adjustCullRect(safe, op, is_aa); void DisplayListMatrixClipState::clipRSuperellipse(
const DlRoundSuperellipse& rse,
DlClipOp op,
bool is_aa) {
DlRect bounds = rse.GetBounds();
if (rse.IsRect()) {
return clipRect(bounds, op, is_aa);
}
switch (op) {
case DlClipOp::kIntersect:
adjustCullRect(bounds, op, is_aa);
break;
case DlClipOp::kDifference: {
if (rsuperellipse_covers_cull(rse)) {
cull_rect_ = DlRect();
return;
}
auto safe_rects = RoundingRadiiSafeRects(bounds, rse.GetRadii());
adjustCullRect(safe_rects[0], op, is_aa);
adjustCullRect(safe_rects[1], op, is_aa);
break; break;
} }
} }
@ -299,6 +333,38 @@ bool DisplayListMatrixClipState::rrect_covers_cull(
return true; return true;
} }
bool DisplayListMatrixClipState::rsuperellipse_covers_cull(
const DlRoundSuperellipse& content) const {
if (content.IsEmpty()) {
return false;
}
if (cull_rect_.IsEmpty()) {
return true;
}
if (content.IsRect()) {
return rect_covers_cull(content.GetBounds());
}
if (content.IsOval()) {
return oval_covers_cull(content.GetBounds());
}
DlPoint corners[4];
if (!getLocalCullCorners(corners)) {
return false;
}
auto outer = content.GetBounds();
auto param = impeller::RoundSuperellipseParam::MakeBoundsRadii(
outer, content.GetRadii());
for (auto corner : corners) {
if (!outer.Contains(corner)) {
return false;
}
if (!param.Contains(corner)) {
return false;
}
}
return true;
}
bool DisplayListMatrixClipState::getLocalCullCorners(DlPoint corners[4]) const { bool DisplayListMatrixClipState::getLocalCullCorners(DlPoint corners[4]) const {
if (!is_matrix_invertable()) { if (!is_matrix_invertable()) {
return false; return false;

View File

@ -43,6 +43,7 @@ class DisplayListMatrixClipState {
bool rect_covers_cull(const DlRect& content) const; bool rect_covers_cull(const DlRect& content) const;
bool oval_covers_cull(const DlRect& content_bounds) const; bool oval_covers_cull(const DlRect& content_bounds) const;
bool rrect_covers_cull(const DlRoundRect& content) const; bool rrect_covers_cull(const DlRoundRect& content) const;
bool rsuperellipse_covers_cull(const DlRoundSuperellipse& content) const;
bool content_culled(const DlRect& content_bounds) const; bool content_culled(const DlRect& content_bounds) const;
bool is_cull_rect_empty() const { return cull_rect_.IsEmpty(); } bool is_cull_rect_empty() const { return cull_rect_.IsEmpty(); }
@ -107,6 +108,9 @@ class DisplayListMatrixClipState {
void clipRect(const DlRect& rect, DlClipOp op, bool is_aa); void clipRect(const DlRect& rect, DlClipOp op, bool is_aa);
void clipOval(const DlRect& bounds, DlClipOp op, bool is_aa); void clipOval(const DlRect& bounds, DlClipOp op, bool is_aa);
void clipRRect(const DlRoundRect& rrect, DlClipOp op, bool is_aa); void clipRRect(const DlRoundRect& rrect, DlClipOp op, bool is_aa);
void clipRSuperellipse(const DlRoundSuperellipse& rse,
DlClipOp op,
bool is_aa);
void clipPath(const DlPath& path, DlClipOp op, bool is_aa); void clipPath(const DlPath& path, DlClipOp op, bool is_aa);
private: private:

View File

@ -47,6 +47,9 @@ class IgnoreClipDispatchHelper : public virtual DlOpReceiver {
DlClipOp clip_op, DlClipOp clip_op,
bool is_aa) override {} bool is_aa) override {}
void clipPath(const DlPath& path, DlClipOp clip_op, bool is_aa) override {} void clipPath(const DlPath& path, DlClipOp clip_op, bool is_aa) override {}
void clipRoundSuperellipse(const DlRoundSuperellipse& rse,
DlClipOp clip_op,
bool is_aa) override {}
}; };
// A utility class that will ignore all DlOpReceiver methods relating // A utility class that will ignore all DlOpReceiver methods relating
@ -92,6 +95,7 @@ class IgnoreDrawDispatchHelper : public virtual DlOpReceiver {
void drawRoundRect(const DlRoundRect& rrect) override {} void drawRoundRect(const DlRoundRect& rrect) override {}
void drawDiffRoundRect(const DlRoundRect& outer, void drawDiffRoundRect(const DlRoundRect& outer,
const DlRoundRect& inner) override {} const DlRoundRect& inner) override {}
void drawRoundSuperellipse(const DlRoundSuperellipse& rse) override {}
void drawPath(const DlPath& path) override {} void drawPath(const DlPath& path) override {}
void drawArc(const DlRect& oval_bounds, void drawArc(const DlRect& oval_bounds,
DlScalar start_degrees, DlScalar start_degrees,

View File

@ -31,6 +31,8 @@ source_set("flow") {
"layers/clip_rect_layer.h", "layers/clip_rect_layer.h",
"layers/clip_rrect_layer.cc", "layers/clip_rrect_layer.cc",
"layers/clip_rrect_layer.h", "layers/clip_rrect_layer.h",
"layers/clip_rsuperellipse_layer.cc",
"layers/clip_rsuperellipse_layer.h",
"layers/clip_shape_layer.h", "layers/clip_shape_layer.h",
"layers/color_filter_layer.cc", "layers/color_filter_layer.cc",
"layers/color_filter_layer.h", "layers/color_filter_layer.h",
@ -161,6 +163,7 @@ if (enable_unittests) {
"layers/clip_path_layer_unittests.cc", "layers/clip_path_layer_unittests.cc",
"layers/clip_rect_layer_unittests.cc", "layers/clip_rect_layer_unittests.cc",
"layers/clip_rrect_layer_unittests.cc", "layers/clip_rrect_layer_unittests.cc",
"layers/clip_rsuperellipse_layer_unittests.cc",
"layers/color_filter_layer_unittests.cc", "layers/color_filter_layer_unittests.cc",
"layers/container_layer_unittests.cc", "layers/container_layer_unittests.cc",
"layers/display_list_layer_unittests.cc", "layers/display_list_layer_unittests.cc",

View File

@ -0,0 +1,23 @@
// 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/flow/layers/clip_rsuperellipse_layer.h"
namespace flutter {
ClipRSuperellipseLayer::ClipRSuperellipseLayer(
const DlRoundSuperellipse& clip_rsuperellipse,
Clip clip_behavior)
: ClipShapeLayer(clip_rsuperellipse, clip_behavior) {}
const DlRect ClipRSuperellipseLayer::clip_shape_bounds() const {
return clip_shape().GetBounds();
}
void ClipRSuperellipseLayer::ApplyClip(
LayerStateStack::MutatorContext& mutator) const {
mutator.clipRSuperellipse(clip_shape(), clip_behavior() != Clip::kHardEdge);
}
} // namespace flutter

View File

@ -0,0 +1,28 @@
// 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.
#ifndef FLUTTER_FLOW_LAYERS_CLIP_RSUPERELLIPSE_LAYER_H_
#define FLUTTER_FLOW_LAYERS_CLIP_RSUPERELLIPSE_LAYER_H_
#include "flutter/flow/layers/clip_shape_layer.h"
namespace flutter {
class ClipRSuperellipseLayer : public ClipShapeLayer<DlRoundSuperellipse> {
public:
ClipRSuperellipseLayer(const DlRoundSuperellipse& clip_rsuperellipse,
Clip clip_behavior);
protected:
const DlRect clip_shape_bounds() const override;
void ApplyClip(LayerStateStack::MutatorContext& mutator) const override;
private:
FML_DISALLOW_COPY_AND_ASSIGN(ClipRSuperellipseLayer);
};
} // namespace flutter
#endif // FLUTTER_FLOW_LAYERS_CLIP_RSUPERELLIPSE_LAYER_H_

View File

@ -0,0 +1,628 @@
// 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/flow/layers/clip_rsuperellipse_layer.h"
#include "flutter/flow/layers/layer_tree.h"
#include "flutter/flow/layers/opacity_layer.h"
#include "flutter/flow/layers/platform_view_layer.h"
#include "flutter/flow/testing/layer_test.h"
#include "flutter/flow/testing/mock_embedder.h"
#include "flutter/flow/testing/mock_layer.h"
#include "flutter/fml/macros.h"
// TODO(zanderso): https://github.com/flutter/flutter/issues/127701
// NOLINTBEGIN(bugprone-unchecked-optional-access)
namespace flutter {
namespace testing {
using ClipRSuperellipseLayerTest = LayerTest;
#ifndef NDEBUG
TEST_F(ClipRSuperellipseLayerTest, ClipNoneBehaviorDies) {
const DlRoundSuperellipse layer_rsuperellipse = DlRoundSuperellipse();
EXPECT_DEATH_IF_SUPPORTED(
auto clip = std::make_shared<ClipRSuperellipseLayer>(layer_rsuperellipse,
Clip::kNone),
"clip_behavior != Clip::kNone");
}
TEST_F(ClipRSuperellipseLayerTest, PaintingEmptyLayerDies) {
const DlRoundSuperellipse layer_rsuperellipse = DlRoundSuperellipse();
auto layer = std::make_shared<ClipRSuperellipseLayer>(layer_rsuperellipse,
Clip::kHardEdge);
layer->Preroll(preroll_context());
// Untouched
EXPECT_EQ(preroll_context()->state_stack.device_cull_rect(), kGiantRect);
EXPECT_TRUE(preroll_context()->state_stack.is_empty());
EXPECT_EQ(layer->paint_bounds(), DlRect());
EXPECT_EQ(layer->child_paint_bounds(), DlRect());
EXPECT_FALSE(layer->needs_painting(paint_context()));
EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()),
"needs_painting\\(context\\)");
}
TEST_F(ClipRSuperellipseLayerTest, PaintBeforePrerollDies) {
const DlRect layer_bounds = DlRect::MakeXYWH(0.5, 1.0, 5.0, 6.0);
const DlRoundSuperellipse layer_rsuperellipse =
DlRoundSuperellipse::MakeRect(layer_bounds);
auto layer = std::make_shared<ClipRSuperellipseLayer>(layer_rsuperellipse,
Clip::kHardEdge);
EXPECT_EQ(layer->paint_bounds(), DlRect());
EXPECT_EQ(layer->child_paint_bounds(), DlRect());
EXPECT_FALSE(layer->needs_painting(paint_context()));
EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()),
"needs_painting\\(context\\)");
}
TEST_F(ClipRSuperellipseLayerTest, PaintingCulledLayerDies) {
const DlMatrix initial_matrix = DlMatrix::MakeTranslation({0.5f, 1.0f});
const DlRect child_bounds = DlRect::MakeXYWH(1.0, 2.0, 2.0, 2.0);
const DlRect layer_bounds = DlRect::MakeXYWH(0.5, 1.0, 5.0, 6.0);
const DlRect distant_bounds = DlRect::MakeXYWH(100.0, 100.0, 10.0, 10.0);
const DlPath child_path = DlPath::MakeRect(child_bounds);
const DlRoundSuperellipse layer_rsuperellipse =
DlRoundSuperellipse::MakeRect(layer_bounds);
const DlPaint child_paint = DlPaint(DlColor::kYellow());
auto mock_layer = std::make_shared<MockLayer>(child_path, child_paint);
auto layer = std::make_shared<ClipRSuperellipseLayer>(layer_rsuperellipse,
Clip::kHardEdge);
layer->Add(mock_layer);
// Cull these children
preroll_context()->state_stack.set_preroll_delegate(distant_bounds,
initial_matrix);
layer->Preroll(preroll_context());
// Untouched
EXPECT_EQ(preroll_context()->state_stack.device_cull_rect(), distant_bounds);
EXPECT_TRUE(preroll_context()->state_stack.is_empty());
EXPECT_EQ(mock_layer->paint_bounds(), child_bounds);
EXPECT_EQ(layer->paint_bounds(), child_bounds);
EXPECT_EQ(layer->child_paint_bounds(), child_bounds);
EXPECT_TRUE(mock_layer->needs_painting(paint_context()));
EXPECT_TRUE(layer->needs_painting(paint_context()));
EXPECT_EQ(mock_layer->parent_cull_rect(), DlRect());
EXPECT_EQ(mock_layer->parent_matrix(), initial_matrix);
EXPECT_EQ(mock_layer->parent_mutators(),
std::vector({Mutator(ToApproximateSkRRect(layer_rsuperellipse))}));
auto mutator = paint_context().state_stack.save();
mutator.clipRect(distant_bounds, false);
EXPECT_FALSE(mock_layer->needs_painting(paint_context()));
EXPECT_FALSE(layer->needs_painting(paint_context()));
EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()),
"needs_painting\\(context\\)");
}
#endif
TEST_F(ClipRSuperellipseLayerTest, ChildOutsideBounds) {
const DlMatrix initial_matrix = DlMatrix::MakeTranslation({0.5f, 1.0f});
const DlRect local_cull_bounds = DlRect::MakeXYWH(0.0, 0.0, 2.0, 4.0);
const DlRect device_cull_bounds =
local_cull_bounds.TransformAndClipBounds(initial_matrix);
const DlRect child_bounds = DlRect::MakeXYWH(2.5, 5.0, 4.5, 4.0);
const DlRect clip_bounds = DlRect::MakeXYWH(0.5, 1.0, 5.0, 6.0);
const DlPath child_path = DlPath::MakeRect(child_bounds);
const DlRoundSuperellipse clip_rsuperellipse =
DlRoundSuperellipse::MakeRect(clip_bounds);
const DlPaint child_paint = DlPaint(DlColor::kYellow());
auto mock_layer = std::make_shared<MockLayer>(child_path, child_paint);
auto layer = std::make_shared<ClipRSuperellipseLayer>(clip_rsuperellipse,
Clip::kHardEdge);
layer->Add(mock_layer);
auto clip_cull_rect = clip_bounds.Intersection(local_cull_bounds);
ASSERT_TRUE(clip_cull_rect.has_value());
auto clip_layer_bounds = child_bounds.Intersection(clip_bounds);
ASSERT_TRUE(clip_layer_bounds.has_value());
// Set up both contexts to cull clipped child
preroll_context()->state_stack.set_preroll_delegate(device_cull_bounds,
initial_matrix);
paint_context().canvas->ClipRect(device_cull_bounds);
paint_context().canvas->Transform(initial_matrix);
layer->Preroll(preroll_context());
// Untouched
EXPECT_EQ(preroll_context()->state_stack.device_cull_rect(),
device_cull_bounds);
EXPECT_EQ(preroll_context()->state_stack.local_cull_rect(),
local_cull_bounds);
EXPECT_TRUE(preroll_context()->state_stack.is_empty());
EXPECT_EQ(mock_layer->paint_bounds(), child_bounds);
EXPECT_EQ(layer->paint_bounds(), clip_layer_bounds.value());
EXPECT_EQ(layer->child_paint_bounds(), child_bounds);
EXPECT_EQ(mock_layer->parent_cull_rect(), clip_cull_rect.value());
EXPECT_EQ(mock_layer->parent_matrix(), initial_matrix);
EXPECT_EQ(mock_layer->parent_mutators(),
std::vector({Mutator(ToApproximateSkRRect(clip_rsuperellipse))}));
EXPECT_FALSE(mock_layer->needs_painting(paint_context()));
ASSERT_FALSE(layer->needs_painting(paint_context()));
// Top level layer not visible so calling layer->Paint()
// would trip an FML_DCHECK
}
TEST_F(ClipRSuperellipseLayerTest, FullyContainedChild) {
const DlMatrix initial_matrix = DlMatrix::MakeTranslation({0.5f, 1.0f});
const DlRect child_bounds = DlRect::MakeXYWH(1.0, 2.0, 2.0, 2.0);
const DlRect layer_bounds = DlRect::MakeXYWH(0.5, 1.0, 5.0, 6.0);
const DlPath child_path = DlPath::MakeRect(child_bounds) +
DlPath::MakeOval(child_bounds.Expand(-0.1f));
const DlRoundSuperellipse layer_rsuperellipse =
DlRoundSuperellipse::MakeRectXY(layer_bounds, 0.1, 0.1);
const DlPaint child_paint = DlPaint(DlColor::kYellow());
auto mock_layer = std::make_shared<MockLayer>(child_path, child_paint);
auto layer = std::make_shared<ClipRSuperellipseLayer>(layer_rsuperellipse,
Clip::kHardEdge);
layer->Add(mock_layer);
preroll_context()->state_stack.set_preroll_delegate(initial_matrix);
layer->Preroll(preroll_context());
// Untouched
EXPECT_EQ(preroll_context()->state_stack.device_cull_rect(), kGiantRect);
EXPECT_TRUE(preroll_context()->state_stack.is_empty());
EXPECT_EQ(mock_layer->paint_bounds(), child_bounds);
EXPECT_EQ(layer->paint_bounds(), mock_layer->paint_bounds());
EXPECT_EQ(layer->child_paint_bounds(), child_bounds);
EXPECT_TRUE(mock_layer->needs_painting(paint_context()));
EXPECT_TRUE(layer->needs_painting(paint_context()));
EXPECT_EQ(mock_layer->parent_cull_rect(), layer_bounds);
EXPECT_EQ(mock_layer->parent_matrix(), initial_matrix);
EXPECT_EQ(mock_layer->parent_mutators(),
std::vector({Mutator(ToApproximateSkRRect(layer_rsuperellipse))}));
layer->Paint(display_list_paint_context());
DisplayListBuilder expected_builder;
/* (ClipRSuperellipse)layer::Paint */ {
expected_builder.Save();
{
expected_builder.ClipRoundSuperellipse(layer_rsuperellipse);
/* mock_layer::Paint */ {
expected_builder.DrawPath(child_path, child_paint);
}
}
expected_builder.Restore();
}
EXPECT_TRUE(DisplayListsEQ_Verbose(display_list(), expected_builder.Build()));
}
TEST_F(ClipRSuperellipseLayerTest, PartiallyContainedChild) {
const DlMatrix initial_matrix = DlMatrix::MakeTranslation({0.5f, 1.0f});
const DlRect local_cull_bounds = DlRect::MakeXYWH(0.0, 0.0, 4.0, 5.5);
const DlRect device_cull_bounds =
local_cull_bounds.TransformAndClipBounds(initial_matrix);
const DlRect child_bounds = DlRect::MakeXYWH(2.5, 5.0, 4.5, 4.0);
const DlRect clip_bounds = DlRect::MakeXYWH(0.5, 1.0, 5.0, 6.0);
const DlPath child_path = DlPath::MakeRect(child_bounds) +
DlPath::MakeOval(child_bounds.Expand(-0.1f));
const DlRoundSuperellipse clip_rsuperellipse =
DlRoundSuperellipse::MakeRectXY(clip_bounds, 0.1, 0.1);
const DlPaint child_paint = DlPaint(DlColor::kYellow());
auto mock_layer = std::make_shared<MockLayer>(child_path, child_paint);
auto layer = std::make_shared<ClipRSuperellipseLayer>(clip_rsuperellipse,
Clip::kHardEdge);
layer->Add(mock_layer);
auto clip_cull_rect = clip_bounds.Intersection(local_cull_bounds);
ASSERT_TRUE(clip_cull_rect.has_value());
auto clip_layer_bounds = child_bounds.Intersection(clip_bounds);
ASSERT_TRUE(clip_layer_bounds.has_value());
preroll_context()->state_stack.set_preroll_delegate(device_cull_bounds,
initial_matrix);
layer->Preroll(preroll_context());
// Untouched
EXPECT_EQ(preroll_context()->state_stack.device_cull_rect(),
device_cull_bounds);
EXPECT_EQ(preroll_context()->state_stack.local_cull_rect(),
local_cull_bounds);
EXPECT_TRUE(preroll_context()->state_stack.is_empty());
EXPECT_EQ(mock_layer->paint_bounds(), child_bounds);
EXPECT_EQ(layer->paint_bounds(), clip_layer_bounds.value());
EXPECT_EQ(layer->child_paint_bounds(), child_bounds);
EXPECT_EQ(mock_layer->parent_cull_rect(), clip_cull_rect.value());
EXPECT_EQ(mock_layer->parent_matrix(), initial_matrix);
EXPECT_EQ(mock_layer->parent_mutators(),
std::vector({Mutator(ToApproximateSkRRect(clip_rsuperellipse))}));
layer->Paint(display_list_paint_context());
DisplayListBuilder expected_builder;
/* (ClipRSuperellipse)layer::Paint */ {
expected_builder.Save();
{
expected_builder.ClipRoundSuperellipse(clip_rsuperellipse);
/* mock_layer::Paint */ {
expected_builder.DrawPath(child_path, child_paint);
}
}
expected_builder.Restore();
}
EXPECT_TRUE(DisplayListsEQ_Verbose(display_list(), expected_builder.Build()));
}
static bool ReadbackResult(PrerollContext* context,
Clip clip_behavior,
const std::shared_ptr<Layer>& child,
bool before) {
const DlRect layer_bounds = DlRect::MakeXYWH(0.5, 1.0, 5.0, 6.0);
const DlRoundSuperellipse layer_rsuperellipse =
DlRoundSuperellipse::MakeRect(layer_bounds);
auto layer = std::make_shared<ClipRSuperellipseLayer>(layer_rsuperellipse,
clip_behavior);
if (child != nullptr) {
layer->Add(child);
}
context->surface_needs_readback = before;
layer->Preroll(context);
return context->surface_needs_readback;
}
TEST_F(ClipRSuperellipseLayerTest, Readback) {
PrerollContext* context = preroll_context();
DlPath path;
DlPaint paint;
const Clip hard = Clip::kHardEdge;
const Clip soft = Clip::kAntiAlias;
const Clip save_layer = Clip::kAntiAliasWithSaveLayer;
std::shared_ptr<MockLayer> nochild;
auto reader = std::make_shared<MockLayer>(path, paint);
reader->set_fake_reads_surface(true);
auto nonreader = std::make_shared<MockLayer>(path, paint);
// No children, no prior readback -> no readback after
EXPECT_FALSE(ReadbackResult(context, hard, nochild, false));
EXPECT_FALSE(ReadbackResult(context, soft, nochild, false));
EXPECT_FALSE(ReadbackResult(context, save_layer, nochild, false));
// No children, prior readback -> readback after
EXPECT_TRUE(ReadbackResult(context, hard, nochild, true));
EXPECT_TRUE(ReadbackResult(context, soft, nochild, true));
EXPECT_TRUE(ReadbackResult(context, save_layer, nochild, true));
// Non readback child, no prior readback -> no readback after
EXPECT_FALSE(ReadbackResult(context, hard, nonreader, false));
EXPECT_FALSE(ReadbackResult(context, soft, nonreader, false));
EXPECT_FALSE(ReadbackResult(context, save_layer, nonreader, false));
// Non readback child, prior readback -> readback after
EXPECT_TRUE(ReadbackResult(context, hard, nonreader, true));
EXPECT_TRUE(ReadbackResult(context, soft, nonreader, true));
EXPECT_TRUE(ReadbackResult(context, save_layer, nonreader, true));
// Readback child, no prior readback -> readback after unless SaveLayer
EXPECT_TRUE(ReadbackResult(context, hard, reader, false));
EXPECT_TRUE(ReadbackResult(context, soft, reader, false));
EXPECT_FALSE(ReadbackResult(context, save_layer, reader, false));
// Readback child, prior readback -> readback after
EXPECT_TRUE(ReadbackResult(context, hard, reader, true));
EXPECT_TRUE(ReadbackResult(context, soft, reader, true));
EXPECT_TRUE(ReadbackResult(context, save_layer, reader, true));
}
TEST_F(ClipRSuperellipseLayerTest, OpacityInheritance) {
auto path1 = DlPath::MakeRectLTRB(10, 10, 30, 30);
auto mock1 = MockLayer::MakeOpacityCompatible(path1);
DlRect clip_rect = DlRect::MakeWH(500, 500);
DlRoundSuperellipse clip_rsuperellipse =
DlRoundSuperellipse::MakeRectXY(clip_rect, 20, 20);
auto clip_rsuperellipse_layer = std::make_shared<ClipRSuperellipseLayer>(
clip_rsuperellipse, Clip::kHardEdge);
clip_rsuperellipse_layer->Add(mock1);
// ClipRectLayer will pass through compatibility from a compatible child
PrerollContext* context = preroll_context();
clip_rsuperellipse_layer->Preroll(context);
EXPECT_EQ(context->renderable_state_flags,
LayerStateStack::kCallerCanApplyOpacity);
auto path2 = DlPath::MakeRectLTRB(40, 40, 50, 50);
auto mock2 = MockLayer::MakeOpacityCompatible(path2);
clip_rsuperellipse_layer->Add(mock2);
// ClipRectLayer will pass through compatibility from multiple
// non-overlapping compatible children
clip_rsuperellipse_layer->Preroll(context);
EXPECT_EQ(context->renderable_state_flags,
LayerStateStack::kCallerCanApplyOpacity);
auto path3 = DlPath::MakeRectLTRB(20, 20, 40, 40);
auto mock3 = MockLayer::MakeOpacityCompatible(path3);
clip_rsuperellipse_layer->Add(mock3);
// ClipRectLayer will not pass through compatibility from multiple
// overlapping children even if they are individually compatible
clip_rsuperellipse_layer->Preroll(context);
EXPECT_EQ(context->renderable_state_flags, 0);
{
// ClipRectLayer(aa with saveLayer) will always be compatible
auto clip_rsuperellipse_savelayer =
std::make_shared<ClipRSuperellipseLayer>(clip_rsuperellipse,
Clip::kAntiAliasWithSaveLayer);
clip_rsuperellipse_savelayer->Add(mock1);
clip_rsuperellipse_savelayer->Add(mock2);
// Double check first two children are compatible and non-overlapping
clip_rsuperellipse_savelayer->Preroll(context);
EXPECT_EQ(context->renderable_state_flags, Layer::kSaveLayerRenderFlags);
// Now add the overlapping child and test again, should still be compatible
clip_rsuperellipse_savelayer->Add(mock3);
clip_rsuperellipse_savelayer->Preroll(context);
EXPECT_EQ(context->renderable_state_flags, Layer::kSaveLayerRenderFlags);
}
// An incompatible, but non-overlapping child for the following tests
auto path4 = DlPath::MakeRectLTRB(60, 60, 70, 70);
auto mock4 = MockLayer::Make(path4);
{
// ClipRectLayer with incompatible child will not be compatible
auto clip_rsuperellipse_bad_child =
std::make_shared<ClipRSuperellipseLayer>(clip_rsuperellipse,
Clip::kHardEdge);
clip_rsuperellipse_bad_child->Add(mock1);
clip_rsuperellipse_bad_child->Add(mock2);
// Double check first two children are compatible and non-overlapping
clip_rsuperellipse_bad_child->Preroll(context);
EXPECT_EQ(context->renderable_state_flags,
LayerStateStack::kCallerCanApplyOpacity);
clip_rsuperellipse_bad_child->Add(mock4);
// The third child is non-overlapping, but not compatible so the
// TransformLayer should end up incompatible
clip_rsuperellipse_bad_child->Preroll(context);
EXPECT_EQ(context->renderable_state_flags, 0);
}
{
// ClipRectLayer(aa with saveLayer) will always be compatible
auto clip_rsuperellipse_savelayer_bad_child =
std::make_shared<ClipRSuperellipseLayer>(clip_rsuperellipse,
Clip::kAntiAliasWithSaveLayer);
clip_rsuperellipse_savelayer_bad_child->Add(mock1);
clip_rsuperellipse_savelayer_bad_child->Add(mock2);
// Double check first two children are compatible and non-overlapping
clip_rsuperellipse_savelayer_bad_child->Preroll(context);
EXPECT_EQ(context->renderable_state_flags, Layer::kSaveLayerRenderFlags);
// Now add the incompatible child and test again, should still be compatible
clip_rsuperellipse_savelayer_bad_child->Add(mock4);
clip_rsuperellipse_savelayer_bad_child->Preroll(context);
EXPECT_EQ(context->renderable_state_flags, Layer::kSaveLayerRenderFlags);
}
}
TEST_F(ClipRSuperellipseLayerTest, OpacityInheritancePainting) {
auto path1 = DlPath::MakeRectLTRB(10, 10, 30, 30);
auto mock1 = MockLayer::MakeOpacityCompatible(path1);
auto path2 = DlPath::MakeRectLTRB(40, 40, 50, 50);
auto mock2 = MockLayer::MakeOpacityCompatible(path2);
DlRect clip_rect = DlRect::MakeWH(500, 500);
DlRoundSuperellipse clip_rsuperellipse =
DlRoundSuperellipse::MakeRectXY(clip_rect, 20, 20);
auto clip_rect_layer = std::make_shared<ClipRSuperellipseLayer>(
clip_rsuperellipse, Clip::kAntiAlias);
clip_rect_layer->Add(mock1);
clip_rect_layer->Add(mock2);
// ClipRectLayer will pass through compatibility from multiple
// non-overlapping compatible children
PrerollContext* context = preroll_context();
clip_rect_layer->Preroll(context);
EXPECT_EQ(context->renderable_state_flags,
LayerStateStack::kCallerCanApplyOpacity);
int opacity_alpha = 0x7F;
DlPoint offset = DlPoint(10, 10);
auto opacity_layer = std::make_shared<OpacityLayer>(opacity_alpha, offset);
opacity_layer->Add(clip_rect_layer);
opacity_layer->Preroll(context);
EXPECT_TRUE(opacity_layer->children_can_accept_opacity());
DisplayListBuilder expected_builder;
/* OpacityLayer::Paint() */ {
expected_builder.Save();
{
expected_builder.Translate(offset.x, offset.y);
/* ClipRectLayer::Paint() */ {
expected_builder.Save();
expected_builder.ClipRoundSuperellipse(clip_rsuperellipse,
DlClipOp::kIntersect, true);
/* child layer1 paint */ {
expected_builder.DrawPath(path1, DlPaint().setAlpha(opacity_alpha));
}
/* child layer2 paint */ {
expected_builder.DrawPath(path2, DlPaint().setAlpha(opacity_alpha));
}
expected_builder.Restore();
}
}
expected_builder.Restore();
}
opacity_layer->Paint(display_list_paint_context());
EXPECT_TRUE(DisplayListsEQ_Verbose(expected_builder.Build(), display_list()));
}
TEST_F(ClipRSuperellipseLayerTest, OpacityInheritanceSaveLayerPainting) {
auto path1 = DlPath::MakeRectLTRB(10, 10, 30, 30);
auto mock1 = MockLayer::MakeOpacityCompatible(path1);
auto path2 = DlPath::MakeRectLTRB(20, 20, 40, 40);
auto mock2 = MockLayer::MakeOpacityCompatible(path2);
auto children_bounds = path1.GetBounds().Union(path2.GetBounds());
DlRect clip_rect = DlRect::MakeWH(500, 500);
DlRoundSuperellipse clip_rsuperellipse =
DlRoundSuperellipse::MakeRectXY(clip_rect, 20, 20);
auto clip_rsuperellipse_layer = std::make_shared<ClipRSuperellipseLayer>(
clip_rsuperellipse, Clip::kAntiAliasWithSaveLayer);
clip_rsuperellipse_layer->Add(mock1);
clip_rsuperellipse_layer->Add(mock2);
// ClipRectLayer will pass through compatibility from multiple
// non-overlapping compatible children
PrerollContext* context = preroll_context();
clip_rsuperellipse_layer->Preroll(context);
EXPECT_EQ(context->renderable_state_flags, Layer::kSaveLayerRenderFlags);
int opacity_alpha = 0x7F;
DlPoint offset = DlPoint(10, 10);
auto opacity_layer = std::make_shared<OpacityLayer>(opacity_alpha, offset);
opacity_layer->Add(clip_rsuperellipse_layer);
opacity_layer->Preroll(context);
EXPECT_TRUE(opacity_layer->children_can_accept_opacity());
DisplayListBuilder expected_builder;
/* OpacityLayer::Paint() */ {
expected_builder.Save();
{
expected_builder.Translate(offset.x, offset.y);
/* ClipRectLayer::Paint() */ {
expected_builder.Save();
expected_builder.ClipRoundSuperellipse(clip_rsuperellipse,
DlClipOp::kIntersect, true);
expected_builder.SaveLayer(children_bounds,
&DlPaint().setAlpha(opacity_alpha));
/* child layer1 paint */ {
expected_builder.DrawPath(path1, DlPaint());
}
/* child layer2 paint */ { //
expected_builder.DrawPath(path2, DlPaint());
}
expected_builder.Restore();
}
}
expected_builder.Restore();
}
opacity_layer->Paint(display_list_paint_context());
EXPECT_TRUE(DisplayListsEQ_Verbose(expected_builder.Build(), display_list()));
}
TEST_F(ClipRSuperellipseLayerTest, LayerCached) {
auto path1 = DlPath::MakeRectLTRB(10, 10, 30, 30);
DlPaint paint = DlPaint();
auto mock1 = MockLayer::MakeOpacityCompatible(path1);
DlRect clip_rect = DlRect::MakeWH(500, 500);
DlRoundSuperellipse clip_rsuperellipse =
DlRoundSuperellipse::MakeRectXY(clip_rect, 20, 20);
auto layer = std::make_shared<ClipRSuperellipseLayer>(
clip_rsuperellipse, Clip::kAntiAliasWithSaveLayer);
layer->Add(mock1);
auto initial_transform = DlMatrix::MakeTranslation({50.0, 25.5});
DlMatrix cache_ctm = initial_transform;
DisplayListBuilder cache_canvas;
cache_canvas.Transform(cache_ctm);
use_mock_raster_cache();
preroll_context()->state_stack.set_preroll_delegate(initial_transform);
const auto* clip_cache_item = layer->raster_cache_item();
layer->Preroll(preroll_context());
LayerTree::TryToRasterCache(cacheable_items(), &paint_context());
EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)0);
EXPECT_EQ(clip_cache_item->cache_state(), RasterCacheItem::CacheState::kNone);
layer->Preroll(preroll_context());
LayerTree::TryToRasterCache(cacheable_items(), &paint_context());
EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)0);
EXPECT_EQ(clip_cache_item->cache_state(), RasterCacheItem::CacheState::kNone);
layer->Preroll(preroll_context());
LayerTree::TryToRasterCache(cacheable_items(), &paint_context());
EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)1);
EXPECT_EQ(clip_cache_item->cache_state(),
RasterCacheItem::CacheState::kCurrent);
EXPECT_TRUE(raster_cache()->Draw(clip_cache_item->GetId().value(),
cache_canvas, &paint));
}
TEST_F(ClipRSuperellipseLayerTest, NoSaveLayerShouldNotCache) {
auto path1 = DlPath::MakeRectLTRB(10, 10, 30, 30);
auto mock1 = MockLayer::MakeOpacityCompatible(path1);
DlRect clip_rect = DlRect::MakeWH(500, 500);
DlRoundSuperellipse clip_rsuperellipse =
DlRoundSuperellipse::MakeRectXY(clip_rect, 20, 20);
auto layer = std::make_shared<ClipRSuperellipseLayer>(clip_rsuperellipse,
Clip::kAntiAlias);
layer->Add(mock1);
auto initial_transform = DlMatrix::MakeTranslation({50.0, 25.5});
use_mock_raster_cache();
preroll_context()->state_stack.set_preroll_delegate(initial_transform);
const auto* clip_cache_item = layer->raster_cache_item();
layer->Preroll(preroll_context());
LayerTree::TryToRasterCache(cacheable_items(), &paint_context());
EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)0);
EXPECT_EQ(clip_cache_item->cache_state(), RasterCacheItem::CacheState::kNone);
layer->Preroll(preroll_context());
LayerTree::TryToRasterCache(cacheable_items(), &paint_context());
EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)0);
EXPECT_EQ(clip_cache_item->cache_state(), RasterCacheItem::CacheState::kNone);
layer->Preroll(preroll_context());
LayerTree::TryToRasterCache(cacheable_items(), &paint_context());
EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)0);
EXPECT_EQ(clip_cache_item->cache_state(), RasterCacheItem::CacheState::kNone);
}
TEST_F(ClipRSuperellipseLayerTest, EmptyClipDoesNotCullPlatformView) {
const DlPoint view_offset = DlPoint(0.0f, 0.0f);
const DlSize view_size = DlSize(8.0f, 8.0f);
const int64_t view_id = 42;
auto platform_view =
std::make_shared<PlatformViewLayer>(view_offset, view_size, view_id);
DlRoundSuperellipse clip_rsuperellipse =
DlRoundSuperellipse::MakeRectXY(DlRect(), 20, 20);
auto clip = std::make_shared<ClipRSuperellipseLayer>(clip_rsuperellipse,
Clip::kAntiAlias);
clip->Add(platform_view);
auto embedder = MockViewEmbedder();
DisplayListBuilder fake_overlay_builder;
embedder.AddCanvas(&fake_overlay_builder);
preroll_context()->view_embedder = &embedder;
paint_context().view_embedder = &embedder;
clip->Preroll(preroll_context());
EXPECT_EQ(embedder.prerolled_views(), std::vector<int64_t>({view_id}));
clip->Paint(paint_context());
EXPECT_EQ(embedder.painted_views(), std::vector<int64_t>({view_id}));
}
} // namespace testing
} // namespace flutter
// NOLINTEND(bugprone-unchecked-optional-access)

View File

@ -58,6 +58,9 @@ class DummyDelegate : public LayerStateStack::Delegate {
void clipRect(const DlRect& rect, DlClipOp op, bool is_aa) override {} void clipRect(const DlRect& rect, DlClipOp op, bool is_aa) override {}
void clipRRect(const DlRoundRect& rrect, DlClipOp op, bool is_aa) override {} void clipRRect(const DlRoundRect& rrect, DlClipOp op, bool is_aa) override {}
void clipRSuperellipse(const DlRoundSuperellipse& rse,
DlClipOp op,
bool is_aa) override {}
void clipPath(const DlPath& path, DlClipOp op, bool is_aa) override {} void clipPath(const DlPath& path, DlClipOp op, bool is_aa) override {}
private: private:
@ -121,6 +124,11 @@ class DlCanvasDelegate : public LayerStateStack::Delegate {
void clipRRect(const DlRoundRect& rrect, DlClipOp op, bool is_aa) override { void clipRRect(const DlRoundRect& rrect, DlClipOp op, bool is_aa) override {
canvas_->ClipRoundRect(rrect, op, is_aa); canvas_->ClipRoundRect(rrect, op, is_aa);
} }
void clipRSuperellipse(const DlRoundSuperellipse& rse,
DlClipOp op,
bool is_aa) override {
canvas_->ClipRoundSuperellipse(rse, op, is_aa);
}
void clipPath(const DlPath& path, DlClipOp op, bool is_aa) override { void clipPath(const DlPath& path, DlClipOp op, bool is_aa) override {
canvas_->ClipPath(path, op, is_aa); canvas_->ClipPath(path, op, is_aa);
} }
@ -176,6 +184,11 @@ class PrerollDelegate : public LayerStateStack::Delegate {
void clipRRect(const DlRoundRect& rrect, DlClipOp op, bool is_aa) override { void clipRRect(const DlRoundRect& rrect, DlClipOp op, bool is_aa) override {
state().clipRRect(rrect, op, is_aa); state().clipRRect(rrect, op, is_aa);
} }
void clipRSuperellipse(const DlRoundSuperellipse& rse,
DlClipOp op,
bool is_aa) override {
state().clipRSuperellipse(rse, op, is_aa);
}
void clipPath(const DlPath& path, DlClipOp op, bool is_aa) override { void clipPath(const DlPath& path, DlClipOp op, bool is_aa) override {
state().clipPath(path, op, is_aa); state().clipPath(path, op, is_aa);
} }
@ -446,6 +459,33 @@ class ClipRRectEntry : public LayerStateStack::StateEntry {
FML_DISALLOW_COPY_ASSIGN_AND_MOVE(ClipRRectEntry); FML_DISALLOW_COPY_ASSIGN_AND_MOVE(ClipRRectEntry);
}; };
class ClipRSuperellipseEntry : public LayerStateStack::StateEntry {
public:
ClipRSuperellipseEntry(const DlRoundSuperellipse& clip_rsuperellipse,
bool is_aa)
: clip_rsuperellipse_(clip_rsuperellipse), is_aa_(is_aa) {}
void apply(LayerStateStack* stack) const override {
stack->delegate_->clipRSuperellipse(clip_rsuperellipse_,
DlClipOp::kIntersect, is_aa_);
}
void update_mutators(MutatorsStack* mutators_stack) const override {
// MutatorsStack doesn't support non-Skia classes, and therefore this method
// has to use approximate RRect, which might cause trouble for certain
// embedded apps.
// TODO(dkwingsmt): Make this method push a correct ClipRoundedSuperellipse
// mutator.
// https://github.com/flutter/flutter/issues/163716
mutators_stack->PushClipRRect(ToApproximateSkRRect(clip_rsuperellipse_));
}
private:
const DlRoundSuperellipse clip_rsuperellipse_;
const bool is_aa_;
FML_DISALLOW_COPY_ASSIGN_AND_MOVE(ClipRSuperellipseEntry);
};
class ClipPathEntry : public LayerStateStack::StateEntry { class ClipPathEntry : public LayerStateStack::StateEntry {
public: public:
ClipPathEntry(const DlPath& clip_path, bool is_aa) ClipPathEntry(const DlPath& clip_path, bool is_aa)
@ -570,6 +610,13 @@ void MutatorContext::clipRRect(const DlRoundRect& rrect, bool is_aa) {
layer_state_stack_->push_clip_rrect(rrect, is_aa); layer_state_stack_->push_clip_rrect(rrect, is_aa);
} }
void MutatorContext::clipRSuperellipse(const DlRoundSuperellipse& rse,
bool is_aa) {
layer_state_stack_->maybe_save_layer_for_clip(save_needed_);
save_needed_ = false;
layer_state_stack_->push_clip_rsuperellipse(rse, is_aa);
}
void MutatorContext::clipPath(const DlPath& path, bool is_aa) { void MutatorContext::clipPath(const DlPath& path, bool is_aa) {
layer_state_stack_->maybe_save_layer_for_clip(save_needed_); layer_state_stack_->maybe_save_layer_for_clip(save_needed_);
save_needed_ = false; save_needed_ = false;
@ -700,6 +747,13 @@ void LayerStateStack::push_clip_rrect(const DlRoundRect& rrect, bool is_aa) {
apply_last_entry(); apply_last_entry();
} }
void LayerStateStack::push_clip_rsuperellipse(const DlRoundSuperellipse& rse,
bool is_aa) {
state_stack_.emplace_back(
std::make_unique<ClipRSuperellipseEntry>(rse, is_aa));
apply_last_entry();
}
void LayerStateStack::push_clip_path(const DlPath& path, bool is_aa) { void LayerStateStack::push_clip_path(const DlPath& path, bool is_aa) {
state_stack_.emplace_back(std::make_unique<ClipPathEntry>(path, is_aa)); state_stack_.emplace_back(std::make_unique<ClipPathEntry>(path, is_aa));
apply_last_entry(); apply_last_entry();

View File

@ -207,6 +207,7 @@ class LayerStateStack {
void clipRect(const DlRect& rect, bool is_aa); void clipRect(const DlRect& rect, bool is_aa);
void clipRRect(const DlRoundRect& rrect, bool is_aa); void clipRRect(const DlRoundRect& rrect, bool is_aa);
void clipRSuperellipse(const DlRoundSuperellipse& rse, bool is_aa);
void clipPath(const DlPath& path, bool is_aa); void clipPath(const DlPath& path, bool is_aa);
private: private:
@ -334,6 +335,7 @@ class LayerStateStack {
void push_clip_rect(const DlRect& rect, bool is_aa); void push_clip_rect(const DlRect& rect, bool is_aa);
void push_clip_rrect(const DlRoundRect& rrect, bool is_aa); void push_clip_rrect(const DlRoundRect& rrect, bool is_aa);
void push_clip_rsuperellipse(const DlRoundSuperellipse& rse, bool is_aa);
void push_clip_path(const DlPath& path, bool is_aa); void push_clip_path(const DlPath& path, bool is_aa);
// --------------------- // ---------------------
@ -408,6 +410,7 @@ class LayerStateStack {
friend class ClipEntry; friend class ClipEntry;
friend class ClipRectEntry; friend class ClipRectEntry;
friend class ClipRRectEntry; friend class ClipRRectEntry;
friend class ClipRSuperellipseEntry;
friend class ClipPathEntry; friend class ClipPathEntry;
class Delegate { class Delegate {
@ -447,6 +450,9 @@ class LayerStateStack {
virtual void clipRRect(const DlRoundRect& rrect, virtual void clipRRect(const DlRoundRect& rrect,
DlClipOp op, DlClipOp op,
bool is_aa) = 0; bool is_aa) = 0;
virtual void clipRSuperellipse(const DlRoundSuperellipse& rse,
DlClipOp op,
bool is_aa) = 0;
virtual void clipPath(const DlPath& path, DlClipOp op, bool is_aa) = 0; virtual void clipPath(const DlPath& path, DlClipOp op, bool is_aa) = 0;
}; };
friend class DummyDelegate; friend class DummyDelegate;

View File

@ -540,6 +540,29 @@ void Canvas::DrawRoundRect(const RoundRect& round_rect, const Paint& paint) {
DrawPath(path, paint); DrawPath(path, paint);
} }
void Canvas::DrawRoundSuperellipse(const RoundSuperellipse& rse,
const Paint& paint) {
if (paint.style == Paint::Style::kFill) {
// TODO(dkwingsmt): Investigate if RSE can use the `AttemptDrawBlurredRRect`
// optimization at some point, such as a large enough mask radius.
// https://github.com/flutter/flutter/issues/163893
Entity entity;
entity.SetTransform(GetCurrentTransform());
entity.SetBlendMode(paint.blend_mode);
RoundSuperellipseGeometry geom(rse.GetBounds(), rse.GetRadii());
AddRenderEntityWithFiltersToCurrentPass(entity, &geom, paint);
return;
}
auto path = PathBuilder{}
.SetConvexity(Convexity::kConvex)
.AddRoundSuperellipse(rse)
.SetBounds(rse.GetBounds())
.TakePath();
DrawPath(path, paint);
}
void Canvas::DrawCircle(const Point& center, void Canvas::DrawCircle(const Point& center,
Scalar radius, Scalar radius,
const Paint& paint) { const Paint& paint) {

View File

@ -201,6 +201,8 @@ class Canvas {
void DrawRoundRect(const RoundRect& rect, const Paint& paint); void DrawRoundRect(const RoundRect& rect, const Paint& paint);
void DrawRoundSuperellipse(const RoundSuperellipse& rse, const Paint& paint);
void DrawCircle(const Point& center, Scalar radius, const Paint& paint); void DrawCircle(const Point& center, Scalar radius, const Paint& paint);
void DrawPoints(const Point points[], void DrawPoints(const Point points[],

View File

@ -29,6 +29,7 @@
#include "impeller/entity/geometry/fill_path_geometry.h" #include "impeller/entity/geometry/fill_path_geometry.h"
#include "impeller/entity/geometry/rect_geometry.h" #include "impeller/entity/geometry/rect_geometry.h"
#include "impeller/entity/geometry/round_rect_geometry.h" #include "impeller/entity/geometry/round_rect_geometry.h"
#include "impeller/entity/geometry/round_superellipse_geometry.h"
#include "impeller/geometry/color.h" #include "impeller/geometry/color.h"
#include "impeller/geometry/path.h" #include "impeller/geometry/path.h"
#include "impeller/geometry/path_builder.h" #include "impeller/geometry/path_builder.h"
@ -468,6 +469,25 @@ void DlDispatcherBase::clipRoundRect(const DlRoundRect& rrect,
} }
} }
// |flutter::DlOpReceiver|
void DlDispatcherBase::clipRoundSuperellipse(const DlRoundSuperellipse& rse,
flutter::DlClipOp sk_op,
bool is_aa) {
AUTO_DEPTH_WATCHER(0u);
auto clip_op = ToClipOperation(sk_op);
if (rse.IsRect()) {
RectGeometry geom(rse.GetBounds());
GetCanvas().ClipGeometry(geom, clip_op, /*is_aa=*/is_aa);
} else if (rse.IsOval()) {
EllipseGeometry geom(rse.GetBounds());
GetCanvas().ClipGeometry(geom, clip_op);
} else {
RoundSuperellipseGeometry geom(rse.GetBounds(), rse.GetRadii());
GetCanvas().ClipGeometry(geom, clip_op);
}
}
// |flutter::DlOpReceiver| // |flutter::DlOpReceiver|
void DlDispatcherBase::clipPath(const DlPath& path, void DlDispatcherBase::clipPath(const DlPath& path,
flutter::DlClipOp sk_op, flutter::DlClipOp sk_op,
@ -603,6 +623,13 @@ void DlDispatcherBase::drawDiffRoundRect(const DlRoundRect& outer,
GetCanvas().DrawPath(builder.TakePath(FillType::kOdd), paint_); GetCanvas().DrawPath(builder.TakePath(FillType::kOdd), paint_);
} }
// |flutter::DlOpReceiver|
void DlDispatcherBase::drawRoundSuperellipse(const DlRoundSuperellipse& rse) {
AUTO_DEPTH_WATCHER(1u);
GetCanvas().DrawRoundSuperellipse(rse, paint_);
}
// |flutter::DlOpReceiver| // |flutter::DlOpReceiver|
void DlDispatcherBase::drawPath(const DlPath& path) { void DlDispatcherBase::drawPath(const DlPath& path) {
AUTO_DEPTH_WATCHER(1u); AUTO_DEPTH_WATCHER(1u);

View File

@ -25,6 +25,7 @@ using DlPoint = flutter::DlPoint;
using DlRect = flutter::DlRect; using DlRect = flutter::DlRect;
using DlIRect = flutter::DlIRect; using DlIRect = flutter::DlIRect;
using DlRoundRect = flutter::DlRoundRect; using DlRoundRect = flutter::DlRoundRect;
using DlRoundSuperellipse = flutter::DlRoundSuperellipse;
using DlPath = flutter::DlPath; using DlPath = flutter::DlPath;
class DlDispatcherBase : public flutter::DlOpReceiver { class DlDispatcherBase : public flutter::DlOpReceiver {
@ -138,6 +139,11 @@ class DlDispatcherBase : public flutter::DlOpReceiver {
flutter::DlClipOp clip_op, flutter::DlClipOp clip_op,
bool is_aa) override; bool is_aa) override;
// |flutter::DlOpReceiver|
void clipRoundSuperellipse(const DlRoundSuperellipse& rse,
flutter::DlClipOp clip_op,
bool is_aa) override;
// |flutter::DlOpReceiver| // |flutter::DlOpReceiver|
void clipPath(const DlPath& path, void clipPath(const DlPath& path,
flutter::DlClipOp clip_op, flutter::DlClipOp clip_op,
@ -174,6 +180,9 @@ class DlDispatcherBase : public flutter::DlOpReceiver {
void drawDiffRoundRect(const DlRoundRect& outer, void drawDiffRoundRect(const DlRoundRect& outer,
const DlRoundRect& inner) override; const DlRoundRect& inner) override;
// |flutter::DlOpReceiver|
void drawRoundSuperellipse(const DlRoundSuperellipse& rse) override;
// |flutter::DlOpReceiver| // |flutter::DlOpReceiver|
void drawPath(const DlPath& path) override; void drawPath(const DlPath& path) override;

View File

@ -15,6 +15,7 @@
#include "impeller/entity/geometry/line_geometry.h" #include "impeller/entity/geometry/line_geometry.h"
#include "impeller/entity/geometry/rect_geometry.h" #include "impeller/entity/geometry/rect_geometry.h"
#include "impeller/entity/geometry/round_rect_geometry.h" #include "impeller/entity/geometry/round_rect_geometry.h"
#include "impeller/entity/geometry/round_superellipse_geometry.h"
#include "impeller/entity/geometry/stroke_path_geometry.h" #include "impeller/entity/geometry/stroke_path_geometry.h"
#include "impeller/geometry/rect.h" #include "impeller/geometry/rect.h"
@ -110,6 +111,12 @@ std::unique_ptr<Geometry> Geometry::MakeRoundRect(const Rect& rect,
return std::make_unique<RoundRectGeometry>(rect, radii); return std::make_unique<RoundRectGeometry>(rect, radii);
} }
std::unique_ptr<Geometry> Geometry::MakeRoundSuperellipse(
const Rect& rect,
Scalar corner_radius) {
return std::make_unique<RoundSuperellipseGeometry>(rect, corner_radius);
}
bool Geometry::CoversArea(const Matrix& transform, const Rect& rect) const { bool Geometry::CoversArea(const Matrix& transform, const Rect& rect) const {
return false; return false;
} }

View File

@ -83,6 +83,9 @@ class Geometry {
static std::unique_ptr<Geometry> MakeRoundRect(const Rect& rect, static std::unique_ptr<Geometry> MakeRoundRect(const Rect& rect,
const Size& radii); const Size& radii);
static std::unique_ptr<Geometry> MakeRoundSuperellipse(const Rect& rect,
Scalar corner_radius);
virtual GeometryResult GetPositionBuffer(const ContentContext& renderer, virtual GeometryResult GetPositionBuffer(const ContentContext& renderer,
const Entity& entity, const Entity& entity,
RenderPass& pass) const = 0; RenderPass& pass) const = 0;

View File

@ -12,14 +12,12 @@ namespace impeller {
/// Geometry class that can generate vertices for a rounded superellipse. /// Geometry class that can generate vertices for a rounded superellipse.
/// ///
/// A superellipse is an ellipse-like shape that is defined by the parameters N, /// A rounded superellipse is a shape similar to a typical rounded rectangle
/// alpha, and beta: /// (`RoundRect`), but with smoother transitions between the straight sides and
/// /// the rounded corners. It resembles the `RoundedRectangle` shape in SwiftUI
/// 1 = |x / b| ^n + |y / a| ^n /// with the `.continuous` corner style. Technically, it is created by replacing
/// /// the four corners of a superellipse (also known as a Lamé curve) with
/// A rounded superellipse is a square-like superellipse (a=b) with its four /// circular arcs.
/// corners replaced by circular arcs. It replicates the `RoundedRectangle`
/// shape in SwiftUI with corner style `.continuous`.
/// ///
/// The `bounds` defines the position and size of the shape. The `corner_radius` /// The `bounds` defines the position and size of the shape. The `corner_radius`
/// corresponds to SwiftUI's `cornerRadius` parameter, which is close to, but /// corresponds to SwiftUI's `cornerRadius` parameter, which is close to, but

View File

@ -4,6 +4,7 @@
#include "flutter/impeller/geometry/round_superellipse.h" #include "flutter/impeller/geometry/round_superellipse.h"
#include "flutter/impeller/geometry/round_rect.h"
#include "flutter/impeller/geometry/round_superellipse_param.h" #include "flutter/impeller/geometry/round_superellipse_param.h"
namespace impeller { namespace impeller {
@ -30,4 +31,11 @@ RoundSuperellipse RoundSuperellipse::MakeRectRadii(
return param.Contains(p); return param.Contains(p);
} }
RoundRect RoundSuperellipse::ToApproximateRoundRect() const {
// Experiments have shown that using the same corner radii for the RRect
// provides an approximation that is close to optimal, as achieving a perfect
// match is not feasible.
return RoundRect::MakeRectRadii(GetBounds(), GetRadii());
}
} // namespace impeller } // namespace impeller

View File

@ -12,6 +12,8 @@
namespace impeller { namespace impeller {
struct RoundRect;
struct RoundSuperellipse { struct RoundSuperellipse {
RoundSuperellipse() = default; RoundSuperellipse() = default;
@ -124,6 +126,14 @@ struct RoundSuperellipse {
return !(*this == r); return !(*this == r);
} }
// Approximates a rounded superellipse with a round rectangle to the
// best practical accuracy.
//
// This is used for Skia backends, which does not support rounded
// superellipses directly, so rendering rounded superellipses
// falls back to RRect.
[[nodiscard]] RoundRect ToApproximateRoundRect() const;
private: private:
constexpr RoundSuperellipse(const Rect& bounds, const RoundingRadii& radii) constexpr RoundSuperellipse(const Rect& bounds, const RoundingRadii& radii)
: bounds_(bounds), radii_(radii) {} : bounds_(bounds), radii_(radii) {}

View File

@ -103,6 +103,8 @@ source_set("ui") {
"painting/picture_recorder.h", "painting/picture_recorder.h",
"painting/rrect.cc", "painting/rrect.cc",
"painting/rrect.h", "painting/rrect.h",
"painting/rsuperellipse.cc",
"painting/rsuperellipse.h",
"painting/shader.cc", "painting/shader.cc",
"painting/shader.h", "painting/shader.h",
"painting/single_frame_codec.cc", "painting/single_frame_codec.cc",

View File

@ -174,6 +174,15 @@ class ClipRRectEngineLayer extends _EngineLayerWrapper {
ClipRRectEngineLayer._(super.nativeLayer) : super._(); ClipRRectEngineLayer._(super.nativeLayer) : super._();
} }
/// An opaque handle to a clip rounded superellipse engine layer.
///
/// Instances of this class are created by [SceneBuilder.pushClipRSuperellipse].
///
/// {@macro dart.ui.sceneBuilder.oldLayerCompatibility}
class ClipRSuperellipseEngineLayer extends _EngineLayerWrapper {
ClipRSuperellipseEngineLayer._(super.nativeLayer) : super._();
}
/// An opaque handle to a clip path engine layer. /// An opaque handle to a clip path engine layer.
/// ///
/// Instances of this class are created by [SceneBuilder.pushClipPath]. /// Instances of this class are created by [SceneBuilder.pushClipPath].
@ -325,6 +334,22 @@ abstract class SceneBuilder {
ClipRRectEngineLayer? oldLayer, ClipRRectEngineLayer? oldLayer,
}); });
/// Pushes a rounded-superellipse clip operation onto the operation stack.
///
/// Rasterization outside the given rounded superellipse is discarded.
///
/// {@macro dart.ui.sceneBuilder.oldLayer}
///
/// {@macro dart.ui.sceneBuilder.oldLayerVsRetained}
///
/// See [pop] for details about the operation stack, and [Clip] for different clip modes.
/// By default, the clip will be anti-aliased (clip = [Clip.antiAlias]).
ClipRSuperellipseEngineLayer pushClipRSuperellipse(
RSuperellipse rse, {
Clip clipBehavior = Clip.antiAlias,
ClipRSuperellipseEngineLayer? oldLayer,
});
/// Pushes a path clip operation onto the operation stack. /// Pushes a path clip operation onto the operation stack.
/// ///
/// Rasterization outside the given path is discarded. /// Rasterization outside the given path is discarded.
@ -720,6 +745,36 @@ base class _NativeSceneBuilder extends NativeFieldWrapperClass1 implements Scene
EngineLayer? oldLayer, EngineLayer? oldLayer,
); );
@override
ClipRSuperellipseEngineLayer pushClipRSuperellipse(
RSuperellipse rse, {
Clip clipBehavior = Clip.antiAlias,
ClipRSuperellipseEngineLayer? oldLayer,
}) {
assert(clipBehavior != Clip.none);
assert(_debugCheckCanBeUsedAsOldLayer(oldLayer, 'pushClipRSuperellipse'));
final EngineLayer engineLayer = _NativeEngineLayer._();
_pushClipRSuperellipse(
engineLayer,
rse._getValue32(),
clipBehavior.index,
oldLayer?._nativeLayer,
);
final ClipRSuperellipseEngineLayer layer = ClipRSuperellipseEngineLayer._(engineLayer);
assert(_debugPushLayer(layer));
return layer;
}
@Native<Void Function(Pointer<Void>, Handle, Handle, Int32, Handle)>(
symbol: 'SceneBuilder::pushClipRSuperellipse',
)
external void _pushClipRSuperellipse(
EngineLayer layer,
Float32List rrect,
int clipBehavior,
EngineLayer? oldLayer,
);
@override @override
ClipPathEngineLayer pushClipPath( ClipPathEngineLayer pushClipPath(
Path path, { Path path, {

View File

@ -10,6 +10,7 @@
#include "flutter/flow/layers/clip_path_layer.h" #include "flutter/flow/layers/clip_path_layer.h"
#include "flutter/flow/layers/clip_rect_layer.h" #include "flutter/flow/layers/clip_rect_layer.h"
#include "flutter/flow/layers/clip_rrect_layer.h" #include "flutter/flow/layers/clip_rrect_layer.h"
#include "flutter/flow/layers/clip_rsuperellipse_layer.h"
#include "flutter/flow/layers/color_filter_layer.h" #include "flutter/flow/layers/color_filter_layer.h"
#include "flutter/flow/layers/container_layer.h" #include "flutter/flow/layers/container_layer.h"
#include "flutter/flow/layers/display_list_layer.h" #include "flutter/flow/layers/display_list_layer.h"
@ -101,6 +102,21 @@ void SceneBuilder::pushClipRRect(Dart_Handle layer_handle,
} }
} }
void SceneBuilder::pushClipRSuperellipse(
Dart_Handle layer_handle,
const RSuperellipse& rse,
int clip_behavior,
const fml::RefPtr<EngineLayer>& old_layer) {
auto layer = std::make_shared<flutter::ClipRSuperellipseLayer>(
rse.rsuperellipse, static_cast<flutter::Clip>(clip_behavior));
PushLayer(layer);
EngineLayer::MakeRetained(layer_handle, layer);
if (old_layer && old_layer->Layer()) {
layer->AssignOldLayer(old_layer->Layer().get());
}
}
void SceneBuilder::pushClipPath(Dart_Handle layer_handle, void SceneBuilder::pushClipPath(Dart_Handle layer_handle,
const CanvasPath* path, const CanvasPath* path,
int clip_behavior, int clip_behavior,

View File

@ -17,6 +17,7 @@
#include "flutter/lib/ui/painting/path.h" #include "flutter/lib/ui/painting/path.h"
#include "flutter/lib/ui/painting/picture.h" #include "flutter/lib/ui/painting/picture.h"
#include "flutter/lib/ui/painting/rrect.h" #include "flutter/lib/ui/painting/rrect.h"
#include "flutter/lib/ui/painting/rsuperellipse.h"
#include "flutter/lib/ui/painting/shader.h" #include "flutter/lib/ui/painting/shader.h"
#include "third_party/tonic/typed_data/typed_list.h" #include "third_party/tonic/typed_data/typed_list.h"
@ -59,6 +60,10 @@ class SceneBuilder : public RefCountedDartWrappable<SceneBuilder> {
const RRect& rrect, const RRect& rrect,
int clip_behavior, int clip_behavior,
const fml::RefPtr<EngineLayer>& old_layer); const fml::RefPtr<EngineLayer>& old_layer);
void pushClipRSuperellipse(Dart_Handle layer_handle,
const RSuperellipse& rse,
int clip_behavior,
const fml::RefPtr<EngineLayer>& old_layer);
void pushClipPath(Dart_Handle layer_handle, void pushClipPath(Dart_Handle layer_handle,
const CanvasPath* path, const CanvasPath* path,
int clip_behavior, int clip_behavior,

View File

@ -138,11 +138,13 @@ typedef CanvasPath Path;
V(Canvas, clipPath) \ V(Canvas, clipPath) \
V(Canvas, clipRect) \ V(Canvas, clipRect) \
V(Canvas, clipRRect) \ V(Canvas, clipRRect) \
V(Canvas, clipRSuperellipse) \
V(Canvas, drawArc) \ V(Canvas, drawArc) \
V(Canvas, drawAtlas) \ V(Canvas, drawAtlas) \
V(Canvas, drawCircle) \ V(Canvas, drawCircle) \
V(Canvas, drawColor) \ V(Canvas, drawColor) \
V(Canvas, drawDRRect) \ V(Canvas, drawDRRect) \
V(Canvas, drawRSuperellipse) \
V(Canvas, drawImage) \ V(Canvas, drawImage) \
V(Canvas, drawImageNine) \ V(Canvas, drawImageNine) \
V(Canvas, drawImageRect) \ V(Canvas, drawImageRect) \
@ -288,8 +290,9 @@ typedef CanvasPath Path;
V(SceneBuilder, pop) \ V(SceneBuilder, pop) \
V(SceneBuilder, pushBackdropFilter) \ V(SceneBuilder, pushBackdropFilter) \
V(SceneBuilder, pushClipPath) \ V(SceneBuilder, pushClipPath) \
V(SceneBuilder, pushClipRRect) \
V(SceneBuilder, pushClipRect) \ V(SceneBuilder, pushClipRect) \
V(SceneBuilder, pushClipRRect) \
V(SceneBuilder, pushClipRSuperellipse) \
V(SceneBuilder, pushColorFilter) \ V(SceneBuilder, pushColorFilter) \
V(SceneBuilder, pushImageFilter) \ V(SceneBuilder, pushImageFilter) \
V(SceneBuilder, pushOffset) \ V(SceneBuilder, pushOffset) \

View File

@ -1093,162 +1093,21 @@ class Radius {
} }
} }
/// An immutable rounded rectangle with the custom radii for all four corners. // The common base class for `RRect` and `RSuperellipse`.
class RRect { abstract class _RRectLike<T extends _RRectLike<T>> {
/// Construct a rounded rectangle from its left, top, right, and bottom edges, const _RRectLike({
/// and the same radii along its horizontal axis and its vertical axis. required this.left,
/// required this.top,
/// Will assert in debug mode if `radiusX` or `radiusY` are negative. required this.right,
const RRect.fromLTRBXY( required this.bottom,
double left, required this.tlRadiusX,
double top, required this.tlRadiusY,
double right, required this.trRadiusX,
double bottom, required this.trRadiusY,
double radiusX, required this.brRadiusX,
double radiusY, required this.brRadiusY,
) : this._raw( required this.blRadiusX,
top: top, required this.blRadiusY,
left: left,
right: right,
bottom: bottom,
tlRadiusX: radiusX,
tlRadiusY: radiusY,
trRadiusX: radiusX,
trRadiusY: radiusY,
blRadiusX: radiusX,
blRadiusY: radiusY,
brRadiusX: radiusX,
brRadiusY: radiusY,
);
/// Construct a rounded rectangle from its left, top, right, and bottom edges,
/// and the same radius in each corner.
///
/// Will assert in debug mode if the `radius` is negative in either x or y.
RRect.fromLTRBR(double left, double top, double right, double bottom, Radius radius)
: this._raw(
top: top,
left: left,
right: right,
bottom: bottom,
tlRadiusX: radius.x,
tlRadiusY: radius.y,
trRadiusX: radius.x,
trRadiusY: radius.y,
blRadiusX: radius.x,
blRadiusY: radius.y,
brRadiusX: radius.x,
brRadiusY: radius.y,
);
/// Construct a rounded rectangle from its bounding box and the same radii
/// along its horizontal axis and its vertical axis.
///
/// Will assert in debug mode if `radiusX` or `radiusY` are negative.
RRect.fromRectXY(Rect rect, double radiusX, double radiusY)
: this._raw(
top: rect.top,
left: rect.left,
right: rect.right,
bottom: rect.bottom,
tlRadiusX: radiusX,
tlRadiusY: radiusY,
trRadiusX: radiusX,
trRadiusY: radiusY,
blRadiusX: radiusX,
blRadiusY: radiusY,
brRadiusX: radiusX,
brRadiusY: radiusY,
);
/// Construct a rounded rectangle from its bounding box and a radius that is
/// the same in each corner.
///
/// Will assert in debug mode if the `radius` is negative in either x or y.
RRect.fromRectAndRadius(Rect rect, Radius radius)
: this._raw(
top: rect.top,
left: rect.left,
right: rect.right,
bottom: rect.bottom,
tlRadiusX: radius.x,
tlRadiusY: radius.y,
trRadiusX: radius.x,
trRadiusY: radius.y,
blRadiusX: radius.x,
blRadiusY: radius.y,
brRadiusX: radius.x,
brRadiusY: radius.y,
);
/// Construct a rounded rectangle from its left, top, right, and bottom edges,
/// and topLeft, topRight, bottomRight, and bottomLeft radii.
///
/// The corner radii default to [Radius.zero], i.e. right-angled corners. Will
/// assert in debug mode if any of the radii are negative in either x or y.
RRect.fromLTRBAndCorners(
double left,
double top,
double right,
double bottom, {
Radius topLeft = Radius.zero,
Radius topRight = Radius.zero,
Radius bottomRight = Radius.zero,
Radius bottomLeft = Radius.zero,
}) : this._raw(
top: top,
left: left,
right: right,
bottom: bottom,
tlRadiusX: topLeft.x,
tlRadiusY: topLeft.y,
trRadiusX: topRight.x,
trRadiusY: topRight.y,
blRadiusX: bottomLeft.x,
blRadiusY: bottomLeft.y,
brRadiusX: bottomRight.x,
brRadiusY: bottomRight.y,
);
/// Construct a rounded rectangle from its bounding box and topLeft,
/// topRight, bottomRight, and bottomLeft radii.
///
/// The corner radii default to [Radius.zero], i.e. right-angled corners. Will
/// assert in debug mode if any of the radii are negative in either x or y.
RRect.fromRectAndCorners(
Rect rect, {
Radius topLeft = Radius.zero,
Radius topRight = Radius.zero,
Radius bottomRight = Radius.zero,
Radius bottomLeft = Radius.zero,
}) : this._raw(
top: rect.top,
left: rect.left,
right: rect.right,
bottom: rect.bottom,
tlRadiusX: topLeft.x,
tlRadiusY: topLeft.y,
trRadiusX: topRight.x,
trRadiusY: topRight.y,
blRadiusX: bottomLeft.x,
blRadiusY: bottomLeft.y,
brRadiusX: bottomRight.x,
brRadiusY: bottomRight.y,
);
const RRect._raw({
this.left = 0.0,
this.top = 0.0,
this.right = 0.0,
this.bottom = 0.0,
this.tlRadiusX = 0.0,
this.tlRadiusY = 0.0,
this.trRadiusX = 0.0,
this.trRadiusY = 0.0,
this.brRadiusX = 0.0,
this.brRadiusY = 0.0,
this.blRadiusX = 0.0,
this.blRadiusY = 0.0,
}) : assert(tlRadiusX >= 0), }) : assert(tlRadiusX >= 0),
assert(tlRadiusY >= 0), assert(tlRadiusY >= 0),
assert(trRadiusX >= 0), assert(trRadiusX >= 0),
@ -1258,6 +1117,25 @@ class RRect {
assert(blRadiusX >= 0), assert(blRadiusX >= 0),
assert(blRadiusY >= 0); assert(blRadiusY >= 0);
// Implemented by a subclass to return an object constructed with the given
// parameters.
//
// Used by various methods that construct an object of the same shape.
T _create({
required double left,
required double top,
required double right,
required double bottom,
required double tlRadiusX,
required double tlRadiusY,
required double trRadiusX,
required double trRadiusY,
required double brRadiusX,
required double brRadiusY,
required double blRadiusX,
required double blRadiusY,
});
Float32List _getValue32() { Float32List _getValue32() {
final Float32List result = Float32List(12); final Float32List result = Float32List(12);
result[0] = left; result[0] = left;
@ -1323,12 +1201,9 @@ class RRect {
/// The bottom-left [Radius]. /// The bottom-left [Radius].
Radius get blRadius => Radius.elliptical(blRadiusX, blRadiusY); Radius get blRadius => Radius.elliptical(blRadiusX, blRadiusY);
/// A rounded rectangle with all the values set to zero. /// Returns a clone translated by the given offset.
static const RRect zero = RRect._raw(); T shift(Offset offset) {
return _create(
/// Returns a new [RRect] translated by the given offset.
RRect shift(Offset offset) {
return RRect._raw(
left: left + offset.dx, left: left + offset.dx,
top: top + offset.dy, top: top + offset.dy,
right: right + offset.dx, right: right + offset.dx,
@ -1344,10 +1219,10 @@ class RRect {
); );
} }
/// Returns a new [RRect] with edges and radii moved outwards by the given /// Returns a clone with edges and radii moved outwards by the given
/// delta. /// delta.
RRect inflate(double delta) { T inflate(double delta) {
return RRect._raw( return _create(
left: left - delta, left: left - delta,
top: top - delta, top: top - delta,
right: right + delta, right: right + delta,
@ -1363,8 +1238,8 @@ class RRect {
); );
} }
/// Returns a new [RRect] with edges and radii moved inwards by the given delta. /// Returns a clone with edges and radii moved inwards by the given delta.
RRect deflate(double delta) => inflate(-delta); T deflate(double delta) => inflate(-delta);
/// The distance between the left and right edges of this rectangle. /// The distance between the left and right edges of this rectangle.
double get width => right - left; double get width => right - left;
@ -1516,7 +1391,7 @@ class RRect {
/// ///
/// See the [Skia scaling implementation](https://github.com/google/skia/blob/main/src/core/SkRRect.cpp) /// See the [Skia scaling implementation](https://github.com/google/skia/blob/main/src/core/SkRRect.cpp)
/// for more details. /// for more details.
RRect scaleRadii() { T scaleRadii() {
double scale = 1.0; double scale = 1.0;
scale = _getMin(scale, blRadiusY, tlRadiusY, height); scale = _getMin(scale, blRadiusY, tlRadiusY, height);
scale = _getMin(scale, tlRadiusX, trRadiusX, width); scale = _getMin(scale, tlRadiusX, trRadiusX, width);
@ -1525,7 +1400,7 @@ class RRect {
assert(scale >= 0); assert(scale >= 0);
if (scale < 1.0) { if (scale < 1.0) {
return RRect._raw( return _create(
top: top, top: top,
left: left, left: left,
right: right, right: right,
@ -1541,7 +1416,7 @@ class RRect {
); );
} }
return RRect._raw( return _create(
top: top, top: top,
left: left, left: left,
right: right, right: right,
@ -1557,6 +1432,294 @@ class RRect {
); );
} }
// Linearly interpolate between this object and another of the same shape.
T _lerpTo(T? b, double t) {
assert(runtimeType == T);
if (b == null) {
final double k = 1.0 - t;
return _create(
left: left * k,
top: top * k,
right: right * k,
bottom: bottom * k,
tlRadiusX: math.max(0, tlRadiusX * k),
tlRadiusY: math.max(0, tlRadiusY * k),
trRadiusX: math.max(0, trRadiusX * k),
trRadiusY: math.max(0, trRadiusY * k),
brRadiusX: math.max(0, brRadiusX * k),
brRadiusY: math.max(0, brRadiusY * k),
blRadiusX: math.max(0, blRadiusX * k),
blRadiusY: math.max(0, blRadiusY * k),
);
} else {
return _create(
left: _lerpDouble(left, b.left, t),
top: _lerpDouble(top, b.top, t),
right: _lerpDouble(right, b.right, t),
bottom: _lerpDouble(bottom, b.bottom, t),
tlRadiusX: math.max(0, _lerpDouble(tlRadiusX, b.tlRadiusX, t)),
tlRadiusY: math.max(0, _lerpDouble(tlRadiusY, b.tlRadiusY, t)),
trRadiusX: math.max(0, _lerpDouble(trRadiusX, b.trRadiusX, t)),
trRadiusY: math.max(0, _lerpDouble(trRadiusY, b.trRadiusY, t)),
brRadiusX: math.max(0, _lerpDouble(brRadiusX, b.brRadiusX, t)),
brRadiusY: math.max(0, _lerpDouble(brRadiusY, b.brRadiusY, t)),
blRadiusX: math.max(0, _lerpDouble(blRadiusX, b.blRadiusX, t)),
blRadiusY: math.max(0, _lerpDouble(blRadiusY, b.blRadiusY, t)),
);
}
}
@override
bool operator ==(Object other) {
if (identical(this, other)) {
return true;
}
if (runtimeType != other.runtimeType) {
return false;
}
return other is _RRectLike &&
other.left == left &&
other.top == top &&
other.right == right &&
other.bottom == bottom &&
other.tlRadiusX == tlRadiusX &&
other.tlRadiusY == tlRadiusY &&
other.trRadiusX == trRadiusX &&
other.trRadiusY == trRadiusY &&
other.blRadiusX == blRadiusX &&
other.blRadiusY == blRadiusY &&
other.brRadiusX == brRadiusX &&
other.brRadiusY == brRadiusY;
}
@override
int get hashCode => Object.hash(
left,
top,
right,
bottom,
tlRadiusX,
tlRadiusY,
trRadiusX,
trRadiusY,
blRadiusX,
blRadiusY,
brRadiusX,
brRadiusY,
);
String _toString({required String className}) {
final String rect =
'${left.toStringAsFixed(1)}, '
'${top.toStringAsFixed(1)}, '
'${right.toStringAsFixed(1)}, '
'${bottom.toStringAsFixed(1)}';
if (tlRadius == trRadius && trRadius == brRadius && brRadius == blRadius) {
if (tlRadius.x == tlRadius.y) {
return '$className.fromLTRBR($rect, ${tlRadius.x.toStringAsFixed(1)})';
}
return '$className.fromLTRBXY($rect, ${tlRadius.x.toStringAsFixed(1)}, ${tlRadius.y.toStringAsFixed(1)})';
}
return '$className.fromLTRBAndCorners('
'$rect, '
'topLeft: $tlRadius, '
'topRight: $trRadius, '
'bottomRight: $brRadius, '
'bottomLeft: $blRadius'
')';
}
}
/// An immutable rounded rectangle with the custom radii for all four corners.
class RRect extends _RRectLike<RRect> {
/// Construct a rounded rectangle from its left, top, right, and bottom edges,
/// and the same radii along its horizontal axis and its vertical axis.
///
/// Will assert in debug mode if `radiusX` or `radiusY` are negative.
const RRect.fromLTRBXY(
double left,
double top,
double right,
double bottom,
double radiusX,
double radiusY,
) : this._raw(
top: top,
left: left,
right: right,
bottom: bottom,
tlRadiusX: radiusX,
tlRadiusY: radiusY,
trRadiusX: radiusX,
trRadiusY: radiusY,
blRadiusX: radiusX,
blRadiusY: radiusY,
brRadiusX: radiusX,
brRadiusY: radiusY,
);
/// Construct a rounded rectangle from its left, top, right, and bottom edges,
/// and the same radius in each corner.
///
/// Will assert in debug mode if the `radius` is negative in either x or y.
RRect.fromLTRBR(double left, double top, double right, double bottom, Radius radius)
: this._raw(
top: top,
left: left,
right: right,
bottom: bottom,
tlRadiusX: radius.x,
tlRadiusY: radius.y,
trRadiusX: radius.x,
trRadiusY: radius.y,
blRadiusX: radius.x,
blRadiusY: radius.y,
brRadiusX: radius.x,
brRadiusY: radius.y,
);
/// Construct a rounded rectangle from its bounding box and the same radii
/// along its horizontal axis and its vertical axis.
///
/// Will assert in debug mode if `radiusX` or `radiusY` are negative.
RRect.fromRectXY(Rect rect, double radiusX, double radiusY)
: this._raw(
top: rect.top,
left: rect.left,
right: rect.right,
bottom: rect.bottom,
tlRadiusX: radiusX,
tlRadiusY: radiusY,
trRadiusX: radiusX,
trRadiusY: radiusY,
blRadiusX: radiusX,
blRadiusY: radiusY,
brRadiusX: radiusX,
brRadiusY: radiusY,
);
/// Construct a rounded rectangle from its bounding box and a radius that is
/// the same in each corner.
///
/// Will assert in debug mode if the `radius` is negative in either x or y.
RRect.fromRectAndRadius(Rect rect, Radius radius)
: this._raw(
top: rect.top,
left: rect.left,
right: rect.right,
bottom: rect.bottom,
tlRadiusX: radius.x,
tlRadiusY: radius.y,
trRadiusX: radius.x,
trRadiusY: radius.y,
blRadiusX: radius.x,
blRadiusY: radius.y,
brRadiusX: radius.x,
brRadiusY: radius.y,
);
/// Construct a rounded rectangle from its left, top, right, and bottom edges,
/// and topLeft, topRight, bottomRight, and bottomLeft radii.
///
/// The corner radii default to [Radius.zero], i.e. right-angled corners. Will
/// assert in debug mode if any of the radii are negative in either x or y.
RRect.fromLTRBAndCorners(
double left,
double top,
double right,
double bottom, {
Radius topLeft = Radius.zero,
Radius topRight = Radius.zero,
Radius bottomRight = Radius.zero,
Radius bottomLeft = Radius.zero,
}) : this._raw(
top: top,
left: left,
right: right,
bottom: bottom,
tlRadiusX: topLeft.x,
tlRadiusY: topLeft.y,
trRadiusX: topRight.x,
trRadiusY: topRight.y,
blRadiusX: bottomLeft.x,
blRadiusY: bottomLeft.y,
brRadiusX: bottomRight.x,
brRadiusY: bottomRight.y,
);
/// Construct a rounded rectangle from its bounding box and topLeft,
/// topRight, bottomRight, and bottomLeft radii.
///
/// The corner radii default to [Radius.zero], i.e. right-angled corners. Will
/// assert in debug mode if any of the radii are negative in either x or y.
RRect.fromRectAndCorners(
Rect rect, {
Radius topLeft = Radius.zero,
Radius topRight = Radius.zero,
Radius bottomRight = Radius.zero,
Radius bottomLeft = Radius.zero,
}) : this._raw(
top: rect.top,
left: rect.left,
right: rect.right,
bottom: rect.bottom,
tlRadiusX: topLeft.x,
tlRadiusY: topLeft.y,
trRadiusX: topRight.x,
trRadiusY: topRight.y,
blRadiusX: bottomLeft.x,
blRadiusY: bottomLeft.y,
brRadiusX: bottomRight.x,
brRadiusY: bottomRight.y,
);
const RRect._raw({
super.left = 0.0,
super.top = 0.0,
super.right = 0.0,
super.bottom = 0.0,
super.tlRadiusX = 0.0,
super.tlRadiusY = 0.0,
super.trRadiusX = 0.0,
super.trRadiusY = 0.0,
super.brRadiusX = 0.0,
super.brRadiusY = 0.0,
super.blRadiusX = 0.0,
super.blRadiusY = 0.0,
});
@override
RRect _create({
required double left,
required double top,
required double right,
required double bottom,
required double tlRadiusX,
required double tlRadiusY,
required double trRadiusX,
required double trRadiusY,
required double brRadiusX,
required double brRadiusY,
required double blRadiusX,
required double blRadiusY,
}) => RRect._raw(
top: top,
left: left,
right: right,
bottom: bottom,
tlRadiusX: tlRadiusX,
tlRadiusY: tlRadiusY,
trRadiusX: trRadiusX,
trRadiusY: trRadiusY,
blRadiusX: blRadiusX,
blRadiusY: blRadiusY,
brRadiusX: brRadiusX,
brRadiusY: brRadiusY,
);
/// A rounded rectangle with all the values set to zero.
static const RRect zero = RRect._raw();
/// Whether the point specified by the given offset (which is assumed to be /// Whether the point specified by the given offset (which is assumed to be
/// relative to the origin) lies inside the rounded rectangle. /// relative to the origin) lies inside the rounded rectangle.
/// ///
@ -1626,120 +1789,254 @@ class RRect {
/// Values for `t` are usually obtained from an [Animation<double>], such as /// Values for `t` are usually obtained from an [Animation<double>], such as
/// an [AnimationController]. /// an [AnimationController].
static RRect? lerp(RRect? a, RRect? b, double t) { static RRect? lerp(RRect? a, RRect? b, double t) {
if (b == null) { if (a == null) {
if (a == null) { if (b == null) {
return null; return null;
} else {
final double k = 1.0 - t;
return RRect._raw(
left: a.left * k,
top: a.top * k,
right: a.right * k,
bottom: a.bottom * k,
tlRadiusX: math.max(0, a.tlRadiusX * k),
tlRadiusY: math.max(0, a.tlRadiusY * k),
trRadiusX: math.max(0, a.trRadiusX * k),
trRadiusY: math.max(0, a.trRadiusY * k),
brRadiusX: math.max(0, a.brRadiusX * k),
brRadiusY: math.max(0, a.brRadiusY * k),
blRadiusX: math.max(0, a.blRadiusX * k),
blRadiusY: math.max(0, a.blRadiusY * k),
);
}
} else {
if (a == null) {
return RRect._raw(
left: b.left * t,
top: b.top * t,
right: b.right * t,
bottom: b.bottom * t,
tlRadiusX: math.max(0, b.tlRadiusX * t),
tlRadiusY: math.max(0, b.tlRadiusY * t),
trRadiusX: math.max(0, b.trRadiusX * t),
trRadiusY: math.max(0, b.trRadiusY * t),
brRadiusX: math.max(0, b.brRadiusX * t),
brRadiusY: math.max(0, b.brRadiusY * t),
blRadiusX: math.max(0, b.blRadiusX * t),
blRadiusY: math.max(0, b.blRadiusY * t),
);
} else {
return RRect._raw(
left: _lerpDouble(a.left, b.left, t),
top: _lerpDouble(a.top, b.top, t),
right: _lerpDouble(a.right, b.right, t),
bottom: _lerpDouble(a.bottom, b.bottom, t),
tlRadiusX: math.max(0, _lerpDouble(a.tlRadiusX, b.tlRadiusX, t)),
tlRadiusY: math.max(0, _lerpDouble(a.tlRadiusY, b.tlRadiusY, t)),
trRadiusX: math.max(0, _lerpDouble(a.trRadiusX, b.trRadiusX, t)),
trRadiusY: math.max(0, _lerpDouble(a.trRadiusY, b.trRadiusY, t)),
brRadiusX: math.max(0, _lerpDouble(a.brRadiusX, b.brRadiusX, t)),
brRadiusY: math.max(0, _lerpDouble(a.brRadiusY, b.brRadiusY, t)),
blRadiusX: math.max(0, _lerpDouble(a.blRadiusX, b.blRadiusX, t)),
blRadiusY: math.max(0, _lerpDouble(a.blRadiusY, b.blRadiusY, t)),
);
} }
return b._lerpTo(null, 1 - t);
} }
return a._lerpTo(b, t);
} }
@override
bool operator ==(Object other) {
if (identical(this, other)) {
return true;
}
if (runtimeType != other.runtimeType) {
return false;
}
return other is RRect &&
other.left == left &&
other.top == top &&
other.right == right &&
other.bottom == bottom &&
other.tlRadiusX == tlRadiusX &&
other.tlRadiusY == tlRadiusY &&
other.trRadiusX == trRadiusX &&
other.trRadiusY == trRadiusY &&
other.blRadiusX == blRadiusX &&
other.blRadiusY == blRadiusY &&
other.brRadiusX == brRadiusX &&
other.brRadiusY == brRadiusY;
}
@override
int get hashCode => Object.hash(
left,
top,
right,
bottom,
tlRadiusX,
tlRadiusY,
trRadiusX,
trRadiusY,
blRadiusX,
blRadiusY,
brRadiusX,
brRadiusY,
);
@override @override
String toString() { String toString() {
final String rect = return _toString(className: 'RRect');
'${left.toStringAsFixed(1)}, ' }
'${top.toStringAsFixed(1)}, ' }
'${right.toStringAsFixed(1)}, '
'${bottom.toStringAsFixed(1)}'; /// An immutable rounded superellipse.
if (tlRadius == trRadius && trRadius == brRadius && brRadius == blRadius) { ///
if (tlRadius.x == tlRadius.y) { /// A rounded superellipse is a shape similar to a typical rounded rectangle
return 'RRect.fromLTRBR($rect, ${tlRadius.x.toStringAsFixed(1)})'; /// ([RRect]), but with smoother transitions between the straight sides and the
/// rounded corners. It resembles the `RoundedRectangle` shape in SwiftUI with
/// the `.continuous` corner style.
///
/// Technically, a canonical rounded superellipse, i.e. one with a uniform
/// corner radius ([RSuperellipse.fromRectAndRadius]), is created by replacing
/// the four corners of a superellipse (also known as a Lamé curve) with
/// circular arcs. A rounded superellipse with non-uniform radii is extended on it
/// by concatenating arc segments and transformation.
///
/// The corner radius parameters used in this class corresponds to SwiftUI's
/// `cornerRadius` parameter, which is close to, but not exactly equals to, the
/// radius of the corner circles.
class RSuperellipse extends _RRectLike<RSuperellipse> {
/// Construct a rounded rectangle from its left, top, right, and bottom edges,
/// and the same radii along its horizontal axis and its vertical axis.
///
/// Will assert in debug mode if `radiusX` or `radiusY` are negative.
const RSuperellipse.fromLTRBXY(
double left,
double top,
double right,
double bottom,
double radiusX,
double radiusY,
) : this._raw(
top: top,
left: left,
right: right,
bottom: bottom,
tlRadiusX: radiusX,
tlRadiusY: radiusY,
trRadiusX: radiusX,
trRadiusY: radiusY,
blRadiusX: radiusX,
blRadiusY: radiusY,
brRadiusX: radiusX,
brRadiusY: radiusY,
);
/// Construct a rounded rectangle from its left, top, right, and bottom edges,
/// and the same radius in each corner.
///
/// Will assert in debug mode if the `radius` is negative in either x or y.
RSuperellipse.fromLTRBR(double left, double top, double right, double bottom, Radius radius)
: this._raw(
top: top,
left: left,
right: right,
bottom: bottom,
tlRadiusX: radius.x,
tlRadiusY: radius.y,
trRadiusX: radius.x,
trRadiusY: radius.y,
blRadiusX: radius.x,
blRadiusY: radius.y,
brRadiusX: radius.x,
brRadiusY: radius.y,
);
/// Construct a rounded rectangle from its bounding box and the same radii
/// along its horizontal axis and its vertical axis.
///
/// Will assert in debug mode if `radiusX` or `radiusY` are negative.
RSuperellipse.fromRectXY(Rect rect, double radiusX, double radiusY)
: this._raw(
top: rect.top,
left: rect.left,
right: rect.right,
bottom: rect.bottom,
tlRadiusX: radiusX,
tlRadiusY: radiusY,
trRadiusX: radiusX,
trRadiusY: radiusY,
blRadiusX: radiusX,
blRadiusY: radiusY,
brRadiusX: radiusX,
brRadiusY: radiusY,
);
/// Construct a rounded rectangle from its bounding box and a radius that is
/// the same in each corner.
///
/// Will assert in debug mode if the `radius` is negative in either x or y.
RSuperellipse.fromRectAndRadius(Rect rect, Radius radius)
: this._raw(
top: rect.top,
left: rect.left,
right: rect.right,
bottom: rect.bottom,
tlRadiusX: radius.x,
tlRadiusY: radius.y,
trRadiusX: radius.x,
trRadiusY: radius.y,
blRadiusX: radius.x,
blRadiusY: radius.y,
brRadiusX: radius.x,
brRadiusY: radius.y,
);
/// Construct a rounded rectangle from its left, top, right, and bottom edges,
/// and topLeft, topRight, bottomRight, and bottomLeft radii.
///
/// The corner radii default to [Radius.zero], i.e. right-angled corners. Will
/// assert in debug mode if any of the radii are negative in either x or y.
RSuperellipse.fromLTRBAndCorners(
double left,
double top,
double right,
double bottom, {
Radius topLeft = Radius.zero,
Radius topRight = Radius.zero,
Radius bottomRight = Radius.zero,
Radius bottomLeft = Radius.zero,
}) : this._raw(
top: top,
left: left,
right: right,
bottom: bottom,
tlRadiusX: topLeft.x,
tlRadiusY: topLeft.y,
trRadiusX: topRight.x,
trRadiusY: topRight.y,
blRadiusX: bottomLeft.x,
blRadiusY: bottomLeft.y,
brRadiusX: bottomRight.x,
brRadiusY: bottomRight.y,
);
/// Construct a rounded rectangle from its bounding box and topLeft,
/// topRight, bottomRight, and bottomLeft radii.
///
/// The corner radii default to [Radius.zero], i.e. right-angled corners. Will
/// assert in debug mode if any of the radii are negative in either x or y.
RSuperellipse.fromRectAndCorners(
Rect rect, {
Radius topLeft = Radius.zero,
Radius topRight = Radius.zero,
Radius bottomRight = Radius.zero,
Radius bottomLeft = Radius.zero,
}) : this._raw(
top: rect.top,
left: rect.left,
right: rect.right,
bottom: rect.bottom,
tlRadiusX: topLeft.x,
tlRadiusY: topLeft.y,
trRadiusX: topRight.x,
trRadiusY: topRight.y,
blRadiusX: bottomLeft.x,
blRadiusY: bottomLeft.y,
brRadiusX: bottomRight.x,
brRadiusY: bottomRight.y,
);
const RSuperellipse._raw({
super.left = 0.0,
super.top = 0.0,
super.right = 0.0,
super.bottom = 0.0,
super.tlRadiusX = 0.0,
super.tlRadiusY = 0.0,
super.trRadiusX = 0.0,
super.trRadiusY = 0.0,
super.brRadiusX = 0.0,
super.brRadiusY = 0.0,
super.blRadiusX = 0.0,
super.blRadiusY = 0.0,
});
@override
RSuperellipse _create({
required double left,
required double top,
required double right,
required double bottom,
required double tlRadiusX,
required double tlRadiusY,
required double trRadiusX,
required double trRadiusY,
required double brRadiusX,
required double brRadiusY,
required double blRadiusX,
required double blRadiusY,
}) => RSuperellipse._raw(
top: top,
left: left,
right: right,
bottom: bottom,
tlRadiusX: tlRadiusX,
tlRadiusY: tlRadiusY,
trRadiusX: trRadiusX,
trRadiusY: trRadiusY,
blRadiusX: blRadiusX,
blRadiusY: blRadiusY,
brRadiusX: brRadiusX,
brRadiusY: brRadiusY,
);
/// A rounded rectangle with all the values set to zero.
static const RSuperellipse zero = RSuperellipse._raw();
/// Linearly interpolate between two rounded superellipses.
///
/// If either is null, this function substitutes [RSuperellipse.zero] instead.
///
/// The `t` argument represents position on the timeline, with 0.0 meaning
/// that the interpolation has not started, returning `a` (or something
/// equivalent to `a`), 1.0 meaning that the interpolation has finished,
/// returning `b` (or something equivalent to `b`), and values in between
/// meaning that the interpolation is at the relevant point on the timeline
/// between `a` and `b`. The interpolation can be extrapolated beyond 0.0 and
/// 1.0, so negative values and values greater than 1.0 are valid (and can
/// easily be generated by curves such as [Curves.elasticInOut]).
///
/// Values for `t` are usually obtained from an [Animation<double>], such as
/// an [AnimationController].
static RSuperellipse? lerp(RSuperellipse? a, RSuperellipse? b, double t) {
if (a == null) {
if (b == null) {
return null;
} }
return 'RRect.fromLTRBXY($rect, ${tlRadius.x.toStringAsFixed(1)}, ${tlRadius.y.toStringAsFixed(1)})'; return b._lerpTo(null, 1 - t);
} }
return 'RRect.fromLTRBAndCorners(' return a._lerpTo(b, t);
'$rect, ' }
'topLeft: $tlRadius, '
'topRight: $trRadius, ' @override
'bottomRight: $brRadius, ' String toString() {
'bottomLeft: $blRadius' return _toString(className: 'RSuperellipse');
')';
} }
} }

View File

@ -32,6 +32,11 @@ bool _rrectIsValid(RRect rrect) {
return true; return true;
} }
bool _rseIsValid(RSuperellipse rse) {
assert(!rse.hasNaN, 'RSuperellipse argument contained a NaN value.');
return true;
}
bool _offsetIsValid(Offset offset) { bool _offsetIsValid(Offset offset) {
assert(!offset.dx.isNaN && !offset.dy.isNaN, 'Offset argument contained a NaN value.'); assert(!offset.dx.isNaN && !offset.dy.isNaN, 'Offset argument contained a NaN value.');
return true; return true;
@ -5873,6 +5878,18 @@ abstract class Canvas {
/// discussion of how to address that and some examples of using [clipRRect]. /// discussion of how to address that and some examples of using [clipRRect].
void clipRRect(RRect rrect, {bool doAntiAlias = true}); void clipRRect(RRect rrect, {bool doAntiAlias = true});
/// Reduces the clip region to the intersection of the current clip and the
/// given rounded superellipse.
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/clip_rsuperellipse.png)
///
/// If [doAntiAlias] is true, then the clip will be anti-aliased.
///
/// If multiple draw commands intersect with the clip boundary, this can result
/// in incorrect blending at the clip boundary. See [saveLayer] for a
/// discussion of how to address that and some examples of using [clipRSuperellipse].
void clipRSuperellipse(RSuperellipse rse, {bool doAntiAlias = true});
/// Reduces the clip region to the intersection of the current clip and the /// Reduces the clip region to the intersection of the current clip and the
/// given [Path]. /// given [Path].
/// ///
@ -5996,6 +6013,13 @@ abstract class Canvas {
/// This shape is almost but not quite entirely unlike an annulus. /// This shape is almost but not quite entirely unlike an annulus.
void drawDRRect(RRect outer, RRect inner, Paint paint); void drawDRRect(RRect outer, RRect inner, Paint paint);
/// Draws a rounded superellipse with the given [Paint]. The shape is filled,
/// and the value of the [Paint.style] is ignored for this call.
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/canvas_rsuperellipse.png#gh-light-mode-only)
/// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/canvas_rsuperellipse.png#gh-dark-mode-only)
void drawRSuperellipse(RSuperellipse rse, Paint paint);
/// Draws an axis-aligned oval that fills the given axis-aligned rectangle /// Draws an axis-aligned oval that fills the given axis-aligned rectangle
/// with the given [Paint]. Whether the oval is filled or stroked (or both) is /// with the given [Paint]. Whether the oval is filled or stroked (or both) is
/// controlled by [Paint.style]. /// controlled by [Paint.style].
@ -6607,6 +6631,15 @@ base class _NativeCanvas extends NativeFieldWrapperClass1 implements Canvas {
@Native<Void Function(Pointer<Void>, Handle, Bool)>(symbol: 'Canvas::clipRRect') @Native<Void Function(Pointer<Void>, Handle, Bool)>(symbol: 'Canvas::clipRRect')
external void _clipRRect(Float32List rrect, bool doAntiAlias); external void _clipRRect(Float32List rrect, bool doAntiAlias);
@override
void clipRSuperellipse(RSuperellipse rse, {bool doAntiAlias = true}) {
assert(_rseIsValid(rse));
_clipRSuperellipse(rse._getValue32(), doAntiAlias);
}
@Native<Void Function(Pointer<Void>, Handle, Bool)>(symbol: 'Canvas::clipRSuperellipse')
external void _clipRSuperellipse(Float32List rse, bool doAntiAlias);
@override @override
void clipPath(Path path, {bool doAntiAlias = true}) { void clipPath(Path path, {bool doAntiAlias = true}) {
_clipPath(path as _NativePath, doAntiAlias); _clipPath(path as _NativePath, doAntiAlias);
@ -6717,6 +6750,19 @@ base class _NativeCanvas extends NativeFieldWrapperClass1 implements Canvas {
ByteData paintData, ByteData paintData,
); );
@override
void drawRSuperellipse(RSuperellipse rse, Paint paint) {
assert(_rseIsValid(rse));
_drawRSuperellipse(rse._getValue32(), paint._objects, paint._data);
}
@Native<Void Function(Pointer<Void>, Handle, Handle, Handle)>(symbol: 'Canvas::drawRSuperellipse')
external void _drawRSuperellipse(
Float32List rse,
List<Object?>? paintObjects,
ByteData paintData,
);
@override @override
void drawOval(Rect rect, Paint paint) { void drawOval(Rect rect, Paint paint) {
assert(_rectIsValid(rect)); assert(_rectIsValid(rect));

View File

@ -177,6 +177,13 @@ void Canvas::clipRRect(const RRect& rrect, bool doAntiAlias) {
} }
} }
void Canvas::clipRSuperellipse(const RSuperellipse& rse, bool doAntiAlias) {
if (display_list_builder_) {
builder()->ClipRoundSuperellipse(rse.rsuperellipse, DlClipOp::kIntersect,
doAntiAlias);
}
}
void Canvas::clipPath(const CanvasPath* path, bool doAntiAlias) { void Canvas::clipPath(const CanvasPath* path, bool doAntiAlias) {
if (!path) { if (!path) {
Dart_ThrowException( Dart_ThrowException(
@ -295,6 +302,19 @@ void Canvas::drawDRRect(const RRect& outer,
} }
} }
void Canvas::drawRSuperellipse(const RSuperellipse& rse,
Dart_Handle paint_objects,
Dart_Handle paint_data) {
Paint paint(paint_objects, paint_data);
FML_DCHECK(paint.isNotNull());
if (display_list_builder_) {
DlPaint dl_paint;
paint.paint(dl_paint, kDrawDRRectFlags, DlTileMode::kDecal);
builder()->DrawRoundSuperellipse(rse.rsuperellipse, dl_paint);
}
}
void Canvas::drawOval(double left, void Canvas::drawOval(double left,
double top, double top,
double right, double right,

View File

@ -12,6 +12,7 @@
#include "flutter/lib/ui/painting/picture.h" #include "flutter/lib/ui/painting/picture.h"
#include "flutter/lib/ui/painting/picture_recorder.h" #include "flutter/lib/ui/painting/picture_recorder.h"
#include "flutter/lib/ui/painting/rrect.h" #include "flutter/lib/ui/painting/rrect.h"
#include "flutter/lib/ui/painting/rsuperellipse.h"
#include "flutter/lib/ui/painting/vertices.h" #include "flutter/lib/ui/painting/vertices.h"
#include "third_party/tonic/typed_data/typed_list.h" #include "third_party/tonic/typed_data/typed_list.h"
@ -61,6 +62,7 @@ class Canvas : public RefCountedDartWrappable<Canvas>, DisplayListOpFlags {
DlClipOp clipOp, DlClipOp clipOp,
bool doAntiAlias = true); bool doAntiAlias = true);
void clipRRect(const RRect& rrect, bool doAntiAlias = true); void clipRRect(const RRect& rrect, bool doAntiAlias = true);
void clipRSuperellipse(const RSuperellipse& rse, bool doAntiAlias = true);
void clipPath(const CanvasPath* path, bool doAntiAlias = true); void clipPath(const CanvasPath* path, bool doAntiAlias = true);
void getDestinationClipBounds(Dart_Handle rect_handle); void getDestinationClipBounds(Dart_Handle rect_handle);
void getLocalClipBounds(Dart_Handle rect_handle); void getLocalClipBounds(Dart_Handle rect_handle);
@ -92,6 +94,10 @@ class Canvas : public RefCountedDartWrappable<Canvas>, DisplayListOpFlags {
Dart_Handle paint_objects, Dart_Handle paint_objects,
Dart_Handle paint_data); Dart_Handle paint_data);
void drawRSuperellipse(const RSuperellipse& rse,
Dart_Handle paint_objects,
Dart_Handle paint_data);
void drawOval(double left, void drawOval(double left,
double top, double top,
double right, double right,

View File

@ -0,0 +1,59 @@
// 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/lib/ui/painting/rsuperellipse.h"
#include "flutter/fml/logging.h"
#include "third_party/tonic/logging/dart_error.h"
#include "third_party/tonic/typed_data/typed_list.h"
using flutter::RSuperellipse;
namespace tonic {
// Construct an DlRoundSuperellipse from a Dart RSuperellipse object.
// The Dart RSuperellipse is a Float32List containing
// [left, top, right, bottom, xRadius, yRadius]
RSuperellipse DartConverter<flutter::RSuperellipse>::FromDart(
Dart_Handle value) {
Float32List buffer(value);
RSuperellipse result;
result.is_null = true;
if (buffer.data() == nullptr) {
return result;
}
// The Flutter rect may be inverted (upside down, backward, or both)
// Historically, Skia would normalize such rects but we will do that
// manually below when we construct the Impeller RoundRect
flutter::DlRect raw_rect =
flutter::DlRect::MakeLTRB(buffer[0], buffer[1], buffer[2], buffer[3]);
// Flutter has radii in TL,TR,BR,BL (clockwise) order,
// but Impeller uses TL,TR,BL,BR (zig-zag) order
impeller::RoundingRadii radii = {
.top_left = flutter::DlSize(buffer[4], buffer[5]),
.top_right = flutter::DlSize(buffer[6], buffer[7]),
.bottom_left = flutter::DlSize(buffer[10], buffer[11]),
.bottom_right = flutter::DlSize(buffer[8], buffer[9]),
};
result.rsuperellipse = flutter::DlRoundSuperellipse::MakeRectRadii(
raw_rect.GetPositive(), radii);
result.is_null = false;
return result;
}
RSuperellipse DartConverter<flutter::RSuperellipse>::FromArguments(
Dart_NativeArguments args,
int index,
Dart_Handle& exception) {
Dart_Handle value = Dart_GetNativeArgument(args, index);
FML_DCHECK(!CheckAndHandleError(value));
return FromDart(value);
}
} // namespace tonic

View File

@ -0,0 +1,45 @@
// 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.
#ifndef FLUTTER_LIB_UI_PAINTING_RSUPERELLIPSE_H_
#define FLUTTER_LIB_UI_PAINTING_RSUPERELLIPSE_H_
#include "flutter/display_list/geometry/dl_geometry_types.h"
#include "third_party/dart/runtime/include/dart_api.h"
#include "third_party/tonic/converter/dart_converter.h"
namespace flutter {
class RSuperellipse {
public:
DlRoundSuperellipse rsuperellipse;
bool is_null;
};
} // namespace flutter
namespace tonic {
template <>
struct DartConverter<flutter::RSuperellipse> {
using NativeType = flutter::RSuperellipse;
using FfiType = Dart_Handle;
static constexpr const char* kFfiRepresentation = "Handle";
static constexpr const char* kDartRepresentation = "Object";
static constexpr bool kAllowedInLeafCall = false;
static NativeType FromDart(Dart_Handle handle);
static NativeType FromArguments(Dart_NativeArguments args,
int index,
Dart_Handle& exception);
static NativeType FromFfi(FfiType val) { return FromDart(val); }
static const char* GetFfiRepresentation() { return kFfiRepresentation; }
static const char* GetDartRepresentation() { return kDartRepresentation; }
static bool AllowedInLeafCall() { return kAllowedInLeafCall; }
};
} // namespace tonic
#endif // FLUTTER_LIB_UI_PAINTING_RSUPERELLIPSE_H_

View File

@ -68,6 +68,7 @@ abstract class Canvas {
Float64List getTransform(); Float64List getTransform();
void clipRect(Rect rect, {ClipOp clipOp = ClipOp.intersect, bool doAntiAlias = true}); void clipRect(Rect rect, {ClipOp clipOp = ClipOp.intersect, bool doAntiAlias = true});
void clipRRect(RRect rrect, {bool doAntiAlias = true}); void clipRRect(RRect rrect, {bool doAntiAlias = true});
void clipRSuperellipse(RSuperellipse rse, {bool doAntiAlias = true});
void clipPath(Path path, {bool doAntiAlias = true}); void clipPath(Path path, {bool doAntiAlias = true});
Rect getLocalClipBounds(); Rect getLocalClipBounds();
Rect getDestinationClipBounds(); Rect getDestinationClipBounds();
@ -76,6 +77,7 @@ abstract class Canvas {
void drawPaint(Paint paint); void drawPaint(Paint paint);
void drawRect(Rect rect, Paint paint); void drawRect(Rect rect, Paint paint);
void drawRRect(RRect rrect, Paint paint); void drawRRect(RRect rrect, Paint paint);
void drawRSuperellipse(RSuperellipse rse, Paint paint);
void drawDRRect(RRect outer, RRect inner, Paint paint); void drawDRRect(RRect outer, RRect inner, Paint paint);
void drawOval(Rect rect, Paint paint); void drawOval(Rect rect, Paint paint);
void drawCircle(Offset c, double radius, Paint paint); void drawCircle(Offset c, double radius, Paint paint);

View File

@ -18,6 +18,8 @@ abstract class ClipRectEngineLayer implements EngineLayer {}
abstract class ClipRRectEngineLayer implements EngineLayer {} abstract class ClipRRectEngineLayer implements EngineLayer {}
abstract class ClipRSuperellipseEngineLayer implements EngineLayer {}
abstract class ClipPathEngineLayer implements EngineLayer {} abstract class ClipPathEngineLayer implements EngineLayer {}
abstract class OpacityEngineLayer implements EngineLayer {} abstract class OpacityEngineLayer implements EngineLayer {}
@ -45,6 +47,11 @@ abstract class SceneBuilder {
required Clip clipBehavior, required Clip clipBehavior,
ClipRRectEngineLayer? oldLayer, ClipRRectEngineLayer? oldLayer,
}); });
ClipRSuperellipseEngineLayer pushClipRSuperellipse(
RSuperellipse rse, {
required Clip clipBehavior,
ClipRSuperellipseEngineLayer? oldLayer,
});
ClipPathEngineLayer pushClipPath( ClipPathEngineLayer pushClipPath(
Path path, { Path path, {
Clip clipBehavior = Clip.antiAlias, Clip clipBehavior = Clip.antiAlias,

View File

@ -405,7 +405,337 @@ class Radius {
} }
} }
class RRect { abstract class _RRectLike<T extends _RRectLike<T>> {
const _RRectLike({
required this.left,
required this.top,
required this.right,
required this.bottom,
required this.tlRadiusX,
required this.tlRadiusY,
required this.trRadiusX,
required this.trRadiusY,
required this.brRadiusX,
required this.brRadiusY,
required this.blRadiusX,
required this.blRadiusY,
required bool uniformRadii,
}) : assert(tlRadiusX >= 0),
assert(tlRadiusY >= 0),
assert(trRadiusX >= 0),
assert(trRadiusY >= 0),
assert(brRadiusX >= 0),
assert(brRadiusY >= 0),
assert(blRadiusX >= 0),
assert(blRadiusY >= 0),
webOnlyUniformRadii = uniformRadii;
T _create({
required double left,
required double top,
required double right,
required double bottom,
required double tlRadiusX,
required double tlRadiusY,
required double trRadiusX,
required double trRadiusY,
required double brRadiusX,
required double brRadiusY,
required double blRadiusX,
required double blRadiusY,
});
final double left;
final double top;
final double right;
final double bottom;
final double tlRadiusX;
final double tlRadiusY;
Radius get tlRadius => Radius.elliptical(tlRadiusX, tlRadiusY);
final double trRadiusX;
final double trRadiusY;
Radius get trRadius => Radius.elliptical(trRadiusX, trRadiusY);
final double brRadiusX;
final double brRadiusY;
Radius get brRadius => Radius.elliptical(brRadiusX, brRadiusY);
final double blRadiusX;
final double blRadiusY;
// webOnly
final bool webOnlyUniformRadii;
Radius get blRadius => Radius.elliptical(blRadiusX, blRadiusY);
T shift(Offset offset) {
return _create(
left: left + offset.dx,
top: top + offset.dy,
right: right + offset.dx,
bottom: bottom + offset.dy,
tlRadiusX: tlRadiusX,
tlRadiusY: tlRadiusY,
trRadiusX: trRadiusX,
trRadiusY: trRadiusY,
blRadiusX: blRadiusX,
blRadiusY: blRadiusY,
brRadiusX: brRadiusX,
brRadiusY: brRadiusY,
);
}
T inflate(double delta) {
return _create(
left: left - delta,
top: top - delta,
right: right + delta,
bottom: bottom + delta,
tlRadiusX: math.max(0, tlRadiusX + delta),
tlRadiusY: math.max(0, tlRadiusY + delta),
trRadiusX: math.max(0, trRadiusX + delta),
trRadiusY: math.max(0, trRadiusY + delta),
blRadiusX: math.max(0, blRadiusX + delta),
blRadiusY: math.max(0, blRadiusY + delta),
brRadiusX: math.max(0, brRadiusX + delta),
brRadiusY: math.max(0, brRadiusY + delta),
);
}
T deflate(double delta) => inflate(-delta);
double get width => right - left;
double get height => bottom - top;
Rect get outerRect => Rect.fromLTRB(left, top, right, bottom);
Rect get safeInnerRect {
const double kInsetFactor = 0.29289321881; // 1-cos(pi/4)
final double leftRadius = math.max(blRadiusX, tlRadiusX);
final double topRadius = math.max(tlRadiusY, trRadiusY);
final double rightRadius = math.max(trRadiusX, brRadiusX);
final double bottomRadius = math.max(brRadiusY, blRadiusY);
return Rect.fromLTRB(
left + leftRadius * kInsetFactor,
top + topRadius * kInsetFactor,
right - rightRadius * kInsetFactor,
bottom - bottomRadius * kInsetFactor,
);
}
Rect get middleRect {
final double leftRadius = math.max(blRadiusX, tlRadiusX);
final double topRadius = math.max(tlRadiusY, trRadiusY);
final double rightRadius = math.max(trRadiusX, brRadiusX);
final double bottomRadius = math.max(brRadiusY, blRadiusY);
return Rect.fromLTRB(
left + leftRadius,
top + topRadius,
right - rightRadius,
bottom - bottomRadius,
);
}
Rect get wideMiddleRect {
final double topRadius = math.max(tlRadiusY, trRadiusY);
final double bottomRadius = math.max(brRadiusY, blRadiusY);
return Rect.fromLTRB(left, top + topRadius, right, bottom - bottomRadius);
}
Rect get tallMiddleRect {
final double leftRadius = math.max(blRadiusX, tlRadiusX);
final double rightRadius = math.max(trRadiusX, brRadiusX);
return Rect.fromLTRB(left + leftRadius, top, right - rightRadius, bottom);
}
bool get isEmpty => left >= right || top >= bottom;
bool get isFinite => left.isFinite && top.isFinite && right.isFinite && bottom.isFinite;
bool get isRect {
return (tlRadiusX == 0.0 || tlRadiusY == 0.0) &&
(trRadiusX == 0.0 || trRadiusY == 0.0) &&
(blRadiusX == 0.0 || blRadiusY == 0.0) &&
(brRadiusX == 0.0 || brRadiusY == 0.0);
}
bool get isStadium {
return tlRadius == trRadius &&
trRadius == brRadius &&
brRadius == blRadius &&
(width <= 2.0 * tlRadiusX || height <= 2.0 * tlRadiusY);
}
bool get isEllipse {
return tlRadius == trRadius &&
trRadius == brRadius &&
brRadius == blRadius &&
width <= 2.0 * tlRadiusX &&
height <= 2.0 * tlRadiusY;
}
bool get isCircle => width == height && isEllipse;
double get shortestSide => math.min(width.abs(), height.abs());
double get longestSide => math.max(width.abs(), height.abs());
bool get hasNaN =>
left.isNaN ||
top.isNaN ||
right.isNaN ||
bottom.isNaN ||
trRadiusX.isNaN ||
trRadiusY.isNaN ||
tlRadiusX.isNaN ||
tlRadiusY.isNaN ||
brRadiusX.isNaN ||
brRadiusY.isNaN ||
blRadiusX.isNaN ||
blRadiusY.isNaN;
Offset get center => Offset(left + width / 2.0, top + height / 2.0);
// Returns the minimum between min and scale to which radius1 and radius2
// should be scaled with in order not to exceed the limit.
double _getMin(double min, double radius1, double radius2, double limit) {
final double sum = radius1 + radius2;
if (sum > limit && sum != 0.0) {
return math.min(min, limit / sum);
}
return min;
}
T scaleRadii() {
double scale = 1.0;
final double absWidth = width.abs();
final double absHeight = height.abs();
scale = _getMin(scale, blRadiusY, tlRadiusY, absHeight);
scale = _getMin(scale, tlRadiusX, trRadiusX, absWidth);
scale = _getMin(scale, trRadiusY, brRadiusY, absHeight);
scale = _getMin(scale, brRadiusX, blRadiusX, absWidth);
if (scale < 1.0) {
return _create(
top: top,
left: left,
right: right,
bottom: bottom,
tlRadiusX: tlRadiusX * scale,
tlRadiusY: tlRadiusY * scale,
trRadiusX: trRadiusX * scale,
trRadiusY: trRadiusY * scale,
blRadiusX: blRadiusX * scale,
blRadiusY: blRadiusY * scale,
brRadiusX: brRadiusX * scale,
brRadiusY: brRadiusY * scale,
);
}
return _create(
top: top,
left: left,
right: right,
bottom: bottom,
tlRadiusX: tlRadiusX,
tlRadiusY: tlRadiusY,
trRadiusX: trRadiusX,
trRadiusY: trRadiusY,
blRadiusX: blRadiusX,
blRadiusY: blRadiusY,
brRadiusX: brRadiusX,
brRadiusY: brRadiusY,
);
}
// Linearly interpolate between this object and another of the same shape.
T _lerpTo(T? b, double t) {
assert(runtimeType == T);
if (b == null) {
final double k = 1.0 - t;
return _create(
left: left * k,
top: top * k,
right: right * k,
bottom: bottom * k,
tlRadiusX: math.max(0, tlRadiusX * k),
tlRadiusY: math.max(0, tlRadiusY * k),
trRadiusX: math.max(0, trRadiusX * k),
trRadiusY: math.max(0, trRadiusY * k),
brRadiusX: math.max(0, brRadiusX * k),
brRadiusY: math.max(0, brRadiusY * k),
blRadiusX: math.max(0, blRadiusX * k),
blRadiusY: math.max(0, blRadiusY * k),
);
} else {
return _create(
left: _lerpDouble(left, b.left, t),
top: _lerpDouble(top, b.top, t),
right: _lerpDouble(right, b.right, t),
bottom: _lerpDouble(bottom, b.bottom, t),
tlRadiusX: math.max(0, _lerpDouble(tlRadiusX, b.tlRadiusX, t)),
tlRadiusY: math.max(0, _lerpDouble(tlRadiusY, b.tlRadiusY, t)),
trRadiusX: math.max(0, _lerpDouble(trRadiusX, b.trRadiusX, t)),
trRadiusY: math.max(0, _lerpDouble(trRadiusY, b.trRadiusY, t)),
brRadiusX: math.max(0, _lerpDouble(brRadiusX, b.brRadiusX, t)),
brRadiusY: math.max(0, _lerpDouble(brRadiusY, b.brRadiusY, t)),
blRadiusX: math.max(0, _lerpDouble(blRadiusX, b.blRadiusX, t)),
blRadiusY: math.max(0, _lerpDouble(blRadiusY, b.blRadiusY, t)),
);
}
}
@override
bool operator ==(Object other) {
if (identical(this, other)) {
return true;
}
if (runtimeType != other.runtimeType) {
return false;
}
return other is _RRectLike &&
other.left == left &&
other.top == top &&
other.right == right &&
other.bottom == bottom &&
other.tlRadiusX == tlRadiusX &&
other.tlRadiusY == tlRadiusY &&
other.trRadiusX == trRadiusX &&
other.trRadiusY == trRadiusY &&
other.blRadiusX == blRadiusX &&
other.blRadiusY == blRadiusY &&
other.brRadiusX == brRadiusX &&
other.brRadiusY == brRadiusY;
}
@override
int get hashCode => Object.hash(
left,
top,
right,
bottom,
tlRadiusX,
tlRadiusY,
trRadiusX,
trRadiusY,
blRadiusX,
blRadiusY,
brRadiusX,
brRadiusY,
);
String _toString({required String className}) {
final String rect =
'${left.toStringAsFixed(1)}, '
'${top.toStringAsFixed(1)}, '
'${right.toStringAsFixed(1)}, '
'${bottom.toStringAsFixed(1)}';
if (tlRadius == trRadius && trRadius == brRadius && brRadius == blRadius) {
if (tlRadius.x == tlRadius.y) {
return '$className.fromLTRBR($rect, ${tlRadius.x.toStringAsFixed(1)})';
}
return '$className.fromLTRBXY($rect, ${tlRadius.x.toStringAsFixed(1)}, ${tlRadius.y.toStringAsFixed(1)})';
}
return '$className.fromLTRBAndCorners('
'$rect, '
'topLeft: $tlRadius, '
'topRight: $trRadius, '
'bottomRight: $brRadius, '
'bottomLeft: $blRadius'
')';
}
}
class RRect extends _RRectLike<RRect> {
const RRect.fromLTRBXY( const RRect.fromLTRBXY(
double left, double left,
double top, double top,
@ -542,222 +872,52 @@ class RRect {
); );
const RRect._raw({ const RRect._raw({
this.left = 0.0, super.left = 0.0,
this.top = 0.0, super.top = 0.0,
this.right = 0.0, super.right = 0.0,
this.bottom = 0.0, super.bottom = 0.0,
this.tlRadiusX = 0.0, super.tlRadiusX = 0.0,
this.tlRadiusY = 0.0, super.tlRadiusY = 0.0,
this.trRadiusX = 0.0, super.trRadiusX = 0.0,
this.trRadiusY = 0.0, super.trRadiusY = 0.0,
this.brRadiusX = 0.0, super.brRadiusX = 0.0,
this.brRadiusY = 0.0, super.brRadiusY = 0.0,
this.blRadiusX = 0.0, super.blRadiusX = 0.0,
this.blRadiusY = 0.0, super.blRadiusY = 0.0,
bool uniformRadii = false, super.uniformRadii = false,
}) : assert(tlRadiusX >= 0), });
assert(tlRadiusY >= 0),
assert(trRadiusX >= 0), @override
assert(trRadiusY >= 0), RRect _create({
assert(brRadiusX >= 0), required double left,
assert(brRadiusY >= 0), required double top,
assert(blRadiusX >= 0), required double right,
assert(blRadiusY >= 0), required double bottom,
webOnlyUniformRadii = uniformRadii; required double tlRadiusX,
required double tlRadiusY,
required double trRadiusX,
required double trRadiusY,
required double brRadiusX,
required double brRadiusY,
required double blRadiusX,
required double blRadiusY,
}) => RRect._raw(
top: top,
left: left,
right: right,
bottom: bottom,
tlRadiusX: tlRadiusX,
tlRadiusY: tlRadiusY,
trRadiusX: trRadiusX,
trRadiusY: trRadiusY,
blRadiusX: blRadiusX,
blRadiusY: blRadiusY,
brRadiusX: brRadiusX,
brRadiusY: brRadiusY,
);
final double left;
final double top;
final double right;
final double bottom;
final double tlRadiusX;
final double tlRadiusY;
Radius get tlRadius => Radius.elliptical(tlRadiusX, tlRadiusY);
final double trRadiusX;
final double trRadiusY;
Radius get trRadius => Radius.elliptical(trRadiusX, trRadiusY);
final double brRadiusX;
final double brRadiusY;
Radius get brRadius => Radius.elliptical(brRadiusX, brRadiusY);
final double blRadiusX;
final double blRadiusY;
// webOnly
final bool webOnlyUniformRadii;
Radius get blRadius => Radius.elliptical(blRadiusX, blRadiusY);
static const RRect zero = RRect._raw(); static const RRect zero = RRect._raw();
RRect shift(Offset offset) {
return RRect._raw(
left: left + offset.dx,
top: top + offset.dy,
right: right + offset.dx,
bottom: bottom + offset.dy,
tlRadiusX: tlRadiusX,
tlRadiusY: tlRadiusY,
trRadiusX: trRadiusX,
trRadiusY: trRadiusY,
blRadiusX: blRadiusX,
blRadiusY: blRadiusY,
brRadiusX: brRadiusX,
brRadiusY: brRadiusY,
);
}
RRect inflate(double delta) {
return RRect._raw(
left: left - delta,
top: top - delta,
right: right + delta,
bottom: bottom + delta,
tlRadiusX: math.max(0, tlRadiusX + delta),
tlRadiusY: math.max(0, tlRadiusY + delta),
trRadiusX: math.max(0, trRadiusX + delta),
trRadiusY: math.max(0, trRadiusY + delta),
blRadiusX: math.max(0, blRadiusX + delta),
blRadiusY: math.max(0, blRadiusY + delta),
brRadiusX: math.max(0, brRadiusX + delta),
brRadiusY: math.max(0, brRadiusY + delta),
);
}
RRect deflate(double delta) => inflate(-delta);
double get width => right - left;
double get height => bottom - top;
Rect get outerRect => Rect.fromLTRB(left, top, right, bottom);
Rect get safeInnerRect {
const double kInsetFactor = 0.29289321881; // 1-cos(pi/4)
final double leftRadius = math.max(blRadiusX, tlRadiusX);
final double topRadius = math.max(tlRadiusY, trRadiusY);
final double rightRadius = math.max(trRadiusX, brRadiusX);
final double bottomRadius = math.max(brRadiusY, blRadiusY);
return Rect.fromLTRB(
left + leftRadius * kInsetFactor,
top + topRadius * kInsetFactor,
right - rightRadius * kInsetFactor,
bottom - bottomRadius * kInsetFactor,
);
}
Rect get middleRect {
final double leftRadius = math.max(blRadiusX, tlRadiusX);
final double topRadius = math.max(tlRadiusY, trRadiusY);
final double rightRadius = math.max(trRadiusX, brRadiusX);
final double bottomRadius = math.max(brRadiusY, blRadiusY);
return Rect.fromLTRB(
left + leftRadius,
top + topRadius,
right - rightRadius,
bottom - bottomRadius,
);
}
Rect get wideMiddleRect {
final double topRadius = math.max(tlRadiusY, trRadiusY);
final double bottomRadius = math.max(brRadiusY, blRadiusY);
return Rect.fromLTRB(left, top + topRadius, right, bottom - bottomRadius);
}
Rect get tallMiddleRect {
final double leftRadius = math.max(blRadiusX, tlRadiusX);
final double rightRadius = math.max(trRadiusX, brRadiusX);
return Rect.fromLTRB(left + leftRadius, top, right - rightRadius, bottom);
}
bool get isEmpty => left >= right || top >= bottom;
bool get isFinite => left.isFinite && top.isFinite && right.isFinite && bottom.isFinite;
bool get isRect {
return (tlRadiusX == 0.0 || tlRadiusY == 0.0) &&
(trRadiusX == 0.0 || trRadiusY == 0.0) &&
(blRadiusX == 0.0 || blRadiusY == 0.0) &&
(brRadiusX == 0.0 || brRadiusY == 0.0);
}
bool get isStadium {
return tlRadius == trRadius &&
trRadius == brRadius &&
brRadius == blRadius &&
(width <= 2.0 * tlRadiusX || height <= 2.0 * tlRadiusY);
}
bool get isEllipse {
return tlRadius == trRadius &&
trRadius == brRadius &&
brRadius == blRadius &&
width <= 2.0 * tlRadiusX &&
height <= 2.0 * tlRadiusY;
}
bool get isCircle => width == height && isEllipse;
double get shortestSide => math.min(width.abs(), height.abs());
double get longestSide => math.max(width.abs(), height.abs());
bool get hasNaN =>
left.isNaN ||
top.isNaN ||
right.isNaN ||
bottom.isNaN ||
trRadiusX.isNaN ||
trRadiusY.isNaN ||
tlRadiusX.isNaN ||
tlRadiusY.isNaN ||
brRadiusX.isNaN ||
brRadiusY.isNaN ||
blRadiusX.isNaN ||
blRadiusY.isNaN;
Offset get center => Offset(left + width / 2.0, top + height / 2.0);
// Returns the minimum between min and scale to which radius1 and radius2
// should be scaled with in order not to exceed the limit.
double _getMin(double min, double radius1, double radius2, double limit) {
final double sum = radius1 + radius2;
if (sum > limit && sum != 0.0) {
return math.min(min, limit / sum);
}
return min;
}
RRect scaleRadii() {
double scale = 1.0;
final double absWidth = width.abs();
final double absHeight = height.abs();
scale = _getMin(scale, blRadiusY, tlRadiusY, absHeight);
scale = _getMin(scale, tlRadiusX, trRadiusX, absWidth);
scale = _getMin(scale, trRadiusY, brRadiusY, absHeight);
scale = _getMin(scale, brRadiusX, blRadiusX, absWidth);
if (scale < 1.0) {
return RRect._raw(
top: top,
left: left,
right: right,
bottom: bottom,
tlRadiusX: tlRadiusX * scale,
tlRadiusY: tlRadiusY * scale,
trRadiusX: trRadiusX * scale,
trRadiusY: trRadiusY * scale,
blRadiusX: blRadiusX * scale,
blRadiusY: blRadiusY * scale,
brRadiusX: brRadiusX * scale,
brRadiusY: brRadiusY * scale,
);
}
return RRect._raw(
top: top,
left: left,
right: right,
bottom: bottom,
tlRadiusX: tlRadiusX,
tlRadiusY: tlRadiusY,
trRadiusX: trRadiusX,
trRadiusY: trRadiusY,
blRadiusX: blRadiusX,
blRadiusY: blRadiusY,
brRadiusX: brRadiusX,
brRadiusY: brRadiusY,
);
}
bool contains(Offset point) { bool contains(Offset point) {
if (point.dx < left || point.dx >= right || point.dy < top || point.dy >= bottom) { if (point.dx < left || point.dx >= right || point.dy < top || point.dy >= bottom) {
return false; return false;
@ -805,120 +965,242 @@ class RRect {
} }
static RRect? lerp(RRect? a, RRect? b, double t) { static RRect? lerp(RRect? a, RRect? b, double t) {
if (b == null) { if (a == null) {
if (a == null) { if (b == null) {
return null; return null;
} else {
final double k = 1.0 - t;
return RRect._raw(
left: a.left * k,
top: a.top * k,
right: a.right * k,
bottom: a.bottom * k,
tlRadiusX: math.max(0, a.tlRadiusX * k),
tlRadiusY: math.max(0, a.tlRadiusY * k),
trRadiusX: math.max(0, a.trRadiusX * k),
trRadiusY: math.max(0, a.trRadiusY * k),
brRadiusX: math.max(0, a.brRadiusX * k),
brRadiusY: math.max(0, a.brRadiusY * k),
blRadiusX: math.max(0, a.blRadiusX * k),
blRadiusY: math.max(0, a.blRadiusY * k),
);
}
} else {
if (a == null) {
return RRect._raw(
left: b.left * t,
top: b.top * t,
right: b.right * t,
bottom: b.bottom * t,
tlRadiusX: math.max(0, b.tlRadiusX * t),
tlRadiusY: math.max(0, b.tlRadiusY * t),
trRadiusX: math.max(0, b.trRadiusX * t),
trRadiusY: math.max(0, b.trRadiusY * t),
brRadiusX: math.max(0, b.brRadiusX * t),
brRadiusY: math.max(0, b.brRadiusY * t),
blRadiusX: math.max(0, b.blRadiusX * t),
blRadiusY: math.max(0, b.blRadiusY * t),
);
} else {
return RRect._raw(
left: _lerpDouble(a.left, b.left, t),
top: _lerpDouble(a.top, b.top, t),
right: _lerpDouble(a.right, b.right, t),
bottom: _lerpDouble(a.bottom, b.bottom, t),
tlRadiusX: math.max(0, _lerpDouble(a.tlRadiusX, b.tlRadiusX, t)),
tlRadiusY: math.max(0, _lerpDouble(a.tlRadiusY, b.tlRadiusY, t)),
trRadiusX: math.max(0, _lerpDouble(a.trRadiusX, b.trRadiusX, t)),
trRadiusY: math.max(0, _lerpDouble(a.trRadiusY, b.trRadiusY, t)),
brRadiusX: math.max(0, _lerpDouble(a.brRadiusX, b.brRadiusX, t)),
brRadiusY: math.max(0, _lerpDouble(a.brRadiusY, b.brRadiusY, t)),
blRadiusX: math.max(0, _lerpDouble(a.blRadiusX, b.blRadiusX, t)),
blRadiusY: math.max(0, _lerpDouble(a.blRadiusY, b.blRadiusY, t)),
);
} }
return b._lerpTo(null, 1 - t);
} }
return a._lerpTo(b, t);
} }
@override
bool operator ==(Object other) {
if (identical(this, other)) {
return true;
}
if (runtimeType != other.runtimeType) {
return false;
}
return other is RRect &&
other.left == left &&
other.top == top &&
other.right == right &&
other.bottom == bottom &&
other.tlRadiusX == tlRadiusX &&
other.tlRadiusY == tlRadiusY &&
other.trRadiusX == trRadiusX &&
other.trRadiusY == trRadiusY &&
other.blRadiusX == blRadiusX &&
other.blRadiusY == blRadiusY &&
other.brRadiusX == brRadiusX &&
other.brRadiusY == brRadiusY;
}
@override
int get hashCode => Object.hash(
left,
top,
right,
bottom,
tlRadiusX,
tlRadiusY,
trRadiusX,
trRadiusY,
blRadiusX,
blRadiusY,
brRadiusX,
brRadiusY,
);
@override @override
String toString() { String toString() {
final String rect = return _toString(className: 'RRect');
'${left.toStringAsFixed(1)}, ' }
'${top.toStringAsFixed(1)}, ' }
'${right.toStringAsFixed(1)}, '
'${bottom.toStringAsFixed(1)}'; class RSuperellipse extends _RRectLike<RSuperellipse> {
if (tlRadius == trRadius && trRadius == brRadius && brRadius == blRadius) { const RSuperellipse.fromLTRBXY(
if (tlRadius.x == tlRadius.y) { double left,
return 'RRect.fromLTRBR($rect, ${tlRadius.x.toStringAsFixed(1)})'; double top,
double right,
double bottom,
double radiusX,
double radiusY,
) : this._raw(
top: top,
left: left,
right: right,
bottom: bottom,
tlRadiusX: radiusX,
tlRadiusY: radiusY,
trRadiusX: radiusX,
trRadiusY: radiusY,
blRadiusX: radiusX,
blRadiusY: radiusY,
brRadiusX: radiusX,
brRadiusY: radiusY,
uniformRadii: radiusX == radiusY,
);
RSuperellipse.fromLTRBR(double left, double top, double right, double bottom, Radius radius)
: this._raw(
top: top,
left: left,
right: right,
bottom: bottom,
tlRadiusX: radius.x,
tlRadiusY: radius.y,
trRadiusX: radius.x,
trRadiusY: radius.y,
blRadiusX: radius.x,
blRadiusY: radius.y,
brRadiusX: radius.x,
brRadiusY: radius.y,
uniformRadii: radius.x == radius.y,
);
RSuperellipse.fromRectXY(Rect rect, double radiusX, double radiusY)
: this._raw(
top: rect.top,
left: rect.left,
right: rect.right,
bottom: rect.bottom,
tlRadiusX: radiusX,
tlRadiusY: radiusY,
trRadiusX: radiusX,
trRadiusY: radiusY,
blRadiusX: radiusX,
blRadiusY: radiusY,
brRadiusX: radiusX,
brRadiusY: radiusY,
uniformRadii: radiusX == radiusY,
);
RSuperellipse.fromRectAndRadius(Rect rect, Radius radius)
: this._raw(
top: rect.top,
left: rect.left,
right: rect.right,
bottom: rect.bottom,
tlRadiusX: radius.x,
tlRadiusY: radius.y,
trRadiusX: radius.x,
trRadiusY: radius.y,
blRadiusX: radius.x,
blRadiusY: radius.y,
brRadiusX: radius.x,
brRadiusY: radius.y,
uniformRadii: radius.x == radius.y,
);
RSuperellipse.fromLTRBAndCorners(
double left,
double top,
double right,
double bottom, {
Radius topLeft = Radius.zero,
Radius topRight = Radius.zero,
Radius bottomRight = Radius.zero,
Radius bottomLeft = Radius.zero,
}) : this._raw(
top: top,
left: left,
right: right,
bottom: bottom,
tlRadiusX: topLeft.x,
tlRadiusY: topLeft.y,
trRadiusX: topRight.x,
trRadiusY: topRight.y,
blRadiusX: bottomLeft.x,
blRadiusY: bottomLeft.y,
brRadiusX: bottomRight.x,
brRadiusY: bottomRight.y,
uniformRadii:
topLeft.x == topLeft.y &&
topLeft.x == topRight.x &&
topLeft.x == topRight.y &&
topLeft.x == bottomLeft.x &&
topLeft.x == bottomLeft.y &&
topLeft.x == bottomRight.x &&
topLeft.x == bottomRight.y,
);
RSuperellipse.fromRectAndCorners(
Rect rect, {
Radius topLeft = Radius.zero,
Radius topRight = Radius.zero,
Radius bottomRight = Radius.zero,
Radius bottomLeft = Radius.zero,
}) : this._raw(
top: rect.top,
left: rect.left,
right: rect.right,
bottom: rect.bottom,
tlRadiusX: topLeft.x,
tlRadiusY: topLeft.y,
trRadiusX: topRight.x,
trRadiusY: topRight.y,
blRadiusX: bottomLeft.x,
blRadiusY: bottomLeft.y,
brRadiusX: bottomRight.x,
brRadiusY: bottomRight.y,
uniformRadii:
topLeft.x == topLeft.y &&
topLeft.x == topRight.x &&
topLeft.x == topRight.y &&
topLeft.x == bottomLeft.x &&
topLeft.x == bottomLeft.y &&
topLeft.x == bottomRight.x &&
topLeft.x == bottomRight.y,
);
const RSuperellipse._raw({
super.left = 0.0,
super.top = 0.0,
super.right = 0.0,
super.bottom = 0.0,
super.tlRadiusX = 0.0,
super.tlRadiusY = 0.0,
super.trRadiusX = 0.0,
super.trRadiusY = 0.0,
super.brRadiusX = 0.0,
super.brRadiusY = 0.0,
super.blRadiusX = 0.0,
super.blRadiusY = 0.0,
super.uniformRadii = false,
});
@override
RSuperellipse _create({
required double left,
required double top,
required double right,
required double bottom,
required double tlRadiusX,
required double tlRadiusY,
required double trRadiusX,
required double trRadiusY,
required double brRadiusX,
required double brRadiusY,
required double blRadiusX,
required double blRadiusY,
}) => RSuperellipse._raw(
top: top,
left: left,
right: right,
bottom: bottom,
tlRadiusX: tlRadiusX,
tlRadiusY: tlRadiusY,
trRadiusX: trRadiusX,
trRadiusY: trRadiusY,
blRadiusX: blRadiusX,
blRadiusY: blRadiusY,
brRadiusX: brRadiusX,
brRadiusY: brRadiusY,
);
// Approximates a rounded superellipse with a round rectangle to the
// best practical accuracy.
//
// This workaround is needed until the rounded superellipse is implemented on
// Web. https://github.com/flutter/flutter/issues/163718
RRect toApproximateRRect() {
// Experiments have shown that using the same corner radii for the RRect
// provides an approximation that is close to optimal, as achieving a perfect
// match is not feasible.
return RRect._raw(
top: top,
left: left,
right: right,
bottom: bottom,
tlRadiusX: tlRadiusX,
tlRadiusY: tlRadiusY,
trRadiusX: trRadiusX,
trRadiusY: trRadiusY,
blRadiusX: blRadiusX,
blRadiusY: blRadiusY,
brRadiusX: brRadiusX,
brRadiusY: brRadiusY,
);
}
static const RSuperellipse zero = RSuperellipse._raw();
static RSuperellipse? lerp(RSuperellipse? a, RSuperellipse? b, double t) {
if (a == null) {
if (b == null) {
return null;
} }
return 'RRect.fromLTRBXY($rect, ${tlRadius.x.toStringAsFixed(1)}, ${tlRadius.y.toStringAsFixed(1)})'; return b._lerpTo(null, 1 - t);
} }
return 'RRect.fromLTRBAndCorners(' return a._lerpTo(b, t);
'$rect, ' }
'topLeft: $tlRadius, '
'topRight: $trRadius, ' @override
'bottomRight: $brRadius, ' String toString() {
'bottomLeft: $blRadius' return _toString(className: 'RSuperellipse');
')';
} }
} }
// Modeled after Skia's SkRSXform. // Modeled after Skia's SkRSXform.

View File

@ -520,6 +520,17 @@ class CanvasPool extends _SaveStackTracking {
} }
} }
@override
void clipRSuperellipse(ui.RSuperellipse rse) {
// TODO(dkwingsmt): Properly implement clipRSuperellipse on Web instead of falling
// back to RRect. https://github.com/flutter/flutter/issues/163718
final ui.RRect rrect = rse.toApproximateRRect();
super.clipRSuperellipse(rse);
if (_canvas != null) {
_clipRRect(context, rrect);
}
}
void _clipRRect(DomCanvasRenderingContext2D ctx, ui.RRect rrect) { void _clipRRect(DomCanvasRenderingContext2D ctx, ui.RRect rrect) {
final ui.Path path = ui.Path()..addRRect(rrect); final ui.Path path = ui.Path()..addRRect(rrect);
_runPath(ctx, path as SurfacePath); _runPath(ctx, path as SurfacePath);
@ -1244,6 +1255,14 @@ class _SaveStackTracking {
clipStack!.add(SaveClipEntry.rrect(rrect, _currentTransform.clone())); clipStack!.add(SaveClipEntry.rrect(rrect, _currentTransform.clone()));
} }
/// Adds a round rectangle to clipping stack.
@mustCallSuper
void clipRSuperellipse(ui.RSuperellipse rse) {
// TODO(dkwingsmt): Properly implement clipRSE on Web instead of falling
// back to RRect. https://github.com/flutter/flutter/issues/163718
clipRRect(rse.toApproximateRRect());
}
/// Adds a path to clipping stack. /// Adds a path to clipping stack.
@mustCallSuper @mustCallSuper
void clipPath(ui.Path path) { void clipPath(ui.Path path) {

View File

@ -52,6 +52,12 @@ class CkCanvas {
skCanvas.clipRRect(toSkRRect(rrect), _clipOpIntersect, doAntiAlias); skCanvas.clipRRect(toSkRRect(rrect), _clipOpIntersect, doAntiAlias);
} }
void clipRSuperellipse(ui.RSuperellipse rse, bool doAntiAlias) {
// TODO(dkwingsmt): Properly implement clipRSE on Web instead of falling
// back to RRect. https://github.com/flutter/flutter/issues/163718
skCanvas.clipRRect(toSkRRect(rse.toApproximateRRect()), _clipOpIntersect, doAntiAlias);
}
void clipRect(ui.Rect rect, ui.ClipOp clipOp, bool doAntiAlias) { void clipRect(ui.Rect rect, ui.ClipOp clipOp, bool doAntiAlias) {
skCanvas.clipRect(toSkRect(rect), toSkClipOp(clipOp), doAntiAlias); skCanvas.clipRect(toSkRect(rect), toSkClipOp(clipOp), doAntiAlias);
} }

View File

@ -130,6 +130,14 @@ class CanvasKitCanvas implements ui.Canvas {
_canvas.clipRRect(rrect, doAntiAlias); _canvas.clipRRect(rrect, doAntiAlias);
} }
@override
void clipRSuperellipse(ui.RSuperellipse rse, {bool doAntiAlias = true}) {
assert(rsuperellipseIsValid(rse));
// TODO(dkwingsmt): Properly implement clipRSE on Web instead of falling
// back to RRect. https://github.com/flutter/flutter/issues/163718
_clipRRect(rse.toApproximateRRect(), doAntiAlias);
}
@override @override
void clipPath(ui.Path path, {bool doAntiAlias = true}) { void clipPath(ui.Path path, {bool doAntiAlias = true}) {
_canvas.clipPath(path as CkPath, doAntiAlias); _canvas.clipPath(path as CkPath, doAntiAlias);
@ -199,6 +207,14 @@ class CanvasKitCanvas implements ui.Canvas {
_canvas.drawRRect(rrect, paint as CkPaint); _canvas.drawRRect(rrect, paint as CkPaint);
} }
@override
void drawRSuperellipse(ui.RSuperellipse rse, ui.Paint paint) {
assert(rsuperellipseIsValid(rse));
// TODO(dkwingsmt): Properly implement clipRSE on Web instead of falling
// back to RRect. https://github.com/flutter/flutter/issues/163718
_drawRRect(rse.toApproximateRRect(), paint);
}
@override @override
void drawDRRect(ui.RRect outer, ui.RRect inner, ui.Paint paint) { void drawDRRect(ui.RRect outer, ui.RRect inner, ui.Paint paint) {
assert(rrectIsValid(outer)); assert(rrectIsValid(outer));

View File

@ -116,6 +116,22 @@ class ClipRRectEngineLayer extends ContainerLayer implements ui.ClipRRectEngineL
} }
} }
/// A layer that clips its child layers by a given [RRect].
class ClipRSuperellipseEngineLayer extends ContainerLayer
implements ui.ClipRSuperellipseEngineLayer {
ClipRSuperellipseEngineLayer(this.clipRSuperellipse, this.clipBehavior)
: assert(clipBehavior != ui.Clip.none);
/// The rounded superellipse used to clip child layers.
final ui.RSuperellipse clipRSuperellipse;
final ui.Clip? clipBehavior;
@override
void accept(LayerVisitor visitor) {
visitor.visitClipRSuperellipse(this);
}
}
/// A layer that paints its children with the given opacity. /// A layer that paints its children with the given opacity.
class OpacityEngineLayer extends ContainerLayer implements ui.OpacityEngineLayer { class OpacityEngineLayer extends ContainerLayer implements ui.OpacityEngineLayer {
OpacityEngineLayer(this.alpha, this.offset); OpacityEngineLayer(this.alpha, this.offset);

View File

@ -126,6 +126,15 @@ class LayerSceneBuilder implements ui.SceneBuilder {
return pushLayer<ClipRRectEngineLayer>(ClipRRectEngineLayer(rrect, clipBehavior)); return pushLayer<ClipRRectEngineLayer>(ClipRRectEngineLayer(rrect, clipBehavior));
} }
@override
ClipRSuperellipseEngineLayer pushClipRSuperellipse(
ui.RSuperellipse rse, {
ui.Clip? clipBehavior,
ui.EngineLayer? oldLayer,
}) {
return pushLayer<ClipRSuperellipseEngineLayer>(ClipRSuperellipseEngineLayer(rse, clipBehavior));
}
@override @override
ClipRectEngineLayer pushClipRect( ClipRectEngineLayer pushClipRect(
ui.Rect rect, { ui.Rect rect, {

View File

@ -13,6 +13,7 @@ abstract class LayerVisitor {
void visitClipPath(ClipPathEngineLayer clipPath); void visitClipPath(ClipPathEngineLayer clipPath);
void visitClipRect(ClipRectEngineLayer clipRect); void visitClipRect(ClipRectEngineLayer clipRect);
void visitClipRRect(ClipRRectEngineLayer clipRRect); void visitClipRRect(ClipRRectEngineLayer clipRRect);
void visitClipRSuperellipse(ClipRSuperellipseEngineLayer clipRSuperellipse);
void visitOpacity(OpacityEngineLayer opacity); void visitOpacity(OpacityEngineLayer opacity);
void visitTransform(TransformEngineLayer transform); void visitTransform(TransformEngineLayer transform);
void visitOffset(OffsetEngineLayer offset); void visitOffset(OffsetEngineLayer offset);
@ -109,6 +110,20 @@ class PrerollVisitor extends LayerVisitor {
mutatorsStack.pop(); mutatorsStack.pop();
} }
@override
void visitClipRSuperellipse(ClipRSuperellipseEngineLayer clipRSuperellipse) {
// TODO(dkwingsmt): Properly implement clipRSE on Web instead of falling
// back to RRect. https://github.com/flutter/flutter/issues/163718
mutatorsStack.pushClipRRect(clipRSuperellipse.clipRSuperellipse.toApproximateRRect());
final ui.Rect childPaintBounds = prerollChildren(clipRSuperellipse);
if (childPaintBounds.overlaps(clipRSuperellipse.clipRSuperellipse.outerRect)) {
clipRSuperellipse.paintBounds = childPaintBounds.intersect(
clipRSuperellipse.clipRSuperellipse.outerRect,
);
}
mutatorsStack.pop();
}
@override @override
void visitClipRect(ClipRectEngineLayer clipRect) { void visitClipRect(ClipRectEngineLayer clipRect) {
mutatorsStack.pushClipRect(clipRect.clipRect); mutatorsStack.pushClipRect(clipRect.clipRect);
@ -310,6 +325,25 @@ class MeasureVisitor extends LayerVisitor {
measuringCanvas.restore(); measuringCanvas.restore();
} }
@override
void visitClipRSuperellipse(ClipRSuperellipseEngineLayer clipRSuperellipse) {
assert(clipRSuperellipse.needsPainting);
measuringCanvas.save();
measuringCanvas.clipRSuperellipse(
clipRSuperellipse.clipRSuperellipse,
clipRSuperellipse.clipBehavior != ui.Clip.hardEdge,
);
if (clipRSuperellipse.clipBehavior == ui.Clip.antiAliasWithSaveLayer) {
measuringCanvas.saveLayer(clipRSuperellipse.paintBounds, null);
}
measureChildren(clipRSuperellipse);
if (clipRSuperellipse.clipBehavior == ui.Clip.antiAliasWithSaveLayer) {
measuringCanvas.restore();
}
measuringCanvas.restore();
}
@override @override
void visitOpacity(OpacityEngineLayer opacity) { void visitOpacity(OpacityEngineLayer opacity) {
assert(opacity.needsPainting); assert(opacity.needsPainting);
@ -532,6 +566,27 @@ class PaintVisitor extends LayerVisitor {
nWayCanvas.restore(); nWayCanvas.restore();
} }
@override
void visitClipRSuperellipse(ClipRSuperellipseEngineLayer clipRSuperellipse) {
assert(clipRSuperellipse.needsPainting);
nWayCanvas.save();
// TODO(dkwingsmt): Properly implement clipRSE on Web instead of falling
// back to RRect. https://github.com/flutter/flutter/issues/163718
nWayCanvas.clipRRect(
clipRSuperellipse.clipRSuperellipse.toApproximateRRect(),
clipRSuperellipse.clipBehavior != ui.Clip.hardEdge,
);
if (clipRSuperellipse.clipBehavior == ui.Clip.antiAliasWithSaveLayer) {
nWayCanvas.saveLayer(clipRSuperellipse.paintBounds, null);
}
paintChildren(clipRSuperellipse);
if (clipRSuperellipse.clipBehavior == ui.Clip.antiAliasWithSaveLayer) {
nWayCanvas.restore();
}
nWayCanvas.restore();
}
@override @override
void visitOpacity(OpacityEngineLayer opacity) { void visitOpacity(OpacityEngineLayer opacity) {
assert(opacity.needsPainting); assert(opacity.needsPainting);

View File

@ -47,6 +47,8 @@ abstract class EngineCanvas {
void clipRRect(ui.RRect rrect); void clipRRect(ui.RRect rrect);
void clipRSuperellipse(ui.RSuperellipse rse);
void clipPath(ui.Path path); void clipPath(ui.Path path);
void drawColor(ui.Color color, ui.BlendMode blendMode); void drawColor(ui.Color color, ui.BlendMode blendMode);
@ -234,6 +236,17 @@ mixin SaveStackTracking on EngineCanvas {
_clipStack!.add(SaveClipEntry.rrect(rrect, _currentTransform.clone())); _clipStack!.add(SaveClipEntry.rrect(rrect, _currentTransform.clone()));
} }
/// Adds a round superellipse to clipping stack.
///
/// Classes that override this method must call `super.clipRSuperellipse()`.
@override
void clipRSuperellipse(ui.RSuperellipse rse) {
_clipStack ??= <SaveClipEntry>[];
// TODO(dkwingsmt): Properly implement clipRSE on Web instead of falling
// back to RRect. https://github.com/flutter/flutter/issues/163718
_clipStack!.add(SaveClipEntry.rrect(rse.toApproximateRRect(), _currentTransform.clone()));
}
/// Adds a path to clipping stack. /// Adds a path to clipping stack.
/// ///
/// Classes that override this method must call `super.clipPath()`. /// Classes that override this method must call `super.clipPath()`.

View File

@ -343,6 +343,13 @@ class BitmapCanvas extends EngineCanvas {
_canvasPool.clipRRect(rrect); _canvasPool.clipRRect(rrect);
} }
@override
void clipRSuperellipse(ui.RSuperellipse rse) {
// TODO(dkwingsmt): Properly implement clipRSE on Web instead of falling
// back to RRect. https://github.com/flutter/flutter/issues/163718
_canvasPool.clipRRect(rse.toApproximateRRect());
}
@override @override
void clipPath(ui.Path path) { void clipPath(ui.Path path) {
_canvasPool.clipPath(path); _canvasPool.clipPath(path);

View File

@ -123,6 +123,14 @@ class SurfaceCanvas implements ui.Canvas {
_canvas.clipRRect(rrect); _canvas.clipRRect(rrect);
} }
@override
void clipRSuperellipse(ui.RSuperellipse rse, {bool doAntiAlias = true}) {
assert(rsuperellipseIsValid(rse));
// TODO(dkwingsmt): Properly implement clipRSE on Web instead of falling
// back to RRect. https://github.com/flutter/flutter/issues/163718
_clipRRect(rse.toApproximateRRect(), doAntiAlias);
}
@override @override
void clipPath(ui.Path path, {bool doAntiAlias = true}) { void clipPath(ui.Path path, {bool doAntiAlias = true}) {
_clipPath(path, doAntiAlias); _clipPath(path, doAntiAlias);
@ -220,6 +228,14 @@ class SurfaceCanvas implements ui.Canvas {
_canvas.drawDRRect(outer, inner, paint as SurfacePaint); _canvas.drawDRRect(outer, inner, paint as SurfacePaint);
} }
@override
void drawRSuperellipse(ui.RSuperellipse rse, ui.Paint paint) {
assert(rsuperellipseIsValid(rse));
// TODO(dkwingsmt): Properly implement clipRSE on Web instead of falling
// back to RRect. https://github.com/flutter/flutter/issues/163718
_drawRRect(rse.toApproximateRRect(), paint);
}
@override @override
void drawOval(ui.Rect rect, ui.Paint paint) { void drawOval(ui.Rect rect, ui.Paint paint) {
assert(rectIsValid(rect)); assert(rectIsValid(rect));

View File

@ -186,6 +186,71 @@ class PersistedClipRRect extends PersistedContainerSurface
bool get isClipping => true; bool get isClipping => true;
} }
/// A surface that creates a rounded superellipse clip.
///
/// Implemented by falling back to RRect.
class PersistedClipRSuperellipse extends PersistedContainerSurface
with _DomClip
implements ui.ClipRSuperellipseEngineLayer {
PersistedClipRSuperellipse(ui.EngineLayer? oldLayer, this.rse, this.clipBehavior)
: super(oldLayer as PersistedSurface?);
final ui.RSuperellipse rse;
// TODO(yjbanov): can this be controlled in the browser?
final ui.Clip? clipBehavior;
@override
void recomputeTransformAndClip() {
transform = parent!.transform;
if (clipBehavior != ui.Clip.none) {
localClipBounds = rse.outerRect;
} else {
localClipBounds = null;
}
projectedClip = null;
}
@override
DomElement createElement() {
// Fall back to rrect.
return super.createElement()..setAttribute('clip-type', 'rrect');
}
@override
void apply() {
final DomCSSStyleDeclaration style = rootElement!.style;
style
..left = '${rse.left}px'
..top = '${rse.top}px'
..width = '${rse.width}px'
..height = '${rse.height}px'
..borderTopLeftRadius = '${rse.tlRadiusX}px'
..borderTopRightRadius = '${rse.trRadiusX}px'
..borderBottomRightRadius = '${rse.brRadiusX}px'
..borderBottomLeftRadius = '${rse.blRadiusX}px';
applyOverflow(rootElement!, clipBehavior);
// Translate the child container in the opposite direction to compensate for
// the shift in the coordinate system introduced by the translation of the
// rootElement. Clipping in Flutter has no effect on the coordinate system.
childContainer!.style
..left = '${-rse.left}px'
..top = '${-rse.top}px';
}
@override
void update(PersistedClipRSuperellipse oldSurface) {
super.update(oldSurface);
if (rse != oldSurface.rse || clipBehavior != oldSurface.clipBehavior) {
localClipBounds = null;
apply();
}
}
@override
bool get isClipping => true;
}
/// A surface that clips it's children. /// A surface that clips it's children.
class PersistedClipPath extends PersistedContainerSurface implements ui.ClipPathEngineLayer { class PersistedClipPath extends PersistedContainerSurface implements ui.ClipPathEngineLayer {
PersistedClipPath(PersistedClipPath? super.oldLayer, this.clipPath, this.clipBehavior); PersistedClipPath(PersistedClipPath? super.oldLayer, this.clipPath, this.clipBehavior);

View File

@ -44,6 +44,11 @@ class DomCanvas extends EngineCanvas with SaveElementStackTracking {
throw UnimplementedError(); throw UnimplementedError();
} }
@override
void clipRSuperellipse(ui.RSuperellipse rse, {bool doAntiAlias = true}) {
throw UnimplementedError();
}
@override @override
void clipPath(ui.Path path) { void clipPath(ui.Path path) {
throw UnimplementedError(); throw UnimplementedError();

View File

@ -147,6 +147,22 @@ class SurfaceSceneBuilder implements ui.SceneBuilder {
return _pushSurface<PersistedClipRRect>(PersistedClipRRect(oldLayer, rrect, clipBehavior)); return _pushSurface<PersistedClipRRect>(PersistedClipRRect(oldLayer, rrect, clipBehavior));
} }
/// Pushes a rounded-superellipse clip operation onto the operation stack.
///
/// Rasterization outside the given rounded rectangle is discarded.
///
/// See [pop] for details about the operation stack.
@override
ui.ClipRSuperellipseEngineLayer pushClipRSuperellipse(
ui.RSuperellipse rse, {
ui.Clip? clipBehavior,
ui.ClipRSuperellipseEngineLayer? oldLayer,
}) {
return _pushSurface<PersistedClipRSuperellipse>(
PersistedClipRSuperellipse(oldLayer, rse, clipBehavior),
);
}
/// Pushes a path clip operation onto the operation stack. /// Pushes a path clip operation onto the operation stack.
/// ///
/// Rasterization outside the given path is discarded. /// Rasterization outside the given path is discarded.

View File

@ -226,6 +226,56 @@ class ClipRRectOperation implements LayerOperation {
String toString() => 'ClipRRectOperation(rrect: $rrect, clip: $clip)'; String toString() => 'ClipRRectOperation(rrect: $rrect, clip: $clip)';
} }
class ClipRSuperellipseLayer with PictureEngineLayer implements ui.ClipRSuperellipseEngineLayer {
ClipRSuperellipseLayer(this.operation);
@override
final ClipRSuperellipseOperation operation;
@override
ClipRSuperellipseLayer emptyClone() => ClipRSuperellipseLayer(operation);
}
class ClipRSuperellipseOperation implements LayerOperation {
const ClipRSuperellipseOperation(this.rse, this.clip);
final ui.RSuperellipse rse;
final ui.Clip clip;
@override
ui.Rect mapRect(ui.Rect contentRect) => contentRect.intersect(rse.outerRect);
@override
void pre(SceneCanvas canvas) {
canvas.save();
canvas.clipRSuperellipse(rse, doAntiAlias: clip != ui.Clip.hardEdge);
if (clip == ui.Clip.antiAliasWithSaveLayer) {
canvas.saveLayer(rse.outerRect, ui.Paint());
}
}
@override
void post(SceneCanvas canvas) {
if (clip == ui.Clip.antiAliasWithSaveLayer) {
canvas.restore();
}
canvas.restore();
}
@override
PlatformViewStyling createPlatformViewStyling() {
// TODO(dkwingsmt): Properly implement clipRSE on Web instead of falling
// back to RRect. https://github.com/flutter/flutter/issues/163718
return PlatformViewStyling(clip: PlatformViewRRectClip(rse.toApproximateRRect()));
}
@override
bool get affectsBackdrop => false;
@override
String toString() => 'ClipRSuperellipseOperation(rse: $rse, clip: $clip)';
}
class ColorFilterLayer with PictureEngineLayer implements ui.ColorFilterEngineLayer { class ColorFilterLayer with PictureEngineLayer implements ui.ColorFilterEngineLayer {
ColorFilterLayer(this.operation); ColorFilterLayer(this.operation);

View File

@ -385,6 +385,17 @@ class EngineSceneBuilder implements ui.SceneBuilder {
ui.ClipRRectEngineLayer? oldLayer, ui.ClipRRectEngineLayer? oldLayer,
}) => pushLayer<ClipRRectLayer>(ClipRRectLayer(ClipRRectOperation(rrect, clipBehavior))); }) => pushLayer<ClipRRectLayer>(ClipRRectLayer(ClipRRectOperation(rrect, clipBehavior)));
@override
ui.ClipRSuperellipseEngineLayer pushClipRSuperellipse(
ui.RSuperellipse rse, {
required ui.Clip clipBehavior,
ui.ClipRSuperellipseEngineLayer? oldLayer,
}) {
return pushLayer<ClipRSuperellipseLayer>(
ClipRSuperellipseLayer(ClipRSuperellipseOperation(rse, clipBehavior)),
);
}
@override @override
ui.ClipRectEngineLayer pushClipRect( ui.ClipRectEngineLayer pushClipRect(
ui.Rect rect, { ui.Rect rect, {

View File

@ -134,6 +134,13 @@ class SkwasmCanvas implements SceneCanvas {
}); });
} }
@override
void clipRSuperellipse(ui.RSuperellipse rse, {bool doAntiAlias = true}) {
// TODO(dkwingsmt): Properly implement clipRSE on Web instead of falling
// back to RRect. https://github.com/flutter/flutter/issues/163718
clipRRect(rse.toApproximateRRect(), doAntiAlias: doAntiAlias);
}
@override @override
void clipPath(ui.Path path, {bool doAntiAlias = true}) { void clipPath(ui.Path path, {bool doAntiAlias = true}) {
path as SkwasmPath; path as SkwasmPath;
@ -176,6 +183,13 @@ class SkwasmCanvas implements SceneCanvas {
paintDispose(paintHandle); paintDispose(paintHandle);
} }
@override
void drawRSuperellipse(ui.RSuperellipse rse, ui.Paint paint) {
// TODO(dkwingsmt): Properly implement clipRSE on Web instead of falling
// back to RRect. https://github.com/flutter/flutter/issues/163718
drawRRect(rse.toApproximateRRect(), paint);
}
@override @override
void drawDRRect(ui.RRect outer, ui.RRect inner, ui.Paint paint) { void drawDRRect(ui.RRect outer, ui.RRect inner, ui.Paint paint) {
final paintHandle = (paint as SkwasmPaint).toRawPaint(); final paintHandle = (paint as SkwasmPaint).toRawPaint();

View File

@ -22,6 +22,14 @@ bool rrectIsValid(ui.RRect rrect) {
return true; return true;
} }
bool rsuperellipseIsValid(ui.RSuperellipse rse) {
assert(
!(rse.left.isNaN || rse.right.isNaN || rse.top.isNaN || rse.bottom.isNaN),
'RSuperellipse argument contained a NaN value.',
);
return true;
}
bool offsetIsValid(ui.Offset offset) { bool offsetIsValid(ui.Offset offset) {
assert(!offset.dx.isNaN && !offset.dy.isNaN, 'Offset argument contained a NaN value.'); assert(!offset.dx.isNaN && !offset.dy.isNaN, 'Offset argument contained a NaN value.');
return true; return true;

View File

@ -92,6 +92,11 @@ class MockEngineCanvas implements EngineCanvas {
_called('clipRRect', arguments: rrect); _called('clipRRect', arguments: rrect);
} }
@override
void clipRSuperellipse(RSuperellipse rse) {
_called('clipRSuperellipse', arguments: rse);
}
@override @override
void clipPath(Path path) { void clipPath(Path path) {
_called('clipPath', arguments: path); _called('clipPath', arguments: path);

View File

@ -89,6 +89,9 @@ class StubSceneCanvas implements SceneCanvas {
@override @override
void clipRRect(ui.RRect rrect, {bool doAntiAlias = true}) {} void clipRRect(ui.RRect rrect, {bool doAntiAlias = true}) {}
@override
void clipRSuperellipse(ui.RSuperellipse rse, {bool doAntiAlias = true}) {}
@override @override
void clipRect(ui.Rect rect, {ui.ClipOp clipOp = ui.ClipOp.intersect, bool doAntiAlias = true}) {} void clipRect(ui.Rect rect, {ui.ClipOp clipOp = ui.ClipOp.intersect, bool doAntiAlias = true}) {}
@ -151,6 +154,9 @@ class StubSceneCanvas implements SceneCanvas {
@override @override
void drawRRect(ui.RRect rrect, ui.Paint paint) {} void drawRRect(ui.RRect rrect, ui.Paint paint) {}
@override
void drawRSuperellipse(ui.RSuperellipse rse, ui.Paint paint) {}
@override @override
void drawRawAtlas( void drawRawAtlas(
ui.Image atlas, ui.Image atlas,

View File

@ -65,6 +65,9 @@ void DlOpSpy::drawDiffRoundRect(const DlRoundRect& outer,
const DlRoundRect& inner) { const DlRoundRect& inner) {
did_draw_ |= will_draw_; did_draw_ |= will_draw_;
} }
void DlOpSpy::drawRoundSuperellipse(const DlRoundSuperellipse& rse) {
did_draw_ |= will_draw_;
}
void DlOpSpy::drawPath(const DlPath& path) { void DlOpSpy::drawPath(const DlPath& path) {
did_draw_ |= will_draw_; did_draw_ |= will_draw_;
} }

View File

@ -57,6 +57,7 @@ class DlOpSpy final : public virtual DlOpReceiver,
void drawRoundRect(const DlRoundRect& rrect) override; void drawRoundRect(const DlRoundRect& rrect) override;
void drawDiffRoundRect(const DlRoundRect& outer, void drawDiffRoundRect(const DlRoundRect& outer,
const DlRoundRect& inner) override; const DlRoundRect& inner) override;
void drawRoundSuperellipse(const DlRoundSuperellipse& rse) override;
void drawPath(const DlPath& path) override; void drawPath(const DlPath& path) override;
void drawArc(const DlRect& oval_bounds, void drawArc(const DlRect& oval_bounds,
DlScalar start_degrees, DlScalar start_degrees,

View File

@ -207,8 +207,8 @@ extern std::ostream& operator<<(std::ostream& os, const DlPath& path) {
std::ostream& operator<<(std::ostream& os, const flutter::DlClipOp& op) { std::ostream& operator<<(std::ostream& os, const flutter::DlClipOp& op) {
switch (op) { switch (op) {
case flutter::DlClipOp::kDifference: return os << "ClipOp::kDifference"; case flutter::DlClipOp::kDifference: return os << "DlClipOp::kDifference";
case flutter::DlClipOp::kIntersect: return os << "ClipOp::kIntersect"; case flutter::DlClipOp::kIntersect: return os << "DlClipOp::kIntersect";
} }
} }
@ -784,6 +784,15 @@ void DisplayListStreamDispatcher::clipRoundRect(const DlRoundRect& rrect,
<< "isaa: " << is_aa << "isaa: " << is_aa
<< ");" << std::endl; << ");" << std::endl;
} }
void DisplayListStreamDispatcher::clipRoundSuperellipse(const DlRoundSuperellipse& rse,
DlClipOp clip_op,
bool is_aa) {
startl() << "clipRoundSuperellipse("
<< rse << ", "
<< clip_op << ", "
<< "isaa: " << is_aa
<< ");" << std::endl;
}
void DisplayListStreamDispatcher::clipPath(const DlPath& path, DlClipOp clip_op, void DisplayListStreamDispatcher::clipPath(const DlPath& path, DlClipOp clip_op,
bool is_aa) { bool is_aa) {
startl() << "clipPath(" startl() << "clipPath("
@ -835,6 +844,9 @@ void DisplayListStreamDispatcher::drawDiffRoundRect(const DlRoundRect& outer,
startl() << "drawDRRect(outer: " << outer << ", " << std::endl; startl() << "drawDRRect(outer: " << outer << ", " << std::endl;
startl() << " inner: " << inner << ");" << std::endl; startl() << " inner: " << inner << ");" << std::endl;
} }
void DisplayListStreamDispatcher::drawRoundSuperellipse(const DlRoundSuperellipse& rse) {
startl() << "drawRSuperellipse(" << rse << ");" << std::endl;
}
void DisplayListStreamDispatcher::drawPath(const DlPath& path) { void DisplayListStreamDispatcher::drawPath(const DlPath& path) {
startl() << "drawPath(" << path << ");" << std::endl; startl() << "drawPath(" << path << ");" << std::endl;
} }

View File

@ -135,6 +135,9 @@ class DisplayListStreamDispatcher final : public DlOpReceiver {
void clipRoundRect(const DlRoundRect& rrect, void clipRoundRect(const DlRoundRect& rrect,
DlClipOp clip_op, DlClipOp clip_op,
bool is_aa) override; bool is_aa) override;
void clipRoundSuperellipse(const DlRoundSuperellipse& rse,
DlClipOp clip_op,
bool is_aa) override;
void clipPath(const DlPath& path, DlClipOp clip_op, bool is_aa) override; void clipPath(const DlPath& path, DlClipOp clip_op, bool is_aa) override;
void drawColor(DlColor color, DlBlendMode mode) override; void drawColor(DlColor color, DlBlendMode mode) override;
@ -150,6 +153,7 @@ class DisplayListStreamDispatcher final : public DlOpReceiver {
void drawRoundRect(const DlRoundRect& rrect) override; void drawRoundRect(const DlRoundRect& rrect) override;
void drawDiffRoundRect(const DlRoundRect& outer, void drawDiffRoundRect(const DlRoundRect& outer,
const DlRoundRect& inner) override; const DlRoundRect& inner) override;
void drawRoundSuperellipse(const DlRoundSuperellipse& rse) override;
void drawPath(const DlPath& path) override; void drawPath(const DlPath& path) override;
void drawArc(const DlRect& oval_bounds, void drawArc(const DlRect& oval_bounds,
DlScalar start_degrees, DlScalar start_degrees,
@ -382,6 +386,18 @@ class DisplayListGeneralReceiver : public DlOpReceiver {
break; break;
} }
} }
void clipRoundSuperellipse(const DlRoundSuperellipse& rse,
DlClipOp clip_op,
bool is_aa) override {
switch (clip_op) {
case DlClipOp::kIntersect:
RecordByType(DisplayListOpType::kClipIntersectRoundSuperellipse);
break;
case DlClipOp::kDifference:
RecordByType(DisplayListOpType::kClipDifferenceRoundSuperellipse);
break;
}
}
void clipPath(const DlPath& path, DlClipOp clip_op, bool is_aa) override { void clipPath(const DlPath& path, DlClipOp clip_op, bool is_aa) override {
switch (clip_op) { switch (clip_op) {
case DlClipOp::kIntersect: case DlClipOp::kIntersect:
@ -435,6 +451,9 @@ class DisplayListGeneralReceiver : public DlOpReceiver {
const DlRoundRect& inner) override { const DlRoundRect& inner) override {
RecordByType(DisplayListOpType::kDrawDiffRoundRect); RecordByType(DisplayListOpType::kDrawDiffRoundRect);
} }
void drawRoundSuperellipse(const DlRoundSuperellipse& rse) override {
RecordByType(DisplayListOpType::kDrawRoundSuperellipse);
}
void drawPath(const DlPath& path) override { void drawPath(const DlPath& path) override {
RecordByType(DisplayListOpType::kDrawPath); RecordByType(DisplayListOpType::kDrawPath);
} }