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.launcher3; 18 19 import android.content.Context; 20 import android.graphics.Bitmap; 21 import android.graphics.BlurMaskFilter; 22 import android.graphics.Canvas; 23 import android.graphics.Color; 24 import android.graphics.Paint; 25 import android.graphics.PorterDuff; 26 import android.graphics.PorterDuffXfermode; 27 import android.graphics.Rect; 28 import android.graphics.Region.Op; 29 30 public class HolographicOutlineHelper { 31 32 private static final Rect sTempRect = new Rect(); 33 34 private final Canvas mCanvas = new Canvas(); 35 private final Paint mDrawPaint = new Paint(); 36 private final Paint mBlurPaint = new Paint(); 37 private final Paint mErasePaint = new Paint(); 38 39 private final BlurMaskFilter mMediumOuterBlurMaskFilter; 40 private final BlurMaskFilter mThinOuterBlurMaskFilter; 41 private final BlurMaskFilter mMediumInnerBlurMaskFilter; 42 43 private final BlurMaskFilter mShaowBlurMaskFilter; 44 private final int mShadowOffset; 45 46 /** 47 * Padding used when creating shadow bitmap; 48 */ 49 final int shadowBitmapPadding; 50 51 static HolographicOutlineHelper INSTANCE; 52 53 private HolographicOutlineHelper(Context context) { 54 final float scale = LauncherAppState.getInstance().getScreenDensity(); 55 56 mMediumOuterBlurMaskFilter = new BlurMaskFilter(scale * 2.0f, BlurMaskFilter.Blur.OUTER); 57 mThinOuterBlurMaskFilter = new BlurMaskFilter(scale * 1.0f, BlurMaskFilter.Blur.OUTER); 58 mMediumInnerBlurMaskFilter = new BlurMaskFilter(scale * 2.0f, BlurMaskFilter.Blur.NORMAL); 59 60 mShaowBlurMaskFilter = new BlurMaskFilter(scale * 4.0f, BlurMaskFilter.Blur.NORMAL); 61 mShadowOffset = (int) (scale * 2.0f); 62 shadowBitmapPadding = (int) (scale * 4.0f); 63 64 mDrawPaint.setFilterBitmap(true); 65 mDrawPaint.setAntiAlias(true); 66 mBlurPaint.setFilterBitmap(true); 67 mBlurPaint.setAntiAlias(true); 68 mErasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT)); 69 mErasePaint.setFilterBitmap(true); 70 mErasePaint.setAntiAlias(true); 71 } 72 73 public static HolographicOutlineHelper obtain(Context context) { 74 if (INSTANCE == null) { 75 INSTANCE = new HolographicOutlineHelper(context); 76 } 77 return INSTANCE; 78 } 79 80 /** 81 * Applies a more expensive and accurate outline to whatever is currently drawn in a specified 82 * bitmap. 83 */ 84 void applyExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas, int color, 85 int outlineColor) { 86 applyExpensiveOutlineWithBlur(srcDst, srcDstCanvas, color, outlineColor, true); 87 } 88 void applyExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas, int color, 89 int outlineColor, boolean clipAlpha) { 90 91 // We start by removing most of the alpha channel so as to ignore shadows, and 92 // other types of partial transparency when defining the shape of the object 93 if (clipAlpha) { 94 int[] srcBuffer = new int[srcDst.getWidth() * srcDst.getHeight()]; 95 srcDst.getPixels(srcBuffer, 96 0, srcDst.getWidth(), 0, 0, srcDst.getWidth(), srcDst.getHeight()); 97 for (int i = 0; i < srcBuffer.length; i++) { 98 final int alpha = srcBuffer[i] >>> 24; 99 if (alpha < 188) { 100 srcBuffer[i] = 0; 101 } 102 } 103 srcDst.setPixels(srcBuffer, 104 0, srcDst.getWidth(), 0, 0, srcDst.getWidth(), srcDst.getHeight()); 105 } 106 Bitmap glowShape = srcDst.extractAlpha(); 107 108 // calculate the outer blur first 109 mBlurPaint.setMaskFilter(mMediumOuterBlurMaskFilter); 110 int[] outerBlurOffset = new int[2]; 111 Bitmap thickOuterBlur = glowShape.extractAlpha(mBlurPaint, outerBlurOffset); 112 113 mBlurPaint.setMaskFilter(mThinOuterBlurMaskFilter); 114 int[] brightOutlineOffset = new int[2]; 115 Bitmap brightOutline = glowShape.extractAlpha(mBlurPaint, brightOutlineOffset); 116 117 // calculate the inner blur 118 srcDstCanvas.setBitmap(glowShape); 119 srcDstCanvas.drawColor(0xFF000000, PorterDuff.Mode.SRC_OUT); 120 mBlurPaint.setMaskFilter(mMediumInnerBlurMaskFilter); 121 int[] thickInnerBlurOffset = new int[2]; 122 Bitmap thickInnerBlur = glowShape.extractAlpha(mBlurPaint, thickInnerBlurOffset); 123 124 // mask out the inner blur 125 srcDstCanvas.setBitmap(thickInnerBlur); 126 srcDstCanvas.drawBitmap(glowShape, -thickInnerBlurOffset[0], 127 -thickInnerBlurOffset[1], mErasePaint); 128 srcDstCanvas.drawRect(0, 0, -thickInnerBlurOffset[0], thickInnerBlur.getHeight(), 129 mErasePaint); 130 srcDstCanvas.drawRect(0, 0, thickInnerBlur.getWidth(), -thickInnerBlurOffset[1], 131 mErasePaint); 132 133 // draw the inner and outer blur 134 srcDstCanvas.setBitmap(srcDst); 135 srcDstCanvas.drawColor(0, PorterDuff.Mode.CLEAR); 136 mDrawPaint.setColor(color); 137 srcDstCanvas.drawBitmap(thickInnerBlur, thickInnerBlurOffset[0], thickInnerBlurOffset[1], 138 mDrawPaint); 139 srcDstCanvas.drawBitmap(thickOuterBlur, outerBlurOffset[0], outerBlurOffset[1], 140 mDrawPaint); 141 142 // draw the bright outline 143 mDrawPaint.setColor(outlineColor); 144 srcDstCanvas.drawBitmap(brightOutline, brightOutlineOffset[0], brightOutlineOffset[1], 145 mDrawPaint); 146 147 // cleanup 148 srcDstCanvas.setBitmap(null); 149 brightOutline.recycle(); 150 thickOuterBlur.recycle(); 151 thickInnerBlur.recycle(); 152 glowShape.recycle(); 153 } 154 155 Bitmap createMediumDropShadow(BubbleTextView view) { 156 final Bitmap result = Bitmap.createBitmap( 157 view.getWidth() + shadowBitmapPadding + shadowBitmapPadding, 158 view.getHeight() + shadowBitmapPadding + shadowBitmapPadding + mShadowOffset, 159 Bitmap.Config.ARGB_8888); 160 161 mCanvas.setBitmap(result); 162 163 final Rect clipRect = sTempRect; 164 view.getDrawingRect(sTempRect); 165 // adjust the clip rect so that we don't include the text label 166 clipRect.bottom = view.getExtendedPaddingTop() - (int) BubbleTextView.PADDING_V 167 + view.getLayout().getLineTop(0); 168 169 // Draw the View into the bitmap. 170 // The translate of scrollX and scrollY is necessary when drawing TextViews, because 171 // they set scrollX and scrollY to large values to achieve centered text 172 mCanvas.save(); 173 mCanvas.scale(view.getScaleX(), view.getScaleY(), 174 view.getWidth() / 2 + shadowBitmapPadding, 175 view.getHeight() / 2 + shadowBitmapPadding); 176 mCanvas.translate(-view.getScrollX() + shadowBitmapPadding, 177 -view.getScrollY() + shadowBitmapPadding); 178 mCanvas.clipRect(clipRect, Op.REPLACE); 179 view.draw(mCanvas); 180 mCanvas.restore(); 181 182 int[] blurOffst = new int[2]; 183 mBlurPaint.setMaskFilter(mShaowBlurMaskFilter); 184 Bitmap blurBitmap = result.extractAlpha(mBlurPaint, blurOffst); 185 186 mCanvas.save(); 187 mCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); 188 mCanvas.translate(blurOffst[0], blurOffst[1]); 189 190 mDrawPaint.setColor(Color.BLACK); 191 mDrawPaint.setAlpha(30); 192 mCanvas.drawBitmap(blurBitmap, 0, 0, mDrawPaint); 193 194 mDrawPaint.setAlpha(60); 195 mCanvas.drawBitmap(blurBitmap, 0, mShadowOffset, mDrawPaint); 196 mCanvas.restore(); 197 198 mCanvas.setBitmap(null); 199 blurBitmap.recycle(); 200 201 return result; 202 } 203 } 204