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.graphics; 18 19 import android.content.Context; 20 import android.content.res.Resources; 21 import android.graphics.Bitmap; 22 import android.graphics.BlurMaskFilter; 23 import android.graphics.Canvas; 24 import android.graphics.Color; 25 import android.graphics.Paint; 26 import android.graphics.PorterDuff; 27 import android.graphics.PorterDuffXfermode; 28 import android.graphics.Rect; 29 import android.graphics.drawable.Drawable; 30 import android.util.SparseArray; 31 32 import com.android.launcher3.BubbleTextView; 33 import com.android.launcher3.R; 34 import com.android.launcher3.config.FeatureFlags; 35 36 import java.nio.ByteBuffer; 37 38 /** 39 * Utility class to generate shadow and outline effect, which are used for click feedback 40 * and drag-n-drop respectively. 41 */ 42 public class HolographicOutlineHelper { 43 44 private static HolographicOutlineHelper sInstance; 45 46 private final Canvas mCanvas = new Canvas(); 47 private final Paint mDrawPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); 48 private final Paint mBlurPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); 49 private final Paint mErasePaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); 50 51 private final BlurMaskFilter mMediumOuterBlurMaskFilter; 52 private final BlurMaskFilter mThinOuterBlurMaskFilter; 53 private final BlurMaskFilter mMediumInnerBlurMaskFilter; 54 55 private final float mShadowBitmapShift; 56 private final BlurMaskFilter mShadowBlurMaskFilter; 57 58 // We have 4 different icon sizes: homescreen, hotseat, folder & all-apps 59 private final SparseArray<Bitmap> mBitmapCache = new SparseArray<>(4); 60 HolographicOutlineHelper(Context context)61 private HolographicOutlineHelper(Context context) { 62 Resources res = context.getResources(); 63 64 float mediumBlur = res.getDimension(R.dimen.blur_size_medium_outline); 65 mMediumOuterBlurMaskFilter = new BlurMaskFilter(mediumBlur, BlurMaskFilter.Blur.OUTER); 66 mMediumInnerBlurMaskFilter = new BlurMaskFilter(mediumBlur, BlurMaskFilter.Blur.NORMAL); 67 68 mThinOuterBlurMaskFilter = new BlurMaskFilter( 69 res.getDimension(R.dimen.blur_size_thin_outline), BlurMaskFilter.Blur.OUTER); 70 71 mShadowBitmapShift = res.getDimension(R.dimen.blur_size_click_shadow); 72 mShadowBlurMaskFilter = new BlurMaskFilter(mShadowBitmapShift, BlurMaskFilter.Blur.NORMAL); 73 74 mErasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT)); 75 } 76 getInstance(Context context)77 public static HolographicOutlineHelper getInstance(Context context) { 78 if (sInstance == null) { 79 sInstance = new HolographicOutlineHelper(context.getApplicationContext()); 80 } 81 return sInstance; 82 } 83 84 /** 85 * Applies a more expensive and accurate outline to whatever is currently drawn in a specified 86 * bitmap. 87 */ applyExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas)88 public void applyExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas) { 89 if (FeatureFlags.IS_DOGFOOD_BUILD && srcDst.getConfig() != Bitmap.Config.ALPHA_8) { 90 throw new RuntimeException("Outline blue is only supported on alpha bitmaps"); 91 } 92 93 // We start by removing most of the alpha channel so as to ignore shadows, and 94 // other types of partial transparency when defining the shape of the object 95 byte[] pixels = new byte[srcDst.getWidth() * srcDst.getHeight()]; 96 ByteBuffer buffer = ByteBuffer.wrap(pixels); 97 buffer.rewind(); 98 srcDst.copyPixelsToBuffer(buffer); 99 100 for (int i = 0; i < pixels.length; i++) { 101 if ((pixels[i] & 0xFF) < 188) { 102 pixels[i] = 0; 103 } 104 } 105 106 buffer.rewind(); 107 srcDst.copyPixelsFromBuffer(buffer); 108 109 // calculate the outer blur first 110 mBlurPaint.setMaskFilter(mMediumOuterBlurMaskFilter); 111 int[] outerBlurOffset = new int[2]; 112 Bitmap thickOuterBlur = srcDst.extractAlpha(mBlurPaint, outerBlurOffset); 113 114 mBlurPaint.setMaskFilter(mThinOuterBlurMaskFilter); 115 int[] brightOutlineOffset = new int[2]; 116 Bitmap brightOutline = srcDst.extractAlpha(mBlurPaint, brightOutlineOffset); 117 118 // calculate the inner blur 119 srcDstCanvas.setBitmap(srcDst); 120 srcDstCanvas.drawColor(0xFF000000, PorterDuff.Mode.SRC_OUT); 121 mBlurPaint.setMaskFilter(mMediumInnerBlurMaskFilter); 122 int[] thickInnerBlurOffset = new int[2]; 123 Bitmap thickInnerBlur = srcDst.extractAlpha(mBlurPaint, thickInnerBlurOffset); 124 125 // mask out the inner blur 126 srcDstCanvas.setBitmap(thickInnerBlur); 127 srcDstCanvas.drawBitmap(srcDst, -thickInnerBlurOffset[0], 128 -thickInnerBlurOffset[1], mErasePaint); 129 srcDstCanvas.drawRect(0, 0, -thickInnerBlurOffset[0], thickInnerBlur.getHeight(), 130 mErasePaint); 131 srcDstCanvas.drawRect(0, 0, thickInnerBlur.getWidth(), -thickInnerBlurOffset[1], 132 mErasePaint); 133 134 // draw the inner and outer blur 135 srcDstCanvas.setBitmap(srcDst); 136 srcDstCanvas.drawColor(0, PorterDuff.Mode.CLEAR); 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 srcDstCanvas.drawBitmap(brightOutline, brightOutlineOffset[0], brightOutlineOffset[1], 144 mDrawPaint); 145 146 // cleanup 147 srcDstCanvas.setBitmap(null); 148 brightOutline.recycle(); 149 thickOuterBlur.recycle(); 150 thickInnerBlur.recycle(); 151 } 152 createMediumDropShadow(BubbleTextView view)153 public Bitmap createMediumDropShadow(BubbleTextView view) { 154 Drawable drawable = view.getIcon(); 155 if (drawable == null) { 156 return null; 157 } 158 159 float scaleX = view.getScaleX(); 160 float scaleY = view.getScaleY(); 161 Rect rect = drawable.getBounds(); 162 163 int bitmapWidth = (int) (rect.width() * scaleX); 164 int bitmapHeight = (int) (rect.height() * scaleY); 165 if (bitmapHeight <= 0 || bitmapWidth <= 0) { 166 return null; 167 } 168 169 int key = (bitmapWidth << 16) | bitmapHeight; 170 Bitmap cache = mBitmapCache.get(key); 171 if (cache == null) { 172 cache = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ALPHA_8); 173 mCanvas.setBitmap(cache); 174 mBitmapCache.put(key, cache); 175 } else { 176 mCanvas.setBitmap(cache); 177 mCanvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR); 178 } 179 180 int saveCount = mCanvas.save(); 181 mCanvas.scale(scaleX, scaleY); 182 mCanvas.translate(-rect.left, -rect.top); 183 drawable.draw(mCanvas); 184 mCanvas.restoreToCount(saveCount); 185 mCanvas.setBitmap(null); 186 187 mBlurPaint.setMaskFilter(mShadowBlurMaskFilter); 188 189 int extraSize = (int) (2 * mShadowBitmapShift); 190 191 int resultWidth = bitmapWidth + extraSize; 192 int resultHeight = bitmapHeight + extraSize; 193 key = (resultWidth << 16) | resultHeight; 194 Bitmap result = mBitmapCache.get(key); 195 if (result == null) { 196 result = Bitmap.createBitmap(resultWidth, resultHeight, Bitmap.Config.ALPHA_8); 197 mCanvas.setBitmap(result); 198 } else { 199 // Use put instead of delete, to avoid unnecessary shrinking of cache array 200 mBitmapCache.put(key, null); 201 mCanvas.setBitmap(result); 202 mCanvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR); 203 } 204 mCanvas.drawBitmap(cache, mShadowBitmapShift, mShadowBitmapShift, mBlurPaint); 205 mCanvas.setBitmap(null); 206 return result; 207 } 208 recycleShadowBitmap(Bitmap bitmap)209 public void recycleShadowBitmap(Bitmap bitmap) { 210 if (bitmap != null) { 211 mBitmapCache.put((bitmap.getWidth() << 16) | bitmap.getHeight(), bitmap); 212 } 213 } 214 } 215