• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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) -&gt; {
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) -&gt; {
138  *      decoder.setPostProcessor((canvas) -&gt; {
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) -&gt; {
164  *      decoder.setOnPartialImageListener((DecodeException e) -&gt; {
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