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