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