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