Home | History | Annotate | Download | only in shadow
      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 
     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      */
     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 
    115     public float getTranslateX() {
    116         return mTranslateX;
    117     }
    118 
    119     public float getTranslateY() {
    120         return mTranslateY;
    121     }
    122 
    123     public void clear() {
    124         mTriangle.clear();
    125     }
    126 
    127     /**
    128      * @return true if generated shadow poly is valid. False otherwise.
    129      */
    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      */
    137     public Bitmap getBitmap() {
    138         return mTriangle.getImage();
    139     }
    140 
    141     @VisibleForTesting
    142     public float[] getStrips() {
    143         return mStrips;
    144     }
    145 
    146     @VisibleForTesting
    147     public void updateLightSource(float x, float y) {
    148         mShadowConfig.setLightCoord(x, y);
    149     }
    150 }
    151