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

Commit b56fd4c1 authored by Derek Sollenberger's avatar Derek Sollenberger Committed by Android (Google) Code Review
Browse files

Merge "Use clipping when layer bounds are too small to mirror the crop's corner"

parents 8f90f970 547d0a6a
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();