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.annotation.NonNull; 20 import android.content.pm.ActivityInfo.Config; 21 import android.content.res.ColorStateList; 22 import android.content.res.Resources; 23 import android.content.res.Resources.Theme; 24 import android.content.res.TypedArray; 25 import android.graphics.Bitmap; 26 import android.graphics.BitmapFactory; 27 import android.graphics.BitmapShader; 28 import android.graphics.Canvas; 29 import android.graphics.ColorFilter; 30 import android.graphics.ImageDecoder; 31 import android.graphics.Insets; 32 import android.graphics.Matrix; 33 import android.graphics.Outline; 34 import android.graphics.Paint; 35 import android.graphics.PixelFormat; 36 import android.graphics.PorterDuff; 37 import android.graphics.PorterDuff.Mode; 38 import android.graphics.PorterDuffColorFilter; 39 import android.graphics.Rect; 40 import android.graphics.Shader; 41 import android.graphics.Xfermode; 42 import android.util.AttributeSet; 43 import android.util.DisplayMetrics; 44 import android.util.LayoutDirection; 45 import android.util.TypedValue; 46 import android.view.Gravity; 47 48 import com.android.internal.R; 49 50 import org.xmlpull.v1.XmlPullParser; 51 import org.xmlpull.v1.XmlPullParserException; 52 53 import java.io.FileInputStream; 54 import java.io.IOException; 55 import java.io.InputStream; 56 57 /** 58 * A Drawable that wraps a bitmap and can be tiled, stretched, or aligned. You can create a 59 * BitmapDrawable from a file path, an input stream, through XML inflation, or from 60 * a {@link android.graphics.Bitmap} object. 61 * <p>It can be defined in an XML file with the <code><bitmap></code> element. For more 62 * information, see the guide to <a 63 * href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p> 64 * <p> 65 * Also see the {@link android.graphics.Bitmap} class, which handles the management and 66 * transformation of raw bitmap graphics, and should be used when drawing to a 67 * {@link android.graphics.Canvas}. 68 * </p> 69 * 70 * @attr ref android.R.styleable#BitmapDrawable_src 71 * @attr ref android.R.styleable#BitmapDrawable_antialias 72 * @attr ref android.R.styleable#BitmapDrawable_filter 73 * @attr ref android.R.styleable#BitmapDrawable_dither 74 * @attr ref android.R.styleable#BitmapDrawable_gravity 75 * @attr ref android.R.styleable#BitmapDrawable_mipMap 76 * @attr ref android.R.styleable#BitmapDrawable_tileMode 77 */ 78 public class BitmapDrawable extends Drawable { 79 private static final int DEFAULT_PAINT_FLAGS = 80 Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG; 81 82 // Constants for {@link android.R.styleable#BitmapDrawable_tileMode}. 83 private static final int TILE_MODE_UNDEFINED = -2; 84 private static final int TILE_MODE_DISABLED = -1; 85 private static final int TILE_MODE_CLAMP = 0; 86 private static final int TILE_MODE_REPEAT = 1; 87 private static final int TILE_MODE_MIRROR = 2; 88 89 private final Rect mDstRect = new Rect(); // #updateDstRectAndInsetsIfDirty() sets this 90 91 private BitmapState mBitmapState; 92 private PorterDuffColorFilter mTintFilter; 93 94 private int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT; 95 96 private boolean mDstRectAndInsetsDirty = true; 97 private boolean mMutated; 98 99 // These are scaled to match the target density. 100 private int mBitmapWidth; 101 private int mBitmapHeight; 102 103 /** Optical insets due to gravity. */ 104 private Insets mOpticalInsets = Insets.NONE; 105 106 // Mirroring matrix for using with Shaders 107 private Matrix mMirrorMatrix; 108 109 /** 110 * Create an empty drawable, not dealing with density. 111 * @deprecated Use {@link #BitmapDrawable(android.content.res.Resources, android.graphics.Bitmap)} 112 * instead to specify a bitmap to draw with and ensure the correct density is set. 113 */ 114 @Deprecated 115 public BitmapDrawable() { 116 init(new BitmapState((Bitmap) null), null); 117 } 118 119 /** 120 * Create an empty drawable, setting initial target density based on 121 * the display metrics of the resources. 122 * 123 * @deprecated Use {@link #BitmapDrawable(android.content.res.Resources, android.graphics.Bitmap)} 124 * instead to specify a bitmap to draw with. 125 */ 126 @SuppressWarnings("unused") 127 @Deprecated 128 public BitmapDrawable(Resources res) { 129 init(new BitmapState((Bitmap) null), res); 130 } 131 132 /** 133 * Create drawable from a bitmap, not dealing with density. 134 * @deprecated Use {@link #BitmapDrawable(Resources, Bitmap)} to ensure 135 * that the drawable has correctly set its target density. 136 */ 137 @Deprecated 138 public BitmapDrawable(Bitmap bitmap) { 139 init(new BitmapState(bitmap), null); 140 } 141 142 /** 143 * Create drawable from a bitmap, setting initial target density based on 144 * the display metrics of the resources. 145 */ 146 public BitmapDrawable(Resources res, Bitmap bitmap) { 147 init(new BitmapState(bitmap), res); 148 } 149 150 /** 151 * Create a drawable by opening a given file path and decoding the bitmap. 152 * @deprecated Use {@link #BitmapDrawable(Resources, String)} to ensure 153 * that the drawable has correctly set its target density. 154 */ 155 @Deprecated 156 public BitmapDrawable(String filepath) { 157 this(null, filepath); 158 } 159 160 /** 161 * Create a drawable by opening a given file path and decoding the bitmap. 162 */ 163 @SuppressWarnings({ "unused", "ChainingConstructorIgnoresParameter" }) 164 public BitmapDrawable(Resources res, String filepath) { 165 Bitmap bitmap = null; 166 try (FileInputStream stream = new FileInputStream(filepath)) { 167 bitmap = ImageDecoder.decodeBitmap(ImageDecoder.createSource(res, stream), 168 (decoder, info, src) -> { 169 decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE); 170 }); 171 } catch (Exception e) { 172 /* do nothing. This matches the behavior of BitmapFactory.decodeFile() 173 If the exception happened on decode, mBitmapState.mBitmap will be null. 174 */ 175 } finally { 176 init(new BitmapState(bitmap), res); 177 if (mBitmapState.mBitmap == null) { 178 android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + filepath); 179 } 180 } 181 } 182 183 /** 184 * Create a drawable by decoding a bitmap from the given input stream. 185 * @deprecated Use {@link #BitmapDrawable(Resources, java.io.InputStream)} to ensure 186 * that the drawable has correctly set its target density. 187 */ 188 @Deprecated 189 public BitmapDrawable(java.io.InputStream is) { 190 this(null, is); 191 } 192 193 /** 194 * Create a drawable by decoding a bitmap from the given input stream. 195 */ 196 @SuppressWarnings({ "unused", "ChainingConstructorIgnoresParameter" }) 197 public BitmapDrawable(Resources res, java.io.InputStream is) { 198 Bitmap bitmap = null; 199 try { 200 bitmap = ImageDecoder.decodeBitmap(ImageDecoder.createSource(res, is), 201 (decoder, info, src) -> { 202 decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE); 203 }); 204 } catch (Exception e) { 205 /* do nothing. This matches the behavior of BitmapFactory.decodeStream() 206 If the exception happened on decode, mBitmapState.mBitmap will be null. 207 */ 208 } finally { 209 init(new BitmapState(bitmap), res); 210 if (mBitmapState.mBitmap == null) { 211 android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + is); 212 } 213 } 214 } 215 216 /** 217 * Returns the paint used to render this drawable. 218 */ 219 public final Paint getPaint() { 220 return mBitmapState.mPaint; 221 } 222 223 /** 224 * Returns the bitmap used by this drawable to render. May be null. 225 */ 226 public final Bitmap getBitmap() { 227 return mBitmapState.mBitmap; 228 } 229 230 private void computeBitmapSize() { 231 final Bitmap bitmap = mBitmapState.mBitmap; 232 if (bitmap != null) { 233 mBitmapWidth = bitmap.getScaledWidth(mTargetDensity); 234 mBitmapHeight = bitmap.getScaledHeight(mTargetDensity); 235 } else { 236 mBitmapWidth = mBitmapHeight = -1; 237 } 238 } 239 240 /** @hide */ 241 public void setBitmap(Bitmap bitmap) { 242 if (mBitmapState.mBitmap != bitmap) { 243 mBitmapState.mBitmap = bitmap; 244 computeBitmapSize(); 245 invalidateSelf(); 246 } 247 } 248 249 /** 250 * Set the density scale at which this drawable will be rendered. This 251 * method assumes the drawable will be rendered at the same density as the 252 * specified canvas. 253 * 254 * @param canvas The Canvas from which the density scale must be obtained. 255 * 256 * @see android.graphics.Bitmap#setDensity(int) 257 * @see android.graphics.Bitmap#getDensity() 258 */ 259 public void setTargetDensity(Canvas canvas) { 260 setTargetDensity(canvas.getDensity()); 261 } 262 263 /** 264 * Set the density scale at which this drawable will be rendered. 265 * 266 * @param metrics The DisplayMetrics indicating the density scale for this drawable. 267 * 268 * @see android.graphics.Bitmap#setDensity(int) 269 * @see android.graphics.Bitmap#getDensity() 270 */ 271 public void setTargetDensity(DisplayMetrics metrics) { 272 setTargetDensity(metrics.densityDpi); 273 } 274 275 /** 276 * Set the density at which this drawable will be rendered. 277 * 278 * @param density The density scale for this drawable. 279 * 280 * @see android.graphics.Bitmap#setDensity(int) 281 * @see android.graphics.Bitmap#getDensity() 282 */ 283 public void setTargetDensity(int density) { 284 if (mTargetDensity != density) { 285 mTargetDensity = density == 0 ? DisplayMetrics.DENSITY_DEFAULT : density; 286 if (mBitmapState.mBitmap != null) { 287 computeBitmapSize(); 288 } 289 invalidateSelf(); 290 } 291 } 292 293 /** Get the gravity used to position/stretch the bitmap within its bounds. 294 * See android.view.Gravity 295 * @return the gravity applied to the bitmap 296 */ 297 public int getGravity() { 298 return mBitmapState.mGravity; 299 } 300 301 /** Set the gravity used to position/stretch the bitmap within its bounds. 302 See android.view.Gravity 303 * @param gravity the gravity 304 */ 305 public void setGravity(int gravity) { 306 if (mBitmapState.mGravity != gravity) { 307 mBitmapState.mGravity = gravity; 308 mDstRectAndInsetsDirty = true; 309 invalidateSelf(); 310 } 311 } 312 313 /** 314 * Enables or disables the mipmap hint for this drawable's bitmap. 315 * See {@link Bitmap#setHasMipMap(boolean)} for more information. 316 * 317 * If the bitmap is null calling this method has no effect. 318 * 319 * @param mipMap True if the bitmap should use mipmaps, false otherwise. 320 * 321 * @see #hasMipMap() 322 */ 323 public void setMipMap(boolean mipMap) { 324 if (mBitmapState.mBitmap != null) { 325 mBitmapState.mBitmap.setHasMipMap(mipMap); 326 invalidateSelf(); 327 } 328 } 329 330 /** 331 * Indicates whether the mipmap hint is enabled on this drawable's bitmap. 332 * 333 * @return True if the mipmap hint is set, false otherwise. If the bitmap 334 * is null, this method always returns false. 335 * 336 * @see #setMipMap(boolean) 337 * @attr ref android.R.styleable#BitmapDrawable_mipMap 338 */ 339 public boolean hasMipMap() { 340 return mBitmapState.mBitmap != null && mBitmapState.mBitmap.hasMipMap(); 341 } 342 343 /** 344 * Enables or disables anti-aliasing for this drawable. Anti-aliasing affects 345 * the edges of the bitmap only so it applies only when the drawable is rotated. 346 * 347 * @param aa True if the bitmap should be anti-aliased, false otherwise. 348 * 349 * @see #hasAntiAlias() 350 */ 351 public void setAntiAlias(boolean aa) { 352 mBitmapState.mPaint.setAntiAlias(aa); 353 invalidateSelf(); 354 } 355 356 /** 357 * Indicates whether anti-aliasing is enabled for this drawable. 358 * 359 * @return True if anti-aliasing is enabled, false otherwise. 360 * 361 * @see #setAntiAlias(boolean) 362 */ 363 public boolean hasAntiAlias() { 364 return mBitmapState.mPaint.isAntiAlias(); 365 } 366 367 @Override 368 public void setFilterBitmap(boolean filter) { 369 mBitmapState.mPaint.setFilterBitmap(filter); 370 invalidateSelf(); 371 } 372 373 @Override 374 public boolean isFilterBitmap() { 375 return mBitmapState.mPaint.isFilterBitmap(); 376 } 377 378 @Override 379 public void setDither(boolean dither) { 380 mBitmapState.mPaint.setDither(dither); 381 invalidateSelf(); 382 } 383 384 /** 385 * Indicates the repeat behavior of this drawable on the X axis. 386 * 387 * @return {@link android.graphics.Shader.TileMode#CLAMP} if the bitmap does not repeat, 388 * {@link android.graphics.Shader.TileMode#REPEAT} or 389 * {@link android.graphics.Shader.TileMode#MIRROR} otherwise. 390 */ 391 public Shader.TileMode getTileModeX() { 392 return mBitmapState.mTileModeX; 393 } 394 395 /** 396 * Indicates the repeat behavior of this drawable on the Y axis. 397 * 398 * @return {@link android.graphics.Shader.TileMode#CLAMP} if the bitmap does not repeat, 399 * {@link android.graphics.Shader.TileMode#REPEAT} or 400 * {@link android.graphics.Shader.TileMode#MIRROR} otherwise. 401 */ 402 public Shader.TileMode getTileModeY() { 403 return mBitmapState.mTileModeY; 404 } 405 406 /** 407 * Sets the repeat behavior of this drawable on the X axis. By default, the drawable 408 * does not repeat its bitmap. Using {@link android.graphics.Shader.TileMode#REPEAT} or 409 * {@link android.graphics.Shader.TileMode#MIRROR} the bitmap can be repeated (or tiled) 410 * if the bitmap is smaller than this drawable. 411 * 412 * @param mode The repeat mode for this drawable. 413 * 414 * @see #setTileModeY(android.graphics.Shader.TileMode) 415 * @see #setTileModeXY(android.graphics.Shader.TileMode, android.graphics.Shader.TileMode) 416 * @attr ref android.R.styleable#BitmapDrawable_tileModeX 417 */ 418 public void setTileModeX(Shader.TileMode mode) { 419 setTileModeXY(mode, mBitmapState.mTileModeY); 420 } 421 422 /** 423 * Sets the repeat behavior of this drawable on the Y axis. By default, the drawable 424 * does not repeat its bitmap. Using {@link android.graphics.Shader.TileMode#REPEAT} or 425 * {@link android.graphics.Shader.TileMode#MIRROR} the bitmap can be repeated (or tiled) 426 * if the bitmap is smaller than this drawable. 427 * 428 * @param mode The repeat mode for this drawable. 429 * 430 * @see #setTileModeX(android.graphics.Shader.TileMode) 431 * @see #setTileModeXY(android.graphics.Shader.TileMode, android.graphics.Shader.TileMode) 432 * @attr ref android.R.styleable#BitmapDrawable_tileModeY 433 */ 434 public final void setTileModeY(Shader.TileMode mode) { 435 setTileModeXY(mBitmapState.mTileModeX, mode); 436 } 437 438 /** 439 * Sets the repeat behavior of this drawable on both axis. By default, the drawable 440 * does not repeat its bitmap. Using {@link android.graphics.Shader.TileMode#REPEAT} or 441 * {@link android.graphics.Shader.TileMode#MIRROR} the bitmap can be repeated (or tiled) 442 * if the bitmap is smaller than this drawable. 443 * 444 * @param xmode The X repeat mode for this drawable. 445 * @param ymode The Y repeat mode for this drawable. 446 * 447 * @see #setTileModeX(android.graphics.Shader.TileMode) 448 * @see #setTileModeY(android.graphics.Shader.TileMode) 449 */ 450 public void setTileModeXY(Shader.TileMode xmode, Shader.TileMode ymode) { 451 final BitmapState state = mBitmapState; 452 if (state.mTileModeX != xmode || state.mTileModeY != ymode) { 453 state.mTileModeX = xmode; 454 state.mTileModeY = ymode; 455 state.mRebuildShader = true; 456 mDstRectAndInsetsDirty = true; 457 invalidateSelf(); 458 } 459 } 460 461 @Override 462 public void setAutoMirrored(boolean mirrored) { 463 if (mBitmapState.mAutoMirrored != mirrored) { 464 mBitmapState.mAutoMirrored = mirrored; 465 invalidateSelf(); 466 } 467 } 468 469 @Override 470 public final boolean isAutoMirrored() { 471 return mBitmapState.mAutoMirrored; 472 } 473 474 @Override 475 public @Config int getChangingConfigurations() { 476 return super.getChangingConfigurations() | mBitmapState.getChangingConfigurations(); 477 } 478 479 private boolean needMirroring() { 480 return isAutoMirrored() && getLayoutDirection() == LayoutDirection.RTL; 481 } 482 483 @Override 484 protected void onBoundsChange(Rect bounds) { 485 mDstRectAndInsetsDirty = true; 486 487 final Bitmap bitmap = mBitmapState.mBitmap; 488 final Shader shader = mBitmapState.mPaint.getShader(); 489 if (bitmap != null && shader != null) { 490 updateShaderMatrix(bitmap, mBitmapState.mPaint, shader, needMirroring()); 491 } 492 } 493 494 @Override 495 public void draw(Canvas canvas) { 496 final Bitmap bitmap = mBitmapState.mBitmap; 497 if (bitmap == null) { 498 return; 499 } 500 501 final BitmapState state = mBitmapState; 502 final Paint paint = state.mPaint; 503 if (state.mRebuildShader) { 504 final Shader.TileMode tmx = state.mTileModeX; 505 final Shader.TileMode tmy = state.mTileModeY; 506 if (tmx == null && tmy == null) { 507 paint.setShader(null); 508 } else { 509 paint.setShader(new BitmapShader(bitmap, 510 tmx == null ? Shader.TileMode.CLAMP : tmx, 511 tmy == null ? Shader.TileMode.CLAMP : tmy)); 512 } 513 514 state.mRebuildShader = false; 515 } 516 517 final int restoreAlpha; 518 if (state.mBaseAlpha != 1.0f) { 519 final Paint p = getPaint(); 520 restoreAlpha = p.getAlpha(); 521 p.setAlpha((int) (restoreAlpha * state.mBaseAlpha + 0.5f)); 522 } else { 523 restoreAlpha = -1; 524 } 525 526 final boolean clearColorFilter; 527 if (mTintFilter != null && paint.getColorFilter() == null) { 528 paint.setColorFilter(mTintFilter); 529 clearColorFilter = true; 530 } else { 531 clearColorFilter = false; 532 } 533 534 updateDstRectAndInsetsIfDirty(); 535 final Shader shader = paint.getShader(); 536 final boolean needMirroring = needMirroring(); 537 if (shader == null) { 538 if (needMirroring) { 539 canvas.save(); 540 // Mirror the bitmap 541 canvas.translate(mDstRect.right - mDstRect.left, 0); 542 canvas.scale(-1.0f, 1.0f); 543 } 544 545 canvas.drawBitmap(bitmap, null, mDstRect, paint); 546 547 if (needMirroring) { 548 canvas.restore(); 549 } 550 } else { 551 updateShaderMatrix(bitmap, paint, shader, needMirroring); 552 canvas.drawRect(mDstRect, paint); 553 } 554 555 if (clearColorFilter) { 556 paint.setColorFilter(null); 557 } 558 559 if (restoreAlpha >= 0) { 560 paint.setAlpha(restoreAlpha); 561 } 562 } 563 564 /** 565 * Updates the {@code paint}'s shader matrix to be consistent with the 566 * destination size and layout direction. 567 * 568 * @param bitmap the bitmap to be drawn 569 * @param paint the paint used to draw the bitmap 570 * @param shader the shader to set on the paint 571 * @param needMirroring whether the bitmap should be mirrored 572 */ 573 private void updateShaderMatrix(@NonNull Bitmap bitmap, @NonNull Paint paint, 574 @NonNull Shader shader, boolean needMirroring) { 575 final int sourceDensity = bitmap.getDensity(); 576 final int targetDensity = mTargetDensity; 577 final boolean needScaling = sourceDensity != 0 && sourceDensity != targetDensity; 578 if (needScaling || needMirroring) { 579 final Matrix matrix = getOrCreateMirrorMatrix(); 580 matrix.reset(); 581 582 if (needMirroring) { 583 final int dx = mDstRect.right - mDstRect.left; 584 matrix.setTranslate(dx, 0); 585 matrix.setScale(-1, 1); 586 } 587 588 if (needScaling) { 589 final float densityScale = targetDensity / (float) sourceDensity; 590 matrix.postScale(densityScale, densityScale); 591 } 592 593 shader.setLocalMatrix(matrix); 594 } else { 595 mMirrorMatrix = null; 596 shader.setLocalMatrix(Matrix.IDENTITY_MATRIX); 597 } 598 599 paint.setShader(shader); 600 } 601 602 private Matrix getOrCreateMirrorMatrix() { 603 if (mMirrorMatrix == null) { 604 mMirrorMatrix = new Matrix(); 605 } 606 return mMirrorMatrix; 607 } 608 609 private void updateDstRectAndInsetsIfDirty() { 610 if (mDstRectAndInsetsDirty) { 611 if (mBitmapState.mTileModeX == null && mBitmapState.mTileModeY == null) { 612 final Rect bounds = getBounds(); 613 final int layoutDirection = getLayoutDirection(); 614 Gravity.apply(mBitmapState.mGravity, mBitmapWidth, mBitmapHeight, 615 bounds, mDstRect, layoutDirection); 616 617 final int left = mDstRect.left - bounds.left; 618 final int top = mDstRect.top - bounds.top; 619 final int right = bounds.right - mDstRect.right; 620 final int bottom = bounds.bottom - mDstRect.bottom; 621 mOpticalInsets = Insets.of(left, top, right, bottom); 622 } else { 623 copyBounds(mDstRect); 624 mOpticalInsets = Insets.NONE; 625 } 626 } 627 mDstRectAndInsetsDirty = false; 628 } 629 630 /** 631 * @hide 632 */ 633 @Override 634 public Insets getOpticalInsets() { 635 updateDstRectAndInsetsIfDirty(); 636 return mOpticalInsets; 637 } 638 639 @Override 640 public void getOutline(@NonNull Outline outline) { 641 updateDstRectAndInsetsIfDirty(); 642 outline.setRect(mDstRect); 643 644 // Only opaque Bitmaps can report a non-0 alpha, 645 // since only they are guaranteed to fill their bounds 646 boolean opaqueOverShape = mBitmapState.mBitmap != null 647 && !mBitmapState.mBitmap.hasAlpha(); 648 outline.setAlpha(opaqueOverShape ? getAlpha() / 255.0f : 0.0f); 649 } 650 651 @Override 652 public void setAlpha(int alpha) { 653 final int oldAlpha = mBitmapState.mPaint.getAlpha(); 654 if (alpha != oldAlpha) { 655 mBitmapState.mPaint.setAlpha(alpha); 656 invalidateSelf(); 657 } 658 } 659 660 @Override 661 public int getAlpha() { 662 return mBitmapState.mPaint.getAlpha(); 663 } 664 665 @Override 666 public void setColorFilter(ColorFilter colorFilter) { 667 mBitmapState.mPaint.setColorFilter(colorFilter); 668 invalidateSelf(); 669 } 670 671 @Override 672 public ColorFilter getColorFilter() { 673 return mBitmapState.mPaint.getColorFilter(); 674 } 675 676 @Override 677 public void setTintList(ColorStateList tint) { 678 final BitmapState state = mBitmapState; 679 if (state.mTint != tint) { 680 state.mTint = tint; 681 mTintFilter = updateTintFilter(mTintFilter, tint, mBitmapState.mTintMode); 682 invalidateSelf(); 683 } 684 } 685 686 @Override 687 public void setTintMode(PorterDuff.Mode tintMode) { 688 final BitmapState state = mBitmapState; 689 if (state.mTintMode != tintMode) { 690 state.mTintMode = tintMode; 691 mTintFilter = updateTintFilter(mTintFilter, mBitmapState.mTint, tintMode); 692 invalidateSelf(); 693 } 694 } 695 696 /** 697 * @hide only needed by a hack within ProgressBar 698 */ 699 public ColorStateList getTint() { 700 return mBitmapState.mTint; 701 } 702 703 /** 704 * @hide only needed by a hack within ProgressBar 705 */ 706 public Mode getTintMode() { 707 return mBitmapState.mTintMode; 708 } 709 710 /** 711 * @hide Candidate for future API inclusion 712 */ 713 @Override 714 public void setXfermode(Xfermode xfermode) { 715 mBitmapState.mPaint.setXfermode(xfermode); 716 invalidateSelf(); 717 } 718 719 /** 720 * A mutable BitmapDrawable still shares its Bitmap with any other Drawable 721 * that comes from the same resource. 722 * 723 * @return This drawable. 724 */ 725 @Override 726 public Drawable mutate() { 727 if (!mMutated && super.mutate() == this) { 728 mBitmapState = new BitmapState(mBitmapState); 729 mMutated = true; 730 } 731 return this; 732 } 733 734 /** 735 * @hide 736 */ 737 public void clearMutated() { 738 super.clearMutated(); 739 mMutated = false; 740 } 741 742 @Override 743 protected boolean onStateChange(int[] stateSet) { 744 final BitmapState state = mBitmapState; 745 if (state.mTint != null && state.mTintMode != null) { 746 mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); 747 return true; 748 } 749 return false; 750 } 751 752 @Override 753 public boolean isStateful() { 754 return (mBitmapState.mTint != null && mBitmapState.mTint.isStateful()) 755 || super.isStateful(); 756 } 757 758 /** @hide */ 759 @Override 760 public boolean hasFocusStateSpecified() { 761 return mBitmapState.mTint != null && mBitmapState.mTint.hasFocusStateSpecified(); 762 } 763 764 @Override 765 public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) 766 throws XmlPullParserException, IOException { 767 super.inflate(r, parser, attrs, theme); 768 769 final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.BitmapDrawable); 770 updateStateFromTypedArray(a, mSrcDensityOverride); 771 verifyRequiredAttributes(a); 772 a.recycle(); 773 774 // Update local properties. 775 updateLocalState(r); 776 } 777 778 /** 779 * Ensures all required attributes are set. 780 * 781 * @throws XmlPullParserException if any required attributes are missing 782 */ 783 private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException { 784 // If we're not waiting on a theme, verify required attributes. 785 final BitmapState state = mBitmapState; 786 if (state.mBitmap == null && (state.mThemeAttrs == null 787 || state.mThemeAttrs[R.styleable.BitmapDrawable_src] == 0)) { 788 throw new XmlPullParserException(a.getPositionDescription() + 789 ": <bitmap> requires a valid 'src' attribute"); 790 } 791 } 792 793 /** 794 * Updates the constant state from the values in the typed array. 795 */ 796 private void updateStateFromTypedArray(TypedArray a, int srcDensityOverride) 797 throws XmlPullParserException { 798 final Resources r = a.getResources(); 799 final BitmapState state = mBitmapState; 800 801 // Account for any configuration changes. 802 state.mChangingConfigurations |= a.getChangingConfigurations(); 803 804 // Extract the theme attributes, if any. 805 state.mThemeAttrs = a.extractThemeAttrs(); 806 807 state.mSrcDensityOverride = srcDensityOverride; 808 809 state.mTargetDensity = Drawable.resolveDensity(r, 0); 810 811 final int srcResId = a.getResourceId(R.styleable.BitmapDrawable_src, 0); 812 if (srcResId != 0) { 813 final TypedValue value = new TypedValue(); 814 r.getValueForDensity(srcResId, srcDensityOverride, value, true); 815 816 // Pretend the requested density is actually the display density. If 817 // the drawable returned is not the requested density, then force it 818 // to be scaled later by dividing its density by the ratio of 819 // requested density to actual device density. Drawables that have 820 // undefined density or no density don't need to be handled here. 821 if (srcDensityOverride > 0 && value.density > 0 822 && value.density != TypedValue.DENSITY_NONE) { 823 if (value.density == srcDensityOverride) { 824 value.density = r.getDisplayMetrics().densityDpi; 825 } else { 826 value.density = 827 (value.density * r.getDisplayMetrics().densityDpi) / srcDensityOverride; 828 } 829 } 830 831 int density = Bitmap.DENSITY_NONE; 832 if (value.density == TypedValue.DENSITY_DEFAULT) { 833 density = DisplayMetrics.DENSITY_DEFAULT; 834 } else if (value.density != TypedValue.DENSITY_NONE) { 835 density = value.density; 836 } 837 838 Bitmap bitmap = null; 839 try (InputStream is = r.openRawResource(srcResId, value)) { 840 ImageDecoder.Source source = ImageDecoder.createSource(r, is, density); 841 bitmap = ImageDecoder.decodeBitmap(source, (decoder, info, src) -> { 842 decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE); 843 }); 844 } catch (Exception e) { 845 // Do nothing and pick up the error below. 846 } 847 848 if (bitmap == null) { 849 throw new XmlPullParserException(a.getPositionDescription() + 850 ": <bitmap> requires a valid 'src' attribute"); 851 } 852 853 state.mBitmap = bitmap; 854 } 855 856 final boolean defMipMap = state.mBitmap != null ? state.mBitmap.hasMipMap() : false; 857 setMipMap(a.getBoolean(R.styleable.BitmapDrawable_mipMap, defMipMap)); 858 859 state.mAutoMirrored = a.getBoolean( 860 R.styleable.BitmapDrawable_autoMirrored, state.mAutoMirrored); 861 state.mBaseAlpha = a.getFloat(R.styleable.BitmapDrawable_alpha, state.mBaseAlpha); 862 863 final int tintMode = a.getInt(R.styleable.BitmapDrawable_tintMode, -1); 864 if (tintMode != -1) { 865 state.mTintMode = Drawable.parseTintMode(tintMode, Mode.SRC_IN); 866 } 867 868 final ColorStateList tint = a.getColorStateList(R.styleable.BitmapDrawable_tint); 869 if (tint != null) { 870 state.mTint = tint; 871 } 872 873 final Paint paint = mBitmapState.mPaint; 874 paint.setAntiAlias(a.getBoolean( 875 R.styleable.BitmapDrawable_antialias, paint.isAntiAlias())); 876 paint.setFilterBitmap(a.getBoolean( 877 R.styleable.BitmapDrawable_filter, paint.isFilterBitmap())); 878 paint.setDither(a.getBoolean(R.styleable.BitmapDrawable_dither, paint.isDither())); 879 880 setGravity(a.getInt(R.styleable.BitmapDrawable_gravity, state.mGravity)); 881 882 final int tileMode = a.getInt(R.styleable.BitmapDrawable_tileMode, TILE_MODE_UNDEFINED); 883 if (tileMode != TILE_MODE_UNDEFINED) { 884 final Shader.TileMode mode = parseTileMode(tileMode); 885 setTileModeXY(mode, mode); 886 } 887 888 final int tileModeX = a.getInt(R.styleable.BitmapDrawable_tileModeX, TILE_MODE_UNDEFINED); 889 if (tileModeX != TILE_MODE_UNDEFINED) { 890 setTileModeX(parseTileMode(tileModeX)); 891 } 892 893 final int tileModeY = a.getInt(R.styleable.BitmapDrawable_tileModeY, TILE_MODE_UNDEFINED); 894 if (tileModeY != TILE_MODE_UNDEFINED) { 895 setTileModeY(parseTileMode(tileModeY)); 896 } 897 } 898 899 @Override 900 public void applyTheme(Theme t) { 901 super.applyTheme(t); 902 903 final BitmapState state = mBitmapState; 904 if (state == null) { 905 return; 906 } 907 908 if (state.mThemeAttrs != null) { 909 final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.BitmapDrawable); 910 try { 911 updateStateFromTypedArray(a, state.mSrcDensityOverride); 912 } catch (XmlPullParserException e) { 913 rethrowAsRuntimeException(e); 914 } finally { 915 a.recycle(); 916 } 917 } 918 919 // Apply theme to contained color state list. 920 if (state.mTint != null && state.mTint.canApplyTheme()) { 921 state.mTint = state.mTint.obtainForTheme(t); 922 } 923 924 // Update local properties. 925 updateLocalState(t.getResources()); 926 } 927 928 private static Shader.TileMode parseTileMode(int tileMode) { 929 switch (tileMode) { 930 case TILE_MODE_CLAMP: 931 return Shader.TileMode.CLAMP; 932 case TILE_MODE_REPEAT: 933 return Shader.TileMode.REPEAT; 934 case TILE_MODE_MIRROR: 935 return Shader.TileMode.MIRROR; 936 default: 937 return null; 938 } 939 } 940 941 @Override 942 public boolean canApplyTheme() { 943 return mBitmapState != null && mBitmapState.canApplyTheme(); 944 } 945 946 @Override 947 public int getIntrinsicWidth() { 948 return mBitmapWidth; 949 } 950 951 @Override 952 public int getIntrinsicHeight() { 953 return mBitmapHeight; 954 } 955 956 @Override 957 public int getOpacity() { 958 if (mBitmapState.mGravity != Gravity.FILL) { 959 return PixelFormat.TRANSLUCENT; 960 } 961 962 final Bitmap bitmap = mBitmapState.mBitmap; 963 return (bitmap == null || bitmap.hasAlpha() || mBitmapState.mPaint.getAlpha() < 255) ? 964 PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE; 965 } 966 967 @Override 968 public final ConstantState getConstantState() { 969 mBitmapState.mChangingConfigurations |= getChangingConfigurations(); 970 return mBitmapState; 971 } 972 973 final static class BitmapState extends ConstantState { 974 final Paint mPaint; 975 976 // Values loaded during inflation. 977 int[] mThemeAttrs = null; 978 Bitmap mBitmap = null; 979 ColorStateList mTint = null; 980 Mode mTintMode = DEFAULT_TINT_MODE; 981 int mGravity = Gravity.FILL; 982 float mBaseAlpha = 1.0f; 983 Shader.TileMode mTileModeX = null; 984 Shader.TileMode mTileModeY = null; 985 986 // The density to use when looking up the bitmap in Resources. A value of 0 means use 987 // the system's density. 988 int mSrcDensityOverride = 0; 989 990 // The density at which to render the bitmap. 991 int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT; 992 993 boolean mAutoMirrored = false; 994 995 @Config int mChangingConfigurations; 996 boolean mRebuildShader; 997 998 BitmapState(Bitmap bitmap) { 999 mBitmap = bitmap; 1000 mPaint = new Paint(DEFAULT_PAINT_FLAGS); 1001 } 1002 1003 BitmapState(BitmapState bitmapState) { 1004 mBitmap = bitmapState.mBitmap; 1005 mTint = bitmapState.mTint; 1006 mTintMode = bitmapState.mTintMode; 1007 mThemeAttrs = bitmapState.mThemeAttrs; 1008 mChangingConfigurations = bitmapState.mChangingConfigurations; 1009 mGravity = bitmapState.mGravity; 1010 mTileModeX = bitmapState.mTileModeX; 1011 mTileModeY = bitmapState.mTileModeY; 1012 mSrcDensityOverride = bitmapState.mSrcDensityOverride; 1013 mTargetDensity = bitmapState.mTargetDensity; 1014 mBaseAlpha = bitmapState.mBaseAlpha; 1015 mPaint = new Paint(bitmapState.mPaint); 1016 mRebuildShader = bitmapState.mRebuildShader; 1017 mAutoMirrored = bitmapState.mAutoMirrored; 1018 } 1019 1020 @Override 1021 public boolean canApplyTheme() { 1022 return mThemeAttrs != null || mTint != null && mTint.canApplyTheme(); 1023 } 1024 1025 @Override 1026 public Drawable newDrawable() { 1027 return new BitmapDrawable(this, null); 1028 } 1029 1030 @Override 1031 public Drawable newDrawable(Resources res) { 1032 return new BitmapDrawable(this, res); 1033 } 1034 1035 @Override 1036 public @Config int getChangingConfigurations() { 1037 return mChangingConfigurations 1038 | (mTint != null ? mTint.getChangingConfigurations() : 0); 1039 } 1040 } 1041 1042 private BitmapDrawable(BitmapState state, Resources res) { 1043 init(state, res); 1044 } 1045 1046 /** 1047 * The one helper to rule them all. This is called by all public & private 1048 * constructors to set the state and initialize local properties. 1049 */ 1050 private void init(BitmapState state, Resources res) { 1051 mBitmapState = state; 1052 updateLocalState(res); 1053 1054 if (mBitmapState != null && res != null) { 1055 mBitmapState.mTargetDensity = mTargetDensity; 1056 } 1057 } 1058 1059 /** 1060 * Initializes local dynamic properties from state. This should be called 1061 * after significant state changes, e.g. from the One True Constructor and 1062 * after inflating or applying a theme. 1063 */ 1064 private void updateLocalState(Resources res) { 1065 mTargetDensity = resolveDensity(res, mBitmapState.mTargetDensity); 1066 mTintFilter = updateTintFilter(mTintFilter, mBitmapState.mTint, mBitmapState.mTintMode); 1067 computeBitmapSize(); 1068 } 1069 } 1070