Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 547d0a6a authored by Derek Sollenberger's avatar Derek Sollenberger
Browse files

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
parent c8a5fd57
Loading
Loading
Loading
Loading
+72 −51
Original line number Diff line number Diff line
@@ -1127,83 +1127,104 @@ inline SkRect SkiaGLRenderEngine::getSkRect(const Rect& rect) {
    return SkRect::MakeLTRB(rect.left, rect.top, rect.right, rect.bottom);
}

inline std::pair<SkRRect, SkRRect> SkiaGLRenderEngine::getBoundsAndClip(const FloatRect& boundsRect,
                                                                        const FloatRect& cropRect,
                                                                        const float cornerRadius) {
    const SkRect bounds = getSkRect(boundsRect);
    const SkRect crop = getSkRect(cropRect);

    SkRRect clip;
    if (cornerRadius > 0) {
        // it the crop and the bounds are equivalent or there is no crop then we don't need a clip
        if (bounds == crop || crop.isEmpty()) {
            return {SkRRect::MakeRectXY(bounds, cornerRadius, cornerRadius), clip};
        }

        // This makes an effort to speed up common, simple bounds + clip combinations by
        // 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);

/**
 *  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) ||
                       insetCrop.contains(bounds.fLeft, bounds.fTop)) {
               (topEqual && bounds.fLeft >= insetCrop.fLeft)) {
        radii[0].set(0, 0);
    } else {
                intersectionIsRoundRect = false;
        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) ||
                       insetCrop.contains(bounds.fRight, bounds.fTop)) {
               (topEqual && bounds.fRight <= insetCrop.fRight)) {
        radii[1].set(0, 0);
    } else {
                intersectionIsRoundRect = false;
        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) ||
                       insetCrop.contains(bounds.fRight, bounds.fBottom)) {
               (bottomEqual && bounds.fRight <= insetCrop.fRight)) {
        radii[2].set(0, 0);
    } else {
                intersectionIsRoundRect = false;
        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) ||
                       insetCrop.contains(bounds.fLeft, bounds.fBottom)) {
               (bottomEqual && bounds.fLeft >= insetCrop.fLeft)) {
        radii[3].set(0, 0);
    } else {
                intersectionIsRoundRect = false;
        return false;
    }

    return true;
}

inline std::pair<SkRRect, SkRRect> SkiaGLRenderEngine::getBoundsAndClip(const FloatRect& boundsRect,
                                                                        const FloatRect& cropRect,
                                                                        const float cornerRadius) {
    const SkRect bounds = getSkRect(boundsRect);
    const SkRect crop = getSkRect(cropRect);

    SkRRect clip;
    if (cornerRadius > 0) {
        // it the crop and the bounds are equivalent or there is no crop then we don't need a clip
        if (bounds == crop || crop.isEmpty()) {
            return {SkRRect::MakeRectXY(bounds, cornerRadius, cornerRadius), clip};
        }

            if (intersectionIsRoundRect) {
        // This makes an effort to speed up common, simple bounds + clip combinations by
        // converting them to a single RRect draw. It is possible there are other cases
        // that can be converted.
        if (crop.contains(bounds)) {
            const auto insetCrop = crop.makeInset(cornerRadius, cornerRadius);
            if (insetCrop.contains(bounds)) {
                return {SkRRect::MakeRect(bounds), clip}; // clip is empty - no rounding required
            }

            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);
    }

+34 −0
Original line number Diff line number Diff line
@@ -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();