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><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