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