Home | History | Annotate | Download | only in graphics
      1 /*
      2  * Copyright (C) 2016 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 com.android.launcher3.graphics;
     18 
     19 import android.content.Context;
     20 import android.graphics.Bitmap;
     21 import android.graphics.Bitmap.Config;
     22 import android.graphics.BlurMaskFilter;
     23 import android.graphics.BlurMaskFilter.Blur;
     24 import android.graphics.Canvas;
     25 import android.graphics.Paint;
     26 import android.graphics.RectF;
     27 
     28 import com.android.launcher3.LauncherAppState;
     29 import com.android.launcher3.util.Preconditions;
     30 
     31 /**
     32  * Utility class to add shadows to bitmaps.
     33  */
     34 public class ShadowGenerator {
     35 
     36     // Percent of actual icon size
     37     private static final float HALF_DISTANCE = 0.5f;
     38     public static final float BLUR_FACTOR = 0.5f/48;
     39 
     40     // Percent of actual icon size
     41     private static final float KEY_SHADOW_DISTANCE = 1f/48;
     42     public static final int KEY_SHADOW_ALPHA = 61;
     43 
     44     public static final int AMBIENT_SHADOW_ALPHA = 30;
     45 
     46     private static final Object LOCK = new Object();
     47     // Singleton object guarded by {@link #LOCK}
     48     private static ShadowGenerator sShadowGenerator;
     49 
     50     private final int mIconSize;
     51 
     52     private final Canvas mCanvas;
     53     private final Paint mBlurPaint;
     54     private final Paint mDrawPaint;
     55 
     56     private ShadowGenerator(Context context) {
     57         mIconSize = LauncherAppState.getIDP(context).iconBitmapSize;
     58         mCanvas = new Canvas();
     59         mBlurPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
     60         mBlurPaint.setMaskFilter(new BlurMaskFilter(mIconSize * BLUR_FACTOR, Blur.NORMAL));
     61         mDrawPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
     62     }
     63 
     64     public synchronized Bitmap recreateIcon(Bitmap icon) {
     65         int[] offset = new int[2];
     66         Bitmap shadow = icon.extractAlpha(mBlurPaint, offset);
     67         Bitmap result = Bitmap.createBitmap(mIconSize, mIconSize, Config.ARGB_8888);
     68         mCanvas.setBitmap(result);
     69 
     70         // Draw ambient shadow
     71         mDrawPaint.setAlpha(AMBIENT_SHADOW_ALPHA);
     72         mCanvas.drawBitmap(shadow, offset[0], offset[1], mDrawPaint);
     73 
     74         // Draw key shadow
     75         mDrawPaint.setAlpha(KEY_SHADOW_ALPHA);
     76         mCanvas.drawBitmap(shadow, offset[0], offset[1] + KEY_SHADOW_DISTANCE * mIconSize, mDrawPaint);
     77 
     78         // Draw the icon
     79         mDrawPaint.setAlpha(255);
     80         mCanvas.drawBitmap(icon, 0, 0, mDrawPaint);
     81 
     82         mCanvas.setBitmap(null);
     83         return result;
     84     }
     85 
     86     public static Bitmap createPillWithShadow(int rectColor, int width, int height) {
     87 
     88         float shadowRadius = height * 1f / 32;
     89         float shadowYOffset = height * 1f / 16;
     90 
     91         int radius = height / 2;
     92 
     93         Canvas canvas = new Canvas();
     94         Paint blurPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
     95         blurPaint.setMaskFilter(new BlurMaskFilter(shadowRadius, Blur.NORMAL));
     96 
     97         int centerX = Math.round(width / 2 + shadowRadius);
     98         int centerY = Math.round(radius + shadowRadius + shadowYOffset);
     99         int center = Math.max(centerX, centerY);
    100         int size = center * 2;
    101         Bitmap result = Bitmap.createBitmap(size, size, Config.ARGB_8888);
    102         canvas.setBitmap(result);
    103 
    104         int left = center - width / 2;
    105         int top = center - height / 2;
    106         int right = center + width / 2;
    107         int bottom = center + height / 2;
    108 
    109         // Draw ambient shadow, center aligned within size
    110         blurPaint.setAlpha(AMBIENT_SHADOW_ALPHA);
    111         canvas.drawRoundRect(left, top, right, bottom, radius, radius, blurPaint);
    112 
    113         // Draw key shadow, bottom aligned within size
    114         blurPaint.setAlpha(KEY_SHADOW_ALPHA);
    115         canvas.drawRoundRect(left, top + shadowYOffset, right, bottom + shadowYOffset,
    116                 radius, radius, blurPaint);
    117 
    118         // Draw the circle
    119         Paint drawPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
    120         drawPaint.setColor(rectColor);
    121         canvas.drawRoundRect(left, top, right, bottom, radius, radius, drawPaint);
    122 
    123         return result;
    124     }
    125 
    126     public static ShadowGenerator getInstance(Context context) {
    127         Preconditions.assertNonUiThread();
    128         synchronized (LOCK) {
    129             if (sShadowGenerator == null) {
    130                 sShadowGenerator = new ShadowGenerator(context);
    131             }
    132         }
    133         return sShadowGenerator;
    134     }
    135 
    136     /**
    137      * Returns the minimum amount by which an icon with {@param bounds} should be scaled
    138      * so that the shadows do not get clipped.
    139      */
    140     public static float getScaleForBounds(RectF bounds) {
    141         float scale = 1;
    142 
    143         // For top, left & right, we need same space.
    144         float minSide = Math.min(Math.min(bounds.left, bounds.right), bounds.top);
    145         if (minSide < BLUR_FACTOR) {
    146             scale = (HALF_DISTANCE - BLUR_FACTOR) / (HALF_DISTANCE - minSide);
    147         }
    148 
    149         float bottomSpace = BLUR_FACTOR + KEY_SHADOW_DISTANCE;
    150         if (bounds.bottom < bottomSpace) {
    151             scale = Math.min(scale, (HALF_DISTANCE - bottomSpace) / (HALF_DISTANCE - bounds.bottom));
    152         }
    153         return scale;
    154     }
    155 }
    156