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