Skip to content

Commit

Permalink
Interpret negative radii as 0 in recording_canvas drawDRRect (flutter…
Browse files Browse the repository at this point in the history
…#12431)

* Interpret negative radii as 0 in recording_canvas drawDRRect

This allows drawing DRRects that may have some negative values on
its corners (caused by deflating a RRect with some non-round corners,
for example)

See added unit test for an example of the above.

Fixes flutter/flutter#40728
  • Loading branch information
ditman authored Sep 25, 2019
1 parent 63949eb commit 44279e2
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 12 deletions.
32 changes: 20 additions & 12 deletions lib/web_ui/lib/src/engine/recording_canvas.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,16 @@ part of engine;
/// Enable this to print every command applied by a canvas.
const bool _debugDumpPaintCommands = false;

// Similar to [Offset.distance]
double _getDistance(double x, double y) => math.sqrt(x * x + y * y);
// Returns the squared length of the x, y (of a border radius)
// It normalizes x, y values before working with them, by
// assuming anything < 0 to be 0, because flutter may pass
// negative radii (which Skia assumes to be 0), see:
// https://skia.org/user/api/SkRRect_Reference#SkRRect_inset
double _measureBorderRadius(double x, double y) {
double clampedX = x < 0 ? 0 : x;
double clampedY = y < 0 ? 0 : y;
return clampedX * clampedX + clampedY * clampedY;
}

/// Records canvas commands to be applied to a [EngineCanvas].
///
Expand Down Expand Up @@ -231,8 +239,8 @@ class RecordingCanvas {
}

void drawDRRect(ui.RRect outer, ui.RRect inner, ui.Paint paint) {
// Ensure inner is fully contained within outer, by comparing its
// defining points (including its border radius)
// Check the inner bounds are contained within the outer bounds
// see: https://cs.chromium.org/chromium/src/third_party/skia/src/core/SkCanvas.cpp?l=1787-1789
ui.Rect innerRect = inner.outerRect;
ui.Rect outerRect = outer.outerRect;
if (outerRect == innerRect || outerRect.intersect(innerRect) != innerRect) {
Expand All @@ -243,15 +251,15 @@ class RecordingCanvas {
final ui.RRect scaledOuter = outer.scaleRadii();
final ui.RRect scaledInner = inner.scaleRadii();

final double outerTl = _getDistance(scaledOuter.tlRadiusX, scaledOuter.tlRadiusY);
final double outerTr = _getDistance(scaledOuter.trRadiusX, scaledOuter.trRadiusY);
final double outerBl = _getDistance(scaledOuter.blRadiusX, scaledOuter.blRadiusY);
final double outerBr = _getDistance(scaledOuter.brRadiusX, scaledOuter.brRadiusY);
final double outerTl = _measureBorderRadius(scaledOuter.tlRadiusX, scaledOuter.tlRadiusY);
final double outerTr = _measureBorderRadius(scaledOuter.trRadiusX, scaledOuter.trRadiusY);
final double outerBl = _measureBorderRadius(scaledOuter.blRadiusX, scaledOuter.blRadiusY);
final double outerBr = _measureBorderRadius(scaledOuter.brRadiusX, scaledOuter.brRadiusY);

final double innerTl = _getDistance(scaledInner.tlRadiusX, scaledInner.tlRadiusY);
final double innerTr = _getDistance(scaledInner.trRadiusX, scaledInner.trRadiusY);
final double innerBl = _getDistance(scaledInner.blRadiusX, scaledInner.blRadiusY);
final double innerBr = _getDistance(scaledInner.brRadiusX, scaledInner.brRadiusY);
final double innerTl = _measureBorderRadius(scaledInner.tlRadiusX, scaledInner.tlRadiusY);
final double innerTr = _measureBorderRadius(scaledInner.trRadiusX, scaledInner.trRadiusY);
final double innerBl = _measureBorderRadius(scaledInner.blRadiusX, scaledInner.blRadiusY);
final double innerBr = _measureBorderRadius(scaledInner.brRadiusX, scaledInner.brRadiusY);

if (innerTl > outerTl || innerTr > outerTr || innerBl > outerBl || innerBr > outerBr) {
return; // Some inner radius is overlapping some outer radius
Expand Down
21 changes: 21 additions & 0 deletions lib/web_ui/test/engine/recording_canvas_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,27 @@ void main() {
expect(mockCanvas.methodCallLog.length, equals(0));
});

test('negative corners in inner RRect get passed through to draw', () {
// This comes from github issue #40728
final RRect outer = RRect.fromRectAndCorners(const Rect.fromLTWH(0, 0, 88, 48),
topLeft: Radius.circular(6), bottomLeft: Radius.circular(6));
final RRect inner = outer.deflate(1);

// If these assertions fail, check [_measureBorderRadius] in recording_canvas.dart
expect(inner.brRadius, equals(Radius.circular(-1)));
expect(inner.trRadius, equals(Radius.circular(-1)));

underTest.drawDRRect(outer, inner, somePaint);
underTest.apply(mockCanvas);

// Expect to draw, even when inner has negative radii (which get ignored by canvas)
_expectDrawCall(mockCanvas, {
'outer': outer,
'inner': inner,
'paint': somePaint.webOnlyPaintData,
});
});

test('preserve old scuba test behavior', () {
final RRect outer = RRect.fromRectAndCorners(const Rect.fromLTRB(10, 20, 30, 40));
final RRect inner = RRect.fromRectAndCorners(const Rect.fromLTRB(12, 22, 28, 38));
Expand Down

0 comments on commit 44279e2

Please sign in to comment.