• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2006 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 android.graphics.drawable;
18 
19 import android.annotation.NonNull;
20 import android.annotation.UnsupportedAppUsage;
21 import android.content.pm.ActivityInfo.Config;
22 import android.content.res.ColorStateList;
23 import android.content.res.Resources;
24 import android.content.res.Resources.Theme;
25 import android.content.res.TypedArray;
26 import android.graphics.Bitmap;
27 import android.graphics.BitmapShader;
28 import android.graphics.BlendMode;
29 import android.graphics.BlendModeColorFilter;
30 import android.graphics.Canvas;
31 import android.graphics.ColorFilter;
32 import android.graphics.ImageDecoder;
33 import android.graphics.Insets;
34 import android.graphics.Matrix;
35 import android.graphics.Outline;
36 import android.graphics.Paint;
37 import android.graphics.PixelFormat;
38 import android.graphics.PorterDuff.Mode;
39 import android.graphics.Rect;
40 import android.graphics.Shader;
41 import android.graphics.Xfermode;
42 import android.util.AttributeSet;
43 import android.util.DisplayMetrics;
44 import android.util.LayoutDirection;
45 import android.util.TypedValue;
46 import android.view.Gravity;
47 
48 import com.android.internal.R;
49 
50 import org.xmlpull.v1.XmlPullParser;
51 import org.xmlpull.v1.XmlPullParserException;
52 
53 import java.io.FileInputStream;
54 import java.io.IOException;
55 import java.io.InputStream;
56 
57 /**
58  * A Drawable that wraps a bitmap and can be tiled, stretched, or aligned. You can create a
59  * BitmapDrawable from a file path, an input stream, through XML inflation, or from
60  * a {@link android.graphics.Bitmap} object.
61  * <p>It can be defined in an XML file with the <code>&lt;bitmap></code> element.  For more
62  * information, see the guide to <a
63  * href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p>
64  * <p>
65  * Also see the {@link android.graphics.Bitmap} class, which handles the management and
66  * transformation of raw bitmap graphics, and should be used when drawing to a
67  * {@link android.graphics.Canvas}.
68  * </p>
69  *
70  * @attr ref android.R.styleable#BitmapDrawable_src
71  * @attr ref android.R.styleable#BitmapDrawable_antialias
72  * @attr ref android.R.styleable#BitmapDrawable_filter
73  * @attr ref android.R.styleable#BitmapDrawable_dither
74  * @attr ref android.R.styleable#BitmapDrawable_gravity
75  * @attr ref android.R.styleable#BitmapDrawable_mipMap
76  * @attr ref android.R.styleable#BitmapDrawable_tileMode
77  */
78 public class BitmapDrawable extends Drawable {
79     private static final int DEFAULT_PAINT_FLAGS =
80             Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG;
81 
82     // Constants for {@link android.R.styleable#BitmapDrawable_tileMode}.
83     private static final int TILE_MODE_UNDEFINED = -2;
84     private static final int TILE_MODE_DISABLED = -1;
85     private static final int TILE_MODE_CLAMP = 0;
86     private static final int TILE_MODE_REPEAT = 1;
87     private static final int TILE_MODE_MIRROR = 2;
88 
89     private final Rect mDstRect = new Rect();   // #updateDstRectAndInsetsIfDirty() sets this
90 
91     @UnsupportedAppUsage
92     private BitmapState mBitmapState;
93     private BlendModeColorFilter mBlendModeFilter;
94 
95     @UnsupportedAppUsage
96     private int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT;
97 
98     private boolean mDstRectAndInsetsDirty = true;
99     private boolean mMutated;
100 
101      // These are scaled to match the target density.
102     private int mBitmapWidth;
103     private int mBitmapHeight;
104 
105     /** Optical insets due to gravity. */
106     private Insets mOpticalInsets = Insets.NONE;
107 
108     // Mirroring matrix for using with Shaders
109     private Matrix mMirrorMatrix;
110 
111     /**
112      * Create an empty drawable, not dealing with density.
113      * @deprecated Use {@link #BitmapDrawable(android.content.res.Resources, android.graphics.Bitmap)}
114      * instead to specify a bitmap to draw with and ensure the correct density is set.
115      */
116     @Deprecated
BitmapDrawable()117     public BitmapDrawable() {
118         init(new BitmapState((Bitmap) null), null);
119     }
120 
121     /**
122      * Create an empty drawable, setting initial target density based on
123      * the display metrics of the resources.
124      *
125      * @deprecated Use {@link #BitmapDrawable(android.content.res.Resources, android.graphics.Bitmap)}
126      * instead to specify a bitmap to draw with.
127      */
128     @SuppressWarnings("unused")
129     @Deprecated
BitmapDrawable(Resources res)130     public BitmapDrawable(Resources res) {
131         init(new BitmapState((Bitmap) null), res);
132     }
133 
134     /**
135      * Create drawable from a bitmap, not dealing with density.
136      * @deprecated Use {@link #BitmapDrawable(Resources, Bitmap)} to ensure
137      * that the drawable has correctly set its target density.
138      */
139     @Deprecated
BitmapDrawable(Bitmap bitmap)140     public BitmapDrawable(Bitmap bitmap) {
141         init(new BitmapState(bitmap), null);
142     }
143 
144     /**
145      * Create drawable from a bitmap, setting initial target density based on
146      * the display metrics of the resources.
147      */
BitmapDrawable(Resources res, Bitmap bitmap)148     public BitmapDrawable(Resources res, Bitmap bitmap) {
149         init(new BitmapState(bitmap), res);
150     }
151 
152     /**
153      * Create a drawable by opening a given file path and decoding the bitmap.
154      * @deprecated Use {@link #BitmapDrawable(Resources, String)} to ensure
155      * that the drawable has correctly set its target density.
156      */
157     @Deprecated
BitmapDrawable(String filepath)158     public BitmapDrawable(String filepath) {
159         this(null, filepath);
160     }
161 
162     /**
163      * Create a drawable by opening a given file path and decoding the bitmap.
164      */
165     @SuppressWarnings({ "unused", "ChainingConstructorIgnoresParameter" })
BitmapDrawable(Resources res, String filepath)166     public BitmapDrawable(Resources res, String filepath) {
167         Bitmap bitmap = null;
168         try (FileInputStream stream = new FileInputStream(filepath)) {
169             bitmap = ImageDecoder.decodeBitmap(ImageDecoder.createSource(res, stream),
170                     (decoder, info, src) -> {
171                 decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
172             });
173         } catch (Exception e) {
174             /*  do nothing. This matches the behavior of BitmapFactory.decodeFile()
175                 If the exception happened on decode, mBitmapState.mBitmap will be null.
176             */
177         } finally {
178             init(new BitmapState(bitmap), res);
179             if (mBitmapState.mBitmap == null) {
180                 android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + filepath);
181             }
182         }
183     }
184 
185     /**
186      * Create a drawable by decoding a bitmap from the given input stream.
187      * @deprecated Use {@link #BitmapDrawable(Resources, java.io.InputStream)} to ensure
188      * that the drawable has correctly set its target density.
189      */
190     @Deprecated
BitmapDrawable(java.io.InputStream is)191     public BitmapDrawable(java.io.InputStream is) {
192         this(null, is);
193     }
194 
195     /**
196      * Create a drawable by decoding a bitmap from the given input stream.
197      */
198     @SuppressWarnings({ "unused", "ChainingConstructorIgnoresParameter" })
BitmapDrawable(Resources res, java.io.InputStream is)199     public BitmapDrawable(Resources res, java.io.InputStream is) {
200         Bitmap bitmap = null;
201         try {
202             bitmap = ImageDecoder.decodeBitmap(ImageDecoder.createSource(res, is),
203                     (decoder, info, src) -> {
204                 decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
205             });
206         } catch (Exception e) {
207             /*  do nothing. This matches the behavior of BitmapFactory.decodeStream()
208                 If the exception happened on decode, mBitmapState.mBitmap will be null.
209             */
210         } finally {
211             init(new BitmapState(bitmap), res);
212             if (mBitmapState.mBitmap == null) {
213                 android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + is);
214             }
215         }
216     }
217 
218     /**
219      * Returns the paint used to render this drawable.
220      */
getPaint()221     public final Paint getPaint() {
222         return mBitmapState.mPaint;
223     }
224 
225     /**
226      * Returns the bitmap used by this drawable to render. May be null.
227      */
getBitmap()228     public final Bitmap getBitmap() {
229         return mBitmapState.mBitmap;
230     }
231 
computeBitmapSize()232     private void computeBitmapSize() {
233         final Bitmap bitmap = mBitmapState.mBitmap;
234         if (bitmap != null) {
235             mBitmapWidth = bitmap.getScaledWidth(mTargetDensity);
236             mBitmapHeight = bitmap.getScaledHeight(mTargetDensity);
237         } else {
238             mBitmapWidth = mBitmapHeight = -1;
239         }
240     }
241 
242     /** @hide */
243     @UnsupportedAppUsage
setBitmap(Bitmap bitmap)244     public void setBitmap(Bitmap bitmap) {
245         if (mBitmapState.mBitmap != bitmap) {
246             mBitmapState.mBitmap = bitmap;
247             computeBitmapSize();
248             invalidateSelf();
249         }
250     }
251 
252     /**
253      * Set the density scale at which this drawable will be rendered. This
254      * method assumes the drawable will be rendered at the same density as the
255      * specified canvas.
256      *
257      * @param canvas The Canvas from which the density scale must be obtained.
258      *
259      * @see android.graphics.Bitmap#setDensity(int)
260      * @see android.graphics.Bitmap#getDensity()
261      */
setTargetDensity(Canvas canvas)262     public void setTargetDensity(Canvas canvas) {
263         setTargetDensity(canvas.getDensity());
264     }
265 
266     /**
267      * Set the density scale at which this drawable will be rendered.
268      *
269      * @param metrics The DisplayMetrics indicating the density scale for this drawable.
270      *
271      * @see android.graphics.Bitmap#setDensity(int)
272      * @see android.graphics.Bitmap#getDensity()
273      */
setTargetDensity(DisplayMetrics metrics)274     public void setTargetDensity(DisplayMetrics metrics) {
275         setTargetDensity(metrics.densityDpi);
276     }
277 
278     /**
279      * Set the density at which this drawable will be rendered.
280      *
281      * @param density The density scale for this drawable.
282      *
283      * @see android.graphics.Bitmap#setDensity(int)
284      * @see android.graphics.Bitmap#getDensity()
285      */
setTargetDensity(int density)286     public void setTargetDensity(int density) {
287         if (mTargetDensity != density) {
288             mTargetDensity = density == 0 ? DisplayMetrics.DENSITY_DEFAULT : density;
289             if (mBitmapState.mBitmap != null) {
290                 computeBitmapSize();
291             }
292             invalidateSelf();
293         }
294     }
295 
296     /** Get the gravity used to position/stretch the bitmap within its bounds.
297      * See android.view.Gravity
298      * @return the gravity applied to the bitmap
299      */
getGravity()300     public int getGravity() {
301         return mBitmapState.mGravity;
302     }
303 
304     /** Set the gravity used to position/stretch the bitmap within its bounds.
305         See android.view.Gravity
306      * @param gravity the gravity
307      */
setGravity(int gravity)308     public void setGravity(int gravity) {
309         if (mBitmapState.mGravity != gravity) {
310             mBitmapState.mGravity = gravity;
311             mDstRectAndInsetsDirty = true;
312             invalidateSelf();
313         }
314     }
315 
316     /**
317      * Enables or disables the mipmap hint for this drawable's bitmap.
318      * See {@link Bitmap#setHasMipMap(boolean)} for more information.
319      *
320      * If the bitmap is null calling this method has no effect.
321      *
322      * @param mipMap True if the bitmap should use mipmaps, false otherwise.
323      *
324      * @see #hasMipMap()
325      */
setMipMap(boolean mipMap)326     public void setMipMap(boolean mipMap) {
327         if (mBitmapState.mBitmap != null) {
328             mBitmapState.mBitmap.setHasMipMap(mipMap);
329             invalidateSelf();
330         }
331     }
332 
333     /**
334      * Indicates whether the mipmap hint is enabled on this drawable's bitmap.
335      *
336      * @return True if the mipmap hint is set, false otherwise. If the bitmap
337      *         is null, this method always returns false.
338      *
339      * @see #setMipMap(boolean)
340      * @attr ref android.R.styleable#BitmapDrawable_mipMap
341      */
hasMipMap()342     public boolean hasMipMap() {
343         return mBitmapState.mBitmap != null && mBitmapState.mBitmap.hasMipMap();
344     }
345 
346     /**
347      * Enables or disables anti-aliasing for this drawable. Anti-aliasing affects
348      * the edges of the bitmap only so it applies only when the drawable is rotated.
349      *
350      * @param aa True if the bitmap should be anti-aliased, false otherwise.
351      *
352      * @see #hasAntiAlias()
353      */
setAntiAlias(boolean aa)354     public void setAntiAlias(boolean aa) {
355         mBitmapState.mPaint.setAntiAlias(aa);
356         invalidateSelf();
357     }
358 
359     /**
360      * Indicates whether anti-aliasing is enabled for this drawable.
361      *
362      * @return True if anti-aliasing is enabled, false otherwise.
363      *
364      * @see #setAntiAlias(boolean)
365      */
hasAntiAlias()366     public boolean hasAntiAlias() {
367         return mBitmapState.mPaint.isAntiAlias();
368     }
369 
370     @Override
setFilterBitmap(boolean filter)371     public void setFilterBitmap(boolean filter) {
372         mBitmapState.mPaint.setFilterBitmap(filter);
373         invalidateSelf();
374     }
375 
376     @Override
isFilterBitmap()377     public boolean isFilterBitmap() {
378         return mBitmapState.mPaint.isFilterBitmap();
379     }
380 
381     @Override
setDither(boolean dither)382     public void setDither(boolean dither) {
383         mBitmapState.mPaint.setDither(dither);
384         invalidateSelf();
385     }
386 
387     /**
388      * Indicates the repeat behavior of this drawable on the X axis.
389      *
390      * @return {@link android.graphics.Shader.TileMode#CLAMP} if the bitmap does not repeat,
391      *         {@link android.graphics.Shader.TileMode#REPEAT} or
392      *         {@link android.graphics.Shader.TileMode#MIRROR} otherwise.
393      */
getTileModeX()394     public Shader.TileMode getTileModeX() {
395         return mBitmapState.mTileModeX;
396     }
397 
398     /**
399      * Indicates the repeat behavior of this drawable on the Y axis.
400      *
401      * @return {@link android.graphics.Shader.TileMode#CLAMP} if the bitmap does not repeat,
402      *         {@link android.graphics.Shader.TileMode#REPEAT} or
403      *         {@link android.graphics.Shader.TileMode#MIRROR} otherwise.
404      */
getTileModeY()405     public Shader.TileMode getTileModeY() {
406         return mBitmapState.mTileModeY;
407     }
408 
409     /**
410      * Sets the repeat behavior of this drawable on the X axis. By default, the drawable
411      * does not repeat its bitmap. Using {@link android.graphics.Shader.TileMode#REPEAT} or
412      * {@link android.graphics.Shader.TileMode#MIRROR} the bitmap can be repeated (or tiled)
413      * if the bitmap is smaller than this drawable.
414      *
415      * @param mode The repeat mode for this drawable.
416      *
417      * @see #setTileModeY(android.graphics.Shader.TileMode)
418      * @see #setTileModeXY(android.graphics.Shader.TileMode, android.graphics.Shader.TileMode)
419      * @attr ref android.R.styleable#BitmapDrawable_tileModeX
420      */
setTileModeX(Shader.TileMode mode)421     public void setTileModeX(Shader.TileMode mode) {
422         setTileModeXY(mode, mBitmapState.mTileModeY);
423     }
424 
425     /**
426      * Sets the repeat behavior of this drawable on the Y axis. By default, the drawable
427      * does not repeat its bitmap. Using {@link android.graphics.Shader.TileMode#REPEAT} or
428      * {@link android.graphics.Shader.TileMode#MIRROR} the bitmap can be repeated (or tiled)
429      * if the bitmap is smaller than this drawable.
430      *
431      * @param mode The repeat mode for this drawable.
432      *
433      * @see #setTileModeX(android.graphics.Shader.TileMode)
434      * @see #setTileModeXY(android.graphics.Shader.TileMode, android.graphics.Shader.TileMode)
435      * @attr ref android.R.styleable#BitmapDrawable_tileModeY
436      */
setTileModeY(Shader.TileMode mode)437     public final void setTileModeY(Shader.TileMode mode) {
438         setTileModeXY(mBitmapState.mTileModeX, mode);
439     }
440 
441     /**
442      * Sets the repeat behavior of this drawable on both axis. By default, the drawable
443      * does not repeat its bitmap. Using {@link android.graphics.Shader.TileMode#REPEAT} or
444      * {@link android.graphics.Shader.TileMode#MIRROR} the bitmap can be repeated (or tiled)
445      * if the bitmap is smaller than this drawable.
446      *
447      * @param xmode The X repeat mode for this drawable.
448      * @param ymode The Y repeat mode for this drawable.
449      *
450      * @see #setTileModeX(android.graphics.Shader.TileMode)
451      * @see #setTileModeY(android.graphics.Shader.TileMode)
452      */
setTileModeXY(Shader.TileMode xmode, Shader.TileMode ymode)453     public void setTileModeXY(Shader.TileMode xmode, Shader.TileMode ymode) {
454         final BitmapState state = mBitmapState;
455         if (state.mTileModeX != xmode || state.mTileModeY != ymode) {
456             state.mTileModeX = xmode;
457             state.mTileModeY = ymode;
458             state.mRebuildShader = true;
459             mDstRectAndInsetsDirty = true;
460             invalidateSelf();
461         }
462     }
463 
464     @Override
setAutoMirrored(boolean mirrored)465     public void setAutoMirrored(boolean mirrored) {
466         if (mBitmapState.mAutoMirrored != mirrored) {
467             mBitmapState.mAutoMirrored = mirrored;
468             invalidateSelf();
469         }
470     }
471 
472     @Override
isAutoMirrored()473     public final boolean isAutoMirrored() {
474         return mBitmapState.mAutoMirrored;
475     }
476 
477     @Override
getChangingConfigurations()478     public @Config int getChangingConfigurations() {
479         return super.getChangingConfigurations() | mBitmapState.getChangingConfigurations();
480     }
481 
needMirroring()482     private boolean needMirroring() {
483         return isAutoMirrored() && getLayoutDirection() == LayoutDirection.RTL;
484     }
485 
486     @Override
onBoundsChange(Rect bounds)487     protected void onBoundsChange(Rect bounds) {
488         mDstRectAndInsetsDirty = true;
489 
490         final Bitmap bitmap = mBitmapState.mBitmap;
491         final Shader shader = mBitmapState.mPaint.getShader();
492         if (bitmap != null && shader != null) {
493             updateShaderMatrix(bitmap, mBitmapState.mPaint, shader, needMirroring());
494         }
495     }
496 
497     @Override
draw(Canvas canvas)498     public void draw(Canvas canvas) {
499         final Bitmap bitmap = mBitmapState.mBitmap;
500         if (bitmap == null) {
501             return;
502         }
503 
504         final BitmapState state = mBitmapState;
505         final Paint paint = state.mPaint;
506         if (state.mRebuildShader) {
507             final Shader.TileMode tmx = state.mTileModeX;
508             final Shader.TileMode tmy = state.mTileModeY;
509             if (tmx == null && tmy == null) {
510                 paint.setShader(null);
511             } else {
512                 paint.setShader(new BitmapShader(bitmap,
513                         tmx == null ? Shader.TileMode.CLAMP : tmx,
514                         tmy == null ? Shader.TileMode.CLAMP : tmy));
515             }
516 
517             state.mRebuildShader = false;
518         }
519 
520         final int restoreAlpha;
521         if (state.mBaseAlpha != 1.0f) {
522             final Paint p = getPaint();
523             restoreAlpha = p.getAlpha();
524             p.setAlpha((int) (restoreAlpha * state.mBaseAlpha + 0.5f));
525         } else {
526             restoreAlpha = -1;
527         }
528 
529         final boolean clearColorFilter;
530         if (mBlendModeFilter != null && paint.getColorFilter() == null) {
531             paint.setColorFilter(mBlendModeFilter);
532             clearColorFilter = true;
533         } else {
534             clearColorFilter = false;
535         }
536 
537         updateDstRectAndInsetsIfDirty();
538         final Shader shader = paint.getShader();
539         final boolean needMirroring = needMirroring();
540         if (shader == null) {
541             if (needMirroring) {
542                 canvas.save();
543                 // Mirror the bitmap
544                 canvas.translate(mDstRect.right - mDstRect.left, 0);
545                 canvas.scale(-1.0f, 1.0f);
546             }
547 
548             canvas.drawBitmap(bitmap, null, mDstRect, paint);
549 
550             if (needMirroring) {
551                 canvas.restore();
552             }
553         } else {
554             updateShaderMatrix(bitmap, paint, shader, needMirroring);
555             canvas.drawRect(mDstRect, paint);
556         }
557 
558         if (clearColorFilter) {
559             paint.setColorFilter(null);
560         }
561 
562         if (restoreAlpha >= 0) {
563             paint.setAlpha(restoreAlpha);
564         }
565     }
566 
567     /**
568      * Updates the {@code paint}'s shader matrix to be consistent with the
569      * destination size and layout direction.
570      *
571      * @param bitmap the bitmap to be drawn
572      * @param paint the paint used to draw the bitmap
573      * @param shader the shader to set on the paint
574      * @param needMirroring whether the bitmap should be mirrored
575      */
updateShaderMatrix(@onNull Bitmap bitmap, @NonNull Paint paint, @NonNull Shader shader, boolean needMirroring)576     private void updateShaderMatrix(@NonNull Bitmap bitmap, @NonNull Paint paint,
577             @NonNull Shader shader, boolean needMirroring) {
578         final int sourceDensity = bitmap.getDensity();
579         final int targetDensity = mTargetDensity;
580         final boolean needScaling = sourceDensity != 0 && sourceDensity != targetDensity;
581         if (needScaling || needMirroring) {
582             final Matrix matrix = getOrCreateMirrorMatrix();
583             matrix.reset();
584 
585             if (needMirroring) {
586                 final int dx = mDstRect.right - mDstRect.left;
587                 matrix.setTranslate(dx, 0);
588                 matrix.setScale(-1, 1);
589             }
590 
591             if (needScaling) {
592                 final float densityScale = targetDensity / (float) sourceDensity;
593                 matrix.postScale(densityScale, densityScale);
594             }
595 
596             shader.setLocalMatrix(matrix);
597         } else {
598             mMirrorMatrix = null;
599             shader.setLocalMatrix(Matrix.IDENTITY_MATRIX);
600         }
601 
602         paint.setShader(shader);
603     }
604 
getOrCreateMirrorMatrix()605     private Matrix getOrCreateMirrorMatrix() {
606         if (mMirrorMatrix == null) {
607             mMirrorMatrix = new Matrix();
608         }
609         return mMirrorMatrix;
610     }
611 
updateDstRectAndInsetsIfDirty()612     private void updateDstRectAndInsetsIfDirty() {
613         if (mDstRectAndInsetsDirty) {
614             if (mBitmapState.mTileModeX == null && mBitmapState.mTileModeY == null) {
615                 final Rect bounds = getBounds();
616                 final int layoutDirection = getLayoutDirection();
617                 Gravity.apply(mBitmapState.mGravity, mBitmapWidth, mBitmapHeight,
618                         bounds, mDstRect, layoutDirection);
619 
620                 final int left = mDstRect.left - bounds.left;
621                 final int top = mDstRect.top - bounds.top;
622                 final int right = bounds.right - mDstRect.right;
623                 final int bottom = bounds.bottom - mDstRect.bottom;
624                 mOpticalInsets = Insets.of(left, top, right, bottom);
625             } else {
626                 copyBounds(mDstRect);
627                 mOpticalInsets = Insets.NONE;
628             }
629         }
630         mDstRectAndInsetsDirty = false;
631     }
632 
633     @Override
getOpticalInsets()634     public Insets getOpticalInsets() {
635         updateDstRectAndInsetsIfDirty();
636         return mOpticalInsets;
637     }
638 
639     @Override
getOutline(@onNull Outline outline)640     public void getOutline(@NonNull Outline outline) {
641         updateDstRectAndInsetsIfDirty();
642         outline.setRect(mDstRect);
643 
644         // Only opaque Bitmaps can report a non-0 alpha,
645         // since only they are guaranteed to fill their bounds
646         boolean opaqueOverShape = mBitmapState.mBitmap != null
647                 && !mBitmapState.mBitmap.hasAlpha();
648         outline.setAlpha(opaqueOverShape ? getAlpha() / 255.0f : 0.0f);
649     }
650 
651     @Override
setAlpha(int alpha)652     public void setAlpha(int alpha) {
653         final int oldAlpha = mBitmapState.mPaint.getAlpha();
654         if (alpha != oldAlpha) {
655             mBitmapState.mPaint.setAlpha(alpha);
656             invalidateSelf();
657         }
658     }
659 
660     @Override
getAlpha()661     public int getAlpha() {
662         return mBitmapState.mPaint.getAlpha();
663     }
664 
665     @Override
setColorFilter(ColorFilter colorFilter)666     public void setColorFilter(ColorFilter colorFilter) {
667         mBitmapState.mPaint.setColorFilter(colorFilter);
668         invalidateSelf();
669     }
670 
671     @Override
getColorFilter()672     public ColorFilter getColorFilter() {
673         return mBitmapState.mPaint.getColorFilter();
674     }
675 
676     @Override
setTintList(ColorStateList tint)677     public void setTintList(ColorStateList tint) {
678         final BitmapState state = mBitmapState;
679         if (state.mTint != tint) {
680             state.mTint = tint;
681             mBlendModeFilter = updateBlendModeFilter(mBlendModeFilter, tint,
682                       mBitmapState.mBlendMode);
683             invalidateSelf();
684         }
685     }
686 
687     @Override
setTintBlendMode(@onNull BlendMode blendMode)688     public void setTintBlendMode(@NonNull BlendMode blendMode) {
689         final BitmapState state = mBitmapState;
690         if (state.mBlendMode != blendMode) {
691             state.mBlendMode = blendMode;
692             mBlendModeFilter = updateBlendModeFilter(mBlendModeFilter, mBitmapState.mTint,
693                     blendMode);
694             invalidateSelf();
695         }
696     }
697 
698     /**
699      * @hide only needed by a hack within ProgressBar
700      */
701     @UnsupportedAppUsage
getTint()702     public ColorStateList getTint() {
703         return mBitmapState.mTint;
704     }
705 
706     /**
707      * @hide only needed by a hack within ProgressBar
708      */
709     @UnsupportedAppUsage
getTintMode()710     public Mode getTintMode() {
711         return BlendMode.blendModeToPorterDuffMode(mBitmapState.mBlendMode);
712     }
713 
714     /**
715      * @hide Candidate for future API inclusion
716      */
717     @Override
setXfermode(Xfermode xfermode)718     public void setXfermode(Xfermode xfermode) {
719         mBitmapState.mPaint.setXfermode(xfermode);
720         invalidateSelf();
721     }
722 
723     /**
724      * A mutable BitmapDrawable still shares its Bitmap with any other Drawable
725      * that comes from the same resource.
726      *
727      * @return This drawable.
728      */
729     @Override
mutate()730     public Drawable mutate() {
731         if (!mMutated && super.mutate() == this) {
732             mBitmapState = new BitmapState(mBitmapState);
733             mMutated = true;
734         }
735         return this;
736     }
737 
738     /**
739      * @hide
740      */
clearMutated()741     public void clearMutated() {
742         super.clearMutated();
743         mMutated = false;
744     }
745 
746     @Override
onStateChange(int[] stateSet)747     protected boolean onStateChange(int[] stateSet) {
748         final BitmapState state = mBitmapState;
749         if (state.mTint != null && state.mBlendMode != null) {
750             mBlendModeFilter = updateBlendModeFilter(mBlendModeFilter, state.mTint,
751                     state.mBlendMode);
752             return true;
753         }
754         return false;
755     }
756 
757     @Override
isStateful()758     public boolean isStateful() {
759         return (mBitmapState.mTint != null && mBitmapState.mTint.isStateful())
760                 || super.isStateful();
761     }
762 
763     /** @hide */
764     @Override
hasFocusStateSpecified()765     public boolean hasFocusStateSpecified() {
766         return mBitmapState.mTint != null && mBitmapState.mTint.hasFocusStateSpecified();
767     }
768 
769     @Override
inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)770     public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
771             throws XmlPullParserException, IOException {
772         super.inflate(r, parser, attrs, theme);
773 
774         final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.BitmapDrawable);
775         updateStateFromTypedArray(a, mSrcDensityOverride);
776         verifyRequiredAttributes(a);
777         a.recycle();
778 
779         // Update local properties.
780         updateLocalState(r);
781     }
782 
783     /**
784      * Ensures all required attributes are set.
785      *
786      * @throws XmlPullParserException if any required attributes are missing
787      */
verifyRequiredAttributes(TypedArray a)788     private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException {
789         // If we're not waiting on a theme, verify required attributes.
790         final BitmapState state = mBitmapState;
791         if (state.mBitmap == null && (state.mThemeAttrs == null
792                 || state.mThemeAttrs[R.styleable.BitmapDrawable_src] == 0)) {
793             throw new XmlPullParserException(a.getPositionDescription() +
794                     ": <bitmap> requires a valid 'src' attribute");
795         }
796     }
797 
798     /**
799      * Updates the constant state from the values in the typed array.
800      */
updateStateFromTypedArray(TypedArray a, int srcDensityOverride)801     private void updateStateFromTypedArray(TypedArray a, int srcDensityOverride)
802             throws XmlPullParserException {
803         final Resources r = a.getResources();
804         final BitmapState state = mBitmapState;
805 
806         // Account for any configuration changes.
807         state.mChangingConfigurations |= a.getChangingConfigurations();
808 
809         // Extract the theme attributes, if any.
810         state.mThemeAttrs = a.extractThemeAttrs();
811 
812         state.mSrcDensityOverride = srcDensityOverride;
813 
814         state.mTargetDensity = Drawable.resolveDensity(r, 0);
815 
816         final int srcResId = a.getResourceId(R.styleable.BitmapDrawable_src, 0);
817         if (srcResId != 0) {
818             final TypedValue value = new TypedValue();
819             r.getValueForDensity(srcResId, srcDensityOverride, value, true);
820 
821             // Pretend the requested density is actually the display density. If
822             // the drawable returned is not the requested density, then force it
823             // to be scaled later by dividing its density by the ratio of
824             // requested density to actual device density. Drawables that have
825             // undefined density or no density don't need to be handled here.
826             if (srcDensityOverride > 0 && value.density > 0
827                     && value.density != TypedValue.DENSITY_NONE) {
828                 if (value.density == srcDensityOverride) {
829                     value.density = r.getDisplayMetrics().densityDpi;
830                 } else {
831                     value.density =
832                             (value.density * r.getDisplayMetrics().densityDpi) / srcDensityOverride;
833                 }
834             }
835 
836             int density = Bitmap.DENSITY_NONE;
837             if (value.density == TypedValue.DENSITY_DEFAULT) {
838                 density = DisplayMetrics.DENSITY_DEFAULT;
839             } else if (value.density != TypedValue.DENSITY_NONE) {
840                 density = value.density;
841             }
842 
843             Bitmap bitmap = null;
844             try (InputStream is = r.openRawResource(srcResId, value)) {
845                 ImageDecoder.Source source = ImageDecoder.createSource(r, is, density);
846                 bitmap = ImageDecoder.decodeBitmap(source, (decoder, info, src) -> {
847                     decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
848                 });
849             } catch (Exception e) {
850                 // Do nothing and pick up the error below.
851             }
852 
853             if (bitmap == null) {
854                 throw new XmlPullParserException(a.getPositionDescription() +
855                         ": <bitmap> requires a valid 'src' attribute");
856             }
857 
858             state.mBitmap = bitmap;
859         }
860 
861         final boolean defMipMap = state.mBitmap != null ? state.mBitmap.hasMipMap() : false;
862         setMipMap(a.getBoolean(R.styleable.BitmapDrawable_mipMap, defMipMap));
863 
864         state.mAutoMirrored = a.getBoolean(
865                 R.styleable.BitmapDrawable_autoMirrored, state.mAutoMirrored);
866         state.mBaseAlpha = a.getFloat(R.styleable.BitmapDrawable_alpha, state.mBaseAlpha);
867 
868         final int tintMode = a.getInt(R.styleable.BitmapDrawable_tintMode, -1);
869         if (tintMode != -1) {
870             state.mBlendMode = Drawable.parseBlendMode(tintMode, BlendMode.SRC_IN);
871         }
872 
873         final ColorStateList tint = a.getColorStateList(R.styleable.BitmapDrawable_tint);
874         if (tint != null) {
875             state.mTint = tint;
876         }
877 
878         final Paint paint = mBitmapState.mPaint;
879         paint.setAntiAlias(a.getBoolean(
880                 R.styleable.BitmapDrawable_antialias, paint.isAntiAlias()));
881         paint.setFilterBitmap(a.getBoolean(
882                 R.styleable.BitmapDrawable_filter, paint.isFilterBitmap()));
883         paint.setDither(a.getBoolean(R.styleable.BitmapDrawable_dither, paint.isDither()));
884 
885         setGravity(a.getInt(R.styleable.BitmapDrawable_gravity, state.mGravity));
886 
887         final int tileMode = a.getInt(R.styleable.BitmapDrawable_tileMode, TILE_MODE_UNDEFINED);
888         if (tileMode != TILE_MODE_UNDEFINED) {
889             final Shader.TileMode mode = parseTileMode(tileMode);
890             setTileModeXY(mode, mode);
891         }
892 
893         final int tileModeX = a.getInt(R.styleable.BitmapDrawable_tileModeX, TILE_MODE_UNDEFINED);
894         if (tileModeX != TILE_MODE_UNDEFINED) {
895             setTileModeX(parseTileMode(tileModeX));
896         }
897 
898         final int tileModeY = a.getInt(R.styleable.BitmapDrawable_tileModeY, TILE_MODE_UNDEFINED);
899         if (tileModeY != TILE_MODE_UNDEFINED) {
900             setTileModeY(parseTileMode(tileModeY));
901         }
902     }
903 
904     @Override
applyTheme(Theme t)905     public void applyTheme(Theme t) {
906         super.applyTheme(t);
907 
908         final BitmapState state = mBitmapState;
909         if (state == null) {
910             return;
911         }
912 
913         if (state.mThemeAttrs != null) {
914             final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.BitmapDrawable);
915             try {
916                 updateStateFromTypedArray(a, state.mSrcDensityOverride);
917             } catch (XmlPullParserException e) {
918                 rethrowAsRuntimeException(e);
919             } finally {
920                 a.recycle();
921             }
922         }
923 
924         // Apply theme to contained color state list.
925         if (state.mTint != null && state.mTint.canApplyTheme()) {
926             state.mTint = state.mTint.obtainForTheme(t);
927         }
928 
929         // Update local properties.
930         updateLocalState(t.getResources());
931     }
932 
parseTileMode(int tileMode)933     private static Shader.TileMode parseTileMode(int tileMode) {
934         switch (tileMode) {
935             case TILE_MODE_CLAMP:
936                 return Shader.TileMode.CLAMP;
937             case TILE_MODE_REPEAT:
938                 return Shader.TileMode.REPEAT;
939             case TILE_MODE_MIRROR:
940                 return Shader.TileMode.MIRROR;
941             default:
942                 return null;
943         }
944     }
945 
946     @Override
canApplyTheme()947     public boolean canApplyTheme() {
948         return mBitmapState != null && mBitmapState.canApplyTheme();
949     }
950 
951     @Override
getIntrinsicWidth()952     public int getIntrinsicWidth() {
953         return mBitmapWidth;
954     }
955 
956     @Override
getIntrinsicHeight()957     public int getIntrinsicHeight() {
958         return mBitmapHeight;
959     }
960 
961     @Override
getOpacity()962     public int getOpacity() {
963         if (mBitmapState.mGravity != Gravity.FILL) {
964             return PixelFormat.TRANSLUCENT;
965         }
966 
967         final Bitmap bitmap = mBitmapState.mBitmap;
968         return (bitmap == null || bitmap.hasAlpha() || mBitmapState.mPaint.getAlpha() < 255) ?
969                 PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE;
970     }
971 
972     @Override
getConstantState()973     public final ConstantState getConstantState() {
974         mBitmapState.mChangingConfigurations |= getChangingConfigurations();
975         return mBitmapState;
976     }
977 
978     final static class BitmapState extends ConstantState {
979         final Paint mPaint;
980 
981         // Values loaded during inflation.
982         int[] mThemeAttrs = null;
983         Bitmap mBitmap = null;
984         ColorStateList mTint = null;
985         BlendMode mBlendMode = DEFAULT_BLEND_MODE;
986 
987         int mGravity = Gravity.FILL;
988         float mBaseAlpha = 1.0f;
989         Shader.TileMode mTileModeX = null;
990         Shader.TileMode mTileModeY = null;
991 
992         // The density to use when looking up the bitmap in Resources. A value of 0 means use
993         // the system's density.
994         int mSrcDensityOverride = 0;
995 
996         // The density at which to render the bitmap.
997         int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT;
998 
999         boolean mAutoMirrored = false;
1000 
1001         @Config int mChangingConfigurations;
1002         boolean mRebuildShader;
1003 
BitmapState(Bitmap bitmap)1004         BitmapState(Bitmap bitmap) {
1005             mBitmap = bitmap;
1006             mPaint = new Paint(DEFAULT_PAINT_FLAGS);
1007         }
1008 
BitmapState(BitmapState bitmapState)1009         BitmapState(BitmapState bitmapState) {
1010             mBitmap = bitmapState.mBitmap;
1011             mTint = bitmapState.mTint;
1012             mBlendMode = bitmapState.mBlendMode;
1013             mThemeAttrs = bitmapState.mThemeAttrs;
1014             mChangingConfigurations = bitmapState.mChangingConfigurations;
1015             mGravity = bitmapState.mGravity;
1016             mTileModeX = bitmapState.mTileModeX;
1017             mTileModeY = bitmapState.mTileModeY;
1018             mSrcDensityOverride = bitmapState.mSrcDensityOverride;
1019             mTargetDensity = bitmapState.mTargetDensity;
1020             mBaseAlpha = bitmapState.mBaseAlpha;
1021             mPaint = new Paint(bitmapState.mPaint);
1022             mRebuildShader = bitmapState.mRebuildShader;
1023             mAutoMirrored = bitmapState.mAutoMirrored;
1024         }
1025 
1026         @Override
canApplyTheme()1027         public boolean canApplyTheme() {
1028             return mThemeAttrs != null || mTint != null && mTint.canApplyTheme();
1029         }
1030 
1031         @Override
newDrawable()1032         public Drawable newDrawable() {
1033             return new BitmapDrawable(this, null);
1034         }
1035 
1036         @Override
newDrawable(Resources res)1037         public Drawable newDrawable(Resources res) {
1038             return new BitmapDrawable(this, res);
1039         }
1040 
1041         @Override
getChangingConfigurations()1042         public @Config int getChangingConfigurations() {
1043             return mChangingConfigurations
1044                     | (mTint != null ? mTint.getChangingConfigurations() : 0);
1045         }
1046     }
1047 
BitmapDrawable(BitmapState state, Resources res)1048     private BitmapDrawable(BitmapState state, Resources res) {
1049         init(state, res);
1050     }
1051 
1052     /**
1053      * The one helper to rule them all. This is called by all public & private
1054      * constructors to set the state and initialize local properties.
1055      */
init(BitmapState state, Resources res)1056     private void init(BitmapState state, Resources res) {
1057         mBitmapState = state;
1058         updateLocalState(res);
1059 
1060         if (mBitmapState != null && res != null) {
1061             mBitmapState.mTargetDensity = mTargetDensity;
1062         }
1063     }
1064 
1065     /**
1066      * Initializes local dynamic properties from state. This should be called
1067      * after significant state changes, e.g. from the One True Constructor and
1068      * after inflating or applying a theme.
1069      */
updateLocalState(Resources res)1070     private void updateLocalState(Resources res) {
1071         mTargetDensity = resolveDensity(res, mBitmapState.mTargetDensity);
1072         mBlendModeFilter = updateBlendModeFilter(mBlendModeFilter, mBitmapState.mTint,
1073                 mBitmapState.mBlendMode);
1074         computeBitmapSize();
1075     }
1076 }
1077