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