Home | History | Annotate | Download | only in drawable
      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
    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
    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
    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      */
    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
    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" })
    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
    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" })
    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      */
    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      */
    226     public final Bitmap getBitmap() {
    227         return mBitmapState.mBitmap;
    228     }
    229 
    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 */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    363     public boolean hasAntiAlias() {
    364         return mBitmapState.mPaint.isAntiAlias();
    365     }
    366 
    367     @Override
    368     public void setFilterBitmap(boolean filter) {
    369         mBitmapState.mPaint.setFilterBitmap(filter);
    370         invalidateSelf();
    371     }
    372 
    373     @Override
    374     public boolean isFilterBitmap() {
    375         return mBitmapState.mPaint.isFilterBitmap();
    376     }
    377 
    378     @Override
    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      */
    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      */
    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      */
    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      */
    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      */
    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
    462     public void setAutoMirrored(boolean mirrored) {
    463         if (mBitmapState.mAutoMirrored != mirrored) {
    464             mBitmapState.mAutoMirrored = mirrored;
    465             invalidateSelf();
    466         }
    467     }
    468 
    469     @Override
    470     public final boolean isAutoMirrored() {
    471         return mBitmapState.mAutoMirrored;
    472     }
    473 
    474     @Override
    475     public @Config int getChangingConfigurations() {
    476         return super.getChangingConfigurations() | mBitmapState.getChangingConfigurations();
    477     }
    478 
    479     private boolean needMirroring() {
    480         return isAutoMirrored() && getLayoutDirection() == LayoutDirection.RTL;
    481     }
    482 
    483     @Override
    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
    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      */
    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 
    602     private Matrix getOrCreateMirrorMatrix() {
    603         if (mMirrorMatrix == null) {
    604             mMirrorMatrix = new Matrix();
    605         }
    606         return mMirrorMatrix;
    607     }
    608 
    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
    634     public Insets getOpticalInsets() {
    635         updateDstRectAndInsetsIfDirty();
    636         return mOpticalInsets;
    637     }
    638 
    639     @Override
    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
    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
    661     public int getAlpha() {
    662         return mBitmapState.mPaint.getAlpha();
    663     }
    664 
    665     @Override
    666     public void setColorFilter(ColorFilter colorFilter) {
    667         mBitmapState.mPaint.setColorFilter(colorFilter);
    668         invalidateSelf();
    669     }
    670 
    671     @Override
    672     public ColorFilter getColorFilter() {
    673         return mBitmapState.mPaint.getColorFilter();
    674     }
    675 
    676     @Override
    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
    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      */
    699     public ColorStateList getTint() {
    700         return mBitmapState.mTint;
    701     }
    702 
    703     /**
    704      * @hide only needed by a hack within ProgressBar
    705      */
    706     public Mode getTintMode() {
    707         return mBitmapState.mTintMode;
    708     }
    709 
    710     /**
    711      * @hide Candidate for future API inclusion
    712      */
    713     @Override
    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
    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      */
    737     public void clearMutated() {
    738         super.clearMutated();
    739         mMutated = false;
    740     }
    741 
    742     @Override
    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
    753     public boolean isStateful() {
    754         return (mBitmapState.mTint != null && mBitmapState.mTint.isStateful())
    755                 || super.isStateful();
    756     }
    757 
    758     /** @hide */
    759     @Override
    760     public boolean hasFocusStateSpecified() {
    761         return mBitmapState.mTint != null && mBitmapState.mTint.hasFocusStateSpecified();
    762     }
    763 
    764     @Override
    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      */
    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      */
    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
    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 
    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
    942     public boolean canApplyTheme() {
    943         return mBitmapState != null && mBitmapState.canApplyTheme();
    944     }
    945 
    946     @Override
    947     public int getIntrinsicWidth() {
    948         return mBitmapWidth;
    949     }
    950 
    951     @Override
    952     public int getIntrinsicHeight() {
    953         return mBitmapHeight;
    954     }
    955 
    956     @Override
    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
    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 
    998         BitmapState(Bitmap bitmap) {
    999             mBitmap = bitmap;
   1000             mPaint = new Paint(DEFAULT_PAINT_FLAGS);
   1001         }
   1002 
   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
   1021         public boolean canApplyTheme() {
   1022             return mThemeAttrs != null || mTint != null && mTint.canApplyTheme();
   1023         }
   1024 
   1025         @Override
   1026         public Drawable newDrawable() {
   1027             return new BitmapDrawable(this, null);
   1028         }
   1029 
   1030         @Override
   1031         public Drawable newDrawable(Resources res) {
   1032             return new BitmapDrawable(this, res);
   1033         }
   1034 
   1035         @Override
   1036         public @Config int getChangingConfigurations() {
   1037             return mChangingConfigurations
   1038                     | (mTint != null ? mTint.getChangingConfigurations() : 0);
   1039         }
   1040     }
   1041 
   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      */
   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      */
   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