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