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