1 /* 2 * Copyright (C) 2018 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.shadow; 18 19 import com.android.ide.common.rendering.api.LayoutLog; 20 import com.android.layoutlib.bridge.Bridge; 21 import com.android.tools.layoutlib.annotations.VisibleForTesting; 22 23 import android.graphics.Bitmap; 24 import android.view.math.Math3DHelper; 25 26 /** 27 * Generate spot shadow bitmap. 28 */ 29 class SpotShadowBitmapGenerator { 30 31 private final SpotShadowConfig mShadowConfig; 32 private final TriangleBuffer mTriangle; 33 private float[] mStrips; 34 private float[] mLightSources; 35 private float mTranslateX; 36 private float mTranslateY; 37 SpotShadowBitmapGenerator(SpotShadowConfig config)38 public SpotShadowBitmapGenerator(SpotShadowConfig config) { 39 // TODO: Reduce the buffer size based on shadow bounds. 40 mTriangle = new TriangleBuffer(); 41 mShadowConfig = config; 42 // For now assume no change to the world size 43 mTriangle.setSize(config.getWidth(), config.getHeight(), 0); 44 } 45 46 /** 47 * Populate the shadow bitmap. 48 */ populateShadow()49 public void populateShadow() { 50 try { 51 mLightSources = SpotShadowVertexCalculator.calculateLight( 52 mShadowConfig.getLightRadius(), 53 mShadowConfig.getLightSourcePoints(), 54 mShadowConfig.getLightCoord()[0], 55 mShadowConfig.getLightCoord()[1], 56 mShadowConfig.getLightCoord()[2]); 57 58 mStrips = new float[3 * SpotShadowVertexCalculator.getStripSize( 59 mShadowConfig.getRays(), 60 mShadowConfig.getLayers())]; 61 62 if (SpotShadowVertexCalculator.calculateShadow( 63 mLightSources, 64 mShadowConfig.getLightSourcePoints(), 65 mShadowConfig.getPoly(), 66 mShadowConfig.getPolyLength(), 67 mShadowConfig.getRays(), 68 mShadowConfig.getLayers(), 69 mShadowConfig.getShadowStrength(), 70 mStrips) != 1) { 71 return; 72 } 73 74 // Bit of a hack to re-adjust spot shadow to fit correctly within parent canvas. 75 // Problem is that outline passed is not a final position, which throws off our 76 // whereas our shadow rendering algorithm, which requires pre-set range for 77 // optimization purposes. 78 float[] shadowBounds = Math3DHelper.flatBound(mStrips, 3); 79 80 if ((shadowBounds[2] - shadowBounds[0]) > mShadowConfig.getWidth() || 81 (shadowBounds[3] - shadowBounds[1]) > mShadowConfig.getHeight()) { 82 // Spot shadow to be casted is larger than the parent canvas, 83 // We'll let ambient shadow do the trick and skip spot shadow here. 84 return; 85 } 86 87 mTranslateX = 0; 88 mTranslateY = 0; 89 if (shadowBounds[0] < 0) { 90 // translate to right by the offset amount. 91 mTranslateX = shadowBounds[0] * -1; 92 } else if (shadowBounds[2] > mShadowConfig.getWidth()) { 93 // translate to left by the offset amount. 94 mTranslateX = shadowBounds[2] - mShadowConfig.getWidth(); 95 } 96 97 if (shadowBounds[1] < 0) { 98 mTranslateY = shadowBounds[1] * -1; 99 } else if (shadowBounds[3] > mShadowConfig.getHeight()) { 100 mTranslateY = shadowBounds[3] - mShadowConfig.getHeight(); 101 } 102 Math3DHelper.translate(mStrips, mTranslateX, mTranslateY, 3); 103 104 mTriangle.drawTriangles(mStrips, mShadowConfig.getShadowStrength()); 105 } catch (IndexOutOfBoundsException|ArithmeticException mathError) { 106 Bridge.getLog().warning(LayoutLog.TAG_INFO, "Arithmetic error while drawing " + 107 "spot shadow", 108 mathError); 109 } catch (Exception ex) { 110 Bridge.getLog().warning(LayoutLog.TAG_INFO, "Error while drawing shadow", 111 ex); 112 } 113 } 114 getTranslateX()115 public float getTranslateX() { 116 return mTranslateX; 117 } 118 getTranslateY()119 public float getTranslateY() { 120 return mTranslateY; 121 } 122 clear()123 public void clear() { 124 mTriangle.clear(); 125 } 126 127 /** 128 * @return true if generated shadow poly is valid. False otherwise. 129 */ validate()130 public boolean validate() { 131 return mStrips != null && mStrips.length >= 9; 132 } 133 134 /** 135 * @return the bitmap of shadow after it's populated 136 */ getBitmap()137 public Bitmap getBitmap() { 138 return mTriangle.getImage(); 139 } 140 141 @VisibleForTesting getStrips()142 public float[] getStrips() { 143 return mStrips; 144 } 145 146 @VisibleForTesting updateLightSource(float x, float y)147 public void updateLightSource(float x, float y) { 148 mShadowConfig.setLightCoord(x, y); 149 } 150 } 151