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