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