summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Derek Sollenberger <djsollen@google.com> 2021-07-27 14:09:17 -0400
committer Derek Sollenberger <djsollen@google.com> 2021-07-29 14:53:30 -0400
commit547d0a6ae89bb08302eb2c545af0098ccf51c20e (patch)
tree4cd8b07e411e29b6b833d98c8928a8acc26ad641
parentc8a5fd575b0701ae494510132f9d10cdeb95ec10 (diff)
Use clipping when layer bounds are too small to mirror the crop's corner
If the corners of the bounds only partially align with the crop we need to ensure that the resulting shape can still be represented as a round rect. This CL accounts for the fact that the round rect implementation will recompute the value of the corner radius if the radius is greater than the width or height to which it is applied. See https://www.w3.org/TR/css-backgrounds-3/#corner-overlap Test: atest librenderengine_test Bug: 145094543 Change-Id: I48d04ea726e8765de0e300c2608ed5c4e6fe6bbc
-rw-r--r--libs/renderengine/skia/SkiaGLRenderEngine.cpp123
-rw-r--r--libs/renderengine/tests/RenderEngineTest.cpp34
2 files changed, 106 insertions, 51 deletions
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
index 16b58844af..9fbbdc34ba 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
@@ -1127,6 +1127,73 @@ inline SkRect SkiaGLRenderEngine::getSkRect(const Rect& rect) {
return SkRect::MakeLTRB(rect.left, rect.top, rect.right, rect.bottom);
}
+/**
+ * Verifies that common, simple bounds + clip combinations can be converted into
+ * a single RRect draw call returning true if possible. If true the radii parameter
+ * will be filled with the correct radii values that combined with bounds param will
+ * produce the insected roundRect. If false, the returned state of the radii param is undefined.
+ */
+static bool intersectionIsRoundRect(const SkRect& bounds, const SkRect& crop,
+ const SkRect& insetCrop, float cornerRadius,
+ SkVector radii[4]) {
+ const bool leftEqual = bounds.fLeft == crop.fLeft;
+ const bool topEqual = bounds.fTop == crop.fTop;
+ const bool rightEqual = bounds.fRight == crop.fRight;
+ const bool bottomEqual = bounds.fBottom == crop.fBottom;
+
+ // In the event that the corners of the bounds only partially align with the crop we
+ // need to ensure that the resulting shape can still be represented as a round rect.
+ // In particular the round rect implementation will scale the value of all corner radii
+ // if the sum of the radius along any edge is greater than the length of that edge.
+ // See https://www.w3.org/TR/css-backgrounds-3/#corner-overlap
+ const bool requiredWidth = bounds.width() > (cornerRadius * 2);
+ const bool requiredHeight = bounds.height() > (cornerRadius * 2);
+ if (!requiredWidth || !requiredHeight) {
+ return false;
+ }
+
+ // Check each cropped corner to ensure that it exactly matches the crop or its corner is
+ // contained within the cropped shape and does not need rounded.
+ // compute the UpperLeft corner radius
+ if (leftEqual && topEqual) {
+ radii[0].set(cornerRadius, cornerRadius);
+ } else if ((leftEqual && bounds.fTop >= insetCrop.fTop) ||
+ (topEqual && bounds.fLeft >= insetCrop.fLeft)) {
+ radii[0].set(0, 0);
+ } else {
+ return false;
+ }
+ // compute the UpperRight corner radius
+ if (rightEqual && topEqual) {
+ radii[1].set(cornerRadius, cornerRadius);
+ } else if ((rightEqual && bounds.fTop >= insetCrop.fTop) ||
+ (topEqual && bounds.fRight <= insetCrop.fRight)) {
+ radii[1].set(0, 0);
+ } else {
+ return false;
+ }
+ // compute the BottomRight corner radius
+ if (rightEqual && bottomEqual) {
+ radii[2].set(cornerRadius, cornerRadius);
+ } else if ((rightEqual && bounds.fBottom <= insetCrop.fBottom) ||
+ (bottomEqual && bounds.fRight <= insetCrop.fRight)) {
+ radii[2].set(0, 0);
+ } else {
+ return false;
+ }
+ // compute the BottomLeft corner radius
+ if (leftEqual && bottomEqual) {
+ radii[3].set(cornerRadius, cornerRadius);
+ } else if ((leftEqual && bounds.fBottom <= insetCrop.fBottom) ||
+ (bottomEqual && bounds.fLeft >= insetCrop.fLeft)) {
+ radii[3].set(0, 0);
+ } else {
+ return false;
+ }
+
+ return true;
+}
+
inline std::pair<SkRRect, SkRRect> SkiaGLRenderEngine::getBoundsAndClip(const FloatRect& boundsRect,
const FloatRect& cropRect,
const float cornerRadius) {
@@ -1144,66 +1211,20 @@ inline std::pair<SkRRect, SkRRect> SkiaGLRenderEngine::getBoundsAndClip(const Fl
// converting them to a single RRect draw. It is possible there are other cases
// that can be converted.
if (crop.contains(bounds)) {
- bool intersectionIsRoundRect = true;
- // check each cropped corner to ensure that it exactly matches the crop or is full
- SkVector radii[4];
-
const auto insetCrop = crop.makeInset(cornerRadius, cornerRadius);
-
- const bool leftEqual = bounds.fLeft == crop.fLeft;
- const bool topEqual = bounds.fTop == crop.fTop;
- const bool rightEqual = bounds.fRight == crop.fRight;
- const bool bottomEqual = bounds.fBottom == crop.fBottom;
-
- // compute the UpperLeft corner radius
- if (leftEqual && topEqual) {
- radii[0].set(cornerRadius, cornerRadius);
- } else if ((leftEqual && bounds.fTop >= insetCrop.fTop) ||
- (topEqual && bounds.fLeft >= insetCrop.fLeft) ||
- insetCrop.contains(bounds.fLeft, bounds.fTop)) {
- radii[0].set(0, 0);
- } else {
- intersectionIsRoundRect = false;
- }
- // compute the UpperRight corner radius
- if (rightEqual && topEqual) {
- radii[1].set(cornerRadius, cornerRadius);
- } else if ((rightEqual && bounds.fTop >= insetCrop.fTop) ||
- (topEqual && bounds.fRight <= insetCrop.fRight) ||
- insetCrop.contains(bounds.fRight, bounds.fTop)) {
- radii[1].set(0, 0);
- } else {
- intersectionIsRoundRect = false;
- }
- // compute the BottomRight corner radius
- if (rightEqual && bottomEqual) {
- radii[2].set(cornerRadius, cornerRadius);
- } else if ((rightEqual && bounds.fBottom <= insetCrop.fBottom) ||
- (bottomEqual && bounds.fRight <= insetCrop.fRight) ||
- insetCrop.contains(bounds.fRight, bounds.fBottom)) {
- radii[2].set(0, 0);
- } else {
- intersectionIsRoundRect = false;
- }
- // compute the BottomLeft corner radius
- if (leftEqual && bottomEqual) {
- radii[3].set(cornerRadius, cornerRadius);
- } else if ((leftEqual && bounds.fBottom <= insetCrop.fBottom) ||
- (bottomEqual && bounds.fLeft >= insetCrop.fLeft) ||
- insetCrop.contains(bounds.fLeft, bounds.fBottom)) {
- radii[3].set(0, 0);
- } else {
- intersectionIsRoundRect = false;
+ if (insetCrop.contains(bounds)) {
+ return {SkRRect::MakeRect(bounds), clip}; // clip is empty - no rounding required
}
- if (intersectionIsRoundRect) {
+ SkVector radii[4];
+ if (intersectionIsRoundRect(bounds, crop, insetCrop, cornerRadius, radii)) {
SkRRect intersectionBounds;
intersectionBounds.setRectRadii(bounds, radii);
return {intersectionBounds, clip};
}
}
- // we didn't it any of our fast paths so set the clip to the cropRect
+ // we didn't hit any of our fast paths so set the clip to the cropRect
clip.setRectXY(crop, cornerRadius, cornerRadius);
}
diff --git a/libs/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp
index ec47f96a7b..33053a0a61 100644
--- a/libs/renderengine/tests/RenderEngineTest.cpp
+++ b/libs/renderengine/tests/RenderEngineTest.cpp
@@ -1869,6 +1869,40 @@ TEST_P(RenderEngineTest, testRoundedCornersParentCrop) {
expectBufferColor(Point(0, (DEFAULT_DISPLAY_HEIGHT / 2) - 1), 0, 255, 0, 255);
}
+TEST_P(RenderEngineTest, testRoundedCornersParentCropSmallBounds) {
+ initializeRenderEngine();
+
+ renderengine::DisplaySettings settings;
+ settings.physicalDisplay = fullscreenRect();
+ settings.clip = fullscreenRect();
+ settings.outputDataspace = ui::Dataspace::V0_SRGB_LINEAR;
+
+ std::vector<const renderengine::LayerSettings*> layers;
+
+ renderengine::LayerSettings redLayer;
+ redLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
+ redLayer.geometry.boundaries = FloatRect(0, 0, DEFAULT_DISPLAY_WIDTH, 32);
+ redLayer.geometry.roundedCornersRadius = 64;
+ redLayer.geometry.roundedCornersCrop = FloatRect(0, 0, DEFAULT_DISPLAY_WIDTH, 128);
+ // Red background.
+ redLayer.source.solidColor = half3(1.0f, 0.0f, 0.0f);
+ redLayer.alpha = 1.0f;
+
+ layers.push_back(&redLayer);
+ invokeDraw(settings, layers);
+
+ // Due to roundedCornersRadius, the top corners are untouched.
+ expectBufferColor(Point(0, 0), 0, 0, 0, 0);
+ expectBufferColor(Point(DEFAULT_DISPLAY_WIDTH - 1, 0), 0, 0, 0, 0);
+
+ // ensure that the entire height of the red layer was clipped by the rounded corners crop.
+ expectBufferColor(Point(0, 31), 0, 0, 0, 0);
+ expectBufferColor(Point(DEFAULT_DISPLAY_WIDTH - 1, 31), 0, 0, 0, 0);
+
+ // the bottom middle should be red
+ expectBufferColor(Point(DEFAULT_DISPLAY_WIDTH / 2, 31), 255, 0, 0, 255);
+}
+
TEST_P(RenderEngineTest, testClear) {
initializeRenderEngine();