• 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.TypedValue;
47 import android.view.Gravity;
48 
49 import com.android.internal.R;
50 
51 import org.xmlpull.v1.XmlPullParser;
52 import org.xmlpull.v1.XmlPullParserException;
53 
54 import java.io.FileInputStream;
55 import java.io.IOException;
56 import java.io.InputStream;
57 
58 /**
59  * A Drawable that wraps a bitmap and can be tiled, stretched, or aligned. You can create a
60  * BitmapDrawable from a file path, an input stream, through XML inflation, or from
61  * a {@link android.graphics.Bitmap} object.
62  * <p>It can be defined in an XML file with the <code>&lt;bitmap></code> element.  For more
63  * information, see the guide to <a
64  * href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p>
65  * <p>
66  * Also see the {@link android.graphics.Bitmap} class, which handles the management and
67  * transformation of raw bitmap graphics, and should be used when drawing to a
68  * {@link android.graphics.Canvas}.
69  * </p>
70  *
71  * @attr ref android.R.styleable#BitmapDrawable_src
72  * @attr ref android.R.styleable#BitmapDrawable_antialias
73  * @attr ref android.R.styleable#BitmapDrawable_filter
74  * @attr ref android.R.styleable#BitmapDrawable_dither
75  * @attr ref android.R.styleable#BitmapDrawable_gravity
76  * @attr ref android.R.styleable#BitmapDrawable_mipMap
77  * @attr ref android.R.styleable#BitmapDrawable_tileMode
78  */
79 public class BitmapDrawable extends Drawable {
80     private static final int DEFAULT_PAINT_FLAGS =
81             Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG;
82 
83     // Constants for {@link android.R.styleable#BitmapDrawable_tileMode}.
84     private static final int TILE_MODE_UNDEFINED = -2;
85     private static final int TILE_MODE_DISABLED = -1;
86     private static final int TILE_MODE_CLAMP = 0;
87     private static final int TILE_MODE_REPEAT = 1;
88     private static final int TILE_MODE_MIRROR = 2;
89 
90     private final Rect mDstRect = new Rect();   // #updateDstRectAndInsetsIfDirty() sets this
91 
92     @UnsupportedAppUsage
93     private BitmapState mBitmapState;
94     private BlendModeColorFilter mBlendModeFilter;
95 
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     /**
243      * Switch to a new Bitmap object.
244      */
setBitmap(@ullable Bitmap bitmap)245     public void setBitmap(@Nullable Bitmap bitmap) {
246         if (mBitmapState.mBitmap != bitmap) {
247             mBitmapState.mBitmap = bitmap;
248             computeBitmapSize();
249             invalidateSelf();
250         }
251     }
252 
253     /**
254      * Set the density scale at which this drawable will be rendered. This
255      * method assumes the drawable will be rendered at the same density as the
256      * specified canvas.
257      *
258      * @param canvas The Canvas from which the density scale must be obtained.
259      *
260      * @see android.graphics.Bitmap#setDensity(int)
261      * @see android.graphics.Bitmap#getDensity()
262      */
setTargetDensity(Canvas canvas)263     public void setTargetDensity(Canvas canvas) {
264         setTargetDensity(canvas.getDensity());
265     }
266 
267     /**
268      * Set the density scale at which this drawable will be rendered.
269      *
270      * @param metrics The DisplayMetrics indicating the density scale for this drawable.
271      *
272      * @see android.graphics.Bitmap#setDensity(int)
273      * @see android.graphics.Bitmap#getDensity()
274      */
setTargetDensity(DisplayMetrics metrics)275     public void setTargetDensity(DisplayMetrics metrics) {
276         setTargetDensity(metrics.densityDpi);
277     }
278 
279     /**
280      * Set the density at which this drawable will be rendered.
281      *
282      * @param density The density scale for this drawable.
283      *
284      * @see android.graphics.Bitmap#setDensity(int)
285      * @see android.graphics.Bitmap#getDensity()
286      */
setTargetDensity(int density)287     public void setTargetDensity(int density) {
288         if (mTargetDensity != density) {
289             mTargetDensity = density == 0 ? DisplayMetrics.DENSITY_DEFAULT : density;
290             if (mBitmapState.mBitmap != null) {
291                 computeBitmapSize();
292             }
293             invalidateSelf();
294         }
295     }
296 
297     /** Get the gravity used to position/stretch the bitmap within its bounds.
298      * See android.view.Gravity
299      * @return the gravity applied to the bitmap
300      */
getGravity()301     public int getGravity() {
302         return mBitmapState.mGravity;
303     }
304 
305     /** Set the gravity used to position/stretch the bitmap within its bounds.
306         See android.view.Gravity
307      * @param gravity the gravity
308      */
setGravity(int gravity)309     public void setGravity(int gravity) {
310         if (mBitmapState.mGravity != gravity) {
311             mBitmapState.mGravity = gravity;
312             mDstRectAndInsetsDirty = true;
313             invalidateSelf();
314         }
315     }
316 
317     /**
318      * Enables or disables the mipmap hint for this drawable's bitmap.
319      * See {@link Bitmap#setHasMipMap(boolean)} for more information.
320      *
321      * If the bitmap is null calling this method has no effect.
322      *
323      * @param mipMap True if the bitmap should use mipmaps, false otherwise.
324      *
325      * @see #hasMipMap()
326      */
setMipMap(boolean mipMap)327     public void setMipMap(boolean mipMap) {
328         if (mBitmapState.mBitmap != null) {
329             mBitmapState.mBitmap.setHasMipMap(mipMap);
330             invalidateSelf();
331         }
332     }
333 
334     /**
335      * Indicates whether the mipmap hint is enabled on this drawable's bitmap.
336      *
337      * @return True if the mipmap hint is set, false otherwise. If the bitmap
338      *         is null, this method always returns false.
339      *
340      * @see #setMipMap(boolean)
341      * @attr ref android.R.styleable#BitmapDrawable_mipMap
342      */
hasMipMap()343     public boolean hasMipMap() {
344         return mBitmapState.mBitmap != null && mBitmapState.mBitmap.hasMipMap();
345     }
346 
347     /**
348      * Enables or disables anti-aliasing for this drawable. Anti-aliasing affects
349      * the edges of the bitmap only so it applies only when the drawable is rotated.
350      *
351      * @param aa True if the bitmap should be anti-aliased, false otherwise.
352      *
353      * @see #hasAntiAlias()
354      */
setAntiAlias(boolean aa)355     public void setAntiAlias(boolean aa) {
356         mBitmapState.mPaint.setAntiAlias(aa);
357         invalidateSelf();
358     }
359 
360     /**
361      * Indicates whether anti-aliasing is enabled for this drawable.
362      *
363      * @return True if anti-aliasing is enabled, false otherwise.
364      *
365      * @see #setAntiAlias(boolean)
366      */
hasAntiAlias()367     public boolean hasAntiAlias() {
368         return mBitmapState.mPaint.isAntiAlias();
369     }
370 
371     @Override
setFilterBitmap(boolean filter)372     public void setFilterBitmap(boolean filter) {
373         mBitmapState.mPaint.setFilterBitmap(filter);
374         invalidateSelf();
375     }
376 
377     @Override
isFilterBitmap()378     public boolean isFilterBitmap() {
379         return mBitmapState.mPaint.isFilterBitmap();
380     }
381 
382     @Override
setDither(boolean dither)383     public void setDither(boolean dither) {
384         mBitmapState.mPaint.setDither(dither);
385         invalidateSelf();
386     }
387 
388     /**
389      * Indicates the repeat behavior of this drawable on the X axis.
390      *
391      * @return {@link android.graphics.Shader.TileMode#CLAMP} if the bitmap does not repeat,
392      *         {@link android.graphics.Shader.TileMode#REPEAT} or
393      *         {@link android.graphics.Shader.TileMode#MIRROR} otherwise.
394      */
getTileModeX()395     public Shader.TileMode getTileModeX() {
396         return mBitmapState.mTileModeX;
397     }
398 
399     /**
400      * Indicates the repeat behavior of this drawable on the Y axis.
401      *
402      * @return {@link android.graphics.Shader.TileMode#CLAMP} if the bitmap does not repeat,
403      *         {@link android.graphics.Shader.TileMode#REPEAT} or
404      *         {@link android.graphics.Shader.TileMode#MIRROR} otherwise.
405      */
getTileModeY()406     public Shader.TileMode getTileModeY() {
407         return mBitmapState.mTileModeY;
408     }
409 
410     /**
411      * Sets the repeat behavior of this drawable on the X axis. By default, the drawable
412      * does not repeat its bitmap. Using {@link android.graphics.Shader.TileMode#REPEAT} or
413      * {@link android.graphics.Shader.TileMode#MIRROR} the bitmap can be repeated (or tiled)
414      * if the bitmap is smaller than this drawable.
415      *
416      * @param mode The repeat mode for this drawable.
417      *
418      * @see #setTileModeY(android.graphics.Shader.TileMode)
419      * @see #setTileModeXY(android.graphics.Shader.TileMode, android.graphics.Shader.TileMode)
420      * @attr ref android.R.styleable#BitmapDrawable_tileModeX
421      */
setTileModeX(Shader.TileMode mode)422     public void setTileModeX(Shader.TileMode mode) {
423         setTileModeXY(mode, mBitmapState.mTileModeY);
424     }
425 
426     /**
427      * Sets the repeat behavior of this drawable on the Y axis. By default, the drawable
428      * does not repeat its bitmap. Using {@link android.graphics.Shader.TileMode#REPEAT} or
429      * {@link android.graphics.Shader.TileMode#MIRROR} the bitmap can be repeated (or tiled)
430      * if the bitmap is smaller than this drawable.
431      *
432      * @param mode The repeat mode for this drawable.
433      *
434      * @see #setTileModeX(android.graphics.Shader.TileMode)
435      * @see #setTileModeXY(android.graphics.Shader.TileMode, android.graphics.Shader.TileMode)
436      * @attr ref android.R.styleable#BitmapDrawable_tileModeY
437      */
setTileModeY(Shader.TileMode mode)438     public final void setTileModeY(Shader.TileMode mode) {
439         setTileModeXY(mBitmapState.mTileModeX, mode);
440     }
441 
442     /**
443      * Sets the repeat behavior of this drawable on both axis. By default, the drawable
444      * does not repeat its bitmap. Using {@link android.graphics.Shader.TileMode#REPEAT} or
445      * {@link android.graphics.Shader.TileMode#MIRROR} the bitmap can be repeated (or tiled)
446      * if the bitmap is smaller than this drawable.
447      *
448      * @param xmode The X repeat mode for this drawable.
449      * @param ymode The Y repeat mode for this drawable.
450      *
451      * @see #setTileModeX(android.graphics.Shader.TileMode)
452      * @see #setTileModeY(android.graphics.Shader.TileMode)
453      */
setTileModeXY(Shader.TileMode xmode, Shader.TileMode ymode)454     public void setTileModeXY(Shader.TileMode xmode, Shader.TileMode ymode) {
455         final BitmapState state = mBitmapState;
456         if (state.mTileModeX != xmode || state.mTileModeY != ymode) {
457             state.mTileModeX = xmode;
458             state.mTileModeY = ymode;
459             state.mRebuildShader = true;
460             mDstRectAndInsetsDirty = true;
461             invalidateSelf();
462         }
463     }
464 
465     @Override
setAutoMirrored(boolean mirrored)466     public void setAutoMirrored(boolean mirrored) {
467         if (mBitmapState.mAutoMirrored != mirrored) {
468             mBitmapState.mAutoMirrored = mirrored;
469             invalidateSelf();
470         }
471     }
472 
473     @Override
isAutoMirrored()474     public final boolean isAutoMirrored() {
475         return mBitmapState.mAutoMirrored;
476     }
477 
478     @Override
getChangingConfigurations()479     public @Config int getChangingConfigurations() {
480         return super.getChangingConfigurations() | mBitmapState.getChangingConfigurations();
481     }
482 
needMirroring()483     private boolean needMirroring() {
484         return isAutoMirrored() && getLayoutDirection() == LayoutDirection.RTL;
485     }
486 
487     @Override
onBoundsChange(Rect bounds)488     protected void onBoundsChange(Rect bounds) {
489         mDstRectAndInsetsDirty = true;
490 
491         final Bitmap bitmap = mBitmapState.mBitmap;
492         final Shader shader = mBitmapState.mPaint.getShader();
493         if (bitmap != null && shader != null) {
494             updateShaderMatrix(bitmap, mBitmapState.mPaint, shader, needMirroring());
495         }
496     }
497 
498     @Override
draw(Canvas canvas)499     public void draw(Canvas canvas) {
500         final Bitmap bitmap = mBitmapState.mBitmap;
501         if (bitmap == null) {
502             return;
503         }
504 
505         final BitmapState state = mBitmapState;
506         final Paint paint = state.mPaint;
507         if (state.mRebuildShader) {
508             final Shader.TileMode tmx = state.mTileModeX;
509             final Shader.TileMode tmy = state.mTileModeY;
510             if (tmx == null && tmy == null) {
511                 paint.setShader(null);
512             } else {
513                 paint.setShader(new BitmapShader(bitmap,
514                         tmx == null ? Shader.TileMode.CLAMP : tmx,
515                         tmy == null ? Shader.TileMode.CLAMP : tmy));
516             }
517 
518             state.mRebuildShader = false;
519         }
520 
521         final int restoreAlpha;
522         if (state.mBaseAlpha != 1.0f) {
523             final Paint p = getPaint();
524             restoreAlpha = p.getAlpha();
525             p.setAlpha((int) (restoreAlpha * state.mBaseAlpha + 0.5f));
526         } else {
527             restoreAlpha = -1;
528         }
529 
530         final boolean clearColorFilter;
531         if (mBlendModeFilter != null && paint.getColorFilter() == null) {
532             paint.setColorFilter(mBlendModeFilter);
533             clearColorFilter = true;
534         } else {
535             clearColorFilter = false;
536         }
537 
538         updateDstRectAndInsetsIfDirty();
539         final Shader shader = paint.getShader();
540         final boolean needMirroring = needMirroring();
541         if (shader == null) {
542             if (needMirroring) {
543                 canvas.save();
544                 // Mirror the bitmap
545                 canvas.translate(mDstRect.right - mDstRect.left, 0);
546                 canvas.scale(-1.0f, 1.0f);
547             }
548 
549             canvas.drawBitmap(bitmap, null, mDstRect, paint);
550 
551             if (needMirroring) {
552                 canvas.restore();
553             }
554         } else {
555             updateShaderMatrix(bitmap, paint, shader, needMirroring);
556             canvas.drawRect(mDstRect, paint);
557         }
558 
559         if (clearColorFilter) {
560             paint.setColorFilter(null);
561         }
562 
563         if (restoreAlpha >= 0) {
564             paint.setAlpha(restoreAlpha);
565         }
566     }
567 
568     /**
569      * Updates the {@code paint}'s shader matrix to be consistent with the
570      * destination size and layout direction.
571      *
572      * @param bitmap the bitmap to be drawn
573      * @param paint the paint used to draw the bitmap
574      * @param shader the shader to set on the paint
575      * @param needMirroring whether the bitmap should be mirrored
576      */
updateShaderMatrix(@onNull Bitmap bitmap, @NonNull Paint paint, @NonNull Shader shader, boolean needMirroring)577     private void updateShaderMatrix(@NonNull Bitmap bitmap, @NonNull Paint paint,
578             @NonNull Shader shader, boolean needMirroring) {
579         final int sourceDensity = bitmap.getDensity();
580         final int targetDensity = mTargetDensity;
581         final boolean needScaling = sourceDensity != 0 && sourceDensity != targetDensity;
582         if (needScaling || needMirroring) {
583             final Matrix matrix = getOrCreateMirrorMatrix();
584             matrix.reset();
585 
586             if (needMirroring) {
587                 final int dx = mDstRect.right - mDstRect.left;
588                 matrix.setTranslate(dx, 0);
589                 matrix.setScale(-1, 1);
590             }
591 
592             if (needScaling) {
593                 final float densityScale = targetDensity / (float) sourceDensity;
594                 matrix.postScale(densityScale, densityScale);
595             }
596 
597             shader.setLocalMatrix(matrix);
598         } else {
599             mMirrorMatrix = null;
600             shader.setLocalMatrix(Matrix.IDENTITY_MATRIX);
601         }
602 
603         paint.setShader(shader);
604     }
605 
getOrCreateMirrorMatrix()606     private Matrix getOrCreateMirrorMatrix() {
607         if (mMirrorMatrix == null) {
608             mMirrorMatrix = new Matrix();
609         }
610         return mMirrorMatrix;
611     }
612 
updateDstRectAndInsetsIfDirty()613     private void updateDstRectAndInsetsIfDirty() {
614         if (mDstRectAndInsetsDirty) {
615             if (mBitmapState.mTileModeX == null && mBitmapState.mTileModeY == null) {
616                 final Rect bounds = getBounds();
617                 final int layoutDirection = getLayoutDirection();
618                 Gravity.apply(mBitmapState.mGravity, mBitmapWidth, mBitmapHeight,
619                         bounds, mDstRect, layoutDirection);
620 
621                 final int left = mDstRect.left - bounds.left;
622                 final int top = mDstRect.top - bounds.top;
623                 final int right = bounds.right - mDstRect.right;
624                 final int bottom = bounds.bottom - mDstRect.bottom;
625                 mOpticalInsets = Insets.of(left, top, right, bottom);
626             } else {
627                 copyBounds(mDstRect);
628                 mOpticalInsets = Insets.NONE;
629             }
630         }
631         mDstRectAndInsetsDirty = false;
632     }
633 
634     @Override
getOpticalInsets()635     public Insets getOpticalInsets() {
636         updateDstRectAndInsetsIfDirty();
637         return mOpticalInsets;
638     }
639 
640     @Override
getOutline(@onNull Outline outline)641     public void getOutline(@NonNull Outline outline) {
642         updateDstRectAndInsetsIfDirty();
643         outline.setRect(mDstRect);
644 
645         // Only opaque Bitmaps can report a non-0 alpha,
646         // since only they are guaranteed to fill their bounds
647         boolean opaqueOverShape = mBitmapState.mBitmap != null
648                 && !mBitmapState.mBitmap.hasAlpha();
649         outline.setAlpha(opaqueOverShape ? getAlpha() / 255.0f : 0.0f);
650     }
651 
652     @Override
setAlpha(int alpha)653     public void setAlpha(int alpha) {
654         final int oldAlpha = mBitmapState.mPaint.getAlpha();
655         if (alpha != oldAlpha) {
656             mBitmapState.mPaint.setAlpha(alpha);
657             invalidateSelf();
658         }
659     }
660 
661     @Override
getAlpha()662     public int getAlpha() {
663         return mBitmapState.mPaint.getAlpha();
664     }
665 
666     @Override
setColorFilter(ColorFilter colorFilter)667     public void setColorFilter(ColorFilter colorFilter) {
668         mBitmapState.mPaint.setColorFilter(colorFilter);
669         invalidateSelf();
670     }
671 
672     @Override
getColorFilter()673     public ColorFilter getColorFilter() {
674         return mBitmapState.mPaint.getColorFilter();
675     }
676 
677     @Override
setTintList(ColorStateList tint)678     public void setTintList(ColorStateList tint) {
679         final BitmapState state = mBitmapState;
680         if (state.mTint != tint) {
681             state.mTint = tint;
682             mBlendModeFilter = updateBlendModeFilter(mBlendModeFilter, tint,
683                       mBitmapState.mBlendMode);
684             invalidateSelf();
685         }
686     }
687 
688     @Override
setTintBlendMode(@onNull BlendMode blendMode)689     public void setTintBlendMode(@NonNull BlendMode blendMode) {
690         final BitmapState state = mBitmapState;
691         if (state.mBlendMode != blendMode) {
692             state.mBlendMode = blendMode;
693             mBlendModeFilter = updateBlendModeFilter(mBlendModeFilter, mBitmapState.mTint,
694                     blendMode);
695             invalidateSelf();
696         }
697     }
698 
699     /**
700      * No longer needed by ProgressBar, but still here due to UnsupportedAppUsage.
701      */
702     @UnsupportedAppUsage
getTint()703     private ColorStateList getTint() {
704         return mBitmapState.mTint;
705     }
706 
707     /**
708      * No longer needed by ProgressBar, but still here due to UnsupportedAppUsage.
709      */
710     @UnsupportedAppUsage
getTintMode()711     private Mode getTintMode() {
712         return BlendMode.blendModeToPorterDuffMode(mBitmapState.mBlendMode);
713     }
714 
715     /**
716      * @hide Candidate for future API inclusion
717      */
718     @Override
setXfermode(Xfermode xfermode)719     public void setXfermode(Xfermode xfermode) {
720         mBitmapState.mPaint.setXfermode(xfermode);
721         invalidateSelf();
722     }
723 
724     /**
725      * A mutable BitmapDrawable still shares its Bitmap with any other Drawable
726      * that comes from the same resource.
727      *
728      * @return This drawable.
729      */
730     @Override
mutate()731     public Drawable mutate() {
732         if (!mMutated && super.mutate() == this) {
733             mBitmapState = new BitmapState(mBitmapState);
734             mMutated = true;
735         }
736         return this;
737     }
738 
739     /**
740      * @hide
741      */
clearMutated()742     public void clearMutated() {
743         super.clearMutated();
744         mMutated = false;
745     }
746 
747     @Override
onStateChange(int[] stateSet)748     protected boolean onStateChange(int[] stateSet) {
749         final BitmapState state = mBitmapState;
750         if (state.mTint != null && state.mBlendMode != null) {
751             mBlendModeFilter = updateBlendModeFilter(mBlendModeFilter, state.mTint,
752                     state.mBlendMode);
753             return true;
754         }
755         return false;
756     }
757 
758     @Override
isStateful()759     public boolean isStateful() {
760         return (mBitmapState.mTint != null && mBitmapState.mTint.isStateful())
761                 || super.isStateful();
762     }
763 
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