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