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.BitmapShader; 24 import android.graphics.Canvas; 25 import android.graphics.ColorFilter; 26 import android.graphics.Paint; 27 import android.graphics.PixelFormat; 28 import android.graphics.Rect; 29 import android.graphics.Shader; 30 import android.util.AttributeSet; 31 import android.util.DisplayMetrics; 32 import android.view.Gravity; 33 import org.xmlpull.v1.XmlPullParser; 34 import org.xmlpull.v1.XmlPullParserException; 35 36 import java.io.IOException; 37 38 /** 39 * A Drawable that wraps a bitmap and can be tiled, stretched, or aligned. You can create a 40 * BitmapDrawable from a file path, an input stream, through XML inflation, or from 41 * a {@link android.graphics.Bitmap} object. 42 * <p>It can be defined in an XML file with the <code><bitmap></code> element. For more 43 * information, see the guide to <a 44 * href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p> 45 * <p> 46 * Also see the {@link android.graphics.Bitmap} class, which handles the management and 47 * transformation of raw bitmap graphics, and should be used when drawing to a 48 * {@link android.graphics.Canvas}. 49 * </p> 50 * 51 * @attr ref android.R.styleable#BitmapDrawable_src 52 * @attr ref android.R.styleable#BitmapDrawable_antialias 53 * @attr ref android.R.styleable#BitmapDrawable_filter 54 * @attr ref android.R.styleable#BitmapDrawable_dither 55 * @attr ref android.R.styleable#BitmapDrawable_gravity 56 * @attr ref android.R.styleable#BitmapDrawable_tileMode 57 */ 58 public class BitmapDrawable extends Drawable { 59 60 private static final int DEFAULT_PAINT_FLAGS = 61 Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG; 62 private BitmapState mBitmapState; 63 private Bitmap mBitmap; 64 private int mTargetDensity; 65 66 private final Rect mDstRect = new Rect(); // Gravity.apply() sets this 67 68 private boolean mApplyGravity; 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 @SuppressWarnings({"UnusedParameters"}) 90 public BitmapDrawable(Resources res) { 91 mBitmapState = new BitmapState((Bitmap) null); 92 mBitmapState.mTargetDensity = mTargetDensity; 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 @Deprecated 101 public BitmapDrawable(Bitmap bitmap) { 102 this(new BitmapState(bitmap), null); 103 } 104 105 /** 106 * Create drawable from a bitmap, setting initial target density based on 107 * the display metrics of the resources. 108 */ 109 public BitmapDrawable(Resources res, Bitmap bitmap) { 110 this(new BitmapState(bitmap), res); 111 mBitmapState.mTargetDensity = mTargetDensity; 112 } 113 114 /** 115 * Create a drawable by opening a given file path and decoding the bitmap. 116 * @deprecated Use {@link #BitmapDrawable(Resources, String)} to ensure 117 * that the drawable has correctly set its target density. 118 */ 119 @Deprecated 120 public BitmapDrawable(String filepath) { 121 this(new BitmapState(BitmapFactory.decodeFile(filepath)), null); 122 if (mBitmap == null) { 123 android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + filepath); 124 } 125 } 126 127 /** 128 * Create a drawable by opening a given file path and decoding the bitmap. 129 */ 130 @SuppressWarnings({"UnusedParameters"}) 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 @SuppressWarnings({"UnusedParameters"}) 156 public BitmapDrawable(Resources res, java.io.InputStream is) { 157 this(new BitmapState(BitmapFactory.decodeStream(is)), null); 158 mBitmapState.mTargetDensity = mTargetDensity; 159 if (mBitmap == null) { 160 android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + is); 161 } 162 } 163 164 /** 165 * Returns the paint used to render this drawable. 166 */ 167 public final Paint getPaint() { 168 return mBitmapState.mPaint; 169 } 170 171 /** 172 * Returns the bitmap used by this drawable to render. May be null. 173 */ 174 public final Bitmap getBitmap() { 175 return mBitmap; 176 } 177 178 private void computeBitmapSize() { 179 mBitmapWidth = mBitmap.getScaledWidth(mTargetDensity); 180 mBitmapHeight = mBitmap.getScaledHeight(mTargetDensity); 181 } 182 183 private void setBitmap(Bitmap bitmap) { 184 if (bitmap != mBitmap) { 185 mBitmap = bitmap; 186 if (bitmap != null) { 187 computeBitmapSize(); 188 } else { 189 mBitmapWidth = mBitmapHeight = -1; 190 } 191 invalidateSelf(); 192 } 193 } 194 195 /** 196 * Set the density scale at which this drawable will be rendered. This 197 * method assumes the drawable will be rendered at the same density as the 198 * specified canvas. 199 * 200 * @param canvas The Canvas from which the density scale must be obtained. 201 * 202 * @see android.graphics.Bitmap#setDensity(int) 203 * @see android.graphics.Bitmap#getDensity() 204 */ 205 public void setTargetDensity(Canvas canvas) { 206 setTargetDensity(canvas.getDensity()); 207 } 208 209 /** 210 * Set the density scale at which this drawable will be rendered. 211 * 212 * @param metrics The DisplayMetrics indicating the density scale for this drawable. 213 * 214 * @see android.graphics.Bitmap#setDensity(int) 215 * @see android.graphics.Bitmap#getDensity() 216 */ 217 public void setTargetDensity(DisplayMetrics metrics) { 218 setTargetDensity(metrics.densityDpi); 219 } 220 221 /** 222 * Set the density at which this drawable will be rendered. 223 * 224 * @param density The density scale for this drawable. 225 * 226 * @see android.graphics.Bitmap#setDensity(int) 227 * @see android.graphics.Bitmap#getDensity() 228 */ 229 public void setTargetDensity(int density) { 230 if (mTargetDensity != density) { 231 mTargetDensity = density == 0 ? DisplayMetrics.DENSITY_DEFAULT : density; 232 if (mBitmap != null) { 233 computeBitmapSize(); 234 } 235 invalidateSelf(); 236 } 237 } 238 239 /** Get the gravity used to position/stretch the bitmap within its bounds. 240 * See android.view.Gravity 241 * @return the gravity applied to the bitmap 242 */ 243 public int getGravity() { 244 return mBitmapState.mGravity; 245 } 246 247 /** Set the gravity used to position/stretch the bitmap within its bounds. 248 See android.view.Gravity 249 * @param gravity the gravity 250 */ 251 public void setGravity(int gravity) { 252 if (mBitmapState.mGravity != gravity) { 253 mBitmapState.mGravity = gravity; 254 mApplyGravity = true; 255 invalidateSelf(); 256 } 257 } 258 259 /** 260 * Enables or disables anti-aliasing for this drawable. Anti-aliasing affects 261 * the edges of the bitmap only so it applies only when the drawable is rotated. 262 * 263 * @param aa True if the bitmap should be anti-aliased, false otherwise. 264 */ 265 public void setAntiAlias(boolean aa) { 266 mBitmapState.mPaint.setAntiAlias(aa); 267 invalidateSelf(); 268 } 269 270 @Override 271 public void setFilterBitmap(boolean filter) { 272 mBitmapState.mPaint.setFilterBitmap(filter); 273 invalidateSelf(); 274 } 275 276 @Override 277 public void setDither(boolean dither) { 278 mBitmapState.mPaint.setDither(dither); 279 invalidateSelf(); 280 } 281 282 /** 283 * Indicates the repeat behavior of this drawable on the X axis. 284 * 285 * @return {@link Shader.TileMode#CLAMP} if the bitmap does not repeat, 286 * {@link Shader.TileMode#REPEAT} or {@link Shader.TileMode#MIRROR} otherwise. 287 */ 288 public Shader.TileMode getTileModeX() { 289 return mBitmapState.mTileModeX; 290 } 291 292 /** 293 * Indicates the repeat behavior of this drawable on the Y axis. 294 * 295 * @return {@link Shader.TileMode#CLAMP} if the bitmap does not repeat, 296 * {@link Shader.TileMode#REPEAT} or {@link Shader.TileMode#MIRROR} otherwise. 297 */ 298 public Shader.TileMode getTileModeY() { 299 return mBitmapState.mTileModeY; 300 } 301 302 /** 303 * Sets the repeat behavior of this drawable on the X axis. By default, the drawable 304 * does not repeat its bitmap. Using {@link Shader.TileMode#REPEAT} or 305 * {@link Shader.TileMode#MIRROR} the bitmap can be repeated (or tiled) if the bitmap 306 * is smaller than this drawable. 307 * 308 * @param mode The repeat mode for this drawable. 309 * 310 * @see #setTileModeY(android.graphics.Shader.TileMode) 311 * @see #setTileModeXY(android.graphics.Shader.TileMode, android.graphics.Shader.TileMode) 312 */ 313 public void setTileModeX(Shader.TileMode mode) { 314 setTileModeXY(mode, mBitmapState.mTileModeY); 315 } 316 317 /** 318 * Sets the repeat behavior of this drawable on the Y axis. By default, the drawable 319 * does not repeat its bitmap. Using {@link Shader.TileMode#REPEAT} or 320 * {@link Shader.TileMode#MIRROR} the bitmap can be repeated (or tiled) if the bitmap 321 * is smaller than this drawable. 322 * 323 * @param mode The repeat mode for this drawable. 324 * 325 * @see #setTileModeX(android.graphics.Shader.TileMode) 326 * @see #setTileModeXY(android.graphics.Shader.TileMode, android.graphics.Shader.TileMode) 327 */ 328 public final void setTileModeY(Shader.TileMode mode) { 329 setTileModeXY(mBitmapState.mTileModeX, mode); 330 } 331 332 /** 333 * Sets the repeat behavior of this drawable on both axis. By default, the drawable 334 * does not repeat its bitmap. Using {@link Shader.TileMode#REPEAT} or 335 * {@link Shader.TileMode#MIRROR} the bitmap can be repeated (or tiled) if the bitmap 336 * is smaller than this drawable. 337 * 338 * @param xmode The X repeat mode for this drawable. 339 * @param ymode The Y repeat mode for this drawable. 340 * 341 * @see #setTileModeX(android.graphics.Shader.TileMode) 342 * @see #setTileModeY(android.graphics.Shader.TileMode) 343 */ 344 public void setTileModeXY(Shader.TileMode xmode, Shader.TileMode ymode) { 345 final BitmapState state = mBitmapState; 346 if (state.mTileModeX != xmode || state.mTileModeY != ymode) { 347 state.mTileModeX = xmode; 348 state.mTileModeY = ymode; 349 state.mRebuildShader = true; 350 invalidateSelf(); 351 } 352 } 353 354 @Override 355 public int getChangingConfigurations() { 356 return super.getChangingConfigurations() | mBitmapState.mChangingConfigurations; 357 } 358 359 @Override 360 protected void onBoundsChange(Rect bounds) { 361 super.onBoundsChange(bounds); 362 mApplyGravity = true; 363 } 364 365 @Override 366 public void draw(Canvas canvas) { 367 Bitmap bitmap = mBitmap; 368 if (bitmap != null) { 369 final BitmapState state = mBitmapState; 370 if (state.mRebuildShader) { 371 Shader.TileMode tmx = state.mTileModeX; 372 Shader.TileMode tmy = state.mTileModeY; 373 374 if (tmx == null && tmy == null) { 375 state.mPaint.setShader(null); 376 } else { 377 state.mPaint.setShader(new BitmapShader(bitmap, 378 tmx == null ? Shader.TileMode.CLAMP : tmx, 379 tmy == null ? Shader.TileMode.CLAMP : tmy)); 380 } 381 state.mRebuildShader = false; 382 copyBounds(mDstRect); 383 } 384 385 Shader shader = state.mPaint.getShader(); 386 if (shader == null) { 387 if (mApplyGravity) { 388 final int layoutDirection = getResolvedLayoutDirectionSelf(); 389 Gravity.apply(state.mGravity, mBitmapWidth, mBitmapHeight, 390 getBounds(), mDstRect, layoutDirection); 391 mApplyGravity = false; 392 } 393 canvas.drawBitmap(bitmap, null, mDstRect, state.mPaint); 394 } else { 395 if (mApplyGravity) { 396 copyBounds(mDstRect); 397 mApplyGravity = false; 398 } 399 canvas.drawRect(mDstRect, state.mPaint); 400 } 401 } 402 } 403 404 @Override 405 public void setAlpha(int alpha) { 406 int oldAlpha = mBitmapState.mPaint.getAlpha(); 407 if (alpha != oldAlpha) { 408 mBitmapState.mPaint.setAlpha(alpha); 409 invalidateSelf(); 410 } 411 } 412 413 @Override 414 public void setColorFilter(ColorFilter cf) { 415 mBitmapState.mPaint.setColorFilter(cf); 416 invalidateSelf(); 417 } 418 419 /** 420 * A mutable BitmapDrawable still shares its Bitmap with any other Drawable 421 * that comes from the same resource. 422 * 423 * @return This drawable. 424 */ 425 @Override 426 public Drawable mutate() { 427 if (!mMutated && super.mutate() == this) { 428 mBitmapState = new BitmapState(mBitmapState); 429 mMutated = true; 430 } 431 return this; 432 } 433 434 @Override 435 public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) 436 throws XmlPullParserException, IOException { 437 super.inflate(r, parser, attrs); 438 439 TypedArray a = r.obtainAttributes(attrs, com.android.internal.R.styleable.BitmapDrawable); 440 441 final int id = a.getResourceId(com.android.internal.R.styleable.BitmapDrawable_src, 0); 442 if (id == 0) { 443 throw new XmlPullParserException(parser.getPositionDescription() + 444 ": <bitmap> requires a valid src attribute"); 445 } 446 final Bitmap bitmap = BitmapFactory.decodeResource(r, id); 447 if (bitmap == null) { 448 throw new XmlPullParserException(parser.getPositionDescription() + 449 ": <bitmap> requires a valid src attribute"); 450 } 451 mBitmapState.mBitmap = bitmap; 452 setBitmap(bitmap); 453 setTargetDensity(r.getDisplayMetrics()); 454 455 final Paint paint = mBitmapState.mPaint; 456 paint.setAntiAlias(a.getBoolean(com.android.internal.R.styleable.BitmapDrawable_antialias, 457 paint.isAntiAlias())); 458 paint.setFilterBitmap(a.getBoolean(com.android.internal.R.styleable.BitmapDrawable_filter, 459 paint.isFilterBitmap())); 460 paint.setDither(a.getBoolean(com.android.internal.R.styleable.BitmapDrawable_dither, 461 paint.isDither())); 462 setGravity(a.getInt(com.android.internal.R.styleable.BitmapDrawable_gravity, Gravity.FILL)); 463 int tileMode = a.getInt(com.android.internal.R.styleable.BitmapDrawable_tileMode, -1); 464 if (tileMode != -1) { 465 switch (tileMode) { 466 case 0: 467 setTileModeXY(Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); 468 break; 469 case 1: 470 setTileModeXY(Shader.TileMode.REPEAT, Shader.TileMode.REPEAT); 471 break; 472 case 2: 473 setTileModeXY(Shader.TileMode.MIRROR, Shader.TileMode.MIRROR); 474 break; 475 } 476 } 477 478 a.recycle(); 479 } 480 481 @Override 482 public int getIntrinsicWidth() { 483 return mBitmapWidth; 484 } 485 486 @Override 487 public int getIntrinsicHeight() { 488 return mBitmapHeight; 489 } 490 491 @Override 492 public int getOpacity() { 493 if (mBitmapState.mGravity != Gravity.FILL) { 494 return PixelFormat.TRANSLUCENT; 495 } 496 Bitmap bm = mBitmap; 497 return (bm == null || bm.hasAlpha() || mBitmapState.mPaint.getAlpha() < 255) ? 498 PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE; 499 } 500 501 @Override 502 public final ConstantState getConstantState() { 503 mBitmapState.mChangingConfigurations = getChangingConfigurations(); 504 return mBitmapState; 505 } 506 507 final static class BitmapState extends ConstantState { 508 Bitmap mBitmap; 509 int mChangingConfigurations; 510 int mGravity = Gravity.FILL; 511 Paint mPaint = new Paint(DEFAULT_PAINT_FLAGS); 512 Shader.TileMode mTileModeX = null; 513 Shader.TileMode mTileModeY = null; 514 int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT; 515 boolean mRebuildShader; 516 517 BitmapState(Bitmap bitmap) { 518 mBitmap = bitmap; 519 } 520 521 BitmapState(BitmapState bitmapState) { 522 this(bitmapState.mBitmap); 523 mChangingConfigurations = bitmapState.mChangingConfigurations; 524 mGravity = bitmapState.mGravity; 525 mTileModeX = bitmapState.mTileModeX; 526 mTileModeY = bitmapState.mTileModeY; 527 mTargetDensity = bitmapState.mTargetDensity; 528 mPaint = new Paint(bitmapState.mPaint); 529 mRebuildShader = bitmapState.mRebuildShader; 530 } 531 532 @Override 533 public Drawable newDrawable() { 534 return new BitmapDrawable(this, null); 535 } 536 537 @Override 538 public Drawable newDrawable(Resources res) { 539 return new BitmapDrawable(this, res); 540 } 541 542 @Override 543 public int getChangingConfigurations() { 544 return mChangingConfigurations; 545 } 546 } 547 548 private BitmapDrawable(BitmapState state, Resources res) { 549 mBitmapState = state; 550 if (res != null) { 551 mTargetDensity = res.getDisplayMetrics().densityDpi; 552 } else { 553 mTargetDensity = state.mTargetDensity; 554 } 555 setBitmap(state != null ? state.mBitmap : null); 556 } 557 } 558