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.UnsupportedAppUsage; 21 import android.content.pm.ActivityInfo.Config; 22 import android.content.res.ColorStateList; 23 import android.content.res.Resources; 24 import android.content.res.Resources.Theme; 25 import android.content.res.TypedArray; 26 import android.graphics.Bitmap; 27 import android.graphics.BitmapShader; 28 import android.graphics.BlendMode; 29 import android.graphics.BlendModeColorFilter; 30 import android.graphics.Canvas; 31 import android.graphics.ColorFilter; 32 import android.graphics.ImageDecoder; 33 import android.graphics.Insets; 34 import android.graphics.Matrix; 35 import android.graphics.Outline; 36 import android.graphics.Paint; 37 import android.graphics.PixelFormat; 38 import android.graphics.PorterDuff.Mode; 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 @UnsupportedAppUsage 92 private BitmapState mBitmapState; 93 private BlendModeColorFilter mBlendModeFilter; 94 95 @UnsupportedAppUsage 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 /** @hide */ 243 @UnsupportedAppUsage setBitmap(Bitmap bitmap)244 public void setBitmap(Bitmap bitmap) { 245 if (mBitmapState.mBitmap != bitmap) { 246 mBitmapState.mBitmap = bitmap; 247 computeBitmapSize(); 248 invalidateSelf(); 249 } 250 } 251 252 /** 253 * Set the density scale at which this drawable will be rendered. This 254 * method assumes the drawable will be rendered at the same density as the 255 * specified canvas. 256 * 257 * @param canvas The Canvas from which the density scale must be obtained. 258 * 259 * @see android.graphics.Bitmap#setDensity(int) 260 * @see android.graphics.Bitmap#getDensity() 261 */ setTargetDensity(Canvas canvas)262 public void setTargetDensity(Canvas canvas) { 263 setTargetDensity(canvas.getDensity()); 264 } 265 266 /** 267 * Set the density scale at which this drawable will be rendered. 268 * 269 * @param metrics The DisplayMetrics indicating the density scale for this drawable. 270 * 271 * @see android.graphics.Bitmap#setDensity(int) 272 * @see android.graphics.Bitmap#getDensity() 273 */ setTargetDensity(DisplayMetrics metrics)274 public void setTargetDensity(DisplayMetrics metrics) { 275 setTargetDensity(metrics.densityDpi); 276 } 277 278 /** 279 * Set the density at which this drawable will be rendered. 280 * 281 * @param density The density scale for this drawable. 282 * 283 * @see android.graphics.Bitmap#setDensity(int) 284 * @see android.graphics.Bitmap#getDensity() 285 */ setTargetDensity(int density)286 public void setTargetDensity(int density) { 287 if (mTargetDensity != density) { 288 mTargetDensity = density == 0 ? DisplayMetrics.DENSITY_DEFAULT : density; 289 if (mBitmapState.mBitmap != null) { 290 computeBitmapSize(); 291 } 292 invalidateSelf(); 293 } 294 } 295 296 /** Get the gravity used to position/stretch the bitmap within its bounds. 297 * See android.view.Gravity 298 * @return the gravity applied to the bitmap 299 */ getGravity()300 public int getGravity() { 301 return mBitmapState.mGravity; 302 } 303 304 /** Set the gravity used to position/stretch the bitmap within its bounds. 305 See android.view.Gravity 306 * @param gravity the gravity 307 */ setGravity(int gravity)308 public void setGravity(int gravity) { 309 if (mBitmapState.mGravity != gravity) { 310 mBitmapState.mGravity = gravity; 311 mDstRectAndInsetsDirty = true; 312 invalidateSelf(); 313 } 314 } 315 316 /** 317 * Enables or disables the mipmap hint for this drawable's bitmap. 318 * See {@link Bitmap#setHasMipMap(boolean)} for more information. 319 * 320 * If the bitmap is null calling this method has no effect. 321 * 322 * @param mipMap True if the bitmap should use mipmaps, false otherwise. 323 * 324 * @see #hasMipMap() 325 */ setMipMap(boolean mipMap)326 public void setMipMap(boolean mipMap) { 327 if (mBitmapState.mBitmap != null) { 328 mBitmapState.mBitmap.setHasMipMap(mipMap); 329 invalidateSelf(); 330 } 331 } 332 333 /** 334 * Indicates whether the mipmap hint is enabled on this drawable's bitmap. 335 * 336 * @return True if the mipmap hint is set, false otherwise. If the bitmap 337 * is null, this method always returns false. 338 * 339 * @see #setMipMap(boolean) 340 * @attr ref android.R.styleable#BitmapDrawable_mipMap 341 */ hasMipMap()342 public boolean hasMipMap() { 343 return mBitmapState.mBitmap != null && mBitmapState.mBitmap.hasMipMap(); 344 } 345 346 /** 347 * Enables or disables anti-aliasing for this drawable. Anti-aliasing affects 348 * the edges of the bitmap only so it applies only when the drawable is rotated. 349 * 350 * @param aa True if the bitmap should be anti-aliased, false otherwise. 351 * 352 * @see #hasAntiAlias() 353 */ setAntiAlias(boolean aa)354 public void setAntiAlias(boolean aa) { 355 mBitmapState.mPaint.setAntiAlias(aa); 356 invalidateSelf(); 357 } 358 359 /** 360 * Indicates whether anti-aliasing is enabled for this drawable. 361 * 362 * @return True if anti-aliasing is enabled, false otherwise. 363 * 364 * @see #setAntiAlias(boolean) 365 */ hasAntiAlias()366 public boolean hasAntiAlias() { 367 return mBitmapState.mPaint.isAntiAlias(); 368 } 369 370 @Override setFilterBitmap(boolean filter)371 public void setFilterBitmap(boolean filter) { 372 mBitmapState.mPaint.setFilterBitmap(filter); 373 invalidateSelf(); 374 } 375 376 @Override isFilterBitmap()377 public boolean isFilterBitmap() { 378 return mBitmapState.mPaint.isFilterBitmap(); 379 } 380 381 @Override setDither(boolean dither)382 public void setDither(boolean dither) { 383 mBitmapState.mPaint.setDither(dither); 384 invalidateSelf(); 385 } 386 387 /** 388 * Indicates the repeat behavior of this drawable on the X axis. 389 * 390 * @return {@link android.graphics.Shader.TileMode#CLAMP} if the bitmap does not repeat, 391 * {@link android.graphics.Shader.TileMode#REPEAT} or 392 * {@link android.graphics.Shader.TileMode#MIRROR} otherwise. 393 */ getTileModeX()394 public Shader.TileMode getTileModeX() { 395 return mBitmapState.mTileModeX; 396 } 397 398 /** 399 * Indicates the repeat behavior of this drawable on the Y axis. 400 * 401 * @return {@link android.graphics.Shader.TileMode#CLAMP} if the bitmap does not repeat, 402 * {@link android.graphics.Shader.TileMode#REPEAT} or 403 * {@link android.graphics.Shader.TileMode#MIRROR} otherwise. 404 */ getTileModeY()405 public Shader.TileMode getTileModeY() { 406 return mBitmapState.mTileModeY; 407 } 408 409 /** 410 * Sets the repeat behavior of this drawable on the X axis. By default, the drawable 411 * does not repeat its bitmap. Using {@link android.graphics.Shader.TileMode#REPEAT} or 412 * {@link android.graphics.Shader.TileMode#MIRROR} the bitmap can be repeated (or tiled) 413 * if the bitmap is smaller than this drawable. 414 * 415 * @param mode The repeat mode for this drawable. 416 * 417 * @see #setTileModeY(android.graphics.Shader.TileMode) 418 * @see #setTileModeXY(android.graphics.Shader.TileMode, android.graphics.Shader.TileMode) 419 * @attr ref android.R.styleable#BitmapDrawable_tileModeX 420 */ setTileModeX(Shader.TileMode mode)421 public void setTileModeX(Shader.TileMode mode) { 422 setTileModeXY(mode, mBitmapState.mTileModeY); 423 } 424 425 /** 426 * Sets the repeat behavior of this drawable on the Y axis. By default, the drawable 427 * does not repeat its bitmap. Using {@link android.graphics.Shader.TileMode#REPEAT} or 428 * {@link android.graphics.Shader.TileMode#MIRROR} the bitmap can be repeated (or tiled) 429 * if the bitmap is smaller than this drawable. 430 * 431 * @param mode The repeat mode for this drawable. 432 * 433 * @see #setTileModeX(android.graphics.Shader.TileMode) 434 * @see #setTileModeXY(android.graphics.Shader.TileMode, android.graphics.Shader.TileMode) 435 * @attr ref android.R.styleable#BitmapDrawable_tileModeY 436 */ setTileModeY(Shader.TileMode mode)437 public final void setTileModeY(Shader.TileMode mode) { 438 setTileModeXY(mBitmapState.mTileModeX, mode); 439 } 440 441 /** 442 * Sets the repeat behavior of this drawable on both axis. By default, the drawable 443 * does not repeat its bitmap. Using {@link android.graphics.Shader.TileMode#REPEAT} or 444 * {@link android.graphics.Shader.TileMode#MIRROR} the bitmap can be repeated (or tiled) 445 * if the bitmap is smaller than this drawable. 446 * 447 * @param xmode The X repeat mode for this drawable. 448 * @param ymode The Y repeat mode for this drawable. 449 * 450 * @see #setTileModeX(android.graphics.Shader.TileMode) 451 * @see #setTileModeY(android.graphics.Shader.TileMode) 452 */ setTileModeXY(Shader.TileMode xmode, Shader.TileMode ymode)453 public void setTileModeXY(Shader.TileMode xmode, Shader.TileMode ymode) { 454 final BitmapState state = mBitmapState; 455 if (state.mTileModeX != xmode || state.mTileModeY != ymode) { 456 state.mTileModeX = xmode; 457 state.mTileModeY = ymode; 458 state.mRebuildShader = true; 459 mDstRectAndInsetsDirty = true; 460 invalidateSelf(); 461 } 462 } 463 464 @Override setAutoMirrored(boolean mirrored)465 public void setAutoMirrored(boolean mirrored) { 466 if (mBitmapState.mAutoMirrored != mirrored) { 467 mBitmapState.mAutoMirrored = mirrored; 468 invalidateSelf(); 469 } 470 } 471 472 @Override isAutoMirrored()473 public final boolean isAutoMirrored() { 474 return mBitmapState.mAutoMirrored; 475 } 476 477 @Override getChangingConfigurations()478 public @Config int getChangingConfigurations() { 479 return super.getChangingConfigurations() | mBitmapState.getChangingConfigurations(); 480 } 481 needMirroring()482 private boolean needMirroring() { 483 return isAutoMirrored() && getLayoutDirection() == LayoutDirection.RTL; 484 } 485 486 @Override onBoundsChange(Rect bounds)487 protected void onBoundsChange(Rect bounds) { 488 mDstRectAndInsetsDirty = true; 489 490 final Bitmap bitmap = mBitmapState.mBitmap; 491 final Shader shader = mBitmapState.mPaint.getShader(); 492 if (bitmap != null && shader != null) { 493 updateShaderMatrix(bitmap, mBitmapState.mPaint, shader, needMirroring()); 494 } 495 } 496 497 @Override draw(Canvas canvas)498 public void draw(Canvas canvas) { 499 final Bitmap bitmap = mBitmapState.mBitmap; 500 if (bitmap == null) { 501 return; 502 } 503 504 final BitmapState state = mBitmapState; 505 final Paint paint = state.mPaint; 506 if (state.mRebuildShader) { 507 final Shader.TileMode tmx = state.mTileModeX; 508 final Shader.TileMode tmy = state.mTileModeY; 509 if (tmx == null && tmy == null) { 510 paint.setShader(null); 511 } else { 512 paint.setShader(new BitmapShader(bitmap, 513 tmx == null ? Shader.TileMode.CLAMP : tmx, 514 tmy == null ? Shader.TileMode.CLAMP : tmy)); 515 } 516 517 state.mRebuildShader = false; 518 } 519 520 final int restoreAlpha; 521 if (state.mBaseAlpha != 1.0f) { 522 final Paint p = getPaint(); 523 restoreAlpha = p.getAlpha(); 524 p.setAlpha((int) (restoreAlpha * state.mBaseAlpha + 0.5f)); 525 } else { 526 restoreAlpha = -1; 527 } 528 529 final boolean clearColorFilter; 530 if (mBlendModeFilter != null && paint.getColorFilter() == null) { 531 paint.setColorFilter(mBlendModeFilter); 532 clearColorFilter = true; 533 } else { 534 clearColorFilter = false; 535 } 536 537 updateDstRectAndInsetsIfDirty(); 538 final Shader shader = paint.getShader(); 539 final boolean needMirroring = needMirroring(); 540 if (shader == null) { 541 if (needMirroring) { 542 canvas.save(); 543 // Mirror the bitmap 544 canvas.translate(mDstRect.right - mDstRect.left, 0); 545 canvas.scale(-1.0f, 1.0f); 546 } 547 548 canvas.drawBitmap(bitmap, null, mDstRect, paint); 549 550 if (needMirroring) { 551 canvas.restore(); 552 } 553 } else { 554 updateShaderMatrix(bitmap, paint, shader, needMirroring); 555 canvas.drawRect(mDstRect, paint); 556 } 557 558 if (clearColorFilter) { 559 paint.setColorFilter(null); 560 } 561 562 if (restoreAlpha >= 0) { 563 paint.setAlpha(restoreAlpha); 564 } 565 } 566 567 /** 568 * Updates the {@code paint}'s shader matrix to be consistent with the 569 * destination size and layout direction. 570 * 571 * @param bitmap the bitmap to be drawn 572 * @param paint the paint used to draw the bitmap 573 * @param shader the shader to set on the paint 574 * @param needMirroring whether the bitmap should be mirrored 575 */ updateShaderMatrix(@onNull Bitmap bitmap, @NonNull Paint paint, @NonNull Shader shader, boolean needMirroring)576 private void updateShaderMatrix(@NonNull Bitmap bitmap, @NonNull Paint paint, 577 @NonNull Shader shader, boolean needMirroring) { 578 final int sourceDensity = bitmap.getDensity(); 579 final int targetDensity = mTargetDensity; 580 final boolean needScaling = sourceDensity != 0 && sourceDensity != targetDensity; 581 if (needScaling || needMirroring) { 582 final Matrix matrix = getOrCreateMirrorMatrix(); 583 matrix.reset(); 584 585 if (needMirroring) { 586 final int dx = mDstRect.right - mDstRect.left; 587 matrix.setTranslate(dx, 0); 588 matrix.setScale(-1, 1); 589 } 590 591 if (needScaling) { 592 final float densityScale = targetDensity / (float) sourceDensity; 593 matrix.postScale(densityScale, densityScale); 594 } 595 596 shader.setLocalMatrix(matrix); 597 } else { 598 mMirrorMatrix = null; 599 shader.setLocalMatrix(Matrix.IDENTITY_MATRIX); 600 } 601 602 paint.setShader(shader); 603 } 604 getOrCreateMirrorMatrix()605 private Matrix getOrCreateMirrorMatrix() { 606 if (mMirrorMatrix == null) { 607 mMirrorMatrix = new Matrix(); 608 } 609 return mMirrorMatrix; 610 } 611 updateDstRectAndInsetsIfDirty()612 private void updateDstRectAndInsetsIfDirty() { 613 if (mDstRectAndInsetsDirty) { 614 if (mBitmapState.mTileModeX == null && mBitmapState.mTileModeY == null) { 615 final Rect bounds = getBounds(); 616 final int layoutDirection = getLayoutDirection(); 617 Gravity.apply(mBitmapState.mGravity, mBitmapWidth, mBitmapHeight, 618 bounds, mDstRect, layoutDirection); 619 620 final int left = mDstRect.left - bounds.left; 621 final int top = mDstRect.top - bounds.top; 622 final int right = bounds.right - mDstRect.right; 623 final int bottom = bounds.bottom - mDstRect.bottom; 624 mOpticalInsets = Insets.of(left, top, right, bottom); 625 } else { 626 copyBounds(mDstRect); 627 mOpticalInsets = Insets.NONE; 628 } 629 } 630 mDstRectAndInsetsDirty = false; 631 } 632 633 @Override getOpticalInsets()634 public Insets getOpticalInsets() { 635 updateDstRectAndInsetsIfDirty(); 636 return mOpticalInsets; 637 } 638 639 @Override getOutline(@onNull Outline outline)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 setAlpha(int alpha)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 getAlpha()661 public int getAlpha() { 662 return mBitmapState.mPaint.getAlpha(); 663 } 664 665 @Override setColorFilter(ColorFilter colorFilter)666 public void setColorFilter(ColorFilter colorFilter) { 667 mBitmapState.mPaint.setColorFilter(colorFilter); 668 invalidateSelf(); 669 } 670 671 @Override getColorFilter()672 public ColorFilter getColorFilter() { 673 return mBitmapState.mPaint.getColorFilter(); 674 } 675 676 @Override setTintList(ColorStateList tint)677 public void setTintList(ColorStateList tint) { 678 final BitmapState state = mBitmapState; 679 if (state.mTint != tint) { 680 state.mTint = tint; 681 mBlendModeFilter = updateBlendModeFilter(mBlendModeFilter, tint, 682 mBitmapState.mBlendMode); 683 invalidateSelf(); 684 } 685 } 686 687 @Override setTintBlendMode(@onNull BlendMode blendMode)688 public void setTintBlendMode(@NonNull BlendMode blendMode) { 689 final BitmapState state = mBitmapState; 690 if (state.mBlendMode != blendMode) { 691 state.mBlendMode = blendMode; 692 mBlendModeFilter = updateBlendModeFilter(mBlendModeFilter, mBitmapState.mTint, 693 blendMode); 694 invalidateSelf(); 695 } 696 } 697 698 /** 699 * @hide only needed by a hack within ProgressBar 700 */ 701 @UnsupportedAppUsage getTint()702 public ColorStateList getTint() { 703 return mBitmapState.mTint; 704 } 705 706 /** 707 * @hide only needed by a hack within ProgressBar 708 */ 709 @UnsupportedAppUsage getTintMode()710 public Mode getTintMode() { 711 return BlendMode.blendModeToPorterDuffMode(mBitmapState.mBlendMode); 712 } 713 714 /** 715 * @hide Candidate for future API inclusion 716 */ 717 @Override setXfermode(Xfermode xfermode)718 public void setXfermode(Xfermode xfermode) { 719 mBitmapState.mPaint.setXfermode(xfermode); 720 invalidateSelf(); 721 } 722 723 /** 724 * A mutable BitmapDrawable still shares its Bitmap with any other Drawable 725 * that comes from the same resource. 726 * 727 * @return This drawable. 728 */ 729 @Override mutate()730 public Drawable mutate() { 731 if (!mMutated && super.mutate() == this) { 732 mBitmapState = new BitmapState(mBitmapState); 733 mMutated = true; 734 } 735 return this; 736 } 737 738 /** 739 * @hide 740 */ clearMutated()741 public void clearMutated() { 742 super.clearMutated(); 743 mMutated = false; 744 } 745 746 @Override onStateChange(int[] stateSet)747 protected boolean onStateChange(int[] stateSet) { 748 final BitmapState state = mBitmapState; 749 if (state.mTint != null && state.mBlendMode != null) { 750 mBlendModeFilter = updateBlendModeFilter(mBlendModeFilter, state.mTint, 751 state.mBlendMode); 752 return true; 753 } 754 return false; 755 } 756 757 @Override isStateful()758 public boolean isStateful() { 759 return (mBitmapState.mTint != null && mBitmapState.mTint.isStateful()) 760 || super.isStateful(); 761 } 762 763 /** @hide */ 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