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.graphics; 18 19 import android.content.Context; 20 import android.graphics.Bitmap; 21 import android.graphics.Bitmap.Config; 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 import android.support.v4.graphics.ColorUtils; 31 32 import com.android.launcher3.LauncherAppState; 33 34 /** 35 * Utility class to add shadows to bitmaps. 36 */ 37 public class ShadowGenerator { 38 39 // Percent of actual icon size 40 private static final float HALF_DISTANCE = 0.5f; 41 public static final float BLUR_FACTOR = 0.5f/48; 42 43 // Percent of actual icon size 44 public static final float KEY_SHADOW_DISTANCE = 1f/48; 45 private static final int KEY_SHADOW_ALPHA = 61; 46 47 private static final int AMBIENT_SHADOW_ALPHA = 30; 48 49 private final int mIconSize; 50 51 private final Paint mBlurPaint; 52 private final Paint mDrawPaint; 53 private final BlurMaskFilter mDefaultBlurMaskFilter; 54 55 public ShadowGenerator(Context context) { 56 mIconSize = LauncherAppState.getIDP(context).iconBitmapSize; 57 mBlurPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); 58 mDrawPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); 59 mDefaultBlurMaskFilter = new BlurMaskFilter(mIconSize * BLUR_FACTOR, Blur.NORMAL); 60 } 61 62 public synchronized void recreateIcon(Bitmap icon, Canvas out) { 63 recreateIcon(icon, mDefaultBlurMaskFilter, AMBIENT_SHADOW_ALPHA, KEY_SHADOW_ALPHA, out); 64 } 65 66 public synchronized void recreateIcon(Bitmap icon, BlurMaskFilter blurMaskFilter, 67 int ambientAlpha, int keyAlpha, Canvas out) { 68 int[] offset = new int[2]; 69 mBlurPaint.setMaskFilter(blurMaskFilter); 70 Bitmap shadow = icon.extractAlpha(mBlurPaint, offset); 71 72 // Draw ambient shadow 73 mDrawPaint.setAlpha(ambientAlpha); 74 out.drawBitmap(shadow, offset[0], offset[1], mDrawPaint); 75 76 // Draw key shadow 77 mDrawPaint.setAlpha(keyAlpha); 78 out.drawBitmap(shadow, offset[0], offset[1] + KEY_SHADOW_DISTANCE * mIconSize, mDrawPaint); 79 80 // Draw the icon 81 mDrawPaint.setAlpha(255); 82 out.drawBitmap(icon, 0, 0, mDrawPaint); 83 } 84 85 /** 86 * Returns the minimum amount by which an icon with {@param bounds} should be scaled 87 * so that the shadows do not get clipped. 88 */ 89 public static float getScaleForBounds(RectF bounds) { 90 float scale = 1; 91 92 // For top, left & right, we need same space. 93 float minSide = Math.min(Math.min(bounds.left, bounds.right), bounds.top); 94 if (minSide < BLUR_FACTOR) { 95 scale = (HALF_DISTANCE - BLUR_FACTOR) / (HALF_DISTANCE - minSide); 96 } 97 98 float bottomSpace = BLUR_FACTOR + KEY_SHADOW_DISTANCE; 99 if (bounds.bottom < bottomSpace) { 100 scale = Math.min(scale, (HALF_DISTANCE - bottomSpace) / (HALF_DISTANCE - bounds.bottom)); 101 } 102 return scale; 103 } 104 105 public static class Builder { 106 107 public final RectF bounds = new RectF(); 108 public final int color; 109 110 public int ambientShadowAlpha = AMBIENT_SHADOW_ALPHA; 111 112 public float shadowBlur; 113 114 public float keyShadowDistance; 115 public int keyShadowAlpha = KEY_SHADOW_ALPHA; 116 public float radius; 117 118 public Builder(int color) { 119 this.color = color; 120 } 121 122 public Builder setupBlurForSize(int height) { 123 shadowBlur = height * 1f / 32; 124 keyShadowDistance = height * 1f / 16; 125 return this; 126 } 127 128 public Bitmap createPill(int width, int height) { 129 radius = height / 2; 130 131 int centerX = Math.round(width / 2 + 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 / 2, center - height / 2); 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 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 ColorUtils.setAlphaComponent(Color.BLACK, keyShadowAlpha)); 150 c.drawRoundRect(bounds, radius, radius, p); 151 152 // Ambient shadow 153 p.setShadowLayer(shadowBlur, 0, 0, 154 ColorUtils.setAlphaComponent(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