diff options
| -rw-r--r-- | libs/renderengine/skia/SkiaGLRenderEngine.cpp | 123 | ||||
| -rw-r--r-- | libs/renderengine/tests/RenderEngineTest.cpp | 34 |
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(); |