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