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 // Amount by which an icon should be scaled down to make room for shadows. 49 // We are ignoring KEY_SHADOW_DISTANCE because regular icons also ignore this: b/298203449 50 public static final float ICON_SCALE_FOR_SHADOWS = 51 (HALF_DISTANCE - BLUR_FACTOR) / HALF_DISTANCE; 52 53 private final int mIconSize; 54 55 private final Paint mBlurPaint; 56 private final Paint mDrawPaint; 57 private final BlurMaskFilter mDefaultBlurMaskFilter; 58 ShadowGenerator(int iconSize)59 public ShadowGenerator(int iconSize) { 60 mIconSize = iconSize; 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 drawShadow(Bitmap icon, Canvas out)66 public synchronized void drawShadow(Bitmap icon, Canvas out) { 67 if (ENABLE_SHADOWS) { 68 int[] offset = new int[2]; 69 mBlurPaint.setMaskFilter(mDefaultBlurMaskFilter); 70 Bitmap shadow = icon.extractAlpha(mBlurPaint, offset); 71 72 // Draw ambient shadow 73 mDrawPaint.setAlpha(AMBIENT_SHADOW_ALPHA); 74 out.drawBitmap(shadow, offset[0], offset[1], mDrawPaint); 75 76 // Draw key shadow 77 mDrawPaint.setAlpha(KEY_SHADOW_ALPHA); 78 out.drawBitmap(shadow, offset[0], offset[1] + KEY_SHADOW_DISTANCE * mIconSize, 79 mDrawPaint); 80 } 81 } 82 83 /** package private **/ addPathShadow(Path path, Canvas out)84 void addPathShadow(Path path, Canvas out) { 85 if (ENABLE_SHADOWS) { 86 mDrawPaint.setMaskFilter(mDefaultBlurMaskFilter); 87 88 // Draw ambient shadow 89 mDrawPaint.setAlpha(AMBIENT_SHADOW_ALPHA); 90 out.drawPath(path, mDrawPaint); 91 92 // Draw key shadow 93 int save = out.save(); 94 mDrawPaint.setAlpha(KEY_SHADOW_ALPHA); 95 out.translate(0, KEY_SHADOW_DISTANCE * mIconSize); 96 out.drawPath(path, mDrawPaint); 97 out.restoreToCount(save); 98 99 mDrawPaint.setMaskFilter(null); 100 } 101 } 102 103 public static class Builder { 104 105 public final RectF bounds = new RectF(); 106 public final int color; 107 108 public int ambientShadowAlpha = AMBIENT_SHADOW_ALPHA; 109 110 public float shadowBlur; 111 112 public float keyShadowDistance; 113 public int keyShadowAlpha = KEY_SHADOW_ALPHA; 114 public float radius; 115 Builder(int color)116 public Builder(int color) { 117 this.color = color; 118 } 119 setupBlurForSize(int height)120 public Builder setupBlurForSize(int height) { 121 if (ENABLE_SHADOWS) { 122 shadowBlur = height * 1f / 24; 123 keyShadowDistance = height * 1f / 16; 124 } else { 125 shadowBlur = 0; 126 keyShadowDistance = 0; 127 } 128 return this; 129 } 130 createPill(int width, int height)131 public Bitmap createPill(int width, int height) { 132 return createPill(width, height, height / 2f); 133 } 134 createPill(int width, int height, float r)135 public Bitmap createPill(int width, int height, float r) { 136 radius = r; 137 138 int centerX = Math.round(width / 2f + shadowBlur); 139 int centerY = Math.round(radius + shadowBlur + keyShadowDistance); 140 int center = Math.max(centerX, centerY); 141 bounds.set(0, 0, width, height); 142 bounds.offsetTo(center - width / 2f, center - height / 2f); 143 144 int size = center * 2; 145 return BitmapRenderer.createHardwareBitmap(size, size, this::drawShadow); 146 } 147 drawShadow(Canvas c)148 public void drawShadow(Canvas c) { 149 Paint p = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); 150 p.setColor(color); 151 152 if (ENABLE_SHADOWS) { 153 // Key shadow 154 p.setShadowLayer(shadowBlur, 0, keyShadowDistance, 155 setColorAlphaBound(Color.BLACK, keyShadowAlpha)); 156 c.drawRoundRect(bounds, radius, radius, p); 157 158 // Ambient shadow 159 p.setShadowLayer(shadowBlur, 0, 0, 160 setColorAlphaBound(Color.BLACK, ambientShadowAlpha)); 161 c.drawRoundRect(bounds, radius, radius, p); 162 } 163 164 if (Color.alpha(color) < 255) { 165 // Clear any content inside the pill-rect for translucent fill. 166 p.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); 167 p.clearShadowLayer(); 168 p.setColor(Color.BLACK); 169 c.drawRoundRect(bounds, radius, radius, p); 170 171 p.setXfermode(null); 172 p.setColor(color); 173 c.drawRoundRect(bounds, radius, radius, p); 174 } 175 } 176 } 177 } 178