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.Color; 26 import android.graphics.Paint; 27 import android.graphics.RectF; 28 import android.support.v4.graphics.ColorUtils; 29 30 import com.android.launcher3.LauncherAppState; 31 32 /** 33 * Utility class to add shadows to bitmaps. 34 */ 35 public class ShadowGenerator { 36 37 // Percent of actual icon size 38 private static final float HALF_DISTANCE = 0.5f; 39 public static final float BLUR_FACTOR = 0.5f/48; 40 41 // Percent of actual icon size 42 public static final float KEY_SHADOW_DISTANCE = 1f/48; 43 private static final int KEY_SHADOW_ALPHA = 61; 44 45 private static final int AMBIENT_SHADOW_ALPHA = 30; 46 47 private static final Object LOCK = new Object(); 48 // Singleton object guarded by {@link #LOCK} 49 private static ShadowGenerator sShadowGenerator; 50 51 private final int mIconSize; 52 53 private final Canvas mCanvas; 54 private final Paint mBlurPaint; 55 private final Paint mDrawPaint; 56 private final BlurMaskFilter mDefaultBlurMaskFilter; 57 ShadowGenerator(Context context)58 private ShadowGenerator(Context context) { 59 mIconSize = LauncherAppState.getIDP(context).iconBitmapSize; 60 mCanvas = new Canvas(); 61 mBlurPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); 62 mDrawPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); 63 mDefaultBlurMaskFilter = new BlurMaskFilter(mIconSize * BLUR_FACTOR, Blur.NORMAL); 64 } 65 recreateIcon(Bitmap icon)66 public synchronized Bitmap recreateIcon(Bitmap icon) { 67 return recreateIcon(icon, true, mDefaultBlurMaskFilter, AMBIENT_SHADOW_ALPHA, 68 KEY_SHADOW_ALPHA); 69 } 70 recreateIcon(Bitmap icon, boolean resize, BlurMaskFilter blurMaskFilter, int ambientAlpha, int keyAlpha)71 public synchronized Bitmap recreateIcon(Bitmap icon, boolean resize, 72 BlurMaskFilter blurMaskFilter, int ambientAlpha, int keyAlpha) { 73 int width = resize ? mIconSize : icon.getWidth(); 74 int height = resize ? mIconSize : icon.getHeight(); 75 int[] offset = new int[2]; 76 77 mBlurPaint.setMaskFilter(blurMaskFilter); 78 Bitmap shadow = icon.extractAlpha(mBlurPaint, offset); 79 Bitmap result = Bitmap.createBitmap(width, height, Config.ARGB_8888); 80 mCanvas.setBitmap(result); 81 82 // Draw ambient shadow 83 mDrawPaint.setAlpha(ambientAlpha); 84 mCanvas.drawBitmap(shadow, offset[0], offset[1], mDrawPaint); 85 86 // Draw key shadow 87 mDrawPaint.setAlpha(keyAlpha); 88 mCanvas.drawBitmap(shadow, offset[0], offset[1] + KEY_SHADOW_DISTANCE * mIconSize, mDrawPaint); 89 90 // Draw the icon 91 mDrawPaint.setAlpha(255); 92 mCanvas.drawBitmap(icon, 0, 0, mDrawPaint); 93 94 mCanvas.setBitmap(null); 95 return result; 96 } 97 getInstance(Context context)98 public static ShadowGenerator getInstance(Context context) { 99 // TODO: This currently fails as the system default icon also needs a shadow as it 100 // uses adaptive icon. 101 // Preconditions.assertNonUiThread(); 102 synchronized (LOCK) { 103 if (sShadowGenerator == null) { 104 sShadowGenerator = new ShadowGenerator(context); 105 } 106 } 107 return sShadowGenerator; 108 } 109 110 /** 111 * Returns the minimum amount by which an icon with {@param bounds} should be scaled 112 * so that the shadows do not get clipped. 113 */ getScaleForBounds(RectF bounds)114 public static float getScaleForBounds(RectF bounds) { 115 float scale = 1; 116 117 // For top, left & right, we need same space. 118 float minSide = Math.min(Math.min(bounds.left, bounds.right), bounds.top); 119 if (minSide < BLUR_FACTOR) { 120 scale = (HALF_DISTANCE - BLUR_FACTOR) / (HALF_DISTANCE - minSide); 121 } 122 123 float bottomSpace = BLUR_FACTOR + KEY_SHADOW_DISTANCE; 124 if (bounds.bottom < bottomSpace) { 125 scale = Math.min(scale, (HALF_DISTANCE - bottomSpace) / (HALF_DISTANCE - bounds.bottom)); 126 } 127 return scale; 128 } 129 130 public static class Builder { 131 132 public final RectF bounds = new RectF(); 133 public final int color; 134 135 public int ambientShadowAlpha = AMBIENT_SHADOW_ALPHA; 136 137 public float shadowBlur; 138 139 public float keyShadowDistance; 140 public int keyShadowAlpha = KEY_SHADOW_ALPHA; 141 public float radius; 142 Builder(int color)143 public Builder(int color) { 144 this.color = color; 145 } 146 setupBlurForSize(int height)147 public Builder setupBlurForSize(int height) { 148 shadowBlur = height * 1f / 32; 149 keyShadowDistance = height * 1f / 16; 150 return this; 151 } 152 createPill(int width, int height)153 public Bitmap createPill(int width, int height) { 154 radius = height / 2; 155 156 int centerX = Math.round(width / 2 + shadowBlur); 157 int centerY = Math.round(radius + shadowBlur + keyShadowDistance); 158 int center = Math.max(centerX, centerY); 159 bounds.set(0, 0, width, height); 160 bounds.offsetTo(center - width / 2, center - height / 2); 161 162 int size = center * 2; 163 Bitmap result = Bitmap.createBitmap(size, size, Config.ARGB_8888); 164 drawShadow(new Canvas(result)); 165 return result; 166 } 167 drawShadow(Canvas c)168 public void drawShadow(Canvas c) { 169 Paint p = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); 170 p.setColor(color); 171 172 // Key shadow 173 p.setShadowLayer(shadowBlur, 0, keyShadowDistance, 174 ColorUtils.setAlphaComponent(Color.BLACK, keyShadowAlpha)); 175 c.drawRoundRect(bounds, radius, radius, p); 176 177 // Ambient shadow 178 p.setShadowLayer(shadowBlur, 0, 0, 179 ColorUtils.setAlphaComponent(Color.BLACK, ambientShadowAlpha)); 180 c.drawRoundRect(bounds, radius, radius, p); 181 } 182 } 183 } 184