1 /* 2 * Copyright (C) 2017 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; 18 19 import static android.system.OsConstants.SEEK_CUR; 20 import static android.system.OsConstants.SEEK_SET; 21 22 import static java.lang.annotation.RetentionPolicy.SOURCE; 23 24 import android.annotation.AnyThread; 25 import android.annotation.IntDef; 26 import android.annotation.IntRange; 27 import android.annotation.NonNull; 28 import android.annotation.Nullable; 29 import android.annotation.Px; 30 import android.annotation.TestApi; 31 import android.annotation.WorkerThread; 32 import android.content.ContentResolver; 33 import android.content.res.AssetFileDescriptor; 34 import android.content.res.AssetManager; 35 import android.content.res.AssetManager.AssetInputStream; 36 import android.content.res.Resources; 37 import android.graphics.drawable.AnimatedImageDrawable; 38 import android.graphics.drawable.BitmapDrawable; 39 import android.graphics.drawable.Drawable; 40 import android.graphics.drawable.NinePatchDrawable; 41 import android.net.Uri; 42 import android.os.Build; 43 import android.os.Trace; 44 import android.system.ErrnoException; 45 import android.system.Os; 46 import android.util.DisplayMetrics; 47 import android.util.Size; 48 import android.util.TypedValue; 49 50 import dalvik.system.CloseGuard; 51 52 import libcore.io.IoUtils; 53 54 import java.io.File; 55 import java.io.FileDescriptor; 56 import java.io.FileInputStream; 57 import java.io.FileNotFoundException; 58 import java.io.IOException; 59 import java.io.InputStream; 60 import java.lang.annotation.Retention; 61 import java.nio.ByteBuffer; 62 import java.util.Locale; 63 import java.util.Objects; 64 import java.util.concurrent.Callable; 65 import java.util.concurrent.atomic.AtomicBoolean; 66 67 /** 68 * <p>A class for converting encoded images (like {@code PNG}, {@code JPEG}, 69 * {@code WEBP}, {@code GIF}, or {@code HEIF}) into {@link Drawable} or 70 * {@link Bitmap} objects. 71 * 72 * <p>To use it, first create a {@link Source Source} using one of the 73 * {@code createSource} overloads. For example, to decode from a {@link Uri}, call 74 * {@link #createSource(ContentResolver, Uri)} and pass the result to 75 * {@link #decodeDrawable(Source)} or {@link #decodeBitmap(Source)}: 76 * 77 * <pre class="prettyprint"> 78 * File file = new File(...); 79 * ImageDecoder.Source source = ImageDecoder.createSource(file); 80 * Drawable drawable = ImageDecoder.decodeDrawable(source); 81 * </pre> 82 * 83 * <p>To change the default settings, pass the {@link Source Source} and an 84 * {@link OnHeaderDecodedListener OnHeaderDecodedListener} to 85 * {@link #decodeDrawable(Source, OnHeaderDecodedListener)} or 86 * {@link #decodeBitmap(Source, OnHeaderDecodedListener)}. For example, to 87 * create a sampled image with half the width and height of the original image, 88 * call {@link #setTargetSampleSize setTargetSampleSize(2)} inside 89 * {@link OnHeaderDecodedListener#onHeaderDecoded onHeaderDecoded}: 90 * 91 * <pre class="prettyprint"> 92 * OnHeaderDecodedListener listener = new OnHeaderDecodedListener() { 93 * public void onHeaderDecoded(ImageDecoder decoder, ImageInfo info, Source source) { 94 * decoder.setTargetSampleSize(2); 95 * } 96 * }; 97 * Drawable drawable = ImageDecoder.decodeDrawable(source, listener); 98 * </pre> 99 * 100 * <p>The {@link ImageInfo ImageInfo} contains information about the encoded image, like 101 * its width and height, and the {@link Source Source} can be used to match to a particular 102 * {@link Source Source} if a single {@link OnHeaderDecodedListener OnHeaderDecodedListener} 103 * is used with multiple {@link Source Source} objects. 104 * 105 * <p>The {@link OnHeaderDecodedListener OnHeaderDecodedListener} can also be implemented 106 * as a lambda: 107 * 108 * <pre class="prettyprint"> 109 * Drawable drawable = ImageDecoder.decodeDrawable(source, (decoder, info, src) -> { 110 * decoder.setTargetSampleSize(2); 111 * }); 112 * </pre> 113 * 114 * <p>If the encoded image is an animated {@code GIF} or {@code WEBP}, 115 * {@link #decodeDrawable decodeDrawable} will return an {@link AnimatedImageDrawable}. To 116 * start its animation, call {@link AnimatedImageDrawable#start AnimatedImageDrawable.start()}: 117 * 118 * <pre class="prettyprint"> 119 * Drawable drawable = ImageDecoder.decodeDrawable(source); 120 * if (drawable instanceof AnimatedImageDrawable) { 121 * ((AnimatedImageDrawable) drawable).start(); 122 * } 123 * </pre> 124 * 125 * <p>By default, a {@link Bitmap} created by {@link ImageDecoder} (including 126 * one that is inside a {@link Drawable}) will be immutable (i.e. 127 * {@link Bitmap#isMutable Bitmap.isMutable()} returns {@code false}), and it 128 * will typically have {@code Config} {@link Bitmap.Config#HARDWARE}. Although 129 * these properties can be changed with {@link #setMutableRequired setMutableRequired(true)} 130 * (which is only compatible with {@link #decodeBitmap(Source)} and 131 * {@link #decodeBitmap(Source, OnHeaderDecodedListener)}) and {@link #setAllocator}, 132 * it is also possible to apply custom effects regardless of the mutability of 133 * the final returned object by passing a {@link PostProcessor} to 134 * {@link #setPostProcessor setPostProcessor}. A {@link PostProcessor} can also be a lambda: 135 * 136 * <pre class="prettyprint"> 137 * Drawable drawable = ImageDecoder.decodeDrawable(source, (decoder, info, src) -> { 138 * decoder.setPostProcessor((canvas) -> { 139 * // This will create rounded corners. 140 * Path path = new Path(); 141 * path.setFillType(Path.FillType.INVERSE_EVEN_ODD); 142 * int width = canvas.getWidth(); 143 * int height = canvas.getHeight(); 144 * path.addRoundRect(0, 0, width, height, 20, 20, Path.Direction.CW); 145 * Paint paint = new Paint(); 146 * paint.setAntiAlias(true); 147 * paint.setColor(Color.TRANSPARENT); 148 * paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC)); 149 * canvas.drawPath(path, paint); 150 * return PixelFormat.TRANSLUCENT; 151 * }); 152 * }); 153 * </pre> 154 * 155 * <p>If the encoded image is incomplete or contains an error, or if an 156 * {@link Exception} occurs during decoding, a {@link DecodeException DecodeException} 157 * will be thrown. In some cases, the {@link ImageDecoder} may have decoded part of 158 * the image. In order to display the partial image, an 159 * {@link OnPartialImageListener OnPartialImageListener} must be passed to 160 * {@link #setOnPartialImageListener setOnPartialImageListener}. For example: 161 * 162 * <pre class="prettyprint"> 163 * Drawable drawable = ImageDecoder.decodeDrawable(source, (decoder, info, src) -> { 164 * decoder.setOnPartialImageListener((DecodeException e) -> { 165 * // Returning true indicates to create a Drawable or Bitmap even 166 * // if the whole image could not be decoded. Any remaining lines 167 * // will be blank. 168 * return true; 169 * }); 170 * }); 171 * </pre> 172 */ 173 public final class ImageDecoder implements AutoCloseable { 174 /** 175 * Source of encoded image data. 176 * 177 * <p>References the data that will be used to decode a {@link Drawable} 178 * or {@link Bitmap} in {@link #decodeDrawable decodeDrawable} or 179 * {@link #decodeBitmap decodeBitmap}. Constructing a {@code Source} (with 180 * one of the overloads of {@code createSource}) can be done on any thread 181 * because the construction simply captures values. The real work is done 182 * in {@link #decodeDrawable decodeDrawable} or {@link #decodeBitmap decodeBitmap}. 183 * 184 * <p>A {@code Source} object can be reused to create multiple versions of the 185 * same image. For example, to decode a full size image and its thumbnail, 186 * the same {@code Source} can be used once with no 187 * {@link OnHeaderDecodedListener OnHeaderDecodedListener} and once with an 188 * implementation of {@link OnHeaderDecodedListener#onHeaderDecoded onHeaderDecoded} 189 * that calls {@link #setTargetSize} with smaller dimensions. One {@code Source} 190 * can even be used simultaneously in multiple threads.</p> 191 */ 192 public static abstract class Source { Source()193 private Source() {} 194 195 @Nullable getResources()196 Resources getResources() { return null; } 197 getDensity()198 int getDensity() { return Bitmap.DENSITY_NONE; } 199 computeDstDensity()200 final int computeDstDensity() { 201 Resources res = getResources(); 202 if (res == null) { 203 return Bitmap.getDefaultDensity(); 204 } 205 206 return res.getDisplayMetrics().densityDpi; 207 } 208 209 @NonNull createImageDecoder(boolean preferAnimation)210 abstract ImageDecoder createImageDecoder(boolean preferAnimation) throws IOException; 211 }; 212 213 private static class ByteArraySource extends Source { ByteArraySource(@onNull byte[] data, int offset, int length)214 ByteArraySource(@NonNull byte[] data, int offset, int length) { 215 mData = data; 216 mOffset = offset; 217 mLength = length; 218 }; 219 private final byte[] mData; 220 private final int mOffset; 221 private final int mLength; 222 223 @Override createImageDecoder(boolean preferAnimation)224 public ImageDecoder createImageDecoder(boolean preferAnimation) throws IOException { 225 return nCreate(mData, mOffset, mLength, preferAnimation, this); 226 } 227 228 @Override toString()229 public String toString() { 230 return "ByteArraySource{len=" + mLength + "}"; 231 } 232 } 233 234 private static class ByteBufferSource extends Source { ByteBufferSource(@onNull ByteBuffer buffer)235 ByteBufferSource(@NonNull ByteBuffer buffer) { 236 mBuffer = buffer; 237 mLength = mBuffer.limit() - mBuffer.position(); 238 } 239 240 private final ByteBuffer mBuffer; 241 private final int mLength; 242 243 @Override createImageDecoder(boolean preferAnimation)244 public ImageDecoder createImageDecoder(boolean preferAnimation) throws IOException { 245 if (!mBuffer.isDirect() && mBuffer.hasArray()) { 246 int offset = mBuffer.arrayOffset() + mBuffer.position(); 247 int length = mBuffer.limit() - mBuffer.position(); 248 return nCreate(mBuffer.array(), offset, length, preferAnimation, this); 249 } 250 ByteBuffer buffer = mBuffer.slice(); 251 return nCreate(buffer, buffer.position(), buffer.limit(), preferAnimation, this); 252 } 253 254 @Override toString()255 public String toString() { 256 return "ByteBufferSource{len=" + mLength + "}"; 257 } 258 } 259 260 private static class ContentResolverSource extends Source { ContentResolverSource(@onNull ContentResolver resolver, @NonNull Uri uri, @Nullable Resources res)261 ContentResolverSource(@NonNull ContentResolver resolver, @NonNull Uri uri, 262 @Nullable Resources res) { 263 mResolver = resolver; 264 mUri = uri; 265 mResources = res; 266 } 267 268 private final ContentResolver mResolver; 269 private final Uri mUri; 270 private final Resources mResources; 271 272 @Nullable getResources()273 Resources getResources() { return mResources; } 274 275 @Override createImageDecoder(boolean preferAnimation)276 public ImageDecoder createImageDecoder(boolean preferAnimation) throws IOException { 277 AssetFileDescriptor assetFd = null; 278 try { 279 if (ContentResolver.SCHEME_CONTENT.equals(mUri.getScheme())) { 280 assetFd = mResolver.openTypedAssetFileDescriptor(mUri, 281 "image/*", null); 282 } else { 283 assetFd = mResolver.openAssetFileDescriptor(mUri, "r"); 284 } 285 } catch (FileNotFoundException e) { 286 // Handled below, along with the case where assetFd was set to null. 287 } 288 289 if (assetFd == null) { 290 // Some images cannot be opened as AssetFileDescriptors (e.g. 291 // bmp, ico). Open them as InputStreams. 292 InputStream is = mResolver.openInputStream(mUri); 293 if (is == null) { 294 throw new FileNotFoundException(mUri.toString()); 295 } 296 297 return createFromStream(is, true, preferAnimation, this); 298 } 299 300 return createFromAssetFileDescriptor(assetFd, preferAnimation, this); 301 } 302 303 @Override toString()304 public String toString() { 305 String uri = mUri.toString(); 306 if (uri.length() > 90) { 307 // We want to keep the Uri usable - usually the authority and the end is important. 308 uri = uri.substring(0, 80) + ".." + uri.substring(uri.length() - 10); 309 } 310 return "ContentResolverSource{uri=" + uri + "}"; 311 } 312 } 313 314 @NonNull createFromFile(@onNull File file, boolean preferAnimation, @NonNull Source source)315 private static ImageDecoder createFromFile(@NonNull File file, 316 boolean preferAnimation, @NonNull Source source) throws IOException { 317 FileInputStream stream = new FileInputStream(file); 318 FileDescriptor fd = stream.getFD(); 319 try { 320 Os.lseek(fd, 0, SEEK_CUR); 321 } catch (ErrnoException e) { 322 return createFromStream(stream, true, preferAnimation, source); 323 } 324 325 ImageDecoder decoder = null; 326 try { 327 decoder = nCreate(fd, AssetFileDescriptor.UNKNOWN_LENGTH, preferAnimation, source); 328 } finally { 329 if (decoder == null) { 330 IoUtils.closeQuietly(stream); 331 } else { 332 decoder.mInputStream = stream; 333 decoder.mOwnsInputStream = true; 334 } 335 } 336 return decoder; 337 } 338 339 @NonNull createFromStream(@onNull InputStream is, boolean closeInputStream, boolean preferAnimation, Source source)340 private static ImageDecoder createFromStream(@NonNull InputStream is, 341 boolean closeInputStream, boolean preferAnimation, Source source) throws IOException { 342 // Arbitrary size matches BitmapFactory. 343 byte[] storage = new byte[16 * 1024]; 344 ImageDecoder decoder = null; 345 try { 346 decoder = nCreate(is, storage, preferAnimation, source); 347 } finally { 348 if (decoder == null) { 349 if (closeInputStream) { 350 IoUtils.closeQuietly(is); 351 } 352 } else { 353 decoder.mInputStream = is; 354 decoder.mOwnsInputStream = closeInputStream; 355 decoder.mTempStorage = storage; 356 } 357 } 358 359 return decoder; 360 } 361 362 @NonNull createFromAssetFileDescriptor(@onNull AssetFileDescriptor assetFd, boolean preferAnimation, Source source)363 private static ImageDecoder createFromAssetFileDescriptor(@NonNull AssetFileDescriptor assetFd, 364 boolean preferAnimation, Source source) throws IOException { 365 if (assetFd == null) { 366 throw new FileNotFoundException(); 367 } 368 final FileDescriptor fd = assetFd.getFileDescriptor(); 369 final long offset = assetFd.getStartOffset(); 370 371 ImageDecoder decoder = null; 372 try { 373 try { 374 Os.lseek(fd, offset, SEEK_SET); 375 decoder = nCreate(fd, assetFd.getDeclaredLength(), preferAnimation, source); 376 } catch (ErrnoException e) { 377 decoder = createFromStream(new FileInputStream(fd), true, preferAnimation, source); 378 } 379 } finally { 380 if (decoder == null) { 381 IoUtils.closeQuietly(assetFd); 382 } else { 383 decoder.mAssetFd = assetFd; 384 } 385 } 386 return decoder; 387 } 388 389 /** 390 * For backwards compatibility, this does *not* close the InputStream. 391 * 392 * Further, unlike other Sources, this one is not reusable. 393 */ 394 private static class InputStreamSource extends Source { InputStreamSource(Resources res, @NonNull InputStream is, int inputDensity)395 InputStreamSource(Resources res, @NonNull InputStream is, int inputDensity) { 396 if (is == null) { 397 throw new IllegalArgumentException("The InputStream cannot be null"); 398 } 399 mResources = res; 400 mInputStream = is; 401 mInputDensity = inputDensity; 402 } 403 404 final Resources mResources; 405 InputStream mInputStream; 406 final int mInputDensity; 407 408 @Override getResources()409 public Resources getResources() { return mResources; } 410 411 @Override getDensity()412 public int getDensity() { return mInputDensity; } 413 414 @Override createImageDecoder(boolean preferAnimation)415 public ImageDecoder createImageDecoder(boolean preferAnimation) throws IOException { 416 417 synchronized (this) { 418 if (mInputStream == null) { 419 throw new IOException("Cannot reuse InputStreamSource"); 420 } 421 InputStream is = mInputStream; 422 mInputStream = null; 423 return createFromStream(is, false, preferAnimation, this); 424 } 425 } 426 427 @Override toString()428 public String toString() { 429 return "InputStream{s=" + mInputStream + "}"; 430 } 431 } 432 433 /** 434 * Takes ownership of the AssetInputStream. 435 * 436 * @hide 437 */ 438 public static class AssetInputStreamSource extends Source { AssetInputStreamSource(@onNull AssetInputStream ais, @NonNull Resources res, @NonNull TypedValue value)439 public AssetInputStreamSource(@NonNull AssetInputStream ais, 440 @NonNull Resources res, @NonNull TypedValue value) { 441 mAssetInputStream = ais; 442 mResources = res; 443 444 if (value.density == TypedValue.DENSITY_DEFAULT) { 445 mDensity = DisplayMetrics.DENSITY_DEFAULT; 446 } else if (value.density != TypedValue.DENSITY_NONE) { 447 mDensity = value.density; 448 } else { 449 mDensity = Bitmap.DENSITY_NONE; 450 } 451 } 452 453 private AssetInputStream mAssetInputStream; 454 private final Resources mResources; 455 private final int mDensity; 456 457 @Override getResources()458 public Resources getResources() { return mResources; } 459 460 @Override getDensity()461 public int getDensity() { 462 return mDensity; 463 } 464 465 @Override createImageDecoder(boolean preferAnimation)466 public ImageDecoder createImageDecoder(boolean preferAnimation) throws IOException { 467 synchronized (this) { 468 if (mAssetInputStream == null) { 469 throw new IOException("Cannot reuse AssetInputStreamSource"); 470 } 471 AssetInputStream ais = mAssetInputStream; 472 mAssetInputStream = null; 473 return createFromAsset(ais, preferAnimation, this); 474 } 475 } 476 477 @Override toString()478 public String toString() { 479 return "AssetInputStream{s=" + mAssetInputStream + "}"; 480 } 481 } 482 483 private static class ResourceSource extends Source { ResourceSource(@onNull Resources res, int resId)484 ResourceSource(@NonNull Resources res, int resId) { 485 mResources = res; 486 mResId = resId; 487 mResDensity = Bitmap.DENSITY_NONE; 488 } 489 490 final Resources mResources; 491 final int mResId; 492 int mResDensity; 493 private Object mLock = new Object(); 494 495 @Override getResources()496 public Resources getResources() { return mResources; } 497 498 @Override getDensity()499 public int getDensity() { 500 synchronized (mLock) { 501 return mResDensity; 502 } 503 } 504 505 @Override createImageDecoder(boolean preferAnimation)506 public ImageDecoder createImageDecoder(boolean preferAnimation) throws IOException { 507 TypedValue value = new TypedValue(); 508 // This is just used in order to access the underlying Asset and 509 // keep it alive. 510 InputStream is = mResources.openRawResource(mResId, value); 511 512 synchronized (mLock) { 513 if (value.density == TypedValue.DENSITY_DEFAULT) { 514 mResDensity = DisplayMetrics.DENSITY_DEFAULT; 515 } else if (value.density != TypedValue.DENSITY_NONE) { 516 mResDensity = value.density; 517 } 518 } 519 520 return createFromAsset((AssetInputStream) is, preferAnimation, this); 521 } 522 523 @Override toString()524 public String toString() { 525 // Try to return a human-readable name for debugging purposes. 526 try { 527 return "Resource{name=" + mResources.getResourceName(mResId) + "}"; 528 } catch (Resources.NotFoundException e) { 529 // It's ok if we don't find it, fall back to ID. 530 } 531 return "Resource{id=" + mResId + "}"; 532 } 533 } 534 535 /** 536 * ImageDecoder will own the AssetInputStream. 537 */ createFromAsset(AssetInputStream ais, boolean preferAnimation, Source source)538 private static ImageDecoder createFromAsset(AssetInputStream ais, 539 boolean preferAnimation, Source source) throws IOException { 540 ImageDecoder decoder = null; 541 try { 542 long asset = ais.getNativeAsset(); 543 decoder = nCreate(asset, preferAnimation, source); 544 } finally { 545 if (decoder == null) { 546 IoUtils.closeQuietly(ais); 547 } else { 548 decoder.mInputStream = ais; 549 decoder.mOwnsInputStream = true; 550 } 551 } 552 return decoder; 553 } 554 555 private static class AssetSource extends Source { AssetSource(@onNull AssetManager assets, @NonNull String fileName)556 AssetSource(@NonNull AssetManager assets, @NonNull String fileName) { 557 mAssets = assets; 558 mFileName = fileName; 559 } 560 561 private final AssetManager mAssets; 562 private final String mFileName; 563 564 @Override createImageDecoder(boolean preferAnimation)565 public ImageDecoder createImageDecoder(boolean preferAnimation) throws IOException { 566 InputStream is = mAssets.open(mFileName); 567 return createFromAsset((AssetInputStream) is, preferAnimation, this); 568 } 569 570 @Override toString()571 public String toString() { 572 return "AssetSource{file=" + mFileName + "}"; 573 } 574 } 575 576 private static class FileSource extends Source { FileSource(@onNull File file)577 FileSource(@NonNull File file) { 578 mFile = file; 579 } 580 581 private final File mFile; 582 583 @Override createImageDecoder(boolean preferAnimation)584 public ImageDecoder createImageDecoder(boolean preferAnimation) throws IOException { 585 return createFromFile(mFile, preferAnimation, this); 586 } 587 588 @Override toString()589 public String toString() { 590 return "FileSource{file=" + mFile + "}"; 591 } 592 } 593 594 private static class CallableSource extends Source { CallableSource(@onNull Callable<AssetFileDescriptor> callable)595 CallableSource(@NonNull Callable<AssetFileDescriptor> callable) { 596 mCallable = callable; 597 } 598 599 private final Callable<AssetFileDescriptor> mCallable; 600 601 @Override createImageDecoder(boolean preferAnimation)602 public ImageDecoder createImageDecoder(boolean preferAnimation) throws IOException { 603 AssetFileDescriptor assetFd = null; 604 try { 605 assetFd = mCallable.call(); 606 } catch (Exception e) { 607 if (e instanceof IOException) { 608 throw (IOException) e; 609 } else { 610 throw new IOException(e); 611 } 612 } 613 return createFromAssetFileDescriptor(assetFd, preferAnimation, this); 614 } 615 616 @Override toString()617 public String toString() { 618 return "CallableSource{obj=" + mCallable.toString() + "}"; 619 } 620 } 621 622 /** 623 * Information about an encoded image. 624 */ 625 public static class ImageInfo { 626 private final Size mSize; 627 private ImageDecoder mDecoder; 628 ImageInfo(@onNull ImageDecoder decoder)629 private ImageInfo(@NonNull ImageDecoder decoder) { 630 mSize = new Size(decoder.mWidth, decoder.mHeight); 631 mDecoder = decoder; 632 } 633 634 /** 635 * Size of the image, without scaling or cropping. 636 */ 637 @NonNull getSize()638 public Size getSize() { 639 return mSize; 640 } 641 642 /** 643 * The mimeType of the image. 644 */ 645 @NonNull getMimeType()646 public String getMimeType() { 647 return mDecoder.getMimeType(); 648 } 649 650 /** 651 * Whether the image is animated. 652 * 653 * <p>If {@code true}, {@link #decodeDrawable decodeDrawable} will 654 * return an {@link AnimatedImageDrawable}.</p> 655 */ isAnimated()656 public boolean isAnimated() { 657 return mDecoder.mAnimated; 658 } 659 660 /** 661 * If known, the color space the decoded bitmap will have. Note that the 662 * output color space is not guaranteed to be the color space the bitmap 663 * is encoded with. If not known (when the config is 664 * {@link Bitmap.Config#ALPHA_8} for instance), or there is an error, 665 * it is set to null. 666 */ 667 @Nullable getColorSpace()668 public ColorSpace getColorSpace() { 669 return mDecoder.getColorSpace(); 670 } 671 }; 672 673 /** @removed 674 * @deprecated Subsumed by {@link #DecodeException}. 675 */ 676 @Deprecated 677 public static class IncompleteException extends IOException {}; 678 679 /** 680 * Interface for changing the default settings of a decode. 681 * 682 * <p>Supply an instance to 683 * {@link #decodeDrawable(Source, OnHeaderDecodedListener) decodeDrawable} 684 * or {@link #decodeBitmap(Source, OnHeaderDecodedListener) decodeBitmap}, 685 * which will call {@link OnHeaderDecodedListener#onHeaderDecoded onHeaderDecoded} 686 * (in the same thread) once the size is known. The implementation of 687 * {@link OnHeaderDecodedListener#onHeaderDecoded onHeaderDecoded} can then 688 * change the decode settings as desired. 689 */ 690 public static interface OnHeaderDecodedListener { 691 /** 692 * Called by {@link ImageDecoder} when the header has been decoded and 693 * the image size is known. 694 * 695 * @param decoder the object performing the decode, for changing 696 * its default settings. 697 * @param info information about the encoded image. 698 * @param source object that created {@code decoder}. 699 */ onHeaderDecoded(@onNull ImageDecoder decoder, @NonNull ImageInfo info, @NonNull Source source)700 public void onHeaderDecoded(@NonNull ImageDecoder decoder, 701 @NonNull ImageInfo info, @NonNull Source source); 702 703 }; 704 705 /** @removed 706 * @deprecated Replaced by {@link #DecodeException#SOURCE_EXCEPTION}. 707 */ 708 @Deprecated 709 public static final int ERROR_SOURCE_EXCEPTION = 1; 710 711 /** @removed 712 * @deprecated Replaced by {@link #DecodeException#SOURCE_INCOMPLETE}. 713 */ 714 @Deprecated 715 public static final int ERROR_SOURCE_INCOMPLETE = 2; 716 717 /** @removed 718 * @deprecated Replaced by {@link #DecodeException#SOURCE_MALFORMED_DATA}. 719 */ 720 @Deprecated 721 public static final int ERROR_SOURCE_ERROR = 3; 722 723 /** 724 * Information about an interrupted decode. 725 */ 726 public static final class DecodeException extends IOException { 727 /** 728 * An Exception was thrown reading the {@link Source}. 729 */ 730 public static final int SOURCE_EXCEPTION = 1; 731 732 /** 733 * The encoded data was incomplete. 734 */ 735 public static final int SOURCE_INCOMPLETE = 2; 736 737 /** 738 * The encoded data contained an error. 739 */ 740 public static final int SOURCE_MALFORMED_DATA = 3; 741 742 /** @hide **/ 743 @Retention(SOURCE) 744 @IntDef(value = { SOURCE_EXCEPTION, SOURCE_INCOMPLETE, SOURCE_MALFORMED_DATA }, 745 prefix = {"SOURCE_"}) 746 public @interface Error {}; 747 748 @Error final int mError; 749 @NonNull final Source mSource; 750 DecodeException(@rror int error, @Nullable Throwable cause, @NonNull Source source)751 DecodeException(@Error int error, @Nullable Throwable cause, @NonNull Source source) { 752 super(errorMessage(error, cause), cause); 753 mError = error; 754 mSource = source; 755 } 756 757 /** 758 * Private method called by JNI. 759 */ 760 @SuppressWarnings("unused") DecodeException(@rror int error, @Nullable String msg, @Nullable Throwable cause, @NonNull Source source)761 DecodeException(@Error int error, @Nullable String msg, @Nullable Throwable cause, 762 @NonNull Source source) { 763 super(msg + errorMessage(error, cause), cause); 764 mError = error; 765 mSource = source; 766 } 767 768 /** 769 * Retrieve the reason that decoding was interrupted. 770 * 771 * <p>If the error is {@link #SOURCE_EXCEPTION}, the underlying 772 * {@link java.lang.Throwable} can be retrieved with 773 * {@link java.lang.Throwable#getCause}.</p> 774 */ 775 @Error getError()776 public int getError() { 777 return mError; 778 } 779 780 /** 781 * Retrieve the {@link Source Source} that was interrupted. 782 * 783 * <p>This can be used for equality checking to find the Source which 784 * failed to completely decode.</p> 785 */ 786 @NonNull getSource()787 public Source getSource() { 788 return mSource; 789 } 790 errorMessage(@rror int error, @Nullable Throwable cause)791 private static String errorMessage(@Error int error, @Nullable Throwable cause) { 792 switch (error) { 793 case SOURCE_EXCEPTION: 794 return "Exception in input: " + cause; 795 case SOURCE_INCOMPLETE: 796 return "Input was incomplete."; 797 case SOURCE_MALFORMED_DATA: 798 return "Input contained an error."; 799 default: 800 return ""; 801 } 802 } 803 } 804 805 /** 806 * Interface for inspecting a {@link DecodeException DecodeException} 807 * and potentially preventing it from being thrown. 808 * 809 * <p>If an instance is passed to 810 * {@link #setOnPartialImageListener setOnPartialImageListener}, a 811 * {@link DecodeException DecodeException} that would otherwise have been 812 * thrown can be inspected inside 813 * {@link OnPartialImageListener#onPartialImage onPartialImage}. 814 * If {@link OnPartialImageListener#onPartialImage onPartialImage} returns 815 * {@code true}, a partial image will be created. 816 */ 817 public static interface OnPartialImageListener { 818 /** 819 * Called by {@link ImageDecoder} when there is only a partial image to 820 * display. 821 * 822 * <p>If decoding is interrupted after having decoded a partial image, 823 * this method will be called. The implementation can inspect the 824 * {@link DecodeException DecodeException} and optionally finish the 825 * rest of the decode creation process to create a partial {@link Drawable} 826 * or {@link Bitmap}. 827 * 828 * @param exception exception containing information about the 829 * decode interruption. 830 * @return {@code true} to create and return a {@link Drawable} or 831 * {@link Bitmap} with partial data. {@code false} (which is the 832 * default) to abort the decode and throw {@code e}. Any undecoded 833 * lines in the image will be blank. 834 */ onPartialImage(@onNull DecodeException exception)835 boolean onPartialImage(@NonNull DecodeException exception); 836 }; 837 838 // Fields 839 private long mNativePtr; 840 private final int mWidth; 841 private final int mHeight; 842 private final boolean mAnimated; 843 private final boolean mIsNinePatch; 844 845 private int mDesiredWidth; 846 private int mDesiredHeight; 847 private int mAllocator = ALLOCATOR_DEFAULT; 848 private boolean mUnpremultipliedRequired = false; 849 private boolean mMutable = false; 850 private boolean mConserveMemory = false; 851 private boolean mDecodeAsAlphaMask = false; 852 private ColorSpace mDesiredColorSpace = null; 853 private Rect mCropRect; 854 private Rect mOutPaddingRect; 855 private Source mSource; 856 857 private PostProcessor mPostProcessor; 858 private OnPartialImageListener mOnPartialImageListener; 859 860 // Objects for interacting with the input. 861 private InputStream mInputStream; 862 private boolean mOwnsInputStream; 863 private byte[] mTempStorage; 864 private AssetFileDescriptor mAssetFd; 865 private final AtomicBoolean mClosed = new AtomicBoolean(); 866 private final CloseGuard mCloseGuard = CloseGuard.get(); 867 868 /** 869 * Private constructor called by JNI. {@link #close} must be 870 * called after decoding to delete native resources. 871 */ 872 @SuppressWarnings("unused") ImageDecoder(long nativePtr, int width, int height, boolean animated, boolean isNinePatch)873 private ImageDecoder(long nativePtr, int width, int height, 874 boolean animated, boolean isNinePatch) { 875 mNativePtr = nativePtr; 876 mWidth = width; 877 mHeight = height; 878 mDesiredWidth = width; 879 mDesiredHeight = height; 880 mAnimated = animated; 881 mIsNinePatch = isNinePatch; 882 mCloseGuard.open("close"); 883 } 884 885 @Override finalize()886 protected void finalize() throws Throwable { 887 try { 888 if (mCloseGuard != null) { 889 mCloseGuard.warnIfOpen(); 890 } 891 892 // Avoid closing these in finalizer. 893 mInputStream = null; 894 mAssetFd = null; 895 896 close(); 897 } finally { 898 super.finalize(); 899 } 900 } 901 902 /** 903 * Return if the given MIME type is a supported file format that can be 904 * decoded by this class. This can be useful to determine if a file can be 905 * decoded directly, or if it needs to be converted into a more general 906 * format using an API like {@link ContentResolver#openTypedAssetFile}. 907 */ isMimeTypeSupported(@onNull String mimeType)908 public static boolean isMimeTypeSupported(@NonNull String mimeType) { 909 Objects.requireNonNull(mimeType); 910 switch (mimeType.toLowerCase(Locale.US)) { 911 case "image/png": 912 case "image/jpeg": 913 case "image/webp": 914 case "image/gif": 915 case "image/heif": 916 case "image/heic": 917 case "image/bmp": 918 case "image/x-ico": 919 case "image/vnd.wap.wbmp": 920 case "image/x-sony-arw": 921 case "image/x-canon-cr2": 922 case "image/x-adobe-dng": 923 case "image/x-nikon-nef": 924 case "image/x-nikon-nrw": 925 case "image/x-olympus-orf": 926 case "image/x-fuji-raf": 927 case "image/x-panasonic-rw2": 928 case "image/x-pentax-pef": 929 case "image/x-samsung-srw": 930 return true; 931 default: 932 return false; 933 } 934 } 935 936 /** 937 * Create a new {@link Source Source} from a resource. 938 * 939 * @param res the {@link Resources} object containing the image data. 940 * @param resId resource ID of the image data. 941 * @return a new Source object, which can be passed to 942 * {@link #decodeDrawable decodeDrawable} or 943 * {@link #decodeBitmap decodeBitmap}. 944 */ 945 @AnyThread 946 @NonNull createSource(@onNull Resources res, int resId)947 public static Source createSource(@NonNull Resources res, int resId) 948 { 949 return new ResourceSource(res, resId); 950 } 951 952 /** 953 * Create a new {@link Source Source} from a {@link android.net.Uri}. 954 * 955 * <h5>Accepts the following URI schemes:</h5> 956 * <ul> 957 * <li>content ({@link ContentResolver#SCHEME_CONTENT})</li> 958 * <li>android.resource ({@link ContentResolver#SCHEME_ANDROID_RESOURCE})</li> 959 * <li>file ({@link ContentResolver#SCHEME_FILE})</li> 960 * </ul> 961 * 962 * @param cr to retrieve from. 963 * @param uri of the image file. 964 * @return a new Source object, which can be passed to 965 * {@link #decodeDrawable decodeDrawable} or 966 * {@link #decodeBitmap decodeBitmap}. 967 */ 968 @AnyThread 969 @NonNull createSource(@onNull ContentResolver cr, @NonNull Uri uri)970 public static Source createSource(@NonNull ContentResolver cr, 971 @NonNull Uri uri) { 972 return new ContentResolverSource(cr, uri, null); 973 } 974 975 /** 976 * Provide Resources for density scaling. 977 * 978 * @hide 979 */ 980 @AnyThread 981 @NonNull createSource(@onNull ContentResolver cr, @NonNull Uri uri, @Nullable Resources res)982 public static Source createSource(@NonNull ContentResolver cr, 983 @NonNull Uri uri, @Nullable Resources res) { 984 return new ContentResolverSource(cr, uri, res); 985 } 986 987 /** 988 * Create a new {@link Source Source} from a file in the "assets" directory. 989 */ 990 @AnyThread 991 @NonNull createSource(@onNull AssetManager assets, @NonNull String fileName)992 public static Source createSource(@NonNull AssetManager assets, @NonNull String fileName) { 993 return new AssetSource(assets, fileName); 994 } 995 996 /** 997 * Create a new {@link Source Source} from a byte array. 998 * 999 * <p>Note: If this {@code Source} is passed to {@link #decodeDrawable decodeDrawable}, 1000 * and the encoded image is animated, the returned {@link AnimatedImageDrawable} 1001 * will continue reading from {@code data}, so its contents must not 1002 * be modified, even after the {@code AnimatedImageDrawable} is returned. 1003 * {@code data}'s contents should never be modified during decode.</p> 1004 * 1005 * @param data byte array of compressed image data. 1006 * @param offset offset into data for where the decoder should begin 1007 * parsing. 1008 * @param length number of bytes, beginning at offset, to parse. 1009 * @return a new Source object, which can be passed to 1010 * {@link #decodeDrawable decodeDrawable} or 1011 * {@link #decodeBitmap decodeBitmap}. 1012 * @throws NullPointerException if data is null. 1013 * @throws ArrayIndexOutOfBoundsException if offset and length are 1014 * not within data. 1015 */ 1016 @AnyThread 1017 @NonNull createSource(@onNull byte[] data, int offset, int length)1018 public static Source createSource(@NonNull byte[] data, int offset, 1019 int length) throws ArrayIndexOutOfBoundsException { 1020 if (data == null) { 1021 throw new NullPointerException("null byte[] in createSource!"); 1022 } 1023 if (offset < 0 || length < 0 || offset >= data.length || 1024 offset + length > data.length) { 1025 throw new ArrayIndexOutOfBoundsException( 1026 "invalid offset/length!"); 1027 } 1028 return new ByteArraySource(data, offset, length); 1029 } 1030 1031 /** 1032 * Create a new {@link Source Source} from a byte array. 1033 * 1034 * <p>Note: If this {@code Source} is passed to {@link #decodeDrawable decodeDrawable}, 1035 * and the encoded image is animated, the returned {@link AnimatedImageDrawable} 1036 * will continue reading from {@code data}, so its contents must not 1037 * be modified, even after the {@code AnimatedImageDrawable} is returned. 1038 * {@code data}'s contents should never be modified during decode.</p> 1039 * 1040 * @param data byte array of compressed image data. 1041 * @return a new Source object, which can be passed to 1042 * {@link #decodeDrawable decodeDrawable} or 1043 * {@link #decodeBitmap decodeBitmap}. 1044 * @throws NullPointerException if data is null. 1045 */ 1046 @AnyThread 1047 @NonNull createSource(@onNull byte[] data)1048 public static Source createSource(@NonNull byte[] data) { 1049 return createSource(data, 0, data.length); 1050 } 1051 1052 /** 1053 * Create a new {@link Source Source} from a {@link java.nio.ByteBuffer}. 1054 * 1055 * <p>Decoding will start from {@link java.nio.ByteBuffer#position() buffer.position()}. 1056 * The position of {@code buffer} will not be affected.</p> 1057 * 1058 * <p>Note: If this {@code Source} is passed to {@link #decodeDrawable decodeDrawable}, 1059 * and the encoded image is animated, the returned {@link AnimatedImageDrawable} 1060 * will continue reading from the {@code buffer}, so its contents must not 1061 * be modified, even after the {@code AnimatedImageDrawable} is returned. 1062 * {@code buffer}'s contents should never be modified during decode.</p> 1063 * 1064 * @return a new Source object, which can be passed to 1065 * {@link #decodeDrawable decodeDrawable} or 1066 * {@link #decodeBitmap decodeBitmap}. 1067 */ 1068 @AnyThread 1069 @NonNull createSource(@onNull ByteBuffer buffer)1070 public static Source createSource(@NonNull ByteBuffer buffer) { 1071 return new ByteBufferSource(buffer); 1072 } 1073 1074 /** 1075 * Internal API used to generate bitmaps for use by Drawables (i.e. BitmapDrawable) 1076 * 1077 * <p>Unlike other Sources, this one cannot be reused.</p> 1078 * 1079 * @hide 1080 */ 1081 @AnyThread 1082 @NonNull createSource(Resources res, @NonNull InputStream is)1083 public static Source createSource(Resources res, @NonNull InputStream is) { 1084 return new InputStreamSource(res, is, Bitmap.getDefaultDensity()); 1085 } 1086 1087 /** 1088 * Internal API used to generate bitmaps for use by Drawables (i.e. BitmapDrawable) 1089 * 1090 * <p>Unlike other Sources, this one cannot be reused.</p> 1091 * 1092 * @hide 1093 */ 1094 @AnyThread 1095 @TestApi 1096 @NonNull createSource(Resources res, @NonNull InputStream is, int density)1097 public static Source createSource(Resources res, @NonNull InputStream is, int density) { 1098 return new InputStreamSource(res, is, density); 1099 } 1100 1101 /** 1102 * Create a new {@link Source Source} from a {@link java.io.File}. 1103 * <p> 1104 * This method should only be used for files that you have direct access to; 1105 * if you'd like to work with files hosted outside your app, use an API like 1106 * {@link #createSource(Callable)} or 1107 * {@link #createSource(ContentResolver, Uri)}. 1108 * @return a new Source object, which can be passed to 1109 * {@link #decodeDrawable decodeDrawable} or 1110 * {@link #decodeBitmap decodeBitmap}. 1111 */ 1112 @AnyThread 1113 @NonNull createSource(@onNull File file)1114 public static Source createSource(@NonNull File file) { 1115 return new FileSource(file); 1116 } 1117 1118 /** 1119 * Create a new {@link Source Source} from a {@link Callable} that returns a 1120 * new {@link AssetFileDescriptor} for each request. This provides control 1121 * over how the {@link AssetFileDescriptor} is created, such as passing 1122 * options into {@link ContentResolver#openTypedAssetFileDescriptor}, or 1123 * enabling use of a {@link android.os.CancellationSignal}. 1124 * <p> 1125 * It's important for the given {@link Callable} to return a new, unique 1126 * {@link AssetFileDescriptor} for each invocation, to support reuse of the 1127 * returned {@link Source Source}. 1128 * 1129 * @return a new Source object, which can be passed to 1130 * {@link #decodeDrawable decodeDrawable} or {@link #decodeBitmap 1131 * decodeBitmap}. 1132 */ 1133 @AnyThread 1134 @NonNull createSource(@onNull Callable<AssetFileDescriptor> callable)1135 public static Source createSource(@NonNull Callable<AssetFileDescriptor> callable) { 1136 return new CallableSource(callable); 1137 } 1138 1139 /** 1140 * Return the width and height of a given sample size. 1141 * 1142 * <p>This takes an input that functions like 1143 * {@link BitmapFactory.Options#inSampleSize}. It returns a width and 1144 * height that can be achieved by sampling the encoded image. Other widths 1145 * and heights may be supported, but will require an additional (internal) 1146 * scaling step. Such internal scaling is *not* supported with 1147 * {@link #setUnpremultipliedRequired} set to {@code true}.</p> 1148 * 1149 * @param sampleSize Sampling rate of the encoded image. 1150 * @return {@link android.util.Size} of the width and height after 1151 * sampling. 1152 */ 1153 @NonNull getSampledSize(int sampleSize)1154 private Size getSampledSize(int sampleSize) { 1155 if (sampleSize <= 0) { 1156 throw new IllegalArgumentException("sampleSize must be positive! " 1157 + "provided " + sampleSize); 1158 } 1159 if (mNativePtr == 0) { 1160 throw new IllegalStateException("ImageDecoder is closed!"); 1161 } 1162 1163 return nGetSampledSize(mNativePtr, sampleSize); 1164 } 1165 1166 // Modifiers 1167 /** @removed 1168 * @deprecated Renamed to {@link #setTargetSize}. 1169 */ 1170 @Deprecated setResize(int width, int height)1171 public ImageDecoder setResize(int width, int height) { 1172 this.setTargetSize(width, height); 1173 return this; 1174 } 1175 1176 /** 1177 * Specify the size of the output {@link Drawable} or {@link Bitmap}. 1178 * 1179 * <p>By default, the output size will match the size of the encoded 1180 * image, which can be retrieved from the {@link ImageInfo ImageInfo} in 1181 * {@link OnHeaderDecodedListener#onHeaderDecoded onHeaderDecoded}.</p> 1182 * 1183 * <p>This will sample or scale the output to an arbitrary size that may 1184 * be smaller or larger than the encoded size.</p> 1185 * 1186 * <p>Only the last call to this or {@link #setTargetSampleSize} is 1187 * respected.</p> 1188 * 1189 * <p>Like all setters on ImageDecoder, this must be called inside 1190 * {@link OnHeaderDecodedListener#onHeaderDecoded onHeaderDecoded}.</p> 1191 * 1192 * @param width width in pixels of the output, must be greater than 0 1193 * @param height height in pixels of the output, must be greater than 0 1194 */ setTargetSize(@x @ntRangefrom = 1) int width, @Px @IntRange(from = 1) int height)1195 public void setTargetSize(@Px @IntRange(from = 1) int width, 1196 @Px @IntRange(from = 1) int height) { 1197 if (width <= 0 || height <= 0) { 1198 throw new IllegalArgumentException("Dimensions must be positive! " 1199 + "provided (" + width + ", " + height + ")"); 1200 } 1201 1202 mDesiredWidth = width; 1203 mDesiredHeight = height; 1204 } 1205 1206 /** @removed 1207 * @deprecated Renamed to {@link #setTargetSampleSize}. 1208 */ 1209 @Deprecated setResize(int sampleSize)1210 public ImageDecoder setResize(int sampleSize) { 1211 this.setTargetSampleSize(sampleSize); 1212 return this; 1213 } 1214 getTargetDimension(int original, int sampleSize, int computed)1215 private int getTargetDimension(int original, int sampleSize, int computed) { 1216 // Sampling will never result in a smaller size than 1. 1217 if (sampleSize >= original) { 1218 return 1; 1219 } 1220 1221 // Use integer divide to find the desired size. If that is what 1222 // getSampledSize computed, that is the size to use. 1223 int target = original / sampleSize; 1224 if (computed == target) { 1225 return computed; 1226 } 1227 1228 // If sampleSize does not divide evenly into original, the decoder 1229 // may round in either direction. It just needs to get a result that 1230 // is close. 1231 int reverse = computed * sampleSize; 1232 if (Math.abs(reverse - original) < sampleSize) { 1233 // This is the size that can be decoded most efficiently. 1234 return computed; 1235 } 1236 1237 // The decoder could not get close (e.g. it is a DNG image). 1238 return target; 1239 } 1240 1241 /** 1242 * Set the target size with a sampleSize. 1243 * 1244 * <p>By default, the output size will match the size of the encoded 1245 * image, which can be retrieved from the {@link ImageInfo ImageInfo} in 1246 * {@link OnHeaderDecodedListener#onHeaderDecoded onHeaderDecoded}.</p> 1247 * 1248 * <p>Requests the decoder to subsample the original image, returning a 1249 * smaller image to save memory. The {@code sampleSize} is the number of pixels 1250 * in either dimension that correspond to a single pixel in the output. 1251 * For example, {@code sampleSize == 4} returns an image that is 1/4 the 1252 * width/height of the original, and 1/16 the number of pixels.</p> 1253 * 1254 * <p>Must be greater than or equal to 1.</p> 1255 * 1256 * <p>This has the same effect as calling {@link #setTargetSize} with 1257 * dimensions based on the {@code sampleSize}. Unlike dividing the original 1258 * width and height by the {@code sampleSize} manually, calling this method 1259 * allows {@code ImageDecoder} to round in the direction that it can do most 1260 * efficiently.</p> 1261 * 1262 * <p>Only the last call to this or {@link #setTargetSize} is respected.</p> 1263 * 1264 * <p>Like all setters on ImageDecoder, this must be called inside 1265 * {@link OnHeaderDecodedListener#onHeaderDecoded onHeaderDecoded}.</p> 1266 * 1267 * @param sampleSize sampling rate of the encoded image. 1268 */ setTargetSampleSize(@ntRangefrom = 1) int sampleSize)1269 public void setTargetSampleSize(@IntRange(from = 1) int sampleSize) { 1270 Size size = this.getSampledSize(sampleSize); 1271 int targetWidth = getTargetDimension(mWidth, sampleSize, size.getWidth()); 1272 int targetHeight = getTargetDimension(mHeight, sampleSize, size.getHeight()); 1273 this.setTargetSize(targetWidth, targetHeight); 1274 } 1275 requestedResize()1276 private boolean requestedResize() { 1277 return mWidth != mDesiredWidth || mHeight != mDesiredHeight; 1278 } 1279 1280 // These need to stay in sync with ImageDecoder.cpp's Allocator enum. 1281 /** 1282 * Use the default allocation for the pixel memory. 1283 * 1284 * Will typically result in a {@link Bitmap.Config#HARDWARE} 1285 * allocation, but may be software for small images. In addition, this will 1286 * switch to software when HARDWARE is incompatible, e.g. 1287 * {@link #setMutableRequired setMutableRequired(true)} or 1288 * {@link #setDecodeAsAlphaMaskEnabled setDecodeAsAlphaMaskEnabled(true)}. 1289 */ 1290 public static final int ALLOCATOR_DEFAULT = 0; 1291 1292 /** 1293 * Use a software allocation for the pixel memory. 1294 * 1295 * <p>Useful for drawing to a software {@link Canvas} or for 1296 * accessing the pixels on the final output. 1297 */ 1298 public static final int ALLOCATOR_SOFTWARE = 1; 1299 1300 /** 1301 * Use shared memory for the pixel memory. 1302 * 1303 * <p>Useful for sharing across processes. 1304 */ 1305 public static final int ALLOCATOR_SHARED_MEMORY = 2; 1306 1307 /** 1308 * Require a {@link Bitmap.Config#HARDWARE} {@link Bitmap}. 1309 * 1310 * <p>When this is combined with incompatible options, like 1311 * {@link #setMutableRequired setMutableRequired(true)} or 1312 * {@link #setDecodeAsAlphaMaskEnabled setDecodeAsAlphaMaskEnabled(true)}, 1313 * {@link #decodeDrawable decodeDrawable} or {@link #decodeBitmap decodeBitmap} 1314 * will throw an {@link java.lang.IllegalStateException}. 1315 */ 1316 public static final int ALLOCATOR_HARDWARE = 3; 1317 1318 /** @hide **/ 1319 @Retention(SOURCE) 1320 @IntDef(value = { ALLOCATOR_DEFAULT, ALLOCATOR_SOFTWARE, 1321 ALLOCATOR_SHARED_MEMORY, ALLOCATOR_HARDWARE }, 1322 prefix = {"ALLOCATOR_"}) 1323 public @interface Allocator {}; 1324 1325 /** 1326 * Choose the backing for the pixel memory. 1327 * 1328 * <p>This is ignored for animated drawables.</p> 1329 * 1330 * <p>Like all setters on ImageDecoder, this must be called inside 1331 * {@link OnHeaderDecodedListener#onHeaderDecoded onHeaderDecoded}.</p> 1332 * 1333 * @param allocator Type of allocator to use. 1334 */ setAllocator(@llocator int allocator)1335 public void setAllocator(@Allocator int allocator) { 1336 if (allocator < ALLOCATOR_DEFAULT || allocator > ALLOCATOR_HARDWARE) { 1337 throw new IllegalArgumentException("invalid allocator " + allocator); 1338 } 1339 mAllocator = allocator; 1340 } 1341 1342 /** 1343 * Return the allocator for the pixel memory. 1344 */ 1345 @Allocator getAllocator()1346 public int getAllocator() { 1347 return mAllocator; 1348 } 1349 1350 /** 1351 * Specify whether the {@link Bitmap} should have unpremultiplied pixels. 1352 * 1353 * <p>By default, ImageDecoder will create a {@link Bitmap} with 1354 * premultiplied pixels, which is required for drawing with the 1355 * {@link android.view.View} system (i.e. to a {@link Canvas}). Calling 1356 * this method with a value of {@code true} will result in 1357 * {@link #decodeBitmap} returning a {@link Bitmap} with unpremultiplied 1358 * pixels. See {@link Bitmap#isPremultiplied Bitmap.isPremultiplied()}. 1359 * This is incompatible with {@link #decodeDrawable decodeDrawable}; 1360 * attempting to decode an unpremultiplied {@link Drawable} will throw an 1361 * {@link java.lang.IllegalStateException}. </p> 1362 * 1363 * <p>Like all setters on ImageDecoder, this must be called inside 1364 * {@link OnHeaderDecodedListener#onHeaderDecoded onHeaderDecoded}.</p> 1365 */ setUnpremultipliedRequired(boolean unpremultipliedRequired)1366 public void setUnpremultipliedRequired(boolean unpremultipliedRequired) { 1367 mUnpremultipliedRequired = unpremultipliedRequired; 1368 } 1369 1370 /** @removed 1371 * @deprecated Renamed to {@link #setUnpremultipliedRequired}. 1372 */ 1373 @Deprecated setRequireUnpremultiplied(boolean unpremultipliedRequired)1374 public ImageDecoder setRequireUnpremultiplied(boolean unpremultipliedRequired) { 1375 this.setUnpremultipliedRequired(unpremultipliedRequired); 1376 return this; 1377 } 1378 1379 /** 1380 * Return whether the {@link Bitmap} will have unpremultiplied pixels. 1381 */ isUnpremultipliedRequired()1382 public boolean isUnpremultipliedRequired() { 1383 return mUnpremultipliedRequired; 1384 } 1385 1386 /** @removed 1387 * @deprecated Renamed to {@link #isUnpremultipliedRequired}. 1388 */ 1389 @Deprecated getRequireUnpremultiplied()1390 public boolean getRequireUnpremultiplied() { 1391 return this.isUnpremultipliedRequired(); 1392 } 1393 1394 /** 1395 * Modify the image after decoding and scaling. 1396 * 1397 * <p>This allows adding effects prior to returning a {@link Drawable} or 1398 * {@link Bitmap}. For a {@code Drawable} or an immutable {@code Bitmap}, 1399 * this is the only way to process the image after decoding.</p> 1400 * 1401 * <p>If combined with {@link #setTargetSize} and/or {@link #setCrop}, 1402 * {@link PostProcessor#onPostProcess} occurs last.</p> 1403 * 1404 * <p>If set on a nine-patch image, the nine-patch data is ignored.</p> 1405 * 1406 * <p>For an animated image, the drawing commands drawn on the 1407 * {@link Canvas} will be recorded immediately and then applied to each 1408 * frame.</p> 1409 * 1410 * <p>Like all setters on ImageDecoder, this must be called inside 1411 * {@link OnHeaderDecodedListener#onHeaderDecoded onHeaderDecoded}.</p> 1412 * 1413 */ setPostProcessor(@ullable PostProcessor postProcessor)1414 public void setPostProcessor(@Nullable PostProcessor postProcessor) { 1415 mPostProcessor = postProcessor; 1416 } 1417 1418 /** 1419 * Return the {@link PostProcessor} currently set. 1420 */ 1421 @Nullable getPostProcessor()1422 public PostProcessor getPostProcessor() { 1423 return mPostProcessor; 1424 } 1425 1426 /** 1427 * Set (replace) the {@link OnPartialImageListener} on this object. 1428 * 1429 * <p>Will be called if there is an error in the input. Without one, an 1430 * error will result in an {@code Exception} being thrown.</p> 1431 * 1432 * <p>Like all setters on ImageDecoder, this must be called inside 1433 * {@link OnHeaderDecodedListener#onHeaderDecoded onHeaderDecoded}.</p> 1434 * 1435 */ setOnPartialImageListener(@ullable OnPartialImageListener listener)1436 public void setOnPartialImageListener(@Nullable OnPartialImageListener listener) { 1437 mOnPartialImageListener = listener; 1438 } 1439 1440 /** 1441 * Return the {@link OnPartialImageListener OnPartialImageListener} currently set. 1442 */ 1443 @Nullable getOnPartialImageListener()1444 public OnPartialImageListener getOnPartialImageListener() { 1445 return mOnPartialImageListener; 1446 } 1447 1448 /** 1449 * Crop the output to {@code subset} of the (possibly) scaled image. 1450 * 1451 * <p>{@code subset} must be contained within the size set by 1452 * {@link #setTargetSize} or the bounds of the image if setTargetSize was 1453 * not called. Otherwise an {@link IllegalStateException} will be thrown by 1454 * {@link #decodeDrawable decodeDrawable}/{@link #decodeBitmap decodeBitmap}.</p> 1455 * 1456 * <p>NOT intended as a replacement for 1457 * {@link BitmapRegionDecoder#decodeRegion BitmapRegionDecoder.decodeRegion()}. 1458 * This supports all formats, but merely crops the output.</p> 1459 * 1460 * <p>Like all setters on ImageDecoder, this must be called inside 1461 * {@link OnHeaderDecodedListener#onHeaderDecoded onHeaderDecoded}.</p> 1462 * 1463 */ setCrop(@ullable Rect subset)1464 public void setCrop(@Nullable Rect subset) { 1465 mCropRect = subset; 1466 } 1467 1468 /** 1469 * Return the cropping rectangle, if set. 1470 */ 1471 @Nullable getCrop()1472 public Rect getCrop() { 1473 return mCropRect; 1474 } 1475 1476 /** 1477 * Set a Rect for retrieving nine patch padding. 1478 * 1479 * If the image is a nine patch, this Rect will be set to the padding 1480 * rectangle during decode. Otherwise it will not be modified. 1481 * 1482 * <p>Like all setters on ImageDecoder, this must be called inside 1483 * {@link OnHeaderDecodedListener#onHeaderDecoded onHeaderDecoded}.</p> 1484 * 1485 * @hide 1486 * Must be public for access from android.graphics.drawable, 1487 * but must not be called from outside the UI module. 1488 */ setOutPaddingRect(@onNull Rect outPadding)1489 public void setOutPaddingRect(@NonNull Rect outPadding) { 1490 mOutPaddingRect = outPadding; 1491 } 1492 1493 /** 1494 * Specify whether the {@link Bitmap} should be mutable. 1495 * 1496 * <p>By default, a {@link Bitmap} created by {@link #decodeBitmap decodeBitmap} 1497 * will be immutable i.e. {@link Bitmap#isMutable() Bitmap.isMutable()} returns 1498 * {@code false}. This can be changed with {@code setMutableRequired(true)}. 1499 * 1500 * <p>Mutable Bitmaps are incompatible with {@link #ALLOCATOR_HARDWARE}, 1501 * because {@link Bitmap.Config#HARDWARE} Bitmaps cannot be mutable. 1502 * Attempting to combine them will throw an 1503 * {@link java.lang.IllegalStateException}.</p> 1504 * 1505 * <p>Mutable Bitmaps are also incompatible with {@link #decodeDrawable decodeDrawable}, 1506 * which would require retrieving the Bitmap from the returned Drawable in 1507 * order to modify. Attempting to decode a mutable {@link Drawable} will 1508 * throw an {@link java.lang.IllegalStateException}.</p> 1509 * 1510 * <p>Like all setters on ImageDecoder, this must be called inside 1511 * {@link OnHeaderDecodedListener#onHeaderDecoded onHeaderDecoded}.</p> 1512 */ setMutableRequired(boolean mutable)1513 public void setMutableRequired(boolean mutable) { 1514 mMutable = mutable; 1515 } 1516 1517 /** @removed 1518 * @deprecated Renamed to {@link #setMutableRequired}. 1519 */ 1520 @Deprecated setMutable(boolean mutable)1521 public ImageDecoder setMutable(boolean mutable) { 1522 this.setMutableRequired(mutable); 1523 return this; 1524 } 1525 1526 /** 1527 * Return whether the decoded {@link Bitmap} will be mutable. 1528 */ isMutableRequired()1529 public boolean isMutableRequired() { 1530 return mMutable; 1531 } 1532 1533 /** @removed 1534 * @deprecated Renamed to {@link #isMutableRequired}. 1535 */ 1536 @Deprecated getMutable()1537 public boolean getMutable() { 1538 return this.isMutableRequired(); 1539 } 1540 1541 /** 1542 * Save memory if possible by using a denser {@link Bitmap.Config} at the 1543 * cost of some image quality. 1544 * 1545 * <p>For example an opaque 8-bit image may be compressed into an 1546 * {@link Bitmap.Config#RGB_565} configuration, sacrificing image 1547 * quality to save memory. 1548 */ 1549 public static final int MEMORY_POLICY_LOW_RAM = 0; 1550 1551 /** 1552 * Use the most natural {@link Bitmap.Config} for the internal {@link Bitmap}. 1553 * 1554 * <p>This is the recommended default for most applications and usages. This 1555 * will use the closest {@link Bitmap.Config} for the encoded source. If the 1556 * encoded source does not exactly match any {@link Bitmap.Config}, the next 1557 * highest quality {@link Bitmap.Config} will be used avoiding any loss in 1558 * image quality. 1559 */ 1560 public static final int MEMORY_POLICY_DEFAULT = 1; 1561 1562 /** @hide **/ 1563 @Retention(SOURCE) 1564 @IntDef(value = { MEMORY_POLICY_DEFAULT, MEMORY_POLICY_LOW_RAM }, 1565 prefix = {"MEMORY_POLICY_"}) 1566 public @interface MemoryPolicy {}; 1567 1568 /** 1569 * Specify the memory policy for the decoded {@link Bitmap}. 1570 * 1571 * <p>Like all setters on ImageDecoder, this must be called inside 1572 * {@link OnHeaderDecodedListener#onHeaderDecoded onHeaderDecoded}.</p> 1573 */ setMemorySizePolicy(@emoryPolicy int policy)1574 public void setMemorySizePolicy(@MemoryPolicy int policy) { 1575 mConserveMemory = (policy == MEMORY_POLICY_LOW_RAM); 1576 } 1577 1578 /** 1579 * Retrieve the memory policy for the decoded {@link Bitmap}. 1580 */ 1581 @MemoryPolicy getMemorySizePolicy()1582 public int getMemorySizePolicy() { 1583 return mConserveMemory ? MEMORY_POLICY_LOW_RAM : MEMORY_POLICY_DEFAULT; 1584 } 1585 1586 /** @removed 1587 * @deprecated Replaced by {@link #setMemorySizePolicy}. 1588 */ 1589 @Deprecated setConserveMemory(boolean conserveMemory)1590 public void setConserveMemory(boolean conserveMemory) { 1591 mConserveMemory = conserveMemory; 1592 } 1593 1594 /** @removed 1595 * @deprecated Replaced by {@link #getMemorySizePolicy}. 1596 */ 1597 @Deprecated getConserveMemory()1598 public boolean getConserveMemory() { 1599 return mConserveMemory; 1600 } 1601 1602 /** 1603 * Specify whether to potentially treat the output as an alpha mask. 1604 * 1605 * <p>If this is set to {@code true} and the image is encoded in a format 1606 * with only one channel, treat that channel as alpha. Otherwise this call has 1607 * no effect.</p> 1608 * 1609 * <p>This is incompatible with {@link #ALLOCATOR_HARDWARE}. Trying to 1610 * combine them will result in {@link #decodeDrawable decodeDrawable}/ 1611 * {@link #decodeBitmap decodeBitmap} throwing an 1612 * {@link java.lang.IllegalStateException}.</p> 1613 * 1614 * <p>Like all setters on ImageDecoder, this must be called inside 1615 * {@link OnHeaderDecodedListener#onHeaderDecoded onHeaderDecoded}.</p> 1616 */ setDecodeAsAlphaMaskEnabled(boolean enabled)1617 public void setDecodeAsAlphaMaskEnabled(boolean enabled) { 1618 mDecodeAsAlphaMask = enabled; 1619 } 1620 1621 /** @removed 1622 * @deprecated Renamed to {@link #setDecodeAsAlphaMaskEnabled}. 1623 */ 1624 @Deprecated setDecodeAsAlphaMask(boolean enabled)1625 public ImageDecoder setDecodeAsAlphaMask(boolean enabled) { 1626 this.setDecodeAsAlphaMaskEnabled(enabled); 1627 return this; 1628 } 1629 1630 /** @removed 1631 * @deprecated Renamed to {@link #setDecodeAsAlphaMaskEnabled}. 1632 */ 1633 @Deprecated setAsAlphaMask(boolean asAlphaMask)1634 public ImageDecoder setAsAlphaMask(boolean asAlphaMask) { 1635 this.setDecodeAsAlphaMask(asAlphaMask); 1636 return this; 1637 } 1638 1639 /** 1640 * Return whether to treat single channel input as alpha. 1641 * 1642 * <p>This returns whether {@link #setDecodeAsAlphaMaskEnabled} was set to 1643 * {@code true}. It may still return {@code true} even if the image has 1644 * more than one channel and therefore will not be treated as an alpha 1645 * mask.</p> 1646 */ isDecodeAsAlphaMaskEnabled()1647 public boolean isDecodeAsAlphaMaskEnabled() { 1648 return mDecodeAsAlphaMask; 1649 } 1650 1651 /** @removed 1652 * @deprecated Renamed to {@link #isDecodeAsAlphaMaskEnabled}. 1653 */ 1654 @Deprecated getDecodeAsAlphaMask()1655 public boolean getDecodeAsAlphaMask() { 1656 return mDecodeAsAlphaMask; 1657 } 1658 1659 /** @removed 1660 * @deprecated Renamed to {@link #isDecodeAsAlphaMaskEnabled}. 1661 */ 1662 @Deprecated getAsAlphaMask()1663 public boolean getAsAlphaMask() { 1664 return this.getDecodeAsAlphaMask(); 1665 } 1666 1667 /** 1668 * Specify the desired {@link ColorSpace} for the output. 1669 * 1670 * <p>If non-null, the decoder will try to decode into {@code colorSpace}. 1671 * If it is null, which is the default, or the request cannot be met, the 1672 * decoder will pick either the color space embedded in the image or the 1673 * {@link ColorSpace} best suited for the requested image configuration 1674 * (for instance {@link ColorSpace.Named#SRGB sRGB} for the 1675 * {@link Bitmap.Config#ARGB_8888} configuration and 1676 * {@link ColorSpace.Named#EXTENDED_SRGB EXTENDED_SRGB} for 1677 * {@link Bitmap.Config#RGBA_F16}).</p> 1678 * 1679 * <p class="note">Only {@link ColorSpace.Model#RGB} color spaces are 1680 * currently supported. An <code>IllegalArgumentException</code> will 1681 * be thrown by {@link #decodeDrawable decodeDrawable}/ 1682 * {@link #decodeBitmap decodeBitmap} when setting a non-RGB color space 1683 * such as {@link ColorSpace.Named#CIE_LAB Lab}.</p> 1684 * 1685 * <p class="note">The specified color space's transfer function must be 1686 * an {@link ColorSpace.Rgb.TransferParameters ICC parametric curve}. An 1687 * <code>IllegalArgumentException</code> will be thrown by the decode methods 1688 * if calling {@link ColorSpace.Rgb#getTransferParameters()} on the 1689 * specified color space returns null.</p> 1690 * 1691 * <p>Like all setters on ImageDecoder, this must be called inside 1692 * {@link OnHeaderDecodedListener#onHeaderDecoded onHeaderDecoded}.</p> 1693 */ setTargetColorSpace(ColorSpace colorSpace)1694 public void setTargetColorSpace(ColorSpace colorSpace) { 1695 mDesiredColorSpace = colorSpace; 1696 } 1697 1698 /** 1699 * Closes this resource, relinquishing any underlying resources. This method 1700 * is invoked automatically on objects managed by the try-with-resources 1701 * statement. 1702 * 1703 * <p>This is an implementation detail of {@link ImageDecoder}, and should 1704 * never be called manually.</p> 1705 */ 1706 @Override close()1707 public void close() { 1708 mCloseGuard.close(); 1709 if (!mClosed.compareAndSet(false, true)) { 1710 return; 1711 } 1712 nClose(mNativePtr); 1713 mNativePtr = 0; 1714 1715 if (mOwnsInputStream) { 1716 IoUtils.closeQuietly(mInputStream); 1717 } 1718 IoUtils.closeQuietly(mAssetFd); 1719 1720 mInputStream = null; 1721 mAssetFd = null; 1722 mTempStorage = null; 1723 } 1724 checkState(boolean animated)1725 private void checkState(boolean animated) { 1726 if (mNativePtr == 0) { 1727 throw new IllegalStateException("Cannot use closed ImageDecoder!"); 1728 } 1729 1730 checkSubset(mDesiredWidth, mDesiredHeight, mCropRect); 1731 1732 // animated ignores the allocator, so no need to check for incompatible 1733 // fields. 1734 if (!animated && mAllocator == ALLOCATOR_HARDWARE) { 1735 if (mMutable) { 1736 throw new IllegalStateException("Cannot make mutable HARDWARE Bitmap!"); 1737 } 1738 if (mDecodeAsAlphaMask) { 1739 throw new IllegalStateException("Cannot make HARDWARE Alpha mask Bitmap!"); 1740 } 1741 } 1742 1743 if (mPostProcessor != null && mUnpremultipliedRequired) { 1744 throw new IllegalStateException("Cannot draw to unpremultiplied pixels!"); 1745 } 1746 } 1747 checkSubset(int width, int height, Rect r)1748 private static void checkSubset(int width, int height, Rect r) { 1749 if (r == null) { 1750 return; 1751 } 1752 if (r.width() <= 0 || r.height() <= 0) { 1753 throw new IllegalStateException("Subset " + r + " is empty/unsorted"); 1754 } 1755 if (r.left < 0 || r.top < 0 || r.right > width || r.bottom > height) { 1756 throw new IllegalStateException("Subset " + r + " not contained by " 1757 + "scaled image bounds: (" + width + " x " + height + ")"); 1758 } 1759 } 1760 checkForExtended()1761 private boolean checkForExtended() { 1762 if (mDesiredColorSpace == null) { 1763 return false; 1764 } 1765 return mDesiredColorSpace == ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB) 1766 || mDesiredColorSpace == ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB); 1767 } 1768 getColorSpacePtr()1769 private long getColorSpacePtr() { 1770 if (mDesiredColorSpace == null) { 1771 return 0; 1772 } 1773 return mDesiredColorSpace.getNativeInstance(); 1774 } 1775 1776 @WorkerThread 1777 @NonNull decodeBitmapInternal()1778 private Bitmap decodeBitmapInternal() throws IOException { 1779 checkState(false); 1780 return nDecodeBitmap(mNativePtr, this, mPostProcessor != null, 1781 mDesiredWidth, mDesiredHeight, mCropRect, 1782 mMutable, mAllocator, mUnpremultipliedRequired, 1783 mConserveMemory, mDecodeAsAlphaMask, getColorSpacePtr(), 1784 checkForExtended()); 1785 } 1786 callHeaderDecoded(@ullable OnHeaderDecodedListener listener, @NonNull Source src)1787 private void callHeaderDecoded(@Nullable OnHeaderDecodedListener listener, 1788 @NonNull Source src) { 1789 if (listener != null) { 1790 ImageInfo info = new ImageInfo(this); 1791 try { 1792 listener.onHeaderDecoded(this, info, src); 1793 } finally { 1794 info.mDecoder = null; 1795 } 1796 } 1797 } 1798 1799 /** 1800 * Create a {@link Drawable} from a {@code Source}. 1801 * 1802 * @param src representing the encoded image. 1803 * @param listener for learning the {@link ImageInfo ImageInfo} and changing any 1804 * default settings on the {@code ImageDecoder}. This will be called on 1805 * the same thread as {@code decodeDrawable} before that method returns. 1806 * This is required in order to change any of the default settings. 1807 * @return Drawable for displaying the image. 1808 * @throws IOException if {@code src} is not found, is an unsupported 1809 * format, or cannot be decoded for any reason. 1810 */ 1811 @WorkerThread 1812 @NonNull decodeDrawable(@onNull Source src, @NonNull OnHeaderDecodedListener listener)1813 public static Drawable decodeDrawable(@NonNull Source src, 1814 @NonNull OnHeaderDecodedListener listener) throws IOException { 1815 if (listener == null) { 1816 throw new IllegalArgumentException("listener cannot be null! " 1817 + "Use decodeDrawable(Source) to not have a listener"); 1818 } 1819 return decodeDrawableImpl(src, listener); 1820 } 1821 1822 @WorkerThread 1823 @NonNull decodeDrawableImpl(@onNull Source src, @Nullable OnHeaderDecodedListener listener)1824 private static Drawable decodeDrawableImpl(@NonNull Source src, 1825 @Nullable OnHeaderDecodedListener listener) throws IOException { 1826 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ImageDecoder#decodeDrawable"); 1827 try (ImageDecoder decoder = src.createImageDecoder(true /*preferAnimation*/)) { 1828 decoder.mSource = src; 1829 decoder.callHeaderDecoded(listener, src); 1830 1831 try (ImageDecoderSourceTrace unused = new ImageDecoderSourceTrace(decoder)) { 1832 if (decoder.mUnpremultipliedRequired) { 1833 // Though this could be supported (ignored) for opaque images, 1834 // it seems better to always report this error. 1835 throw new IllegalStateException( 1836 "Cannot decode a Drawable with unpremultiplied pixels!"); 1837 } 1838 1839 if (decoder.mMutable) { 1840 throw new IllegalStateException("Cannot decode a mutable Drawable!"); 1841 } 1842 1843 // this call potentially manipulates the decoder so it must be performed prior to 1844 // decoding the bitmap and after decode set the density on the resulting bitmap 1845 final int srcDensity = decoder.computeDensity(src); 1846 if (decoder.mAnimated) { 1847 // AnimatedImageDrawable calls postProcessAndRelease only if 1848 // mPostProcessor exists. 1849 ImageDecoder postProcessPtr = decoder.mPostProcessor == null ? null : decoder; 1850 decoder.checkState(true); 1851 Drawable d = new AnimatedImageDrawable(decoder.mNativePtr, 1852 postProcessPtr, decoder.mDesiredWidth, 1853 decoder.mDesiredHeight, decoder.getColorSpacePtr(), 1854 decoder.checkForExtended(), srcDensity, 1855 src.computeDstDensity(), decoder.mCropRect, 1856 decoder.mInputStream, decoder.mAssetFd); 1857 // d has taken ownership of these objects. 1858 decoder.mInputStream = null; 1859 decoder.mAssetFd = null; 1860 return d; 1861 } 1862 1863 Bitmap bm = decoder.decodeBitmapInternal(); 1864 bm.setDensity(srcDensity); 1865 1866 Resources res = src.getResources(); 1867 byte[] np = bm.getNinePatchChunk(); 1868 if (np != null && NinePatch.isNinePatchChunk(np)) { 1869 Rect opticalInsets = new Rect(); 1870 bm.getOpticalInsets(opticalInsets); 1871 Rect padding = decoder.mOutPaddingRect; 1872 if (padding == null) { 1873 padding = new Rect(); 1874 } 1875 nGetPadding(decoder.mNativePtr, padding); 1876 return new NinePatchDrawable(res, bm, np, padding, 1877 opticalInsets, null); 1878 } 1879 1880 return new BitmapDrawable(res, bm); 1881 } 1882 } finally { 1883 // Close the ImageDecoder#decode trace. 1884 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); 1885 } 1886 } 1887 1888 /** 1889 * Create a {@link Drawable} from a {@code Source}. 1890 * 1891 * <p>Since there is no {@link OnHeaderDecodedListener OnHeaderDecodedListener}, 1892 * the default settings will be used. In order to change any settings, call 1893 * {@link #decodeDrawable(Source, OnHeaderDecodedListener)} instead.</p> 1894 * 1895 * @param src representing the encoded image. 1896 * @return Drawable for displaying the image. 1897 * @throws IOException if {@code src} is not found, is an unsupported 1898 * format, or cannot be decoded for any reason. 1899 */ 1900 @WorkerThread 1901 @NonNull decodeDrawable(@onNull Source src)1902 public static Drawable decodeDrawable(@NonNull Source src) 1903 throws IOException { 1904 return decodeDrawableImpl(src, null); 1905 } 1906 1907 /** 1908 * Create a {@link Bitmap} from a {@code Source}. 1909 * 1910 * @param src representing the encoded image. 1911 * @param listener for learning the {@link ImageInfo ImageInfo} and changing any 1912 * default settings on the {@code ImageDecoder}. This will be called on 1913 * the same thread as {@code decodeBitmap} before that method returns. 1914 * This is required in order to change any of the default settings. 1915 * @return Bitmap containing the image. 1916 * @throws IOException if {@code src} is not found, is an unsupported 1917 * format, or cannot be decoded for any reason. 1918 */ 1919 @WorkerThread 1920 @NonNull decodeBitmap(@onNull Source src, @NonNull OnHeaderDecodedListener listener)1921 public static Bitmap decodeBitmap(@NonNull Source src, 1922 @NonNull OnHeaderDecodedListener listener) throws IOException { 1923 if (listener == null) { 1924 throw new IllegalArgumentException("listener cannot be null! " 1925 + "Use decodeBitmap(Source) to not have a listener"); 1926 } 1927 return decodeBitmapImpl(src, listener); 1928 } 1929 1930 @WorkerThread 1931 @NonNull decodeBitmapImpl(@onNull Source src, @Nullable OnHeaderDecodedListener listener)1932 private static Bitmap decodeBitmapImpl(@NonNull Source src, 1933 @Nullable OnHeaderDecodedListener listener) throws IOException { 1934 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ImageDecoder#decodeBitmap"); 1935 try (ImageDecoder decoder = src.createImageDecoder(false /*preferAnimation*/)) { 1936 decoder.mSource = src; 1937 decoder.callHeaderDecoded(listener, src); 1938 try (ImageDecoderSourceTrace unused = new ImageDecoderSourceTrace(decoder)) { 1939 // this call potentially manipulates the decoder so it must be performed prior to 1940 // decoding the bitmap 1941 final int srcDensity = decoder.computeDensity(src); 1942 Bitmap bm = decoder.decodeBitmapInternal(); 1943 bm.setDensity(srcDensity); 1944 1945 Rect padding = decoder.mOutPaddingRect; 1946 if (padding != null) { 1947 byte[] np = bm.getNinePatchChunk(); 1948 if (np != null && NinePatch.isNinePatchChunk(np)) { 1949 nGetPadding(decoder.mNativePtr, padding); 1950 } 1951 } 1952 return bm; 1953 } 1954 } finally { 1955 // Close the ImageDecoder#decode trace. 1956 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); 1957 } 1958 } 1959 1960 /** 1961 * This describes the decoder in traces to ease debugging. It has to be called after 1962 * header has been decoded and width/height have been populated. It should be used 1963 * inside a try-with-resources call to automatically complete the trace. 1964 */ traceDecoderSource(ImageDecoder decoder)1965 private static AutoCloseable traceDecoderSource(ImageDecoder decoder) { 1966 final boolean resourceTracingEnabled = Trace.isTagEnabled(Trace.TRACE_TAG_RESOURCES); 1967 if (resourceTracingEnabled) { 1968 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, describeDecoderForTrace(decoder)); 1969 } 1970 1971 return new AutoCloseable() { 1972 @Override 1973 public void close() throws Exception { 1974 if (resourceTracingEnabled) { 1975 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); 1976 } 1977 } 1978 }; 1979 } 1980 1981 // This method may modify the decoder so it must be called prior to performing the decode 1982 private int computeDensity(@NonNull Source src) { 1983 // if the caller changed the size then we treat the density as unknown 1984 if (this.requestedResize()) { 1985 return Bitmap.DENSITY_NONE; 1986 } 1987 1988 final int srcDensity = src.getDensity(); 1989 if (srcDensity == Bitmap.DENSITY_NONE) { 1990 return srcDensity; 1991 } 1992 1993 // Scaling up nine-patch divs is imprecise and is better handled 1994 // at draw time. An app won't be relying on the internal Bitmap's 1995 // size, so it is safe to let NinePatchDrawable handle scaling. 1996 // mPostProcessor disables nine-patching, so behave normally if 1997 // it is present. 1998 if (mIsNinePatch && mPostProcessor == null) { 1999 return srcDensity; 2000 } 2001 2002 // Special stuff for compatibility mode: if the target density is not 2003 // the same as the display density, but the resource -is- the same as 2004 // the display density, then don't scale it down to the target density. 2005 // This allows us to load the system's density-correct resources into 2006 // an application in compatibility mode, without scaling those down 2007 // to the compatibility density only to have them scaled back up when 2008 // drawn to the screen. 2009 Resources res = src.getResources(); 2010 if (res != null && res.getDisplayMetrics().noncompatDensityDpi == srcDensity) { 2011 return srcDensity; 2012 } 2013 2014 final int dstDensity = src.computeDstDensity(); 2015 if (srcDensity == dstDensity) { 2016 return srcDensity; 2017 } 2018 2019 // For P and above, only resize if it would be a downscale. Scale up prior 2020 // to P in case the app relies on the Bitmap's size without considering density. 2021 if (srcDensity < dstDensity 2022 && Compatibility.getTargetSdkVersion() >= Build.VERSION_CODES.P) { 2023 return srcDensity; 2024 } 2025 2026 float scale = (float) dstDensity / srcDensity; 2027 int scaledWidth = Math.max((int) (mWidth * scale + 0.5f), 1); 2028 int scaledHeight = Math.max((int) (mHeight * scale + 0.5f), 1); 2029 this.setTargetSize(scaledWidth, scaledHeight); 2030 return dstDensity; 2031 } 2032 2033 @NonNull 2034 private String getMimeType() { 2035 return nGetMimeType(mNativePtr); 2036 } 2037 2038 @Nullable 2039 private ColorSpace getColorSpace() { 2040 return nGetColorSpace(mNativePtr); 2041 } 2042 2043 /** 2044 * Create a {@link Bitmap} from a {@code Source}. 2045 * 2046 * <p>Since there is no {@link OnHeaderDecodedListener OnHeaderDecodedListener}, 2047 * the default settings will be used. In order to change any settings, call 2048 * {@link #decodeBitmap(Source, OnHeaderDecodedListener)} instead.</p> 2049 * 2050 * @param src representing the encoded image. 2051 * @return Bitmap containing the image. 2052 * @throws IOException if {@code src} is not found, is an unsupported 2053 * format, or cannot be decoded for any reason. 2054 */ 2055 @WorkerThread 2056 @NonNull 2057 public static Bitmap decodeBitmap(@NonNull Source src) throws IOException { 2058 return decodeBitmapImpl(src, null); 2059 } 2060 2061 /** 2062 * Private method called by JNI. 2063 */ 2064 @SuppressWarnings("unused") 2065 private int postProcessAndRelease(@NonNull Canvas canvas) { 2066 try { 2067 return mPostProcessor.onPostProcess(canvas); 2068 } finally { 2069 canvas.release(); 2070 } 2071 } 2072 2073 /** 2074 * Private method called by JNI. 2075 */ 2076 @SuppressWarnings("unused") 2077 private void onPartialImage(@DecodeException.Error int error, @Nullable Throwable cause) 2078 throws DecodeException { 2079 DecodeException exception = new DecodeException(error, cause, mSource); 2080 if (mOnPartialImageListener == null 2081 || !mOnPartialImageListener.onPartialImage(exception)) { 2082 throw exception; 2083 } 2084 } 2085 2086 /** 2087 * Returns a short string describing what passed ImageDecoder is loading - 2088 * it reports image dimensions, desired dimensions (if any) and source resource. 2089 * 2090 * The string appears in perf traces to simplify search for slow or memory intensive 2091 * image loads. 2092 * 2093 * Example: ID#w=300;h=250;dw=150;dh=150;src=Resource{name=@resource} 2094 * 2095 * @hide 2096 */ 2097 private static String describeDecoderForTrace(@NonNull ImageDecoder decoder) { 2098 StringBuilder builder = new StringBuilder(); 2099 // Source dimensions 2100 builder.append("ID#w="); 2101 builder.append(decoder.mWidth); 2102 builder.append(";h="); 2103 builder.append(decoder.mHeight); 2104 // Desired dimensions (if present) 2105 if (decoder.mDesiredWidth != decoder.mWidth 2106 || decoder.mDesiredHeight != decoder.mHeight) { 2107 builder.append(";dw="); 2108 builder.append(decoder.mDesiredWidth); 2109 builder.append(";dh="); 2110 builder.append(decoder.mDesiredHeight); 2111 } 2112 // Source description 2113 builder.append(";src="); 2114 builder.append(decoder.mSource); 2115 return builder.toString(); 2116 } 2117 2118 /** 2119 * Records a trace with information about the source being decoded - dimensions, 2120 * desired dimensions and source information. 2121 * 2122 * It significantly eases debugging of slow resource loads on main thread and 2123 * possible large memory consumers. 2124 * 2125 * @hide 2126 */ 2127 private static final class ImageDecoderSourceTrace implements AutoCloseable { 2128 2129 private final boolean mResourceTracingEnabled; 2130 2131 ImageDecoderSourceTrace(ImageDecoder decoder) { 2132 mResourceTracingEnabled = Trace.isTagEnabled(Trace.TRACE_TAG_RESOURCES); 2133 if (mResourceTracingEnabled) { 2134 Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, describeDecoderForTrace(decoder)); 2135 } 2136 } 2137 2138 @Override 2139 public void close() { 2140 if (mResourceTracingEnabled) { 2141 Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); 2142 } 2143 } 2144 } 2145 2146 private static native ImageDecoder nCreate(long asset, 2147 boolean preferAnimation, Source src) throws IOException; 2148 private static native ImageDecoder nCreate(ByteBuffer buffer, int position, int limit, 2149 boolean preferAnimation, Source src) throws IOException; 2150 private static native ImageDecoder nCreate(byte[] data, int offset, int length, 2151 boolean preferAnimation, Source src) throws IOException; 2152 private static native ImageDecoder nCreate(InputStream is, byte[] storage, 2153 boolean preferAnimation, Source src) throws IOException; 2154 // The fd must be seekable. 2155 private static native ImageDecoder nCreate(FileDescriptor fd, long length, 2156 boolean preferAnimation, Source src) throws IOException; 2157 @NonNull 2158 private static native Bitmap nDecodeBitmap(long nativePtr, 2159 @NonNull ImageDecoder decoder, 2160 boolean doPostProcess, 2161 int width, int height, 2162 @Nullable Rect cropRect, boolean mutable, 2163 int allocator, boolean unpremulRequired, 2164 boolean conserveMemory, boolean decodeAsAlphaMask, 2165 long desiredColorSpace, boolean extended) 2166 throws IOException; 2167 private static native Size nGetSampledSize(long nativePtr, 2168 int sampleSize); 2169 private static native void nGetPadding(long nativePtr, @NonNull Rect outRect); 2170 private static native void nClose(long nativePtr); 2171 private static native String nGetMimeType(long nativePtr); 2172 private static native ColorSpace nGetColorSpace(long nativePtr); 2173 } 2174