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