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