Merge "Draw rectangle shadow fast in low elevation cases"
diff --git a/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java b/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java
index aed85a7..0866577 100644
--- a/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java
+++ b/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java
@@ -18,13 +18,28 @@
 
 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;
 
@@ -58,23 +73,30 @@
                 return;
             }
 
-            // 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();
+            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;
-            float lightPosY = 0;
-            float lightHeight = 1800;
-            float lightSize = 200;
+                // TODO: get these values from resources.
+                float lightPosX = canvas.getWidth() / 2;
+                float lightPosY = 0;
+                float lightHeight = 1800;
+                float lightSize = 200;
 
-            paintGeometricShadow(rectangleCoordinators, lightPosX, lightPosY, lightHeight,
-                    lightSize, canvas);
+                paintGeometricShadow(rectangleCoordinators, lightPosX, lightPosY, lightHeight,
+                        lightSize, canvas);
+            }
         } finally {
             canvas.restoreToCount(saved);
         }
@@ -86,6 +108,94 @@
         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) {
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/shadows_test.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/shadows_test.png
index 67355b8..4951629 100644
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/shadows_test.png
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/shadows_test.png
Binary files differ