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.content.res.Resources;
     20 import android.content.res.TypedArray;
     21 import android.graphics.Bitmap;
     22 import android.graphics.BitmapFactory;
     23 import android.graphics.Canvas;
     24 import android.graphics.ColorFilter;
     25 import android.graphics.Paint;
     26 import android.graphics.PixelFormat;
     27 import android.graphics.Rect;
     28 import android.graphics.Shader;
     29 import android.graphics.BitmapShader;
     30 import android.util.AttributeSet;
     31 import android.util.DisplayMetrics;
     32 import android.view.Gravity;
     33 
     34 import org.xmlpull.v1.XmlPullParser;
     35 import org.xmlpull.v1.XmlPullParserException;
     36 
     37 import java.io.IOException;
     38 
     39 /**
     40  * A Drawable that wraps a bitmap and can be tiled, stretched, or aligned. You can create a
     41  * BitmapDrawable from a file path, an input stream, through XML inflation, or from
     42  * a {@link android.graphics.Bitmap} object.
     43  * <p>It can be defined in an XML file with the <code>&lt;bitmap></code> element.</p>
     44  * <p>
     45  * Also see the {@link android.graphics.Bitmap} class, which handles the management and
     46  * transformation of raw bitmap graphics, and should be used when drawing to a
     47  * {@link android.graphics.Canvas}.
     48  * </p>
     49  *
     50  * @attr ref android.R.styleable#BitmapDrawable_src
     51  * @attr ref android.R.styleable#BitmapDrawable_antialias
     52  * @attr ref android.R.styleable#BitmapDrawable_filter
     53  * @attr ref android.R.styleable#BitmapDrawable_dither
     54  * @attr ref android.R.styleable#BitmapDrawable_gravity
     55  * @attr ref android.R.styleable#BitmapDrawable_tileMode
     56  */
     57 public class BitmapDrawable extends Drawable {
     58 
     59     private static final int DEFAULT_PAINT_FLAGS =
     60             Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG;
     61     private BitmapState mBitmapState;
     62     private Bitmap mBitmap;
     63     private int mTargetDensity;
     64 
     65     private final Rect mDstRect = new Rect();   // Gravity.apply() sets this
     66 
     67     private boolean mApplyGravity;
     68     private boolean mRebuildShader;
     69     private boolean mMutated;
     70 
     71      // These are scaled to match the target density.
     72     private int mBitmapWidth;
     73     private int mBitmapHeight;
     74 
     75     /**
     76      * Create an empty drawable, not dealing with density.
     77      * @deprecated Use {@link #BitmapDrawable(Resources)} to ensure
     78      * that the drawable has correctly set its target density.
     79      */
     80     @Deprecated
     81     public BitmapDrawable() {
     82         mBitmapState = new BitmapState((Bitmap) null);
     83     }
     84 
     85     /**
     86      * Create an empty drawable, setting initial target density based on
     87      * the display metrics of the resources.
     88      */
     89     public BitmapDrawable(Resources res) {
     90         mBitmapState = new BitmapState((Bitmap) null);
     91         mBitmapState.mTargetDensity = mTargetDensity;
     92     }
     93 
     94     /**
     95      * Create drawable from a bitmap, not dealing with density.
     96      * @deprecated Use {@link #BitmapDrawable(Resources, Bitmap)} to ensure
     97      * that the drawable has correctly set its target density.
     98      */
     99     @Deprecated
    100     public BitmapDrawable(Bitmap bitmap) {
    101         this(new BitmapState(bitmap), null);
    102     }
    103 
    104     /**
    105      * Create drawable from a bitmap, setting initial target density based on
    106      * the display metrics of the resources.
    107      */
    108     public BitmapDrawable(Resources res, Bitmap bitmap) {
    109         this(new BitmapState(bitmap), res);
    110         mBitmapState.mTargetDensity = mTargetDensity;
    111     }
    112 
    113     /**
    114      * Create a drawable by opening a given file path and decoding the bitmap.
    115      * @deprecated Use {@link #BitmapDrawable(Resources, String)} to ensure
    116      * that the drawable has correctly set its target density.
    117      */
    118     @Deprecated
    119     public BitmapDrawable(String filepath) {
    120         this(new BitmapState(BitmapFactory.decodeFile(filepath)), null);
    121         if (mBitmap == null) {
    122             android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + filepath);
    123         }
    124     }
    125 
    126     /**
    127      * Create a drawable by opening a given file path and decoding the bitmap.
    128      */
    129     public BitmapDrawable(Resources res, String filepath) {
    130         this(new BitmapState(BitmapFactory.decodeFile(filepath)), null);
    131         mBitmapState.mTargetDensity = mTargetDensity;
    132         if (mBitmap == null) {
    133             android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + filepath);
    134         }
    135     }
    136 
    137     /**
    138      * Create a drawable by decoding a bitmap from the given input stream.
    139      * @deprecated Use {@link #BitmapDrawable(Resources, java.io.InputStream)} to ensure
    140      * that the drawable has correctly set its target density.
    141      */
    142     @Deprecated
    143     public BitmapDrawable(java.io.InputStream is) {
    144         this(new BitmapState(BitmapFactory.decodeStream(is)), null);
    145         if (mBitmap == null) {
    146             android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + is);
    147         }
    148     }
    149 
    150     /**
    151      * Create a drawable by decoding a bitmap from the given input stream.
    152      */
    153     public BitmapDrawable(Resources res, java.io.InputStream is) {
    154         this(new BitmapState(BitmapFactory.decodeStream(is)), null);
    155         mBitmapState.mTargetDensity = mTargetDensity;
    156         if (mBitmap == null) {
    157             android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + is);
    158         }
    159     }
    160 
    161     public final Paint getPaint() {
    162         return mBitmapState.mPaint;
    163     }
    164 
    165     public final Bitmap getBitmap() {
    166         return mBitmap;
    167     }
    168 
    169     private void computeBitmapSize() {
    170         mBitmapWidth = mBitmap.getScaledWidth(mTargetDensity);
    171         mBitmapHeight = mBitmap.getScaledHeight(mTargetDensity);
    172     }
    173 
    174     private void setBitmap(Bitmap bitmap) {
    175         mBitmap = bitmap;
    176         if (bitmap != null) {
    177             computeBitmapSize();
    178         } else {
    179             mBitmapWidth = mBitmapHeight = -1;
    180         }
    181     }
    182 
    183     /**
    184      * Set the density scale at which this drawable will be rendered. This
    185      * method assumes the drawable will be rendered at the same density as the
    186      * specified canvas.
    187      *
    188      * @param canvas The Canvas from which the density scale must be obtained.
    189      *
    190      * @see android.graphics.Bitmap#setDensity(int)
    191      * @see android.graphics.Bitmap#getDensity()
    192      */
    193     public void setTargetDensity(Canvas canvas) {
    194         setTargetDensity(canvas.getDensity());
    195     }
    196 
    197     /**
    198      * Set the density scale at which this drawable will be rendered.
    199      *
    200      * @param metrics The DisplayMetrics indicating the density scale for this drawable.
    201      *
    202      * @see android.graphics.Bitmap#setDensity(int)
    203      * @see android.graphics.Bitmap#getDensity()
    204      */
    205     public void setTargetDensity(DisplayMetrics metrics) {
    206         mTargetDensity = metrics.densityDpi;
    207         if (mBitmap != null) {
    208             computeBitmapSize();
    209         }
    210     }
    211 
    212     /**
    213      * Set the density at which this drawable will be rendered.
    214      *
    215      * @param density The density scale for this drawable.
    216      *
    217      * @see android.graphics.Bitmap#setDensity(int)
    218      * @see android.graphics.Bitmap#getDensity()
    219      */
    220     public void setTargetDensity(int density) {
    221         mTargetDensity = density == 0 ? DisplayMetrics.DENSITY_DEFAULT : density;
    222         if (mBitmap != null) {
    223             computeBitmapSize();
    224         }
    225     }
    226 
    227     /** Get the gravity used to position/stretch the bitmap within its bounds.
    228      * See android.view.Gravity
    229      * @return the gravity applied to the bitmap
    230      */
    231     public int getGravity() {
    232         return mBitmapState.mGravity;
    233     }
    234 
    235     /** Set the gravity used to position/stretch the bitmap within its bounds.
    236         See android.view.Gravity
    237      * @param gravity the gravity
    238      */
    239     public void setGravity(int gravity) {
    240         mBitmapState.mGravity = gravity;
    241         mApplyGravity = true;
    242     }
    243 
    244     public void setAntiAlias(boolean aa) {
    245         mBitmapState.mPaint.setAntiAlias(aa);
    246     }
    247 
    248     @Override
    249     public void setFilterBitmap(boolean filter) {
    250         mBitmapState.mPaint.setFilterBitmap(filter);
    251     }
    252 
    253     @Override
    254     public void setDither(boolean dither) {
    255         mBitmapState.mPaint.setDither(dither);
    256     }
    257 
    258     public Shader.TileMode getTileModeX() {
    259         return mBitmapState.mTileModeX;
    260     }
    261 
    262     public Shader.TileMode getTileModeY() {
    263         return mBitmapState.mTileModeY;
    264     }
    265 
    266     public void setTileModeX(Shader.TileMode mode) {
    267         setTileModeXY(mode, mBitmapState.mTileModeY);
    268     }
    269 
    270     public final void setTileModeY(Shader.TileMode mode) {
    271         setTileModeXY(mBitmapState.mTileModeX, mode);
    272     }
    273 
    274     public void setTileModeXY(Shader.TileMode xmode, Shader.TileMode ymode) {
    275         final BitmapState state = mBitmapState;
    276         if (state.mPaint.getShader() == null ||
    277                 state.mTileModeX != xmode || state.mTileModeY != ymode) {
    278             state.mTileModeX = xmode;
    279             state.mTileModeY = ymode;
    280             mRebuildShader = true;
    281         }
    282     }
    283 
    284     @Override
    285     public int getChangingConfigurations() {
    286         return super.getChangingConfigurations() | mBitmapState.mChangingConfigurations;
    287     }
    288 
    289     @Override
    290     protected void onBoundsChange(Rect bounds) {
    291         super.onBoundsChange(bounds);
    292         mApplyGravity = true;
    293     }
    294 
    295     @Override
    296     public void draw(Canvas canvas) {
    297         Bitmap bitmap = mBitmap;
    298         if (bitmap != null) {
    299             final BitmapState state = mBitmapState;
    300             if (mRebuildShader) {
    301                 Shader.TileMode tmx = state.mTileModeX;
    302                 Shader.TileMode tmy = state.mTileModeY;
    303 
    304                 if (tmx == null && tmy == null) {
    305                     state.mPaint.setShader(null);
    306                 } else {
    307                     Shader s = new BitmapShader(bitmap,
    308                             tmx == null ? Shader.TileMode.CLAMP : tmx,
    309                             tmy == null ? Shader.TileMode.CLAMP : tmy);
    310                     state.mPaint.setShader(s);
    311                 }
    312                 mRebuildShader = false;
    313                 copyBounds(mDstRect);
    314             }
    315 
    316             Shader shader = state.mPaint.getShader();
    317             if (shader == null) {
    318                 if (mApplyGravity) {
    319                     Gravity.apply(state.mGravity, mBitmapWidth, mBitmapHeight,
    320                             getBounds(), mDstRect);
    321                     mApplyGravity = false;
    322                 }
    323                 canvas.drawBitmap(bitmap, null, mDstRect, state.mPaint);
    324             } else {
    325                 if (mApplyGravity) {
    326                     mDstRect.set(getBounds());
    327                     mApplyGravity = false;
    328                 }
    329                 canvas.drawRect(mDstRect, state.mPaint);
    330             }
    331         }
    332     }
    333 
    334     @Override
    335     public void setAlpha(int alpha) {
    336         mBitmapState.mPaint.setAlpha(alpha);
    337     }
    338 
    339     @Override
    340     public void setColorFilter(ColorFilter cf) {
    341         mBitmapState.mPaint.setColorFilter(cf);
    342     }
    343 
    344     /**
    345      * A mutable BitmapDrawable still shares its Bitmap with any other Drawable
    346      * that comes from the same resource.
    347      *
    348      * @return This drawable.
    349      */
    350     @Override
    351     public Drawable mutate() {
    352         if (!mMutated && super.mutate() == this) {
    353             mBitmapState = new BitmapState(mBitmapState);
    354             mMutated = true;
    355         }
    356         return this;
    357     }
    358 
    359     @Override
    360     public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs)
    361             throws XmlPullParserException, IOException {
    362         super.inflate(r, parser, attrs);
    363 
    364         TypedArray a = r.obtainAttributes(attrs, com.android.internal.R.styleable.BitmapDrawable);
    365 
    366         final int id = a.getResourceId(com.android.internal.R.styleable.BitmapDrawable_src, 0);
    367         if (id == 0) {
    368             throw new XmlPullParserException(parser.getPositionDescription() +
    369                     ": <bitmap> requires a valid src attribute");
    370         }
    371         final Bitmap bitmap = BitmapFactory.decodeResource(r, id);
    372         if (bitmap == null) {
    373             throw new XmlPullParserException(parser.getPositionDescription() +
    374                     ": <bitmap> requires a valid src attribute");
    375         }
    376         mBitmapState.mBitmap = bitmap;
    377         setBitmap(bitmap);
    378         setTargetDensity(r.getDisplayMetrics());
    379 
    380         final Paint paint = mBitmapState.mPaint;
    381         paint.setAntiAlias(a.getBoolean(com.android.internal.R.styleable.BitmapDrawable_antialias,
    382                 paint.isAntiAlias()));
    383         paint.setFilterBitmap(a.getBoolean(com.android.internal.R.styleable.BitmapDrawable_filter,
    384                 paint.isFilterBitmap()));
    385         paint.setDither(a.getBoolean(com.android.internal.R.styleable.BitmapDrawable_dither,
    386                 paint.isDither()));
    387         setGravity(a.getInt(com.android.internal.R.styleable.BitmapDrawable_gravity, Gravity.FILL));
    388         int tileMode = a.getInt(com.android.internal.R.styleable.BitmapDrawable_tileMode, -1);
    389         if (tileMode != -1) {
    390             switch (tileMode) {
    391                 case 0:
    392                     setTileModeXY(Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
    393                     break;
    394                 case 1:
    395                     setTileModeXY(Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
    396                     break;
    397                 case 2:
    398                     setTileModeXY(Shader.TileMode.MIRROR, Shader.TileMode.MIRROR);
    399                     break;
    400             }
    401         }
    402 
    403         a.recycle();
    404     }
    405 
    406     @Override
    407     public int getIntrinsicWidth() {
    408         return mBitmapWidth;
    409     }
    410 
    411     @Override
    412     public int getIntrinsicHeight() {
    413         return mBitmapHeight;
    414     }
    415 
    416     @Override
    417     public int getOpacity() {
    418         if (mBitmapState.mGravity != Gravity.FILL) {
    419             return PixelFormat.TRANSLUCENT;
    420         }
    421         Bitmap bm = mBitmap;
    422         return (bm == null || bm.hasAlpha() || mBitmapState.mPaint.getAlpha() < 255) ?
    423                 PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE;
    424     }
    425 
    426     @Override
    427     public final ConstantState getConstantState() {
    428         mBitmapState.mChangingConfigurations = super.getChangingConfigurations();
    429         return mBitmapState;
    430     }
    431 
    432     final static class BitmapState extends ConstantState {
    433         Bitmap mBitmap;
    434         int mChangingConfigurations;
    435         int mGravity = Gravity.FILL;
    436         Paint mPaint = new Paint(DEFAULT_PAINT_FLAGS);
    437         Shader.TileMode mTileModeX;
    438         Shader.TileMode mTileModeY;
    439         int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT;
    440 
    441         BitmapState(Bitmap bitmap) {
    442             mBitmap = bitmap;
    443         }
    444 
    445         BitmapState(BitmapState bitmapState) {
    446             this(bitmapState.mBitmap);
    447             mChangingConfigurations = bitmapState.mChangingConfigurations;
    448             mGravity = bitmapState.mGravity;
    449             mTileModeX = bitmapState.mTileModeX;
    450             mTileModeY = bitmapState.mTileModeY;
    451             mTargetDensity = bitmapState.mTargetDensity;
    452             mPaint = new Paint(bitmapState.mPaint);
    453         }
    454 
    455         @Override
    456         public Drawable newDrawable() {
    457             return new BitmapDrawable(this, null);
    458         }
    459 
    460         @Override
    461         public Drawable newDrawable(Resources res) {
    462             return new BitmapDrawable(this, res);
    463         }
    464 
    465         @Override
    466         public int getChangingConfigurations() {
    467             return mChangingConfigurations;
    468         }
    469     }
    470 
    471     private BitmapDrawable(BitmapState state, Resources res) {
    472         mBitmapState = state;
    473         if (res != null) {
    474             mTargetDensity = res.getDisplayMetrics().densityDpi;
    475         } else if (state != null) {
    476             mTargetDensity = state.mTargetDensity;
    477         } else {
    478             mTargetDensity = DisplayMetrics.DENSITY_DEFAULT;
    479         }
    480         setBitmap(state.mBitmap);
    481     }
    482 }
    483