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

Commit 86185899 authored by Charlie Tsai's avatar Charlie Tsai
Browse files

Draw rectangle shadow fast in low elevation cases

When the elevation is not high, the shadow is similar
to just add some dark lines on the edges and corners of
rectangle views. Using simple algorithm can also improve
the performance and save more memory.

Test: The render result of unit test is updated.
Bug: 37906145

Change-Id: I28758f868ee6e24e4552368bddfb7ac10fe0a205
parent 47e090c3
Loading
Loading
Loading
Loading
+124 −14
Original line number Diff line number Diff line
@@ -18,13 +18,28 @@ package android.view;

import android.annotation.NonNull;
import android.graphics.Canvas;
import android.graphics.LinearGradient;
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.graphics.Path.FillType;
import android.graphics.RadialGradient;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader.TileMode;

import com.android.layoutlib.bridge.impl.ResourceHelper;
import com.android.layoutlib.bridge.shadowutil.SpotShadow;
import com.android.layoutlib.bridge.shadowutil.ShadowBuffer;

public class RectShadowPainter {

    private static final float SIMPLE_SHADOW_ELEVATION_THRESHOLD = 16f;
    private static final int SIMPLE_SHADOW_START_COLOR = ResourceHelper.getColor("#37000000");
    private static final int SIMPLE_SHADOW_END_COLOR = ResourceHelper.getColor("#03000000");
    private static final float PERPENDICULAR_ANGLE = 90f;

    private static final float SHADOW_STRENGTH = 0.1f;
    private static final int LIGHT_POINTS = 8;

@@ -53,14 +68,20 @@ public class RectShadowPainter {
                return;
            }

            if (elevation <= SIMPLE_SHADOW_ELEVATION_THRESHOLD) {
                // When elevation is not high, the shadow is very similar to a small outline of
                // View. For the performance reason, we draw the shadow in simple way.
                simpleRectangleShadow(canvas, outline, radius, elevation);
            } else {
                // view's absolute position in this canvas.
                int viewLeft = -originCanvasRect.left + outline.left;
                int viewTop = -originCanvasRect.top + outline.top;
                int viewRight = viewLeft + outline.width();
                int viewBottom = viewTop + outline.height();

            float[][] rectangleCoordinators = generateRectangleCoordinates(viewLeft, viewTop,
                    viewRight, viewBottom, radius, elevation);
                float[][] rectangleCoordinators =
                        generateRectangleCoordinates(viewLeft, viewTop, viewRight, viewBottom,
                                radius, elevation);

                // TODO: get these values from resources.
                float lightPosX = canvas.getWidth() / 2;
@@ -70,6 +91,7 @@ public class RectShadowPainter {

                paintGeometricShadow(rectangleCoordinators, lightPosX, lightPosY, lightHeight,
                        lightSize, canvas);
            }
        } finally {
            canvas.restoreToCount(saved);
        }
@@ -81,6 +103,94 @@ public class RectShadowPainter {
        return canvas.save();
    }

    private static void simpleRectangleShadow(@NonNull Canvas canvas, @NonNull Rect outline,
            float radius, float elevation) {
        float shadowSize = elevation / 2;

        Paint cornerPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
        cornerPaint.setStyle(Style.FILL);
        Paint edgePaint = new Paint(cornerPaint);
        edgePaint.setAntiAlias(false);
        float outerArcRadius = radius + shadowSize;
        int[] colors = {SIMPLE_SHADOW_START_COLOR, SIMPLE_SHADOW_START_COLOR,
                SIMPLE_SHADOW_END_COLOR};
        cornerPaint.setShader(new RadialGradient(0, 0, outerArcRadius, colors,
                new float[]{0f, radius / outerArcRadius, 1f}, TileMode.CLAMP));
        edgePaint.setShader(new LinearGradient(0, 0, -shadowSize, 0, SIMPLE_SHADOW_START_COLOR,
                SIMPLE_SHADOW_END_COLOR,
                TileMode.CLAMP));
        Path path = new Path();
        path.setFillType(FillType.EVEN_ODD);
        // A rectangle bounding the complete shadow.
        RectF shadowRect = new RectF(outline);
        shadowRect.inset(-shadowSize, -shadowSize);
        // A rectangle with edges corresponding to the straight edges of the outline.
        RectF inset = new RectF(outline);
        inset.inset(radius, radius);
        // A rectangle used to represent the edge shadow.
        RectF edgeShadowRect = new RectF();


        // left and right sides.
        edgeShadowRect.set(-shadowSize, 0f, 0f, inset.height());
        // Left shadow
        drawSideShadow(canvas, edgePaint, edgeShadowRect, outline.left, inset.top, 0);
        // Right shadow
        drawSideShadow(canvas, edgePaint, edgeShadowRect, outline.right, inset.bottom, 2);
        // Top shadow
        edgeShadowRect.set(-shadowSize, 0, 0, inset.width());
        drawSideShadow(canvas, edgePaint, edgeShadowRect, inset.right, outline.top, 1);
        // bottom shadow. This needs an inset so that blank doesn't appear when the content is
        // moved up.
        edgeShadowRect.set(-shadowSize, 0, shadowSize / 2f, inset.width());
        edgePaint.setShader(
                new LinearGradient(edgeShadowRect.right, 0, edgeShadowRect.left, 0, colors,
                        new float[]{0f, 1 / 3f, 1f}, TileMode.CLAMP));
        drawSideShadow(canvas, edgePaint, edgeShadowRect, inset.left, outline.bottom, 3);

        // Draw corners.
        drawCorner(canvas, cornerPaint, path, inset.right, inset.bottom, outerArcRadius, 0);
        drawCorner(canvas, cornerPaint, path, inset.left, inset.bottom, outerArcRadius, 1);
        drawCorner(canvas, cornerPaint, path, inset.left, inset.top, outerArcRadius, 2);
        drawCorner(canvas, cornerPaint, path, inset.right, inset.top, outerArcRadius, 3);
    }

    private static void drawSideShadow(@NonNull Canvas canvas, @NonNull Paint edgePaint,
            @NonNull RectF shadowRect, float dx, float dy, int rotations) {
        if ((int) shadowRect.left >= (int) shadowRect.right ||
                (int) shadowRect.top >= (int) shadowRect.bottom) {
            // Rect is empty, no need to draw shadow
            return;
        }
        int saved = canvas.save();
        canvas.translate(dx, dy);
        canvas.rotate(rotations * PERPENDICULAR_ANGLE);
        canvas.drawRect(shadowRect, edgePaint);
        canvas.restoreToCount(saved);
    }

    /**
     * @param canvas Canvas to draw the rectangle on.
     * @param paint Paint to use when drawing the corner.
     * @param path A path to reuse. Prevents allocating memory for each path.
     * @param x Center of circle, which this corner is a part of.
     * @param y Center of circle, which this corner is a part of.
     * @param radius radius of the arc
     * @param rotations number of quarter rotations before starting to paint the arc.
     */
    private static void drawCorner(@NonNull Canvas canvas, @NonNull Paint paint, @NonNull Path path,
            float x, float y, float radius, int rotations) {
        int saved = canvas.save();
        canvas.translate(x, y);
        path.reset();
        path.arcTo(-radius, -radius, radius, radius, rotations * PERPENDICULAR_ANGLE,
                PERPENDICULAR_ANGLE, false);
        path.lineTo(0, 0);
        path.close();
        canvas.drawPath(path, paint);
        canvas.restoreToCount(saved);
    }

    @NonNull
    private static float[][] generateRectangleCoordinates(float left, float top, float right,
            float bottom, float radius, float elevation) {
−2.1 KiB (11.2 KiB)
Loading image diff...