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 17 package android.view; 18 19 import android.annotation.NonNull; 20 import android.graphics.Canvas; 21 import android.graphics.Outline; 22 import android.graphics.Rect; 23 import com.android.layoutlib.bridge.shadowutil.SpotShadow; 24 import com.android.layoutlib.bridge.shadowutil.ShadowBuffer; 25 26 public 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 paintShadow(@onNull Outline viewOutline, float elevation, @NonNull Canvas canvas)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 modifyCanvas(@onNull Canvas canvas)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 generateRectangleCoordinates(float left, float top, float right, float bottom, float radius, float elevation)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 paintGeometricShadow(@onNull float[][] coordinates, float lightPosX, float lightPosY, float lightHeight, float lightSize, Canvas canvas)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