/* * Copyright (C) 2006 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.graphics.drawable; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.Shader; import android.graphics.BitmapShader; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.view.Gravity; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; /** * A Drawable that wraps a bitmap and can be tiled, stretched, or aligned. You can create a * BitmapDrawable from a file path, an input stream, through XML inflation, or from * a {@link android.graphics.Bitmap} object. * <p>It can be defined in an XML file with the <code><bitmap></code> element.</p> * <p> * Also see the {@link android.graphics.Bitmap} class, which handles the management and * transformation of raw bitmap graphics, and should be used when drawing to a * {@link android.graphics.Canvas}. * </p> * * @attr ref android.R.styleable#BitmapDrawable_src * @attr ref android.R.styleable#BitmapDrawable_antialias * @attr ref android.R.styleable#BitmapDrawable_filter * @attr ref android.R.styleable#BitmapDrawable_dither * @attr ref android.R.styleable#BitmapDrawable_gravity * @attr ref android.R.styleable#BitmapDrawable_tileMode */ public class BitmapDrawable extends Drawable { private static final int DEFAULT_PAINT_FLAGS = Paint.FILTER_BITMAP_FLAG; private BitmapState mBitmapState; private Bitmap mBitmap; private final Rect mDstRect = new Rect(); // Gravity.apply() sets this private boolean mApplyGravity; private boolean mRebuildShader; private int mBitmapWidth; private int mBitmapHeight; private boolean mMutated; public BitmapDrawable() { mBitmapState = new BitmapState((Bitmap) null); } public BitmapDrawable(Bitmap bitmap) { this(new BitmapState(bitmap)); } public BitmapDrawable(String filepath) { this(new BitmapState(BitmapFactory.decodeFile(filepath))); if (mBitmap == null) { android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + filepath); } } public BitmapDrawable(java.io.InputStream is) { this(new BitmapState(BitmapFactory.decodeStream(is))); if (mBitmap == null) { android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + is); } } public final Paint getPaint() { return mBitmapState.mPaint; } public final Bitmap getBitmap() { return mBitmap; } private void setBitmap(Bitmap bitmap) { mBitmap = bitmap; if (bitmap != null) { mBitmapWidth = bitmap.getWidth(); mBitmapHeight = bitmap.getHeight(); } else { mBitmapWidth = mBitmapHeight = -1; } } /** * Set the density scale at which this drawable will be rendered. This * method assumes the drawable will be rendered at the same density as the * specified canvas. * * @param canvas The Canvas from which the density scale must be obtained. * * @see android.graphics.Bitmap#setDensityScale(float) * @see android.graphics.Bitmap#getDensityScale() * * @hide pending API council approval */ public void setDensityScale(Canvas canvas) { setDensityScale(canvas.getDensityScale()); } /** * Set the density scale at which this drawable will be rendered. * * @param metrics The DisplayMetrics indicating the density scale for this drawable. * * @see android.graphics.Bitmap#setDensityScale(float) * @see android.graphics.Bitmap#getDensityScale() * * @hide pending API council approval */ public void setDensityScale(DisplayMetrics metrics) { setDensityScale(metrics.density); } /** * Set the density scale at which this drawable will be rendered. * * @param density The density scale for this drawable. * * @see android.graphics.Bitmap#setDensityScale(float) * @see android.graphics.Bitmap#getDensityScale() * * @hide pending API council approval */ public void setDensityScale(float density) { density = (density == Bitmap.DENSITY_SCALE_UNKNOWN ? 1.0f : density); mBitmapState.mTargetDensityScale = density; } /** Get the gravity used to position/stretch the bitmap within its bounds. See android.view.Gravity * @return the gravity applied to the bitmap */ public int getGravity() { return mBitmapState.mGravity; } /** Set the gravity used to position/stretch the bitmap within its bounds. See android.view.Gravity * @param gravity the gravity */ public void setGravity(int gravity) { mBitmapState.mGravity = gravity; mApplyGravity = true; } public void setAntiAlias(boolean aa) { mBitmapState.mPaint.setAntiAlias(aa); } @Override public void setFilterBitmap(boolean filter) { mBitmapState.mPaint.setFilterBitmap(filter); } @Override public void setDither(boolean dither) { mBitmapState.mPaint.setDither(dither); } public Shader.TileMode getTileModeX() { return mBitmapState.mTileModeX; } public Shader.TileMode getTileModeY() { return mBitmapState.mTileModeY; } public void setTileModeX(Shader.TileMode mode) { setTileModeXY(mode, mBitmapState.mTileModeY); } public final void setTileModeY(Shader.TileMode mode) { setTileModeXY(mBitmapState.mTileModeX, mode); } public void setTileModeXY(Shader.TileMode xmode, Shader.TileMode ymode) { final BitmapState state = mBitmapState; if (state.mTileModeX != xmode || state.mTileModeY != ymode) { state.mTileModeX = xmode; state.mTileModeY = ymode; mRebuildShader = true; } } @Override public int getChangingConfigurations() { return super.getChangingConfigurations() | mBitmapState.mChangingConfigurations; } @Override protected void onBoundsChange(Rect bounds) { super.onBoundsChange(bounds); mApplyGravity = true; } @Override public void draw(Canvas canvas) { Bitmap bitmap = mBitmap; if (bitmap != null) { final BitmapState state = mBitmapState; if (mRebuildShader) { Shader.TileMode tmx = state.mTileModeX; Shader.TileMode tmy = state.mTileModeY; if (tmx == null && tmy == null) { state.mPaint.setShader(null); } else { Shader s = new BitmapShader(bitmap, tmx == null ? Shader.TileMode.CLAMP : tmx, tmy == null ? Shader.TileMode.CLAMP : tmy); state.mPaint.setShader(s); } mRebuildShader = false; copyBounds(mDstRect); } Shader shader = state.mPaint.getShader(); if (shader == null) { if (mApplyGravity) { Gravity.apply(state.mGravity, mBitmapWidth, mBitmapHeight, getBounds(), mDstRect); mApplyGravity = false; } canvas.drawBitmap(bitmap, null, mDstRect, state.mPaint); } else { if (mApplyGravity) { mDstRect.set(getBounds()); mApplyGravity = false; } canvas.drawRect(mDstRect, state.mPaint); } } } @Override public void setAlpha(int alpha) { mBitmapState.mPaint.setAlpha(alpha); } @Override public void setColorFilter(ColorFilter cf) { mBitmapState.mPaint.setColorFilter(cf); } /** * A mutable BitmapDrawable still shares its Bitmap with any other Drawable * that comes from the same resource. * * @return This drawable. */ @Override public Drawable mutate() { if (!mMutated && super.mutate() == this) { mBitmapState = new BitmapState(mBitmapState); mMutated = true; } return this; } @Override public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) throws XmlPullParserException, IOException { super.inflate(r, parser, attrs); TypedArray a = r.obtainAttributes(attrs, com.android.internal.R.styleable.BitmapDrawable); final int id = a.getResourceId(com.android.internal.R.styleable.BitmapDrawable_src, 0); if (id == 0) { throw new XmlPullParserException(parser.getPositionDescription() + ": <bitmap> requires a valid src attribute"); } final Bitmap bitmap = BitmapFactory.decodeResource(r, id); if (bitmap == null) { throw new XmlPullParserException(parser.getPositionDescription() + ": <bitmap> requires a valid src attribute"); } mBitmapState.mBitmap = bitmap; setBitmap(bitmap); setDensityScale(r.getDisplayMetrics()); final Paint paint = mBitmapState.mPaint; paint.setAntiAlias(a.getBoolean(com.android.internal.R.styleable.BitmapDrawable_antialias, paint.isAntiAlias())); paint.setFilterBitmap(a.getBoolean(com.android.internal.R.styleable.BitmapDrawable_filter, paint.isFilterBitmap())); paint.setDither(a.getBoolean(com.android.internal.R.styleable.BitmapDrawable_dither, paint.isDither())); setGravity(a.getInt(com.android.internal.R.styleable.BitmapDrawable_gravity, Gravity.FILL)); int tileMode = a.getInt(com.android.internal.R.styleable.BitmapDrawable_tileMode, -1); if (tileMode != -1) { switch (tileMode) { case 0: setTileModeXY(Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); break; case 1: setTileModeXY(Shader.TileMode.REPEAT, Shader.TileMode.REPEAT); break; case 2: setTileModeXY(Shader.TileMode.MIRROR, Shader.TileMode.MIRROR); break; } } a.recycle(); } @Override public int getIntrinsicWidth() { final Bitmap bitmap = mBitmap; final BitmapState state = mBitmapState; if (!state.mAutoScale || state.mBitmapScale == Bitmap.DENSITY_SCALE_UNKNOWN) { return mBitmapWidth; } else { return bitmap != null ? (int) (mBitmapWidth / (state.mBitmapScale / state.mTargetDensityScale) + 0.5f) : -1; } } @Override public int getIntrinsicHeight() { final Bitmap bitmap = mBitmap; final BitmapState state = mBitmapState; if (!state.mAutoScale || state.mBitmapScale == Bitmap.DENSITY_SCALE_UNKNOWN) { return mBitmapHeight; } else { return bitmap != null ? (int) (mBitmapHeight / (state.mBitmapScale / state.mTargetDensityScale) + 0.5f) : -1; } } @Override public int getOpacity() { if (mBitmapState.mGravity != Gravity.FILL) { return PixelFormat.TRANSLUCENT; } Bitmap bm = mBitmap; return (bm == null || bm.hasAlpha() || mBitmapState.mPaint.getAlpha() < 255) ? PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE; } @Override public final ConstantState getConstantState() { mBitmapState.mChangingConfigurations = super.getChangingConfigurations(); return mBitmapState; } final static class BitmapState extends ConstantState { Bitmap mBitmap; int mChangingConfigurations; int mGravity = Gravity.FILL; Paint mPaint = new Paint(DEFAULT_PAINT_FLAGS); Shader.TileMode mTileModeX; Shader.TileMode mTileModeY; boolean mAutoScale; float mBitmapScale; float mTargetDensityScale = 1.0f; BitmapState(Bitmap bitmap) { mBitmap = bitmap; if (bitmap != null) { mBitmapScale = bitmap.getDensityScale(); mAutoScale = bitmap.isAutoScalingEnabled(); } else { mBitmapScale = 1.0f; mAutoScale = false; } } BitmapState(BitmapState bitmapState) { this(bitmapState.mBitmap); mChangingConfigurations = bitmapState.mChangingConfigurations; mGravity = bitmapState.mGravity; mTileModeX = bitmapState.mTileModeX; mTileModeY = bitmapState.mTileModeY; mTargetDensityScale = bitmapState.mTargetDensityScale; mPaint = new Paint(bitmapState.mPaint); } @Override public Drawable newDrawable() { return new BitmapDrawable(this); } @Override public int getChangingConfigurations() { return mChangingConfigurations; } } private BitmapDrawable(BitmapState state) { mBitmapState = state; setBitmap(state.mBitmap); } }