1 /* 2 * Copyright (C) 2008 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.launcher2; 18 19 import android.graphics.Bitmap; 20 import android.graphics.BlurMaskFilter; 21 import android.graphics.Canvas; 22 import android.graphics.MaskFilter; 23 import android.graphics.Paint; 24 import android.graphics.PorterDuff; 25 import android.graphics.PorterDuffXfermode; 26 import android.graphics.TableMaskFilter; 27 28 public class HolographicOutlineHelper { 29 private final Paint mHolographicPaint = new Paint(); 30 private final Paint mBlurPaint = new Paint(); 31 private final Paint mErasePaint = new Paint(); 32 private final Paint mAlphaClipPaint = new Paint(); 33 34 public static final int MAX_OUTER_BLUR_RADIUS; 35 public static final int MIN_OUTER_BLUR_RADIUS; 36 37 private static final BlurMaskFilter sExtraThickOuterBlurMaskFilter; 38 private static final BlurMaskFilter sThickOuterBlurMaskFilter; 39 private static final BlurMaskFilter sMediumOuterBlurMaskFilter; 40 private static final BlurMaskFilter sThinOuterBlurMaskFilter; 41 private static final BlurMaskFilter sThickInnerBlurMaskFilter; 42 private static final BlurMaskFilter sExtraThickInnerBlurMaskFilter; 43 private static final BlurMaskFilter sMediumInnerBlurMaskFilter; 44 45 private static final int THICK = 0; 46 private static final int MEDIUM = 1; 47 private static final int EXTRA_THICK = 2; 48 49 static { 50 final float scale = LauncherApplication.getScreenDensity(); 51 52 MIN_OUTER_BLUR_RADIUS = (int) (scale * 1.0f); 53 MAX_OUTER_BLUR_RADIUS = (int) (scale * 12.0f); 54 55 sExtraThickOuterBlurMaskFilter = new BlurMaskFilter(scale * 12.0f, BlurMaskFilter.Blur.OUTER); 56 sThickOuterBlurMaskFilter = new BlurMaskFilter(scale * 6.0f, BlurMaskFilter.Blur.OUTER); 57 sMediumOuterBlurMaskFilter = new BlurMaskFilter(scale * 2.0f, BlurMaskFilter.Blur.OUTER); 58 sThinOuterBlurMaskFilter = new BlurMaskFilter(scale * 1.0f, BlurMaskFilter.Blur.OUTER); 59 sExtraThickInnerBlurMaskFilter = new BlurMaskFilter(scale * 6.0f, BlurMaskFilter.Blur.NORMAL); 60 sThickInnerBlurMaskFilter = new BlurMaskFilter(scale * 4.0f, BlurMaskFilter.Blur.NORMAL); 61 sMediumInnerBlurMaskFilter = new BlurMaskFilter(scale * 2.0f, BlurMaskFilter.Blur.NORMAL); 62 } 63 64 private static final MaskFilter sCoarseClipTable = TableMaskFilter.CreateClipTable(0, 200); 65 66 private int[] mTempOffset = new int[2]; 67 68 HolographicOutlineHelper() { 69 mHolographicPaint.setFilterBitmap(true); 70 mHolographicPaint.setAntiAlias(true); 71 mBlurPaint.setFilterBitmap(true); 72 mBlurPaint.setAntiAlias(true); 73 mErasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT)); 74 mErasePaint.setFilterBitmap(true); 75 mErasePaint.setAntiAlias(true); 76 MaskFilter alphaClipTable = TableMaskFilter.CreateClipTable(180, 255); 77 mAlphaClipPaint.setMaskFilter(alphaClipTable); 78 } 79 80 /** 81 * Returns the interpolated holographic highlight alpha for the effect we want when scrolling 82 * pages. 83 */ 84 public static float highlightAlphaInterpolator(float r) { 85 float maxAlpha = 0.6f; 86 return (float) Math.pow(maxAlpha * (1.0f - r), 1.5f); 87 } 88 89 /** 90 * Returns the interpolated view alpha for the effect we want when scrolling pages. 91 */ 92 public static float viewAlphaInterpolator(float r) { 93 final float pivot = 0.95f; 94 if (r < pivot) { 95 return (float) Math.pow(r / pivot, 1.5f); 96 } else { 97 return 1.0f; 98 } 99 } 100 101 /** 102 * Apply an outer blur to the given bitmap. 103 * You should use OUTER_BLUR_RADIUS to ensure that the bitmap is big enough to draw 104 * the blur without clipping. 105 */ 106 void applyOuterBlur(Bitmap bitmap, Canvas canvas, int color) { 107 mBlurPaint.setMaskFilter(sThickOuterBlurMaskFilter); 108 Bitmap glow = bitmap.extractAlpha(mBlurPaint, mTempOffset); 109 110 // Use the clip table to make the glow heavier closer to the outline 111 mHolographicPaint.setMaskFilter(sCoarseClipTable); 112 mHolographicPaint.setAlpha(150); 113 mHolographicPaint.setColor(color); 114 115 canvas.drawBitmap(glow, mTempOffset[0], mTempOffset[1], mHolographicPaint); 116 glow.recycle(); 117 } 118 119 /** 120 * Applies a more expensive and accurate outline to whatever is currently drawn in a specified 121 * bitmap. 122 */ 123 void applyExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas, int color, 124 int outlineColor, int thickness) { 125 applyExpensiveOutlineWithBlur(srcDst, srcDstCanvas, color, outlineColor, mAlphaClipPaint, 126 thickness); 127 } 128 void applyExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas, int color, 129 int outlineColor, Paint alphaClipPaint, int thickness) { 130 131 // We start by removing most of the alpha channel so as to ignore shadows, and 132 // other types of partial transparency when defining the shape of the object 133 if (alphaClipPaint == null) { 134 alphaClipPaint = mAlphaClipPaint; 135 } 136 Bitmap glowShape = srcDst.extractAlpha(alphaClipPaint, mTempOffset); 137 138 // calculate the outer blur first 139 BlurMaskFilter outerBlurMaskFilter; 140 switch (thickness) { 141 case EXTRA_THICK: 142 outerBlurMaskFilter = sExtraThickOuterBlurMaskFilter; 143 break; 144 case THICK: 145 outerBlurMaskFilter = sThickOuterBlurMaskFilter; 146 break; 147 case MEDIUM: 148 outerBlurMaskFilter = sMediumOuterBlurMaskFilter; 149 break; 150 default: 151 throw new RuntimeException("Invalid blur thickness"); 152 } 153 mBlurPaint.setMaskFilter(outerBlurMaskFilter); 154 int[] outerBlurOffset = new int[2]; 155 Bitmap thickOuterBlur = glowShape.extractAlpha(mBlurPaint, outerBlurOffset); 156 if (thickness == EXTRA_THICK) { 157 mBlurPaint.setMaskFilter(sMediumOuterBlurMaskFilter); 158 } else { 159 mBlurPaint.setMaskFilter(sThinOuterBlurMaskFilter); 160 } 161 162 int[] brightOutlineOffset = new int[2]; 163 Bitmap brightOutline = glowShape.extractAlpha(mBlurPaint, brightOutlineOffset); 164 165 // calculate the inner blur 166 srcDstCanvas.setBitmap(glowShape); 167 srcDstCanvas.drawColor(0xFF000000, PorterDuff.Mode.SRC_OUT); 168 BlurMaskFilter innerBlurMaskFilter; 169 switch (thickness) { 170 case EXTRA_THICK: 171 innerBlurMaskFilter = sExtraThickInnerBlurMaskFilter; 172 break; 173 case THICK: 174 innerBlurMaskFilter = sThickInnerBlurMaskFilter; 175 break; 176 case MEDIUM: 177 innerBlurMaskFilter = sMediumInnerBlurMaskFilter; 178 break; 179 default: 180 throw new RuntimeException("Invalid blur thickness"); 181 } 182 mBlurPaint.setMaskFilter(innerBlurMaskFilter); 183 int[] thickInnerBlurOffset = new int[2]; 184 Bitmap thickInnerBlur = glowShape.extractAlpha(mBlurPaint, thickInnerBlurOffset); 185 186 // mask out the inner blur 187 srcDstCanvas.setBitmap(thickInnerBlur); 188 srcDstCanvas.drawBitmap(glowShape, -thickInnerBlurOffset[0], 189 -thickInnerBlurOffset[1], mErasePaint); 190 srcDstCanvas.drawRect(0, 0, -thickInnerBlurOffset[0], thickInnerBlur.getHeight(), 191 mErasePaint); 192 srcDstCanvas.drawRect(0, 0, thickInnerBlur.getWidth(), -thickInnerBlurOffset[1], 193 mErasePaint); 194 195 // draw the inner and outer blur 196 srcDstCanvas.setBitmap(srcDst); 197 srcDstCanvas.drawColor(0, PorterDuff.Mode.CLEAR); 198 mHolographicPaint.setColor(color); 199 srcDstCanvas.drawBitmap(thickInnerBlur, thickInnerBlurOffset[0], thickInnerBlurOffset[1], 200 mHolographicPaint); 201 srcDstCanvas.drawBitmap(thickOuterBlur, outerBlurOffset[0], outerBlurOffset[1], 202 mHolographicPaint); 203 204 // draw the bright outline 205 mHolographicPaint.setColor(outlineColor); 206 srcDstCanvas.drawBitmap(brightOutline, brightOutlineOffset[0], brightOutlineOffset[1], 207 mHolographicPaint); 208 209 // cleanup 210 srcDstCanvas.setBitmap(null); 211 brightOutline.recycle(); 212 thickOuterBlur.recycle(); 213 thickInnerBlur.recycle(); 214 glowShape.recycle(); 215 } 216 217 void applyExtraThickExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas, int color, 218 int outlineColor) { 219 applyExpensiveOutlineWithBlur(srcDst, srcDstCanvas, color, outlineColor, EXTRA_THICK); 220 } 221 222 void applyThickExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas, int color, 223 int outlineColor) { 224 applyExpensiveOutlineWithBlur(srcDst, srcDstCanvas, color, outlineColor, THICK); 225 } 226 227 void applyMediumExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas, int color, 228 int outlineColor, Paint alphaClipPaint) { 229 applyExpensiveOutlineWithBlur(srcDst, srcDstCanvas, color, outlineColor, alphaClipPaint, 230 MEDIUM); 231 } 232 233 void applyMediumExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas, int color, 234 int outlineColor) { 235 applyExpensiveOutlineWithBlur(srcDst, srcDstCanvas, color, outlineColor, MEDIUM); 236 } 237 238 } 239