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
17package com.android.launcher3;
18
19import android.content.Context;
20import android.content.res.Resources;
21import android.graphics.Bitmap;
22import android.graphics.BlurMaskFilter;
23import android.graphics.Canvas;
24import android.graphics.Color;
25import android.graphics.Paint;
26import android.graphics.PorterDuff;
27import android.graphics.PorterDuffXfermode;
28import android.graphics.Rect;
29import android.graphics.drawable.Drawable;
30import android.util.SparseArray;
31
32/**
33 * Utility class to generate shadow and outline effect, which are used for click feedback
34 * and drag-n-drop respectively.
35 */
36public class HolographicOutlineHelper {
37
38    private static HolographicOutlineHelper sInstance;
39
40    private final Canvas mCanvas = new Canvas();
41    private final Paint mDrawPaint = new Paint();
42    private final Paint mBlurPaint = new Paint();
43    private final Paint mErasePaint = new Paint();
44
45    private final BlurMaskFilter mMediumOuterBlurMaskFilter;
46    private final BlurMaskFilter mThinOuterBlurMaskFilter;
47    private final BlurMaskFilter mMediumInnerBlurMaskFilter;
48
49    private final BlurMaskFilter mShadowBlurMaskFilter;
50
51    // We have 4 different icon sizes: homescreen, hotseat, folder & all-apps
52    private final SparseArray<Bitmap> mBitmapCache = new SparseArray<>(4);
53
54    private HolographicOutlineHelper(Context context) {
55        Resources res = context.getResources();
56
57        float mediumBlur = res.getDimension(R.dimen.blur_size_medium_outline);
58        mMediumOuterBlurMaskFilter = new BlurMaskFilter(mediumBlur, BlurMaskFilter.Blur.OUTER);
59        mMediumInnerBlurMaskFilter = new BlurMaskFilter(mediumBlur, BlurMaskFilter.Blur.NORMAL);
60
61        mThinOuterBlurMaskFilter = new BlurMaskFilter(
62                res.getDimension(R.dimen.blur_size_thin_outline), BlurMaskFilter.Blur.OUTER);
63
64        mShadowBlurMaskFilter = new BlurMaskFilter(
65                res.getDimension(R.dimen.blur_size_click_shadow), BlurMaskFilter.Blur.NORMAL);
66
67        mDrawPaint.setFilterBitmap(true);
68        mDrawPaint.setAntiAlias(true);
69        mBlurPaint.setFilterBitmap(true);
70        mBlurPaint.setAntiAlias(true);
71        mErasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
72        mErasePaint.setFilterBitmap(true);
73        mErasePaint.setAntiAlias(true);
74    }
75
76    public static HolographicOutlineHelper obtain(Context context) {
77        if (sInstance == null) {
78            sInstance = new HolographicOutlineHelper(context);
79        }
80        return sInstance;
81    }
82
83    /**
84     * Applies a more expensive and accurate outline to whatever is currently drawn in a specified
85     * bitmap.
86     */
87    void applyExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas, int color,
88            int outlineColor) {
89        applyExpensiveOutlineWithBlur(srcDst, srcDstCanvas, color, outlineColor, true);
90    }
91    void applyExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas, int color,
92            int outlineColor, boolean clipAlpha) {
93
94        // We start by removing most of the alpha channel so as to ignore shadows, and
95        // other types of partial transparency when defining the shape of the object
96        if (clipAlpha) {
97            int[] srcBuffer = new int[srcDst.getWidth() * srcDst.getHeight()];
98            srcDst.getPixels(srcBuffer,
99                    0, srcDst.getWidth(), 0, 0, srcDst.getWidth(), srcDst.getHeight());
100            for (int i = 0; i < srcBuffer.length; i++) {
101                final int alpha = srcBuffer[i] >>> 24;
102                if (alpha < 188) {
103                    srcBuffer[i] = 0;
104                }
105            }
106            srcDst.setPixels(srcBuffer,
107                    0, srcDst.getWidth(), 0, 0, srcDst.getWidth(), srcDst.getHeight());
108        }
109        Bitmap glowShape = srcDst.extractAlpha();
110
111        // calculate the outer blur first
112        mBlurPaint.setMaskFilter(mMediumOuterBlurMaskFilter);
113        int[] outerBlurOffset = new int[2];
114        Bitmap thickOuterBlur = glowShape.extractAlpha(mBlurPaint, outerBlurOffset);
115
116        mBlurPaint.setMaskFilter(mThinOuterBlurMaskFilter);
117        int[] brightOutlineOffset = new int[2];
118        Bitmap brightOutline = glowShape.extractAlpha(mBlurPaint, brightOutlineOffset);
119
120        // calculate the inner blur
121        srcDstCanvas.setBitmap(glowShape);
122        srcDstCanvas.drawColor(0xFF000000, PorterDuff.Mode.SRC_OUT);
123        mBlurPaint.setMaskFilter(mMediumInnerBlurMaskFilter);
124        int[] thickInnerBlurOffset = new int[2];
125        Bitmap thickInnerBlur = glowShape.extractAlpha(mBlurPaint, thickInnerBlurOffset);
126
127        // mask out the inner blur
128        srcDstCanvas.setBitmap(thickInnerBlur);
129        srcDstCanvas.drawBitmap(glowShape, -thickInnerBlurOffset[0],
130                -thickInnerBlurOffset[1], mErasePaint);
131        srcDstCanvas.drawRect(0, 0, -thickInnerBlurOffset[0], thickInnerBlur.getHeight(),
132                mErasePaint);
133        srcDstCanvas.drawRect(0, 0, thickInnerBlur.getWidth(), -thickInnerBlurOffset[1],
134                mErasePaint);
135
136        // draw the inner and outer blur
137        srcDstCanvas.setBitmap(srcDst);
138        srcDstCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
139        mDrawPaint.setColor(color);
140        srcDstCanvas.drawBitmap(thickInnerBlur, thickInnerBlurOffset[0], thickInnerBlurOffset[1],
141                mDrawPaint);
142        srcDstCanvas.drawBitmap(thickOuterBlur, outerBlurOffset[0], outerBlurOffset[1],
143                mDrawPaint);
144
145        // draw the bright outline
146        mDrawPaint.setColor(outlineColor);
147        srcDstCanvas.drawBitmap(brightOutline, brightOutlineOffset[0], brightOutlineOffset[1],
148                mDrawPaint);
149
150        // cleanup
151        srcDstCanvas.setBitmap(null);
152        brightOutline.recycle();
153        thickOuterBlur.recycle();
154        thickInnerBlur.recycle();
155        glowShape.recycle();
156    }
157
158    Bitmap createMediumDropShadow(BubbleTextView view) {
159        Drawable icon = view.getIcon();
160        if (icon == null) {
161            return null;
162        }
163        Rect rect = icon.getBounds();
164
165        int bitmapWidth = (int) (rect.width() * view.getScaleX());
166        int bitmapHeight = (int) (rect.height() * view.getScaleY());
167
168        int key = (bitmapWidth << 16) | bitmapHeight;
169        Bitmap cache = mBitmapCache.get(key);
170        if (cache == null) {
171            cache = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
172            mCanvas.setBitmap(cache);
173            mBitmapCache.put(key, cache);
174        } else {
175            mCanvas.setBitmap(cache);
176            mCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
177        }
178
179        mCanvas.save(Canvas.MATRIX_SAVE_FLAG);
180        mCanvas.scale(view.getScaleX(), view.getScaleY());
181        mCanvas.translate(-rect.left, -rect.top);
182        icon.draw(mCanvas);
183        mCanvas.restore();
184        mCanvas.setBitmap(null);
185
186        mBlurPaint.setMaskFilter(mShadowBlurMaskFilter);
187        return cache.extractAlpha(mBlurPaint, null);
188    }
189}
190