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