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 android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.content.ContentResolver;
22 import android.content.res.AssetManager.AssetInputStream;
23 import android.content.res.Resources;
24 import android.graphics.drawable.AnimatedImageDrawable;
25 import android.graphics.drawable.Drawable;
26 import android.graphics.drawable.BitmapDrawable;
27 import android.net.Uri;
28 import android.util.DisplayMetrics;
29 import android.util.Size;
30 import android.util.TypedValue;
31 
32 import java.nio.ByteBuffer;
33 import java.io.File;
34 import java.io.IOException;
35 import java.io.InputStream;
36 import java.lang.ArrayIndexOutOfBoundsException;
37 import java.lang.AutoCloseable;
38 import java.lang.NullPointerException;
39 import java.lang.annotation.Retention;
40 import static java.lang.annotation.RetentionPolicy.SOURCE;
41 
42 /**
43  *  Class for decoding images as {@link Bitmap}s or {@link Drawable}s.
44  */
45 public final class ImageDecoder implements AutoCloseable {
46 
47     /**
48      *  Source of the encoded image data.
49      */
50     public static abstract class Source {
Source()51         private Source() {}
52 
53         /* @hide */
54         @Nullable
getResources()55         Resources getResources() { return null; }
56 
57         /* @hide */
getDensity()58         int getDensity() { return Bitmap.DENSITY_NONE; }
59 
60         /* @hide */
computeDstDensity()61         int computeDstDensity() {
62             Resources res = getResources();
63             if (res == null) {
64                 return Bitmap.getDefaultDensity();
65             }
66 
67             return res.getDisplayMetrics().densityDpi;
68         }
69 
70         /* @hide */
71         @NonNull
createImageDecoder()72         abstract ImageDecoder createImageDecoder() throws IOException;
73     };
74 
75     private static class ByteArraySource extends Source {
ByteArraySource(@onNull byte[] data, int offset, int length)76         ByteArraySource(@NonNull byte[] data, int offset, int length) {
77             mData = data;
78             mOffset = offset;
79             mLength = length;
80         };
81         private final byte[] mData;
82         private final int    mOffset;
83         private final int    mLength;
84 
85         @Override
createImageDecoder()86         public ImageDecoder createImageDecoder() throws IOException {
87             return new ImageDecoder();
88         }
89     }
90 
91     private static class ByteBufferSource extends Source {
ByteBufferSource(@onNull ByteBuffer buffer)92         ByteBufferSource(@NonNull ByteBuffer buffer) {
93             mBuffer = buffer;
94         }
95         private final ByteBuffer mBuffer;
96 
97         @Override
createImageDecoder()98         public ImageDecoder createImageDecoder() throws IOException {
99             return new ImageDecoder();
100         }
101     }
102 
103     private static class ContentResolverSource extends Source {
ContentResolverSource(@onNull ContentResolver resolver, @NonNull Uri uri)104         ContentResolverSource(@NonNull ContentResolver resolver, @NonNull Uri uri) {
105             mResolver = resolver;
106             mUri = uri;
107         }
108 
109         private final ContentResolver mResolver;
110         private final Uri mUri;
111 
112         @Override
createImageDecoder()113         public ImageDecoder createImageDecoder() throws IOException {
114             return new ImageDecoder();
115         }
116     }
117 
118     /**
119      * For backwards compatibility, this does *not* close the InputStream.
120      */
121     private static class InputStreamSource extends Source {
InputStreamSource(Resources res, InputStream is, int inputDensity)122         InputStreamSource(Resources res, InputStream is, int inputDensity) {
123             if (is == null) {
124                 throw new IllegalArgumentException("The InputStream cannot be null");
125             }
126             mResources = res;
127             mInputStream = is;
128             mInputDensity = res != null ? inputDensity : Bitmap.DENSITY_NONE;
129         }
130 
131         final Resources mResources;
132         InputStream mInputStream;
133         final int mInputDensity;
134 
135         @Override
getResources()136         public Resources getResources() { return mResources; }
137 
138         @Override
getDensity()139         public int getDensity() { return mInputDensity; }
140 
141         @Override
createImageDecoder()142         public ImageDecoder createImageDecoder() throws IOException {
143             return new ImageDecoder();
144         }
145     }
146 
147     /**
148      * Takes ownership of the AssetInputStream.
149      *
150      * @hide
151      */
152     public static class AssetInputStreamSource extends Source {
AssetInputStreamSource(@onNull AssetInputStream ais, @NonNull Resources res, @NonNull TypedValue value)153         public AssetInputStreamSource(@NonNull AssetInputStream ais,
154                 @NonNull Resources res, @NonNull TypedValue value) {
155             mAssetInputStream = ais;
156             mResources = res;
157 
158             if (value.density == TypedValue.DENSITY_DEFAULT) {
159                 mDensity = DisplayMetrics.DENSITY_DEFAULT;
160             } else if (value.density != TypedValue.DENSITY_NONE) {
161                 mDensity = value.density;
162             } else {
163                 mDensity = Bitmap.DENSITY_NONE;
164             }
165         }
166 
167         private AssetInputStream mAssetInputStream;
168         private final Resources  mResources;
169         private final int        mDensity;
170 
171         @Override
getResources()172         public Resources getResources() { return mResources; }
173 
174         @Override
getDensity()175         public int getDensity() {
176             return mDensity;
177         }
178 
179         @Override
createImageDecoder()180         public ImageDecoder createImageDecoder() throws IOException {
181             return new ImageDecoder();
182         }
183     }
184 
185     private static class ResourceSource extends Source {
ResourceSource(@onNull Resources res, int resId)186         ResourceSource(@NonNull Resources res, int resId) {
187             mResources = res;
188             mResId = resId;
189             mResDensity = Bitmap.DENSITY_NONE;
190         }
191 
192         final Resources mResources;
193         final int       mResId;
194         int             mResDensity;
195 
196         @Override
getResources()197         public Resources getResources() { return mResources; }
198 
199         @Override
getDensity()200         public int getDensity() { return mResDensity; }
201 
202         @Override
createImageDecoder()203         public ImageDecoder createImageDecoder() throws IOException {
204             return new ImageDecoder();
205         }
206     }
207 
208     private static class FileSource extends Source {
FileSource(@onNull File file)209         FileSource(@NonNull File file) {
210             mFile = file;
211         }
212 
213         private final File mFile;
214 
215         @Override
createImageDecoder()216         public ImageDecoder createImageDecoder() throws IOException {
217             return new ImageDecoder();
218         }
219     }
220 
221     /**
222      *  Contains information about the encoded image.
223      */
224     public static class ImageInfo {
225         private ImageDecoder mDecoder;
226 
ImageInfo(@onNull ImageDecoder decoder)227         private ImageInfo(@NonNull ImageDecoder decoder) {
228             mDecoder = decoder;
229         }
230 
231         /**
232          * Size of the image, without scaling or cropping.
233          */
234         @NonNull
getSize()235         public Size getSize() {
236             return new Size(0, 0);
237         }
238 
239         /**
240          * The mimeType of the image.
241          */
242         @NonNull
getMimeType()243         public String getMimeType() {
244             return "";
245         }
246 
247         /**
248          * Whether the image is animated.
249          *
250          * <p>Calling {@link #decodeDrawable} will return an
251          * {@link AnimatedImageDrawable}.</p>
252          */
isAnimated()253         public boolean isAnimated() {
254             return mDecoder.mAnimated;
255         }
256     };
257 
258     /**
259      *  Thrown if the provided data is incomplete.
260      */
261     public static class IncompleteException extends IOException {};
262 
263     /**
264      *  Optional listener supplied to {@link #decodeDrawable} or
265      *  {@link #decodeBitmap}.
266      */
267     public interface OnHeaderDecodedListener {
268         /**
269          *  Called when the header is decoded and the size is known.
270          *
271          *  @param decoder allows changing the default settings of the decode.
272          *  @param info Information about the encoded image.
273          *  @param source that created the decoder.
274          */
onHeaderDecoded(@onNull ImageDecoder decoder, @NonNull ImageInfo info, @NonNull Source source)275         void onHeaderDecoded(@NonNull ImageDecoder decoder,
276                 @NonNull ImageInfo info, @NonNull Source source);
277 
278     };
279 
280     /**
281      *  An Exception was thrown reading the {@link Source}.
282      */
283     public static final int ERROR_SOURCE_EXCEPTION  = 1;
284 
285     /**
286      *  The encoded data was incomplete.
287      */
288     public static final int ERROR_SOURCE_INCOMPLETE = 2;
289 
290     /**
291      *  The encoded data contained an error.
292      */
293     public static final int ERROR_SOURCE_ERROR      = 3;
294 
295     @Retention(SOURCE)
296     public @interface Error {}
297 
298     /**
299      *  Optional listener supplied to the ImageDecoder.
300      *
301      *  Without this listener, errors will throw {@link java.io.IOException}.
302      */
303     public interface OnPartialImageListener {
304         /**
305          *  Called when there is only a partial image to display.
306          *
307          *  If decoding is interrupted after having decoded a partial image,
308          *  this listener lets the client know that and allows them to
309          *  optionally finish the rest of the decode/creation process to create
310          *  a partial {@link Drawable}/{@link Bitmap}.
311          *
312          *  @param error indicating what interrupted the decode.
313          *  @param source that had the error.
314          *  @return True to create and return a {@link Drawable}/{@link Bitmap}
315          *      with partial data. False (which is the default) to abort the
316          *      decode and throw {@link java.io.IOException}.
317          */
onPartialImage(@rror int error, @NonNull Source source)318         boolean onPartialImage(@Error int error, @NonNull Source source);
319     }
320 
321     private boolean mAnimated;
322     private Rect mOutPaddingRect;
323 
ImageDecoder()324     public ImageDecoder() {
325         mAnimated = true; // This is too avoid throwing an exception in AnimatedImageDrawable
326     }
327 
328     /**
329      * Create a new {@link Source} from an asset.
330      * @hide
331      *
332      * @param res the {@link Resources} object containing the image data.
333      * @param resId resource ID of the image data.
334      *      // FIXME: Can be an @DrawableRes?
335      * @return a new Source object, which can be passed to
336      *      {@link #decodeDrawable} or {@link #decodeBitmap}.
337      */
338     @NonNull
createSource(@onNull Resources res, int resId)339     public static Source createSource(@NonNull Resources res, int resId)
340     {
341         return new ResourceSource(res, resId);
342     }
343 
344     /**
345      * Create a new {@link Source} from a {@link android.net.Uri}.
346      *
347      * @param cr to retrieve from.
348      * @param uri of the image file.
349      * @return a new Source object, which can be passed to
350      *      {@link #decodeDrawable} or {@link #decodeBitmap}.
351      */
352     @NonNull
createSource(@onNull ContentResolver cr, @NonNull Uri uri)353     public static Source createSource(@NonNull ContentResolver cr,
354             @NonNull Uri uri) {
355         return new ContentResolverSource(cr, uri);
356     }
357 
358     /**
359      * Create a new {@link Source} from a byte array.
360      *
361      * @param data byte array of compressed image data.
362      * @param offset offset into data for where the decoder should begin
363      *      parsing.
364      * @param length number of bytes, beginning at offset, to parse.
365      * @throws NullPointerException if data is null.
366      * @throws ArrayIndexOutOfBoundsException if offset and length are
367      *      not within data.
368      * @hide
369      */
370     @NonNull
createSource(@onNull byte[] data, int offset, int length)371     public static Source createSource(@NonNull byte[] data, int offset,
372             int length) throws ArrayIndexOutOfBoundsException {
373         if (offset < 0 || length < 0 || offset >= data.length ||
374                 offset + length > data.length) {
375             throw new ArrayIndexOutOfBoundsException(
376                     "invalid offset/length!");
377         }
378         return new ByteArraySource(data, offset, length);
379     }
380 
381     /**
382      * See {@link #createSource(byte[], int, int).
383      * @hide
384      */
385     @NonNull
createSource(@onNull byte[] data)386     public static Source createSource(@NonNull byte[] data) {
387         return createSource(data, 0, data.length);
388     }
389 
390     /**
391      * Create a new {@link Source} from a {@link java.nio.ByteBuffer}.
392      *
393      * <p>The returned {@link Source} effectively takes ownership of the
394      * {@link java.nio.ByteBuffer}; i.e. no other code should modify it after
395      * this call.</p>
396      *
397      * Decoding will start from {@link java.nio.ByteBuffer#position()}. The
398      * position after decoding is undefined.
399      */
400     @NonNull
createSource(@onNull ByteBuffer buffer)401     public static Source createSource(@NonNull ByteBuffer buffer) {
402         return new ByteBufferSource(buffer);
403     }
404 
405     /**
406      * Internal API used to generate bitmaps for use by Drawables (i.e. BitmapDrawable)
407      * @hide
408      */
createSource(Resources res, InputStream is)409     public static Source createSource(Resources res, InputStream is) {
410         return new InputStreamSource(res, is, Bitmap.getDefaultDensity());
411     }
412 
413     /**
414      * Internal API used to generate bitmaps for use by Drawables (i.e. BitmapDrawable)
415      * @hide
416      */
createSource(Resources res, InputStream is, int density)417     public static Source createSource(Resources res, InputStream is, int density) {
418         return new InputStreamSource(res, is, density);
419     }
420 
421     /**
422      * Create a new {@link Source} from a {@link java.io.File}.
423      */
424     @NonNull
createSource(@onNull File file)425     public static Source createSource(@NonNull File file) {
426         return new FileSource(file);
427     }
428 
429     /**
430      *  Return the width and height of a given sample size.
431      *
432      *  <p>This takes an input that functions like
433      *  {@link BitmapFactory.Options#inSampleSize}. It returns a width and
434      *  height that can be acheived by sampling the encoded image. Other widths
435      *  and heights may be supported, but will require an additional (internal)
436      *  scaling step. Such internal scaling is *not* supported with
437      *  {@link #setRequireUnpremultiplied} set to {@code true}.</p>
438      *
439      *  @param sampleSize Sampling rate of the encoded image.
440      *  @return {@link android.util.Size} of the width and height after
441      *      sampling.
442      */
443     @NonNull
getSampledSize(int sampleSize)444     public Size getSampledSize(int sampleSize) {
445         return new Size(0, 0);
446     }
447 
448     // Modifiers
449     /**
450      *  Resize the output to have the following size.
451      *
452      *  @param width must be greater than 0.
453      *  @param height must be greater than 0.
454      */
setResize(int width, int height)455     public void setResize(int width, int height) {
456     }
457 
458     /**
459      *  Resize based on a sample size.
460      *
461      *  <p>This has the same effect as passing the result of
462      *  {@link #getSampledSize} to {@link #setResize(int, int)}.</p>
463      *
464      *  @param sampleSize Sampling rate of the encoded image.
465      */
setResize(int sampleSize)466     public void setResize(int sampleSize) {
467     }
468 
469     // These need to stay in sync with ImageDecoder.cpp's Allocator enum.
470     /**
471      *  Use the default allocation for the pixel memory.
472      *
473      *  Will typically result in a {@link Bitmap.Config#HARDWARE}
474      *  allocation, but may be software for small images. In addition, this will
475      *  switch to software when HARDWARE is incompatible, e.g.
476      *  {@link #setMutable}, {@link #setAsAlphaMask}.
477      */
478     public static final int ALLOCATOR_DEFAULT = 0;
479 
480     /**
481      *  Use a software allocation for the pixel memory.
482      *
483      *  Useful for drawing to a software {@link Canvas} or for
484      *  accessing the pixels on the final output.
485      */
486     public static final int ALLOCATOR_SOFTWARE = 1;
487 
488     /**
489      *  Use shared memory for the pixel memory.
490      *
491      *  Useful for sharing across processes.
492      */
493     public static final int ALLOCATOR_SHARED_MEMORY = 2;
494 
495     /**
496      *  Require a {@link Bitmap.Config#HARDWARE} {@link Bitmap}.
497      *
498      *  When this is combined with incompatible options, like
499      *  {@link #setMutable} or {@link #setAsAlphaMask}, {@link #decodeDrawable}
500      *  / {@link #decodeBitmap} will throw an
501      *  {@link java.lang.IllegalStateException}.
502      */
503     public static final int ALLOCATOR_HARDWARE = 3;
504 
505     /** @hide **/
506     @Retention(SOURCE)
507     public @interface Allocator {};
508 
509     /**
510      *  Choose the backing for the pixel memory.
511      *
512      *  This is ignored for animated drawables.
513      *
514      *  @param allocator Type of allocator to use.
515      */
setAllocator(@llocator int allocator)516     public void setAllocator(@Allocator int allocator) { }
517 
518     /**
519      *  Specify whether the {@link Bitmap} should have unpremultiplied pixels.
520      *
521      *  By default, ImageDecoder will create a {@link Bitmap} with
522      *  premultiplied pixels, which is required for drawing with the
523      *  {@link android.view.View} system (i.e. to a {@link Canvas}). Calling
524      *  this method with a value of {@code true} will result in
525      *  {@link #decodeBitmap} returning a {@link Bitmap} with unpremultiplied
526      *  pixels. See {@link Bitmap#isPremultiplied}. This is incompatible with
527      *  {@link #decodeDrawable}; attempting to decode an unpremultiplied
528      *  {@link Drawable} will throw an {@link java.lang.IllegalStateException}.
529      */
setRequireUnpremultiplied(boolean requireUnpremultiplied)530     public ImageDecoder setRequireUnpremultiplied(boolean requireUnpremultiplied) {
531         return this;
532     }
533 
534     /**
535      *  Modify the image after decoding and scaling.
536      *
537      *  <p>This allows adding effects prior to returning a {@link Drawable} or
538      *  {@link Bitmap}. For a {@code Drawable} or an immutable {@code Bitmap},
539      *  this is the only way to process the image after decoding.</p>
540      *
541      *  <p>If set on a nine-patch image, the nine-patch data is ignored.</p>
542      *
543      *  <p>For an animated image, the drawing commands drawn on the
544      *  {@link Canvas} will be recorded immediately and then applied to each
545      *  frame.</p>
546      */
setPostProcessor(@ullable PostProcessor p)547     public void setPostProcessor(@Nullable PostProcessor p) { }
548 
549     /**
550      *  Set (replace) the {@link OnPartialImageListener} on this object.
551      *
552      *  Will be called if there is an error in the input. Without one, a
553      *  partial {@link Bitmap} will be created.
554      */
setOnPartialImageListener(@ullable OnPartialImageListener l)555     public void setOnPartialImageListener(@Nullable OnPartialImageListener l) { }
556 
557     /**
558      *  Crop the output to {@code subset} of the (possibly) scaled image.
559      *
560      *  <p>{@code subset} must be contained within the size set by
561      *  {@link #setResize} or the bounds of the image if setResize was not
562      *  called. Otherwise an {@link IllegalStateException} will be thrown by
563      *  {@link #decodeDrawable}/{@link #decodeBitmap}.</p>
564      *
565      *  <p>NOT intended as a replacement for
566      *  {@link BitmapRegionDecoder#decodeRegion}. This supports all formats,
567      *  but merely crops the output.</p>
568      */
setCrop(@ullable Rect subset)569     public void setCrop(@Nullable Rect subset) { }
570 
571     /**
572      *  Set a Rect for retrieving nine patch padding.
573      *
574      *  If the image is a nine patch, this Rect will be set to the padding
575      *  rectangle during decode. Otherwise it will not be modified.
576      *
577      *  @hide
578      */
setOutPaddingRect(@onNull Rect outPadding)579     public void setOutPaddingRect(@NonNull Rect outPadding) {
580         mOutPaddingRect = outPadding;
581     }
582 
583     /**
584      *  Specify whether the {@link Bitmap} should be mutable.
585      *
586      *  <p>By default, a {@link Bitmap} created will be immutable, but that can
587      *  be changed with this call.</p>
588      *
589      *  <p>Mutable Bitmaps are incompatible with {@link #ALLOCATOR_HARDWARE},
590      *  because {@link Bitmap.Config#HARDWARE} Bitmaps cannot be mutable.
591      *  Attempting to combine them will throw an
592      *  {@link java.lang.IllegalStateException}.</p>
593      *
594      *  <p>Mutable Bitmaps are also incompatible with {@link #decodeDrawable},
595      *  which would require retrieving the Bitmap from the returned Drawable in
596      *  order to modify. Attempting to decode a mutable {@link Drawable} will
597      *  throw an {@link java.lang.IllegalStateException}.</p>
598      */
setMutable(boolean mutable)599     public ImageDecoder setMutable(boolean mutable) {
600         return this;
601     }
602 
603     /**
604      *  Specify whether to potentially save RAM at the expense of quality.
605      *
606      *  Setting this to {@code true} may result in a {@link Bitmap} with a
607      *  denser {@link Bitmap.Config}, depending on the image. For example, for
608      *  an opaque {@link Bitmap}, this may result in a {@link Bitmap.Config}
609      *  with no alpha information.
610      */
setPreferRamOverQuality(boolean preferRamOverQuality)611     public ImageDecoder setPreferRamOverQuality(boolean preferRamOverQuality) {
612         return this;
613     }
614 
615     /**
616      *  Specify whether to potentially treat the output as an alpha mask.
617      *
618      *  <p>If this is set to {@code true} and the image is encoded in a format
619      *  with only one channel, treat that channel as alpha. Otherwise this call has
620      *  no effect.</p>
621      *
622      *  <p>setAsAlphaMask is incompatible with {@link #ALLOCATOR_HARDWARE}. Trying to
623      *  combine them will result in {@link #decodeDrawable}/
624      *  {@link #decodeBitmap} throwing an
625      *  {@link java.lang.IllegalStateException}.</p>
626      */
setAsAlphaMask(boolean asAlphaMask)627     public ImageDecoder setAsAlphaMask(boolean asAlphaMask) {
628         return this;
629     }
630 
631     @Override
close()632     public void close() {
633     }
634 
635     /**
636      *  Create a {@link Drawable} from a {@code Source}.
637      *
638      *  @param src representing the encoded image.
639      *  @param listener for learning the {@link ImageInfo} and changing any
640      *      default settings on the {@code ImageDecoder}. If not {@code null},
641      *      this will be called on the same thread as {@code decodeDrawable}
642      *      before that method returns.
643      *  @return Drawable for displaying the image.
644      *  @throws IOException if {@code src} is not found, is an unsupported
645      *      format, or cannot be decoded for any reason.
646      */
647     @NonNull
decodeDrawable(@onNull Source src, @Nullable OnHeaderDecodedListener listener)648     public static Drawable decodeDrawable(@NonNull Source src,
649             @Nullable OnHeaderDecodedListener listener) throws IOException {
650         Bitmap bitmap = decodeBitmap(src, listener);
651         return new BitmapDrawable(src.getResources(), bitmap);
652     }
653 
654     /**
655      * See {@link #decodeDrawable(Source, OnHeaderDecodedListener)}.
656      */
657     @NonNull
decodeDrawable(@onNull Source src)658     public static Drawable decodeDrawable(@NonNull Source src)
659             throws IOException {
660         return decodeDrawable(src, null);
661     }
662 
663     /**
664      *  Create a {@link Bitmap} from a {@code Source}.
665      *
666      *  @param src representing the encoded image.
667      *  @param listener for learning the {@link ImageInfo} and changing any
668      *      default settings on the {@code ImageDecoder}. If not {@code null},
669      *      this will be called on the same thread as {@code decodeBitmap}
670      *      before that method returns.
671      *  @return Bitmap containing the image.
672      *  @throws IOException if {@code src} is not found, is an unsupported
673      *      format, or cannot be decoded for any reason.
674      */
675     @NonNull
decodeBitmap(@onNull Source src, @Nullable OnHeaderDecodedListener listener)676     public static Bitmap decodeBitmap(@NonNull Source src,
677             @Nullable OnHeaderDecodedListener listener) throws IOException {
678         TypedValue value = new TypedValue();
679         value.density = src.getDensity();
680         ImageDecoder decoder = src.createImageDecoder();
681         if (listener != null) {
682             listener.onHeaderDecoded(decoder, new ImageInfo(decoder), src);
683         }
684         return BitmapFactory.decodeResourceStream(src.getResources(), value,
685                 ((InputStreamSource) src).mInputStream, decoder.mOutPaddingRect, null);
686     }
687 
688     /**
689      *  See {@link #decodeBitmap(Source, OnHeaderDecodedListener)}.
690      */
691     @NonNull
decodeBitmap(@onNull Source src)692     public static Bitmap decodeBitmap(@NonNull Source src) throws IOException {
693         return decodeBitmap(src, null);
694     }
695 
696     public static final class DecodeException extends IOException {
697         /**
698          *  An Exception was thrown reading the {@link Source}.
699          */
700         public static final int SOURCE_EXCEPTION  = 1;
701 
702         /**
703          *  The encoded data was incomplete.
704          */
705         public static final int SOURCE_INCOMPLETE = 2;
706 
707         /**
708          *  The encoded data contained an error.
709          */
710         public static final int SOURCE_MALFORMED_DATA      = 3;
711 
712         @Error final int mError;
713         @NonNull final Source mSource;
714 
DecodeException(@rror int error, @Nullable Throwable cause, @NonNull Source source)715         DecodeException(@Error int error, @Nullable Throwable cause, @NonNull Source source) {
716             super(errorMessage(error, cause), cause);
717             mError = error;
718             mSource = source;
719         }
720 
721         /**
722          * Private method called by JNI.
723          */
724         @SuppressWarnings("unused")
DecodeException(@rror int error, @Nullable String msg, @Nullable Throwable cause, @NonNull Source source)725         DecodeException(@Error int error, @Nullable String msg, @Nullable Throwable cause,
726                 @NonNull Source source) {
727             super(msg + errorMessage(error, cause), cause);
728             mError = error;
729             mSource = source;
730         }
731 
732         /**
733          *  Retrieve the reason that decoding was interrupted.
734          *
735          *  <p>If the error is {@link #SOURCE_EXCEPTION}, the underlying
736          *  {@link java.lang.Throwable} can be retrieved with
737          *  {@link java.lang.Throwable#getCause}.</p>
738          */
739         @Error
getError()740         public int getError() {
741             return mError;
742         }
743 
744         /**
745          *  Retrieve the {@link Source Source} that was interrupted.
746          *
747          *  <p>This can be used for equality checking to find the Source which
748          *  failed to completely decode.</p>
749          */
750         @NonNull
getSource()751         public Source getSource() {
752             return mSource;
753         }
754 
errorMessage(@rror int error, @Nullable Throwable cause)755         private static String errorMessage(@Error int error, @Nullable Throwable cause) {
756             switch (error) {
757                 case SOURCE_EXCEPTION:
758                     return "Exception in input: " + cause;
759                 case SOURCE_INCOMPLETE:
760                     return "Input was incomplete.";
761                 case SOURCE_MALFORMED_DATA:
762                     return "Input contained an error.";
763                 default:
764                     return "";
765             }
766         }
767     }
768 }
769