• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 static com.android.launcher3.anim.Interpolators.ACCEL;
20 
21 import android.animation.ObjectAnimator;
22 import android.graphics.Bitmap;
23 import android.graphics.Canvas;
24 import android.graphics.Color;
25 import android.graphics.ColorFilter;
26 import android.graphics.ColorMatrix;
27 import android.graphics.ColorMatrixColorFilter;
28 import android.graphics.Paint;
29 import android.graphics.PixelFormat;
30 import android.graphics.PorterDuff;
31 import android.graphics.PorterDuffColorFilter;
32 import android.graphics.Rect;
33 import android.graphics.drawable.Drawable;
34 import android.util.Property;
35 import android.util.SparseArray;
36 
37 import com.android.launcher3.graphics.BitmapInfo;
38 
39 public class FastBitmapDrawable extends Drawable {
40 
41     private static final float PRESSED_SCALE = 1.1f;
42 
43     private static final float DISABLED_DESATURATION = 1f;
44     private static final float DISABLED_BRIGHTNESS = 0.5f;
45 
46     public static final int CLICK_FEEDBACK_DURATION = 200;
47 
48     // Since we don't need 256^2 values for combinations of both the brightness and saturation, we
49     // reduce the value space to a smaller value V, which reduces the number of cached
50     // ColorMatrixColorFilters that we need to keep to V^2
51     private static final int REDUCED_FILTER_VALUE_SPACE = 48;
52 
53     // A cache of ColorFilters for optimizing brightness and saturation animations
54     private static final SparseArray<ColorFilter> sCachedFilter = new SparseArray<>();
55 
56     // Temporary matrices used for calculation
57     private static final ColorMatrix sTempBrightnessMatrix = new ColorMatrix();
58     private static final ColorMatrix sTempFilterMatrix = new ColorMatrix();
59 
60     protected final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG);
61     protected Bitmap mBitmap;
62     protected final int mIconColor;
63 
64     private boolean mIsPressed;
65     private boolean mIsDisabled;
66 
67     // Animator and properties for the fast bitmap drawable's scale
68     private static final Property<FastBitmapDrawable, Float> SCALE
69             = new Property<FastBitmapDrawable, Float>(Float.TYPE, "scale") {
70         @Override
71         public Float get(FastBitmapDrawable fastBitmapDrawable) {
72             return fastBitmapDrawable.mScale;
73         }
74 
75         @Override
76         public void set(FastBitmapDrawable fastBitmapDrawable, Float value) {
77             fastBitmapDrawable.mScale = value;
78             fastBitmapDrawable.invalidateSelf();
79         }
80     };
81     private ObjectAnimator mScaleAnimation;
82     private float mScale = 1;
83 
84 
85     // The saturation and brightness are values that are mapped to REDUCED_FILTER_VALUE_SPACE and
86     // as a result, can be used to compose the key for the cached ColorMatrixColorFilters
87     private int mDesaturation = 0;
88     private int mBrightness = 0;
89     private int mAlpha = 255;
90     private int mPrevUpdateKey = Integer.MAX_VALUE;
91 
FastBitmapDrawable(Bitmap b)92     public FastBitmapDrawable(Bitmap b) {
93         this(b, Color.TRANSPARENT);
94     }
95 
FastBitmapDrawable(BitmapInfo info)96     public FastBitmapDrawable(BitmapInfo info) {
97         this(info.icon, info.color);
98     }
99 
FastBitmapDrawable(ItemInfoWithIcon info)100     public FastBitmapDrawable(ItemInfoWithIcon info) {
101         this(info.iconBitmap, info.iconColor);
102     }
103 
FastBitmapDrawable(Bitmap b, int iconColor)104     protected FastBitmapDrawable(Bitmap b, int iconColor) {
105         mBitmap = b;
106         mIconColor = iconColor;
107         setFilterBitmap(true);
108     }
109 
110     @Override
draw(Canvas canvas)111     public final void draw(Canvas canvas) {
112         if (mScaleAnimation != null) {
113             int count = canvas.save();
114             Rect bounds = getBounds();
115             canvas.scale(mScale, mScale, bounds.exactCenterX(), bounds.exactCenterY());
116             drawInternal(canvas, bounds);
117             canvas.restoreToCount(count);
118         } else {
119             drawInternal(canvas, getBounds());
120         }
121     }
122 
drawInternal(Canvas canvas, Rect bounds)123     protected void drawInternal(Canvas canvas, Rect bounds) {
124         canvas.drawBitmap(mBitmap, null, bounds, mPaint);
125     }
126 
127     @Override
setColorFilter(ColorFilter cf)128     public void setColorFilter(ColorFilter cf) {
129         // No op
130     }
131 
132     @Override
getOpacity()133     public int getOpacity() {
134         return PixelFormat.TRANSLUCENT;
135     }
136 
137     @Override
setAlpha(int alpha)138     public void setAlpha(int alpha) {
139         mAlpha = alpha;
140         mPaint.setAlpha(alpha);
141     }
142 
143     @Override
setFilterBitmap(boolean filterBitmap)144     public void setFilterBitmap(boolean filterBitmap) {
145         mPaint.setFilterBitmap(filterBitmap);
146         mPaint.setAntiAlias(filterBitmap);
147     }
148 
getAlpha()149     public int getAlpha() {
150         return mAlpha;
151     }
152 
getAnimatedScale()153     public float getAnimatedScale() {
154         return mScaleAnimation == null ? 1 : mScale;
155     }
156 
157     @Override
getIntrinsicWidth()158     public int getIntrinsicWidth() {
159         return mBitmap.getWidth();
160     }
161 
162     @Override
getIntrinsicHeight()163     public int getIntrinsicHeight() {
164         return mBitmap.getHeight();
165     }
166 
167     @Override
getMinimumWidth()168     public int getMinimumWidth() {
169         return getBounds().width();
170     }
171 
172     @Override
getMinimumHeight()173     public int getMinimumHeight() {
174         return getBounds().height();
175     }
176 
177     @Override
isStateful()178     public boolean isStateful() {
179         return true;
180     }
181 
182     @Override
getColorFilter()183     public ColorFilter getColorFilter() {
184         return mPaint.getColorFilter();
185     }
186 
187     @Override
onStateChange(int[] state)188     protected boolean onStateChange(int[] state) {
189         boolean isPressed = false;
190         for (int s : state) {
191             if (s == android.R.attr.state_pressed) {
192                 isPressed = true;
193                 break;
194             }
195         }
196         if (mIsPressed != isPressed) {
197             mIsPressed = isPressed;
198 
199             if (mScaleAnimation != null) {
200                 mScaleAnimation.cancel();
201                 mScaleAnimation = null;
202             }
203 
204             if (mIsPressed) {
205                 // Animate when going to pressed state
206                 mScaleAnimation = ObjectAnimator.ofFloat(this, SCALE, PRESSED_SCALE);
207                 mScaleAnimation.setDuration(CLICK_FEEDBACK_DURATION);
208                 mScaleAnimation.setInterpolator(ACCEL);
209                 mScaleAnimation.start();
210             } else {
211                 mScale = 1f;
212                 invalidateSelf();
213             }
214             return true;
215         }
216         return false;
217     }
218 
invalidateDesaturationAndBrightness()219     private void invalidateDesaturationAndBrightness() {
220         setDesaturation(mIsDisabled ? DISABLED_DESATURATION : 0);
221         setBrightness(mIsDisabled ? DISABLED_BRIGHTNESS : 0);
222     }
223 
setIsDisabled(boolean isDisabled)224     public void setIsDisabled(boolean isDisabled) {
225         if (mIsDisabled != isDisabled) {
226             mIsDisabled = isDisabled;
227             invalidateDesaturationAndBrightness();
228         }
229     }
230 
231     /**
232      * Sets the saturation of this icon, 0 [full color] -> 1 [desaturated]
233      */
setDesaturation(float desaturation)234     private void setDesaturation(float desaturation) {
235         int newDesaturation = (int) Math.floor(desaturation * REDUCED_FILTER_VALUE_SPACE);
236         if (mDesaturation != newDesaturation) {
237             mDesaturation = newDesaturation;
238             updateFilter();
239         }
240     }
241 
getDesaturation()242     public float getDesaturation() {
243         return (float) mDesaturation / REDUCED_FILTER_VALUE_SPACE;
244     }
245 
246     /**
247      * Sets the brightness of this icon, 0 [no add. brightness] -> 1 [2bright2furious]
248      */
setBrightness(float brightness)249     private void setBrightness(float brightness) {
250         int newBrightness = (int) Math.floor(brightness * REDUCED_FILTER_VALUE_SPACE);
251         if (mBrightness != newBrightness) {
252             mBrightness = newBrightness;
253             updateFilter();
254         }
255     }
256 
getBrightness()257     private float getBrightness() {
258         return (float) mBrightness / REDUCED_FILTER_VALUE_SPACE;
259     }
260 
261     /**
262      * Updates the paint to reflect the current brightness and saturation.
263      */
updateFilter()264     private void updateFilter() {
265         boolean usePorterDuffFilter = false;
266         int key = -1;
267         if (mDesaturation > 0) {
268             key = (mDesaturation << 16) | mBrightness;
269         } else if (mBrightness > 0) {
270             // Compose a key with a fully saturated icon if we are just animating brightness
271             key = (1 << 16) | mBrightness;
272 
273             // We found that in L, ColorFilters cause drawing artifacts with shadows baked into
274             // icons, so just use a PorterDuff filter when we aren't animating saturation
275             usePorterDuffFilter = true;
276         }
277 
278         // Debounce multiple updates on the same frame
279         if (key == mPrevUpdateKey) {
280             return;
281         }
282         mPrevUpdateKey = key;
283 
284         if (key != -1) {
285             ColorFilter filter = sCachedFilter.get(key);
286             if (filter == null) {
287                 float brightnessF = getBrightness();
288                 int brightnessI = (int) (255 * brightnessF);
289                 if (usePorterDuffFilter) {
290                     filter = new PorterDuffColorFilter(Color.argb(brightnessI, 255, 255, 255),
291                             PorterDuff.Mode.SRC_ATOP);
292                 } else {
293                     float saturationF = 1f - getDesaturation();
294                     sTempFilterMatrix.setSaturation(saturationF);
295                     if (mBrightness > 0) {
296                         // Brightness: C-new = C-old*(1-amount) + amount
297                         float scale = 1f - brightnessF;
298                         float[] mat = sTempBrightnessMatrix.getArray();
299                         mat[0] = scale;
300                         mat[6] = scale;
301                         mat[12] = scale;
302                         mat[4] = brightnessI;
303                         mat[9] = brightnessI;
304                         mat[14] = brightnessI;
305                         sTempFilterMatrix.preConcat(sTempBrightnessMatrix);
306                     }
307                     filter = new ColorMatrixColorFilter(sTempFilterMatrix);
308                 }
309                 sCachedFilter.append(key, filter);
310             }
311             mPaint.setColorFilter(filter);
312         } else {
313             mPaint.setColorFilter(null);
314         }
315         invalidateSelf();
316     }
317 
318     @Override
getConstantState()319     public ConstantState getConstantState() {
320         return new MyConstantState(mBitmap, mIconColor);
321     }
322 
323     protected static class MyConstantState extends ConstantState {
324         protected final Bitmap mBitmap;
325         protected final int mIconColor;
326 
MyConstantState(Bitmap bitmap, int color)327         public MyConstantState(Bitmap bitmap, int color) {
328             mBitmap = bitmap;
329             mIconColor = color;
330         }
331 
332         @Override
newDrawable()333         public Drawable newDrawable() {
334             return new FastBitmapDrawable(mBitmap, mIconColor);
335         }
336 
337         @Override
getChangingConfigurations()338         public int getChangingConfigurations() {
339             return 0;
340         }
341     }
342 }
343