OSDN Git Service

Replace rectangle shadow algorithm
authorCharlie Tsai <chartsai@google.com>
Thu, 2 Mar 2017 18:39:45 +0000 (18:39 +0000)
committerCharlie Tsai <chartsai@google.com>
Sat, 18 Mar 2017 21:34:00 +0000 (21:34 +0000)
Test: Rectangle Shadow test
Change-Id: Id9635df8769e85d835dc6f99201b86e5bba110d2

tools/layoutlib/bridge/src/android/view/RectShadowPainter.java
tools/layoutlib/bridge/src/com/android/layoutlib/bridge/shadowutil/ShadowBuffer.java [new file with mode: 0644]
tools/layoutlib/bridge/src/com/android/layoutlib/bridge/shadowutil/SpotShadow.java [new file with mode: 0644]
tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/shadows_test.png
tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/shadows_test.xml

index 8130bc2..43f4ebc 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2015, 2017 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 
 package android.view;
 
-import com.android.layoutlib.bridge.impl.GcSnapshot;
-import com.android.layoutlib.bridge.impl.ResourceHelper;
-
+import android.annotation.NonNull;
 import android.graphics.Canvas;
-import android.graphics.Canvas_Delegate;
-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.Region.Op;
-import android.graphics.Shader.TileMode;
-
-import java.awt.Rectangle;
+import com.android.layoutlib.bridge.shadowutil.SpotShadow;
+import com.android.layoutlib.bridge.shadowutil.ShadowBuffer;
 
-/**
- * Paints shadow for rounded rectangles. Inspiration from CardView. Couldn't use that directly,
- * since it modifies the size of the content, that we can't do.
- */
 public class RectShadowPainter {
 
+    private static final float SHADOW_STRENGTH = 0.1f;
+    private static final int LIGHT_POINTS = 8;
 
-    private static final int START_COLOR = ResourceHelper.getColor("#37000000");
-    private static final int END_COLOR = ResourceHelper.getColor("#03000000");
-    private static final float PERPENDICULAR_ANGLE = 90f;
+    private static final int QUADRANT_DIVIDED_COUNT = 8;
 
-    public static void paintShadow(Outline viewOutline, float elevation, Canvas canvas) {
+    private static final int RAY_TRACING_RAYS = 180;
+    private static final int RAY_TRACING_LAYERS = 10;
+
+    public static void paintShadow(@NonNull Outline viewOutline, float elevation,
+            @NonNull Canvas canvas) {
         Rect outline = new Rect();
         if (!viewOutline.getRect(outline)) {
             throw new IllegalArgumentException("Outline is not a rect shadow");
         }
 
-        // TODO replacing the algorithm here to create better shadow
-
-        float shadowSize = elevationToShadow(elevation);
-        int saved = modifyCanvas(canvas, shadowSize);
+        Rect originCanvasRect = canvas.getClipBounds();
+        int saved = modifyCanvas(canvas);
         if (saved == -1) {
             return;
         }
-
-        float radius = viewOutline.getRadius();
-        if (radius <= 0) {
-            // We can not paint a shadow with radius 0
-            return;
-        }
-
         try {
-            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 = {START_COLOR, START_COLOR, 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, START_COLOR, 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
-            sideShadow(canvas, edgePaint, edgeShadowRect, outline.left, inset.top, 0);
-            // Right shadow
-            sideShadow(canvas, edgePaint, edgeShadowRect, outline.right, inset.bottom, 2);
-            // Top shadow
-            edgeShadowRect.set(-shadowSize, 0, 0, inset.width());
-            sideShadow(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));
-            sideShadow(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);
+            float radius = viewOutline.getRadius();
+            if (radius <= 0) {
+                // We can not paint a shadow with radius 0
+                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();
+
+            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;
+
+            paintGeometricShadow(rectangleCoordinators, lightPosX, lightPosY, lightHeight,
+                    lightSize, canvas);
         } finally {
             canvas.restoreToCount(saved);
         }
     }
 
-    private static float elevationToShadow(float elevation) {
-        // The factor is chosen by eyeballing the shadow size on device and preview.
-        return elevation * 0.5f;
+    private static int modifyCanvas(@NonNull Canvas canvas) {
+        Rect rect = canvas.getClipBounds();
+        canvas.translate(rect.left, rect.top);
+        return canvas.save();
     }
 
-    /**
-     * Translate canvas by half of shadow size up, so that it appears that light is coming
-     * slightly from above. Also, remove clipping, so that shadow is not clipped.
-     */
-    private static int modifyCanvas(Canvas canvas, float shadowSize) {
-        Rect clipBounds = canvas.getClipBounds();
-        if (clipBounds.isEmpty()) {
-            return -1;
+    @NonNull
+    private static float[][] generateRectangleCoordinates(float left, float top, float right,
+            float bottom, float radius, float elevation) {
+        left = left + radius;
+        top = top + radius;
+        right = right - radius;
+        bottom = bottom - radius;
+
+        final double RADIANS_STEP = 2 * Math.PI / 4 / QUADRANT_DIVIDED_COUNT;
+
+        float[][] ret = new float[QUADRANT_DIVIDED_COUNT * 4][3];
+
+        int points = 0;
+        // left-bottom points
+        for (int i = 0; i < QUADRANT_DIVIDED_COUNT; i++) {
+            ret[points][0] = (float) (left - radius + radius * Math.cos(RADIANS_STEP * i));
+            ret[points][1] = (float) (bottom + radius - radius * Math.cos(RADIANS_STEP * i));
+            ret[points][2] = elevation;
+            points++;
+        }
+        // left-top points
+        for (int i = 0; i < QUADRANT_DIVIDED_COUNT; i++) {
+            ret[points][0] = (float) (left + radius - radius * Math.cos(RADIANS_STEP * i));
+            ret[points][1] = (float) (top + radius - radius * Math.cos(RADIANS_STEP * i));
+            ret[points][2] = elevation;
+            points++;
         }
-        int saved = canvas.save();
-        // Usually canvas has been translated to the top left corner of the view when this is
-        // called. So, setting a clip rect at 0,0 will clip the top left part of the shadow.
-        // Thus, we just expand in each direction by width and height of the canvas, while staying
-        // inside the original drawing region.
-        GcSnapshot snapshot = Canvas_Delegate.getDelegate(canvas).getSnapshot();
-        Rectangle originalClip = snapshot.getOriginalClip();
-        if (originalClip != null) {
-            canvas.clipRect(originalClip.x, originalClip.y, originalClip.x + originalClip.width,
-              originalClip.y + originalClip.height, Op.REPLACE);
-            canvas.clipRect(-canvas.getWidth(), -canvas.getHeight(), canvas.getWidth(),
-              canvas.getHeight(), Op.INTERSECT);
+        // right-top points
+        for (int i = 0; i < QUADRANT_DIVIDED_COUNT; i++) {
+            ret[points][0] = (float) (right + radius - radius * Math.cos(RADIANS_STEP * i));
+            ret[points][1] = (float) (top + radius + radius * Math.cos(RADIANS_STEP * i));
+            ret[points][2] = elevation;
+            points++;
         }
-        canvas.translate(0, shadowSize / 2f);
-        return saved;
+        // right-bottom point
+        for (int i = 0; i < QUADRANT_DIVIDED_COUNT; i++) {
+            ret[points][0] = (float) (right - radius + radius * Math.cos(RADIANS_STEP * i));
+            ret[points][1] = (float) (bottom - radius + radius * Math.cos(RADIANS_STEP * i));
+            ret[points][2] = elevation;
+            points++;
+        }
+
+        return ret;
     }
 
-    private static void sideShadow(Canvas canvas, Paint edgePaint,
-            RectF edgeShadowRect, float dx, float dy, int rotations) {
-        if (isRectEmpty(edgeShadowRect)) {
-            return;
+    private static void paintGeometricShadow(@NonNull float[][] coordinates, float lightPosX,
+            float lightPosY, float lightHeight, float lightSize, Canvas canvas) {
+
+        // The polygon of shadow (same as the original item)
+        float[] shadowPoly = new float[coordinates.length * 3];
+        for (int i = 0; i < coordinates.length; i++) {
+            shadowPoly[i * 3 + 0] = coordinates[i][0];
+            shadowPoly[i * 3 + 1] = coordinates[i][1];
+            shadowPoly[i * 3 + 2] = coordinates[i][2];
         }
-        int saved = canvas.save();
-        canvas.translate(dx, dy);
-        canvas.rotate(rotations * PERPENDICULAR_ANGLE);
-        canvas.drawRect(edgeShadowRect, 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(Canvas canvas, Paint paint, 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);
-    }
+        // TODO: calculate the ambient shadow and mix with Spot shadow.
+
+        // Calculate the shadow of SpotLight
+        float[] light = SpotShadow.calculateLight(lightSize, LIGHT_POINTS, lightPosX,
+                lightPosY, lightHeight);
+
+        int stripSize = 3 * SpotShadow.getStripSize(RAY_TRACING_RAYS, RAY_TRACING_LAYERS);
+        if (stripSize < 9) {
+            return;
+        }
+        float[] strip = new float[stripSize];
+        SpotShadow.calcShadow(light, LIGHT_POINTS, shadowPoly, coordinates.length, RAY_TRACING_RAYS,
+                RAY_TRACING_LAYERS, 1f, strip);
 
-    /**
-     * Differs from {@link RectF#isEmpty()} as this first converts the rect to int and then checks.
-     * <p/>
-     * This is required because {@link Canvas_Delegate#native_drawRect(long, float, float, float,
-     * float, long)} casts the co-ordinates to int and we want to ensure that it doesn't end up
-     * drawing empty rectangles, which results in IllegalArgumentException.
-     */
-    private static boolean isRectEmpty(RectF rect) {
-        return (int) rect.left >= (int) rect.right || (int) rect.top >= (int) rect.bottom;
+        ShadowBuffer buff = new ShadowBuffer(canvas.getWidth(), canvas.getHeight());
+        buff.generateTriangles(strip, SHADOW_STRENGTH);
+        buff.draw(canvas);
     }
 }
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/shadowutil/ShadowBuffer.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/shadowutil/ShadowBuffer.java
new file mode 100644 (file)
index 0000000..ae33e1d
--- /dev/null
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.layoutlib.bridge.shadowutil;
+
+import android.annotation.NonNull;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Canvas;
+
+public class ShadowBuffer {
+
+    private int mWidth;
+    private int mHeight;
+    private Bitmap mBitmap;
+    private int[] mData;
+
+    public ShadowBuffer(int width, int height) {
+        mWidth = width;
+        mHeight = height;
+        mBitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
+        mData = new int[mBitmap.getWidth() * mBitmap.getHeight()];
+        mBitmap.getPixels(mData, 0, mBitmap.getWidth(), 0, 0, mBitmap.getWidth(),
+                mBitmap.getHeight());
+    }
+
+    public void generateTriangles(@NonNull float[] strip, float scale) {
+        for (int i = 0; i < strip.length - 8; i += 3) {
+            float fx3 = strip[i];
+            float fy3 = strip[i + 1];
+            float fz3 = scale * strip[i + 2];
+
+            float fx2 = strip[i + 3];
+            float fy2 = strip[i + 4];
+            float fz2 = scale * strip[i + 5];
+
+            float fx1 = strip[i + 6];
+            float fy1 = strip[i + 7];
+            float fz1 = scale * strip[i + 8];
+
+            if (fx1 * (fy2 - fy3) + fx2 * (fy3 - fy1) + fx3 * (fy1 - fy2) == 0) {
+                continue;
+            }
+
+            triangleZBuffMin(mData, mWidth, mHeight, fx3, fy3, fz3, fx2, fy2, fz2, fx1, fy1, fz1);
+            triangleZBuffMin(mData, mWidth, mHeight, fx1, fy1, fz1, fx2, fy2, fz2, fx3, fy3, fz3);
+        }
+        mBitmap.setPixels(mData, 0, mBitmap.getWidth(), 0, 0, mBitmap.getWidth(),
+                mBitmap.getHeight());
+    }
+
+    private void triangleZBuffMin(@NonNull int[] buff, int w, int h, float fx3, float fy3,
+            float fz3, float fx2, float fy2, float fz2, float fx1, float fy1, float fz1) {
+        if (((fx1 - fx2) * (fy3 - fy2) - (fy1 - fy2) * (fx3 - fx2)) < 0) {
+            float tmpX = fx1;
+            float tmpY = fy1;
+            float tmpZ = fz1;
+            fx1 = fx2;
+            fy1 = fy2;
+            fz1 = fz2;
+            fx2 = tmpX;
+            fy2 = tmpY;
+            fz2 = tmpZ;
+        }
+        double d = (fx1 * (fy3 - fy2) - fx2 * fy3 + fx3 * fy2 + (fx2 - fx3) * fy1);
+
+        if (d == 0) {
+            return;
+        }
+        float dx = (float) (-(fy1 * (fz3 - fz2) - fy2 * fz3 + fy3 * fz2 + (fy2 - fy3) * fz1) / d);
+        float dy = (float) ((fx1 * (fz3 - fz2) - fx2 * fz3 + fx3 * fz2 + (fx2 - fx3) * fz1) / d);
+        float zOff = (float) ((fx1 * (fy3 * fz2 - fy2 * fz3) + fy1 * (fx2 * fz3 - fx3 * fz2) +
+                (fx3 * fy2 - fx2 * fy3) * fz1) / d);
+
+        int Y1 = (int) (16.0f * fy1 + .5f);
+        int Y2 = (int) (16.0f * fy2 + .5f);
+        int Y3 = (int) (16.0f * fy3 + .5f);
+
+        int X1 = (int) (16.0f * fx1 + .5f);
+        int X2 = (int) (16.0f * fx2 + .5f);
+        int X3 = (int) (16.0f * fx3 + .5f);
+
+        int DX12 = X1 - X2;
+        int DX23 = X2 - X3;
+        int DX31 = X3 - X1;
+
+        int DY12 = Y1 - Y2;
+        int DY23 = Y2 - Y3;
+        int DY31 = Y3 - Y1;
+
+        int FDX12 = DX12 << 4;
+        int FDX23 = DX23 << 4;
+        int FDX31 = DX31 << 4;
+
+        int FDY12 = DY12 << 4;
+        int FDY23 = DY23 << 4;
+        int FDY31 = DY31 << 4;
+
+        int minX = (min(X1, X2, X3) + 0xF) >> 4;
+        int maxX = (max(X1, X2, X3) + 0xF) >> 4;
+        int minY = (min(Y1, Y2, Y3) + 0xF) >> 4;
+        int maxY = (max(Y1, Y2, Y3) + 0xF) >> 4;
+
+        if (minY < 0) {
+            minY = 0;
+        }
+        if (minX < 0) {
+            minX = 0;
+        }
+        if (maxX > w) {
+            maxX = w;
+        }
+        if (maxY > h) {
+            maxY = h;
+        }
+        int off = minY * w;
+
+        int C1 = DY12 * X1 - DX12 * Y1;
+        int C2 = DY23 * X2 - DX23 * Y2;
+        int C3 = DY31 * X3 - DX31 * Y3;
+
+        if (DY12 < 0 || (DY12 == 0 && DX12 > 0)) {
+            C1++;
+        }
+        if (DY23 < 0 || (DY23 == 0 && DX23 > 0)) {
+            C2++;
+        }
+        if (DY31 < 0 || (DY31 == 0 && DX31 > 0)) {
+            C3++;
+        }
+        int CY1 = C1 + DX12 * (minY << 4) - DY12 * (minX << 4);
+        int CY2 = C2 + DX23 * (minY << 4) - DY23 * (minX << 4);
+        int CY3 = C3 + DX31 * (minY << 4) - DY31 * (minX << 4);
+
+        for (int y = minY; y < maxY; y++) {
+            int CX1 = CY1;
+            int CX2 = CY2;
+            int CX3 = CY3;
+            float p = zOff + dy * y;
+            for (int x = minX; x < maxX; x++) {
+                if (CX1 > 0 && CX2 > 0 && CX3 > 0) {
+                    int point = x + off;
+                    float zVal = p + dx * x;
+                    buff[point] |= ((int) (zVal * 255)) << 24;
+                }
+                CX1 -= FDY12;
+                CX2 -= FDY23;
+                CX3 -= FDY31;
+            }
+            CY1 += FDX12;
+            CY2 += FDX23;
+            CY3 += FDX31;
+            off += w;
+        }
+    }
+
+    private int min(int x1, int x2, int x3) {
+        return (x1 > x2) ? ((x2 > x3) ? x3 : x2) : ((x1 > x3) ? x3 : x1);
+    }
+
+    private int max(int x1, int x2, int x3) {
+        return (x1 < x2) ? ((x2 < x3) ? x3 : x2) : ((x1 < x3) ? x3 : x1);
+    }
+
+    public void draw(@NonNull Canvas c) {
+        c.drawBitmap(mBitmap, 0, 0, null);
+    }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/shadowutil/SpotShadow.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/shadowutil/SpotShadow.java
new file mode 100644 (file)
index 0000000..33375ff
--- /dev/null
@@ -0,0 +1,630 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.layoutlib.bridge.shadowutil;
+
+import android.annotation.NonNull;
+
+public class SpotShadow {
+
+    private static float rayIntersectPoly(@NonNull float[] poly, int polyLength, float px, float py,
+            float dx, float dy) {
+        int p1 = polyLength - 1;
+        for (int p2 = 0; p2 < polyLength; p2++) {
+            float p1x = poly[p1 * 2 + 0];
+            float p1y = poly[p1 * 2 + 1];
+            float p2x = poly[p2 * 2 + 0];
+            float p2y = poly[p2 * 2 + 1];
+            float div = (dx * (p1y - p2y) + dy * p2x - dy * p1x);
+            if (div != 0) {
+                float t = (dx * (p1y - py) + dy * px - dy * p1x) / (div);
+                if (t >= 0 && t <= 1) {
+                    float t2 = (p1x * (py - p2y) + p2x * (p1y - py) + px * (p2y - p1y)) / div;
+                    if (t2 > 0) {
+                        return t2;
+                    }
+                }
+            }
+            p1 = p2;
+        }
+        return Float.NaN;
+    }
+
+    private static void centroid2d(@NonNull float[] poly, int len, @NonNull float[] ret) {
+        float sumX = 0;
+        float sumY = 0;
+        int p1 = len - 1;
+        float area = 0;
+        for (int p2 = 0; p2 < len; p2++) {
+            float x1 = poly[p1 * 2 + 0];
+            float y1 = poly[p1 * 2 + 1];
+            float x2 = poly[p2 * 2 + 0];
+            float y2 = poly[p2 * 2 + 1];
+            float a = (x1 * y2 - x2 * y1);
+            sumX += (x1 + x2) * a;
+            sumY += (y1 + y2) * a;
+            area += a;
+            p1 = p2;
+        }
+
+        float centroidX = sumX / (3 * area);
+        float centroidY = sumY / (3 * area);
+        ret[0] = centroidX;
+        ret[1] = centroidY;
+    }
+
+    /**
+     * calculates the Centroid of a 3d polygon
+     * @param poly The flatten 3d vertices coordinates of polygon, the format is like
+     * [x0, y0, z0, x1, y1, z1, x2, ...]
+     * @param len The number of polygon vertices. So the length of poly should be len * 3.
+     * @param ret The array used to sotre the result. The length should be 3.
+     */
+    private static void centroid3d(@NonNull float[] poly, int len, @NonNull float[] ret) {
+        int n = len - 1;
+        double area = 0;
+        double cx = 0, cy = 0, cz = 0;
+        for (int i = 1; i < n; i++) {
+            int k = i + 1;
+            float a0 = poly[i * 3 + 0] - poly[0 * 3 + 0];
+            float a1 = poly[i * 3 + 1] - poly[0 * 3 + 1];
+            float a2 = poly[i * 3 + 2] - poly[0 * 3 + 2];
+            float b0 = poly[k * 3 + 0] - poly[0 * 3 + 0];
+            float b1 = poly[k * 3 + 1] - poly[0 * 3 + 1];
+            float b2 = poly[k * 3 + 2] - poly[0 * 3 + 2];
+            float c0 = a1 * b2 - b1 * a2;
+            float c1 = a2 * b0 - b2 * a0;
+            float c2 = a0 * b1 - b0 * a1;
+            double areaOfTriangle = Math.sqrt(c0 * c0 + c1 * c1 + c2 * c2);
+            area += areaOfTriangle;
+            cx += areaOfTriangle * (poly[i * 3 + 0] + poly[k * 3 + 0] + poly[0 * 3 + 0]);
+            cy += areaOfTriangle * (poly[i * 3 + 1] + poly[k * 3 + 1] + poly[0 * 3 + 1]);
+            cz += areaOfTriangle * (poly[i * 3 + 2] + poly[k * 3 + 2] + poly[0 * 3 + 2]);
+        }
+        ret[0] = (float) (cx / (3 * area));
+        ret[1] = (float) (cy / (3 * area));
+        ret[2] = (float) (cz / (3 * area));
+    }
+
+    /**
+     * Extracts the convex hull of a polygon.
+     * @param points The vertices coordinates of polygon
+     * @param pointsLength The number of polygon vertices. So the length of poly should be len * 3.
+     * @param retPoly retPoly is at most the size of the input polygon
+     * @return The number of points in the retPolygon
+     */
+    private static int hull(@NonNull float[] points, int pointsLength, @NonNull float[] retPoly) {
+        quicksortX(points, 0, pointsLength - 1);
+        int n = pointsLength;
+        float[] lUpper = new float[n * 2];
+        lUpper[0] = points[0];
+        lUpper[1] = points[1];
+        lUpper[2] = points[2];
+        lUpper[3] = points[3];
+
+        int lUpperSize = 2;
+
+        for (int i = 2; i < n; i++) {
+            lUpper[lUpperSize * 2 + 0] = points[i * 2 + 0];
+            lUpper[lUpperSize * 2 + 1] = points[i * 2 + 1];
+            lUpperSize++;
+
+            while (lUpperSize > 2 &&
+                    !rightTurn(lUpper[(lUpperSize - 3) * 2], lUpper[(lUpperSize - 3) * 2 + 1],
+                            lUpper[(lUpperSize - 2) * 2], lUpper[(lUpperSize - 2) * 2 + 1],
+                            lUpper[(lUpperSize - 1) * 2], lUpper[(lUpperSize - 1) * 2 + 1])) {
+                // Remove the middle point of the three last
+                lUpper[(lUpperSize - 2) * 2 + 0] = lUpper[(lUpperSize - 1) * 2 + 0];
+                lUpper[(lUpperSize - 2) * 2 + 1] = lUpper[(lUpperSize - 1) * 2 + 1];
+                lUpperSize--;
+            }
+        }
+
+        float[] lLower = new float[n * 2];
+        lLower[0] = points[(n - 1) * 2 + 0];
+        lLower[1] = points[(n - 1) * 2 + 1];
+        lLower[2] = points[(n - 2) * 2 + 0];
+        lLower[3] = points[(n - 2) * 2 + 1];
+
+        int lLowerSize = 2;
+
+        for (int i = n - 3; i >= 0; i--) {
+            lLower[lLowerSize * 2 + 0] = points[i * 2 + 0];
+            lLower[lLowerSize * 2 + 1] = points[i * 2 + 1];
+            lLowerSize++;
+
+            while (lLowerSize > 2 &&
+                    !rightTurn(lLower[(lLowerSize - 3) * 2], lLower[(lLowerSize - 3) * 2 + 1],
+                            lLower[(lLowerSize - 2) * 2], lLower[(lLowerSize - 2) * 2 + 1],
+                            lLower[(lLowerSize - 1) * 2], lLower[(lLowerSize - 1) * 2 + 1])) {
+                // Remove the middle point of the three last
+                lLower[(lLowerSize - 2) * 2 + 0] = lLower[(lLowerSize - 1) * 2 + 0];
+                lLower[(lLowerSize - 2) * 2 + 1] = lLower[(lLowerSize - 1) * 2 + 1];
+                lLowerSize--;
+            }
+        }
+
+        int count = 0;
+        for (int i = 0; i < lUpperSize; i++) {
+            retPoly[count * 2 + 0] = lUpper[i * 2 + 0];
+            retPoly[count * 2 + 1] = lUpper[i * 2 + 1];
+            count++;
+        }
+        for (int i = 1; i < lLowerSize - 1; i++) {
+            retPoly[count * 2 + 0] = lLower[i * 2 + 0];
+            retPoly[count * 2 + 1] = lLower[i * 2 + 1];
+            count++;
+        }
+        return count;
+    }
+
+    private static boolean rightTurn(float ax, float ay, float bx, float by, float cx, float cy) {
+        return (bx - ax) * (cy - ay) - (by - ay) * (cx - ax) > 0.00001;
+    }
+
+    /**
+     * calculates the intersection of poly1 with poly2 and put in poly2
+     * @param poly1 The flatten 2d coordinates of polygon
+     * @param poly1length The vertices number of poly1
+     * @param poly2 The flatten 2d coordinates of polygon
+     * @param poly2length The vertices number of poly2
+     * @return number of vertices in poly2
+     */
+    private static int intersection(@NonNull float[] poly1, int poly1length, @NonNull float[] poly2,
+            int poly2length) {
+        makeClockwise(poly1, poly1length);
+        makeClockwise(poly2, poly2length);
+        float[] poly = new float[(poly1length * poly2length + 2) * 2];
+        int count = 0;
+        int pCount = 0;
+        for (int i = 0; i < poly1length; i++) {
+            if (pointInsidePolygon(poly1[i * 2 + 0], poly1[i * 2 + 1], poly2, poly2length)) {
+                poly[count * 2 + 0] = poly1[i * 2 + 0];
+                poly[count * 2 + 1] = poly1[i * 2 + 1];
+                count++;
+                pCount++;
+            }
+        }
+        int fromP1 = pCount;
+        for (int i = 0; i < poly2length; i++) {
+            if (pointInsidePolygon(poly2[i * 2 + 0], poly2[i * 2 + 1], poly1, poly1length)) {
+                poly[count * 2 + 0] = poly2[i * 2 + 0];
+                poly[count * 2 + 1] = poly2[i * 2 + 1];
+                count++;
+            }
+        }
+        int fromP2 = count - fromP1;
+        if (fromP1 == poly1length) { // use p1
+            for (int i = 0; i < poly1length; i++) {
+                poly2[i * 2 + 0] = poly1[i * 2 + 0];
+                poly2[i * 2 + 1] = poly1[i * 2 + 1];
+            }
+            return poly1length;
+        }
+        if (fromP2 == poly2length) { // use p2
+            return poly2length;
+        }
+        float[] intersection = new float[2];
+        for (int i = 0; i < poly2length; i++) {
+            for (int j = 0; j < poly1length; j++) {
+                int i1_by_2 = i * 2;
+                int i2_by_2 = ((i + 1) % poly2length) * 2;
+                int j1_by_2 = j * 2;
+                int j2_by_2 = ((j + 1) % poly1length) * 2;
+                boolean found =
+                        lineIntersection(poly2[i1_by_2 + 0], poly2[i1_by_2 + 1], poly2[i2_by_2 + 0],
+                                poly2[i2_by_2 + 1], poly1[j1_by_2 + 0], poly1[j1_by_2 + 1],
+                                poly1[j2_by_2 + 0], poly1[j2_by_2 + 1], intersection);
+                if (found) {
+                    poly[count * 2 + 0] = intersection[0];
+                    poly[count * 2 + 1] = intersection[1];
+                    count++;
+                } else {
+                    float dx = poly2[i * 2 + 0] - poly1[j * 2 + 0];
+                    float dy = poly2[i * 2 + 1] - poly1[j * 2 + 1];
+
+                    if (dx * dx + dy * dy < 0.01) {
+                        poly[count * 2 + 0] = poly2[i * 2 + 0];
+                        poly[count * 2 + 1] = poly2[i * 2 + 1];
+                        count++;
+                    }
+                }
+            }
+        }
+        if (count == 0) {
+            return 0;
+        }
+        float avgX = 0;
+        float avgY = 0;
+        for (int i = 0; i < count; i++) {
+            avgX += poly[i * 2 + 0];
+            avgY += poly[i * 2 + 1];
+        }
+        avgX /= count;
+        avgY /= count;
+
+        float[] ctr = new float[]{avgX, avgY};
+        sort(poly, count, ctr);
+        int size = count;
+
+        poly2[0] = poly[0];
+        poly2[1] = poly[1];
+
+        count = 1;
+        for (int i = 1; i < size; i++) {
+            float dx = poly[i * 2 + 0] - poly[(i - 1) * 2 + 0];
+            float dy = poly[i * 2 + 1] - poly[(i - 1) * 2 + 1];
+            if (dx * dx + dy * dy >= 0.01) {
+                poly2[count * 2 + 0] = poly[i * 2 + 0];
+                poly2[count * 2 + 1] = poly[i * 2 + 1];
+                count++;
+            }
+        }
+        return count;
+    }
+
+    public static void sort(@NonNull float[] poly, int polyLength, @NonNull float[] ctr) {
+        quicksortCircle(poly, 0, polyLength - 1, ctr);
+    }
+
+    public static float angle(float x1, float y1, @NonNull float[] ctr) {
+        return -(float) Math.atan2(x1 - ctr[0], y1 - ctr[1]);
+    }
+
+    private static void swapPair(@NonNull float[] points, int i, int j) {
+        float x = points[i * 2 + 0];
+        float y = points[i * 2 + 1];
+        points[i * 2 + 0] = points[j * 2 + 0];
+        points[i * 2 + 1] = points[j * 2 + 1];
+        points[j * 2 + 0] = x;
+        points[j * 2 + 1] = y;
+    }
+
+    private static void quicksortCircle(@NonNull float[] points, int low, int high,
+            @NonNull float[] ctr) {
+        int i = low, j = high;
+        int p = low + (high - low) / 2;
+        float pivot = angle(points[p * 2], points[p * 2 + 1], ctr);
+        while (i <= j) {
+            while (angle(points[i * 2 + 0], points[i * 2 + 1], ctr) < pivot) {
+                i++;
+            }
+            while (angle(points[j * 2 + 0], points[j * 2 + 1], ctr) > pivot) {
+                j--;
+            }
+            if (i <= j) {
+                swapPair(points, i, j);
+                i++;
+                j--;
+            }
+        }
+        if (low < j) {
+            quicksortCircle(points, low, j, ctr);
+        }
+        if (i < high) {
+            quicksortCircle(points, i, high, ctr);
+        }
+    }
+
+    /**
+     * This function do Quick Sort by comparing X axis only.<br>
+     * Note that the input values of points are paired coordinates, e.g. {@code [x0, y0, x1, y1, x2,
+     * y2, ...]).}
+     * @param points The input point pairs. Every {@code (2 * i, 2 * i + 1)} points are pairs.
+     * @param low lowest index used to do quick sort sort
+     * @param high highest index used to do quick sort
+     */
+    private static void quicksortX(@NonNull float[] points, int low, int high) {
+        int i = low, j = high;
+        int p = low + (high - low) / 2;
+        float pivot = points[p * 2];
+        while (i <= j) {
+            while (points[i * 2 + 0] < pivot) {
+                i++;
+            }
+            while (points[j * 2 + 0] > pivot) {
+                j--;
+            }
+
+            if (i <= j) {
+                swapPair(points, i, j);
+                i++;
+                j--;
+            }
+        }
+        if (low < j) {
+            quicksortX(points, low, j);
+        }
+        if (i < high) {
+            quicksortX(points, i, high);
+        }
+    }
+
+    private static boolean pointInsidePolygon(float x, float y, @NonNull float[] poly, int len) {
+        boolean c = false;
+        float testX = x;
+        float testY = y;
+        for (int i = 0, j = len - 1; i < len; j = i++) {
+            if (((poly[i * 2 + 1] > testY) != (poly[j * 2 + 1] > testY)) && (testX <
+                    (poly[j * 2 + 0] - poly[i * 2 + 0]) * (testY - poly[i * 2 + 1]) /
+                            (poly[j * 2 + 1] - poly[i * 2 + 1]) + poly[i * 2 + 0])) {
+                c = !c;
+            }
+        }
+        return c;
+    }
+
+    private static void makeClockwise(@NonNull float[] polygon, int len) {
+        if (polygon == null || len == 0) {
+            return;
+        }
+        if (!isClockwise(polygon, len)) {
+            reverse(polygon, len);
+        }
+    }
+
+    private static boolean isClockwise(@NonNull float[] polygon, int len) {
+        float sum = 0;
+        float p1x = polygon[(len - 1) * 2 + 0];
+        float p1y = polygon[(len - 1) * 2 + 1];
+        for (int i = 0; i < len; i++) {
+            float p2x = polygon[i * 2 + 0];
+            float p2y = polygon[i * 2 + 1];
+            sum += p1x * p2y - p2x * p1y;
+            p1x = p2x;
+            p1y = p2y;
+        }
+        return sum < 0;
+    }
+
+    private static void reverse(@NonNull float[] polygon, int len) {
+        int n = len / 2;
+        for (int i = 0; i < n; i++) {
+            float tmp0 = polygon[i * 2 + 0];
+            float tmp1 = polygon[i * 2 + 1];
+            int k = len - 1 - i;
+            polygon[i * 2 + 0] = polygon[k * 2 + 0];
+            polygon[i * 2 + 1] = polygon[k * 2 + 1];
+            polygon[k * 2 + 0] = tmp0;
+            polygon[k * 2 + 1] = tmp1;
+        }
+    }
+
+    /**
+     * Intersects two lines in parametric form.
+     */
+    private static final boolean lineIntersection(float x1, float y1, float x2, float y2, float x3,
+            float y3, float x4, float y4, @NonNull float[] ret) {
+        float d = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
+        if (d == 0.000f) {
+            return false;
+        }
+
+        float dx = (x1 * y2 - y1 * x2);
+        float dy = (x3 * y4 - y3 * x4);
+        float x = (dx * (x3 - x4) - (x1 - x2) * dy) / d;
+        float y = (dx * (y3 - y4) - (y1 - y2) * dy) / d;
+
+        if (((x - x1) * (x - x2) > 0.0000001) || ((x - x3) * (x - x4) > 0.0000001) ||
+                ((y - y1) * (y - y2) > 0.0000001) || ((y - y3) * (y - y4) > 0.0000001)) {
+            return false;
+        }
+        ret[0] = x;
+        ret[1] = y;
+        return true;
+    }
+
+    @NonNull
+    public static float[] calculateLight(float size, int points, float x, float y, float height) {
+        float[] ret = new float[points * 3];
+        for (int i = 0; i < points; i++) {
+            double angle = 2 * i * Math.PI / points;
+            ret[i * 3 + 0] = (float) Math.cos(angle) * size + x;
+            ret[i * 3 + 1] = (float) Math.sin(angle) * size + y;
+            ret[i * 3 + 2] = (height);
+        }
+        return ret;
+    }
+
+    /**
+     layers indicates the number of circular strips to generate.
+     Strength is how dark a shadow to generate.
+
+    /**
+     * This calculates a collection of triangles that represent the shadow cast by a polygonal
+     * light source (lightPoly) hitting a convex polygon (poly).
+     * @param lightPoly The flatten 3d coordinates of light
+     * @param lightPolyLength The vertices number of light polygon.
+     * @param poly The flatten 3d coordinates of item
+     * @param polyLength The vertices number of light polygon.
+     * @param rays defines the number of points around the perimeter of the shadow to generate
+     * @param layers The number of shadow's fading.
+     * @param strength The factor of the black color of shadow.
+     * @param retStrips Used to store the calculated shadow strength
+     * @return true if the params is able to calculate a shadow, else false.
+     */
+    public static boolean calcShadow(@NonNull float[] lightPoly, int lightPolyLength,
+            @NonNull float[] poly, int polyLength, int rays, int layers, float strength,
+            @NonNull float[] retStrips) {
+        float[] shadowRegion = new float[lightPolyLength * polyLength * 2];
+        float[] outline = new float[polyLength * 2];
+        float[] umbra = new float[polyLength * lightPolyLength * 2];
+        int umbraLength = 0;
+
+        int k = 0;
+        for (int j = 0; j < lightPolyLength; j++) {
+            int m = 0;
+            for (int i = 0; i < polyLength; i++) {
+                float t = lightPoly[j * 3 + 2] - poly[i * 3 + 2];
+                if (t == 0) {
+                    return false;
+                }
+                t = lightPoly[j * 3 + 2] / t;
+                float x = lightPoly[j * 3 + 0] - t * (lightPoly[j * 3 + 0] - poly[i * 3 + 0]);
+                float y = lightPoly[j * 3 + 1] - t * (lightPoly[j * 3 + 1] - poly[i * 3 + 1]);
+
+                shadowRegion[k * 2 + 0] = x;
+                shadowRegion[k * 2 + 1] = y;
+                outline[m * 2 + 0] = x;
+                outline[m * 2 + 1] = y;
+
+                k++;
+                m++;
+            }
+            if (umbraLength == 0) {
+                System.arraycopy(outline, 0, umbra, 0, polyLength);
+                umbraLength = polyLength;
+            } else {
+                umbraLength = intersection(outline, polyLength, umbra, umbraLength);
+                if (umbraLength == 0) {
+                    break;
+                }
+            }
+        }
+        int shadowRegionLength = k;
+
+        float[] penumbra = new float[k * 2];
+        int penumbraLength = hull(shadowRegion, shadowRegionLength, penumbra);
+        if (umbraLength < 3) {// no real umbra make a fake one
+            float[] p = new float[3];
+            centroid3d(lightPoly, lightPolyLength, p);
+            float[] centShadow = new float[polyLength * 2];
+            for (int i = 0; i < polyLength; i++) {
+                float t = p[2] - poly[i * 3 + 2];
+                if (t == 0) {
+                    return false;
+                }
+                t = p[2] / t;
+                float x = p[0] - t * (p[0] - poly[i * 3 + 0]);
+                float y = p[1] - t * (p[1] - poly[i * 3 + 1]);
+
+                centShadow[i * 2 + 0] = x;
+                centShadow[i * 2 + 1] = y;
+            }
+            float[] c = new float[2];
+            centroid2d(centShadow, polyLength, c);
+            for (int i = 0; i < polyLength; i++) {
+                centShadow[i * 2 + 0] = (c[0] * 9 + centShadow[i * 2 + 0]) / 10;
+                centShadow[i * 2 + 1] = (c[1] * 9 + centShadow[i * 2 + 1]) / 10;
+            }
+            umbra = centShadow; // fake umbra
+            umbraLength = polyLength; // same size as the original polygon
+        }
+
+        triangulateConcentricPolygon(penumbra, penumbraLength, umbra, umbraLength, rays, layers,
+                strength, retStrips);
+        return true;
+    }
+
+    /**
+     * triangulate concentric circles.
+     * This takes the inner and outer polygons of the umbra and penumbra and triangulates it.
+     * @param penumbra The 2d flatten vertices of penumbra polygons.
+     * @param penumbraLength The number of vertices in penumbra.
+     * @param umbra The 2d flatten vertices of umbra polygons.
+     * @param umbraLength The number of vertices in umbra.
+     * @param rays defines the number of points around the perimeter of the shadow to generate
+     * @param layers The number of shadow's fading.
+     * @param strength The factor of the black color of shadow.
+     * @param retStrips Used to store the calculated shadow strength.
+     */
+    private static void triangulateConcentricPolygon(@NonNull float[] penumbra, int penumbraLength,
+            @NonNull float[] umbra, int umbraLength, int rays, int layers, float strength,
+            @NonNull float[] retStrips) {
+        int rings = layers + 1;
+        double step = Math.PI * 2 / rays;
+        float[] retXY = new float[2];
+        centroid2d(umbra, umbraLength, retXY);
+        float cx = retXY[0];
+        float cy = retXY[1];
+
+        float[] t1 = new float[rays];
+        float[] t2 = new float[rays];
+
+        for (int i = 0; i < rays; i++) {
+            float dx = (float) Math.cos(Math.PI / 4 + step * i);
+            float dy = (float) Math.sin(Math.PI / 4 + step * i);
+            t2[i] = rayIntersectPoly(umbra, umbraLength, cx, cy, dx, dy);
+            t1[i] = rayIntersectPoly(penumbra, penumbraLength, cx, cy, dx, dy);
+        }
+
+        int p = 0;
+        // Calculate the vertex
+        for (int r = 0; r < layers; r++) {
+            int startP = p;
+            for (int i = 0; i < rays; i++) {
+                float dx = (float) Math.cos(Math.PI / 4 + step * i);
+                float dy = (float) Math.sin(Math.PI / 4 + step * i);
+
+                for (int j = r; j < (r + 2); j++) {
+                    float jf = j / (float) (rings - 1);
+                    float t = t1[i] + jf * (t2[i] - t1[i]);
+                    float op = (jf + 1 - 1 / (1 + (t - t1[i]) * (t - t1[i]))) / 2;
+                    retStrips[p * 3 + 0] = dx * t + cx;
+                    retStrips[p * 3 + 1] = dy * t + cy;
+                    retStrips[p * 3 + 2] = jf * op * strength;
+                    p++;
+
+                }
+            }
+            retStrips[p * 3 + 0] = retStrips[startP * 3 + 0];
+            retStrips[p * 3 + 1] = retStrips[startP * 3 + 1];
+            retStrips[p * 3 + 2] = retStrips[startP * 3 + 2];
+            p++;
+            startP++;
+            retStrips[p * 3 + 0] = retStrips[startP * 3 + 0];
+            retStrips[p * 3 + 1] = retStrips[startP * 3 + 1];
+            retStrips[p * 3 + 2] = retStrips[startP * 3 + 2];
+            p++;
+        }
+        int oldP = p - 1;
+        retStrips[p * 3 + 0] = retStrips[oldP * 3 + 0];
+        retStrips[p * 3 + 1] = retStrips[oldP * 3 + 1];
+        retStrips[p * 3 + 2] = retStrips[oldP * 3 + 2];
+        p++;
+
+        // Skip the first point here, then make it same as last point later.
+        oldP = p;
+        p++;
+        for (int k = 0; k < rays; k++) {
+            int i = k / 2;
+            if ((k & 1) == 1) { // traverse the inside in a zig zag pattern
+                // for strips
+                i = rays - i - 1;
+            }
+            float dx = (float) Math.cos(Math.PI / 4 + step * i);
+            float dy = (float) Math.sin(Math.PI / 4 + step * i);
+
+            float jf = 1;
+
+            float t = t1[i] + jf * (t2[i] - t1[i]);
+            float op = (jf + 1 - 1 / (1 + (t - t1[i]) * (t - t1[i]))) / 2;
+
+            retStrips[p * 3 + 0] = dx * t + cx;
+            retStrips[p * 3 + 1] = dy * t + cy;
+            retStrips[p * 3 + 2] = jf * op * strength;
+            p++;
+        }
+        p = oldP;
+        retStrips[p * 3 + 0] = retStrips[oldP * 3 + 0];
+        retStrips[p * 3 + 1] = retStrips[oldP * 3 + 1];
+        retStrips[p * 3 + 2] = retStrips[oldP * 3 + 2];
+    }
+
+    public static int getStripSize(int rays, int layers) {
+        return (2 + rays + ((layers) * 2 * (rays + 1)));
+    }
+}
index 4f3ed60..67355b8 100644 (file)
Binary files a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/shadows_test.png and b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/shadows_test.png differ
index 59dbbec..b1d6a60 100644 (file)
@@ -65,7 +65,7 @@
             android:layout_height="40dp"
             android:layout_alignParentRight="true"
             android:layout_centerVertical="true"
-            android:elevation="100dp"
+            android:elevation="108dp"
             android:stateListAnimator="@null"/>
 
     </RelativeLayout>
@@ -90,7 +90,7 @@
             android:layout_height="40dp"
             android:layout_alignParentRight="true"
             android:layout_centerVertical="true"
-            android:elevation="36dp"
+            android:elevation="108dp"
             android:stateListAnimator="@null"/>
 
     </RelativeLayout>