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.icons; 18 19 import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound; 20 21 import android.graphics.Bitmap; 22 import android.graphics.Bitmap.Config; 23 import android.graphics.BlurMaskFilter; 24 import android.graphics.BlurMaskFilter.Blur; 25 import android.graphics.Canvas; 26 import android.graphics.Color; 27 import android.graphics.Paint; 28 import android.graphics.PorterDuff; 29 import android.graphics.PorterDuffXfermode; 30 import android.graphics.RectF; 31 32 /** 33 * Utility class to add shadows to bitmaps. 34 */ 35 public class ShadowGenerator { 36 public static final float BLUR_FACTOR = 0.5f/48; 37 38 // Percent of actual icon size 39 public static final float KEY_SHADOW_DISTANCE = 1f/48; 40 private static final int KEY_SHADOW_ALPHA = 61; 41 // Percent of actual icon size 42 private static final float HALF_DISTANCE = 0.5f; 43 private static final int AMBIENT_SHADOW_ALPHA = 30; 44 45 private final int mIconSize; 46 47 private final Paint mBlurPaint; 48 private final Paint mDrawPaint; 49 private final BlurMaskFilter mDefaultBlurMaskFilter; 50 ShadowGenerator(int iconSize)51 public ShadowGenerator(int iconSize) { 52 mIconSize = iconSize; 53 mBlurPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); 54 mDrawPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); 55 mDefaultBlurMaskFilter = new BlurMaskFilter(mIconSize * BLUR_FACTOR, Blur.NORMAL); 56 } 57 recreateIcon(Bitmap icon, Canvas out)58 public synchronized void recreateIcon(Bitmap icon, Canvas out) { 59 recreateIcon(icon, mDefaultBlurMaskFilter, AMBIENT_SHADOW_ALPHA, KEY_SHADOW_ALPHA, out); 60 } 61 recreateIcon(Bitmap icon, BlurMaskFilter blurMaskFilter, int ambientAlpha, int keyAlpha, Canvas out)62 public synchronized void recreateIcon(Bitmap icon, BlurMaskFilter blurMaskFilter, 63 int ambientAlpha, int keyAlpha, Canvas out) { 64 int[] offset = new int[2]; 65 mBlurPaint.setMaskFilter(blurMaskFilter); 66 Bitmap shadow = icon.extractAlpha(mBlurPaint, offset); 67 68 // Draw ambient shadow 69 mDrawPaint.setAlpha(ambientAlpha); 70 out.drawBitmap(shadow, offset[0], offset[1], mDrawPaint); 71 72 // Draw key shadow 73 mDrawPaint.setAlpha(keyAlpha); 74 out.drawBitmap(shadow, offset[0], offset[1] + KEY_SHADOW_DISTANCE * mIconSize, mDrawPaint); 75 76 // Draw the icon 77 mDrawPaint.setAlpha(255); 78 out.drawBitmap(icon, 0, 0, mDrawPaint); 79 } 80 81 /** 82 * Returns the minimum amount by which an icon with {@param bounds} should be scaled 83 * so that the shadows do not get clipped. 84 */ getScaleForBounds(RectF bounds)85 public static float getScaleForBounds(RectF bounds) { 86 float scale = 1; 87 88 // For top, left & right, we need same space. 89 float minSide = Math.min(Math.min(bounds.left, bounds.right), bounds.top); 90 if (minSide < BLUR_FACTOR) { 91 scale = (HALF_DISTANCE - BLUR_FACTOR) / (HALF_DISTANCE - minSide); 92 } 93 94 float bottomSpace = BLUR_FACTOR + KEY_SHADOW_DISTANCE; 95 if (bounds.bottom < bottomSpace) { 96 scale = Math.min(scale, (HALF_DISTANCE - bottomSpace) / (HALF_DISTANCE - bounds.bottom)); 97 } 98 return scale; 99 } 100 101 public static class Builder { 102 103 public final RectF bounds = new RectF(); 104 public final int color; 105 106 public int ambientShadowAlpha = AMBIENT_SHADOW_ALPHA; 107 108 public float shadowBlur; 109 110 public float keyShadowDistance; 111 public int keyShadowAlpha = KEY_SHADOW_ALPHA; 112 public float radius; 113 Builder(int color)114 public Builder(int color) { 115 this.color = color; 116 } 117 setupBlurForSize(int height)118 public Builder setupBlurForSize(int height) { 119 shadowBlur = height * 1f / 24; 120 keyShadowDistance = height * 1f / 16; 121 return this; 122 } 123 createPill(int width, int height)124 public Bitmap createPill(int width, int height) { 125 return createPill(width, height, height / 2f); 126 } 127 createPill(int width, int height, float r)128 public Bitmap createPill(int width, int height, float r) { 129 radius = r; 130 131 int centerX = Math.round(width / 2f + shadowBlur); 132 int centerY = Math.round(radius + shadowBlur + keyShadowDistance); 133 int center = Math.max(centerX, centerY); 134 bounds.set(0, 0, width, height); 135 bounds.offsetTo(center - width / 2f, center - height / 2f); 136 137 int size = center * 2; 138 Bitmap result = Bitmap.createBitmap(size, size, Config.ARGB_8888); 139 drawShadow(new Canvas(result)); 140 return result; 141 } 142 drawShadow(Canvas c)143 public void drawShadow(Canvas c) { 144 Paint p = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); 145 p.setColor(color); 146 147 // Key shadow 148 p.setShadowLayer(shadowBlur, 0, keyShadowDistance, 149 setColorAlphaBound(Color.BLACK, keyShadowAlpha)); 150 c.drawRoundRect(bounds, radius, radius, p); 151 152 // Ambient shadow 153 p.setShadowLayer(shadowBlur, 0, 0, 154 setColorAlphaBound(Color.BLACK, ambientShadowAlpha)); 155 c.drawRoundRect(bounds, radius, radius, p); 156 157 if (Color.alpha(color) < 255) { 158 // Clear any content inside the pill-rect for translucent fill. 159 p.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); 160 p.clearShadowLayer(); 161 p.setColor(Color.BLACK); 162 c.drawRoundRect(bounds, radius, radius, p); 163 164 p.setXfermode(null); 165 p.setColor(color); 166 c.drawRoundRect(bounds, radius, radius, p); 167 } 168 } 169 } 170 } 171