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