1/*
2 * Copyright (C) 2015, 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.view;
18
19import android.annotation.NonNull;
20import android.graphics.Canvas;
21import android.graphics.Outline;
22import android.graphics.Rect;
23import com.android.layoutlib.bridge.shadowutil.SpotShadow;
24import com.android.layoutlib.bridge.shadowutil.ShadowBuffer;
25
26public class RectShadowPainter {
27
28    private static final float SHADOW_STRENGTH = 0.1f;
29    private static final int LIGHT_POINTS = 8;
30
31    private static final int QUADRANT_DIVIDED_COUNT = 8;
32
33    private static final int RAY_TRACING_RAYS = 180;
34    private static final int RAY_TRACING_LAYERS = 10;
35
36    public static void paintShadow(@NonNull Outline viewOutline, float elevation,
37            @NonNull Canvas canvas) {
38        Rect outline = new Rect();
39        if (!viewOutline.getRect(outline)) {
40            assert false : "Outline is not a rect shadow";
41            return;
42        }
43
44        Rect originCanvasRect = canvas.getClipBounds();
45        int saved = modifyCanvas(canvas);
46        if (saved == -1) {
47            return;
48        }
49        try {
50            float radius = viewOutline.getRadius();
51            if (radius <= 0) {
52                // We can not paint a shadow with radius 0
53                return;
54            }
55
56            // view's absolute position in this canvas.
57            int viewLeft = -originCanvasRect.left + outline.left;
58            int viewTop = -originCanvasRect.top + outline.top;
59            int viewRight = viewLeft + outline.width();
60            int viewBottom = viewTop + outline.height();
61
62            float[][] rectangleCoordinators = generateRectangleCoordinates(viewLeft, viewTop,
63                    viewRight, viewBottom, radius, elevation);
64
65            // TODO: get these values from resources.
66            float lightPosX = canvas.getWidth() / 2;
67            float lightPosY = 0;
68            float lightHeight = 1800;
69            float lightSize = 200;
70
71            paintGeometricShadow(rectangleCoordinators, lightPosX, lightPosY, lightHeight,
72                    lightSize, canvas);
73        } finally {
74            canvas.restoreToCount(saved);
75        }
76    }
77
78    private static int modifyCanvas(@NonNull Canvas canvas) {
79        Rect rect = canvas.getClipBounds();
80        canvas.translate(rect.left, rect.top);
81        return canvas.save();
82    }
83
84    @NonNull
85    private static float[][] generateRectangleCoordinates(float left, float top, float right,
86            float bottom, float radius, float elevation) {
87        left = left + radius;
88        top = top + radius;
89        right = right - radius;
90        bottom = bottom - radius;
91
92        final double RADIANS_STEP = 2 * Math.PI / 4 / QUADRANT_DIVIDED_COUNT;
93
94        float[][] ret = new float[QUADRANT_DIVIDED_COUNT * 4][3];
95
96        int points = 0;
97        // left-bottom points
98        for (int i = 0; i < QUADRANT_DIVIDED_COUNT; i++) {
99            ret[points][0] = (float) (left - radius + radius * Math.cos(RADIANS_STEP * i));
100            ret[points][1] = (float) (bottom + radius - radius * Math.cos(RADIANS_STEP * i));
101            ret[points][2] = elevation;
102            points++;
103        }
104        // left-top points
105        for (int i = 0; i < QUADRANT_DIVIDED_COUNT; i++) {
106            ret[points][0] = (float) (left + radius - radius * Math.cos(RADIANS_STEP * i));
107            ret[points][1] = (float) (top + radius - radius * Math.cos(RADIANS_STEP * i));
108            ret[points][2] = elevation;
109            points++;
110        }
111        // right-top points
112        for (int i = 0; i < QUADRANT_DIVIDED_COUNT; i++) {
113            ret[points][0] = (float) (right + radius - radius * Math.cos(RADIANS_STEP * i));
114            ret[points][1] = (float) (top + radius + radius * Math.cos(RADIANS_STEP * i));
115            ret[points][2] = elevation;
116            points++;
117        }
118        // right-bottom point
119        for (int i = 0; i < QUADRANT_DIVIDED_COUNT; i++) {
120            ret[points][0] = (float) (right - radius + radius * Math.cos(RADIANS_STEP * i));
121            ret[points][1] = (float) (bottom - radius + radius * Math.cos(RADIANS_STEP * i));
122            ret[points][2] = elevation;
123            points++;
124        }
125
126        return ret;
127    }
128
129    private static void paintGeometricShadow(@NonNull float[][] coordinates, float lightPosX,
130            float lightPosY, float lightHeight, float lightSize, Canvas canvas) {
131        if (canvas == null || canvas.getWidth() == 0 || canvas.getHeight() == 0) {
132            return;
133        }
134
135        // The polygon of shadow (same as the original item)
136        float[] shadowPoly = new float[coordinates.length * 3];
137        for (int i = 0; i < coordinates.length; i++) {
138            shadowPoly[i * 3 + 0] = coordinates[i][0];
139            shadowPoly[i * 3 + 1] = coordinates[i][1];
140            shadowPoly[i * 3 + 2] = coordinates[i][2];
141        }
142
143        // TODO: calculate the ambient shadow and mix with Spot shadow.
144
145        // Calculate the shadow of SpotLight
146        float[] light = SpotShadow.calculateLight(lightSize, LIGHT_POINTS, lightPosX,
147                lightPosY, lightHeight);
148
149        int stripSize = 3 * SpotShadow.getStripSize(RAY_TRACING_RAYS, RAY_TRACING_LAYERS);
150        if (stripSize < 9) {
151            return;
152        }
153        float[] strip = new float[stripSize];
154        SpotShadow.calcShadow(light, LIGHT_POINTS, shadowPoly, coordinates.length, RAY_TRACING_RAYS,
155                RAY_TRACING_LAYERS, 1f, strip);
156
157        ShadowBuffer buff = new ShadowBuffer(canvas.getWidth(), canvas.getHeight());
158        buff.generateTriangles(strip, SHADOW_STRENGTH);
159        buff.draw(canvas);
160    }
161}
162