Home | History | Annotate | Download | only in graphics
      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 {
     51         private Source() {}
     52 
     53         /* @hide */
     54         @Nullable
     55         Resources getResources() { return null; }
     56 
     57         /* @hide */
     58         int getDensity() { return Bitmap.DENSITY_NONE; }
     59 
     60         /* @hide */
     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
     72         abstract ImageDecoder createImageDecoder() throws IOException;
     73     };
     74 
     75     private static class ByteArraySource extends Source {
     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
     86         public ImageDecoder createImageDecoder() throws IOException {
     87             return new ImageDecoder();
     88         }
     89     }
     90 
     91     private static class ByteBufferSource extends Source {
     92         ByteBufferSource(@NonNull ByteBuffer buffer) {
     93             mBuffer = buffer;
     94         }
     95         private final ByteBuffer mBuffer;
     96 
     97         @Override
     98         public ImageDecoder createImageDecoder() throws IOException {
     99             return new ImageDecoder();
    100         }
    101     }
    102 
    103     private static class ContentResolverSource extends Source {
    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
    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 {
    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
    136         public Resources getResources() { return mResources; }
    137 
    138         @Override
    139         public int getDensity() { return mInputDensity; }
    140 
    141         @Override
    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 {
    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
    172         public Resources getResources() { return mResources; }
    173 
    174         @Override
    175         public int getDensity() {
    176             return mDensity;
    177         }
    178 
    179         @Override
    180         public ImageDecoder createImageDecoder() throws IOException {
    181             return new ImageDecoder();
    182         }
    183     }
    184 
    185     private static class ResourceSource extends Source {
    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
    197         public Resources getResources() { return mResources; }
    198 
    199         @Override
    200         public int getDensity() { return mResDensity; }
    201 
    202         @Override
    203         public ImageDecoder createImageDecoder() throws IOException {
    204             return new ImageDecoder();
    205         }
    206     }
    207 
    208     private static class FileSource extends Source {
    209         FileSource(@NonNull File file) {
    210             mFile = file;
    211         }
    212 
    213         private final File mFile;
    214 
    215         @Override
    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 
    227         private ImageInfo(@NonNull ImageDecoder decoder) {
    228             mDecoder = decoder;
    229         }
    230 
    231         /**
    232          * Size of the image, without scaling or cropping.
    233          */
    234         @NonNull
    235         public Size getSize() {
    236             return new Size(0, 0);
    237         }
    238 
    239         /**
    240          * The mimeType of the image.
    241          */
    242         @NonNull
    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          */
    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          */
    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          */
    318         boolean onPartialImage(@Error int error, @NonNull Source source);
    319     }
    320 
    321     private boolean mAnimated;
    322     private Rect mOutPaddingRect;
    323 
    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
    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
    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
    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
    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
    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      */
    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      */
    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
    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
    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      */
    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      */
    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      */
    516     public ImageDecoder setAllocator(@Allocator int allocator) {
    517         return this;
    518     }
    519 
    520     /**
    521      *  Specify whether the {@link Bitmap} should have unpremultiplied pixels.
    522      *
    523      *  By default, ImageDecoder will create a {@link Bitmap} with
    524      *  premultiplied pixels, which is required for drawing with the
    525      *  {@link android.view.View} system (i.e. to a {@link Canvas}). Calling
    526      *  this method with a value of {@code true} will result in
    527      *  {@link #decodeBitmap} returning a {@link Bitmap} with unpremultiplied
    528      *  pixels. See {@link Bitmap#isPremultiplied}. This is incompatible with
    529      *  {@link #decodeDrawable}; attempting to decode an unpremultiplied
    530      *  {@link Drawable} will throw an {@link java.lang.IllegalStateException}.
    531      */
    532     public ImageDecoder setRequireUnpremultiplied(boolean requireUnpremultiplied) {
    533         return this;
    534     }
    535 
    536     /**
    537      *  Modify the image after decoding and scaling.
    538      *
    539      *  <p>This allows adding effects prior to returning a {@link Drawable} or
    540      *  {@link Bitmap}. For a {@code Drawable} or an immutable {@code Bitmap},
    541      *  this is the only way to process the image after decoding.</p>
    542      *
    543      *  <p>If set on a nine-patch image, the nine-patch data is ignored.</p>
    544      *
    545      *  <p>For an animated image, the drawing commands drawn on the
    546      *  {@link Canvas} will be recorded immediately and then applied to each
    547      *  frame.</p>
    548      */
    549     public ImageDecoder setPostProcessor(@Nullable PostProcessor p) {
    550         return this;
    551     }
    552 
    553     /**
    554      *  Set (replace) the {@link OnPartialImageListener} on this object.
    555      *
    556      *  Will be called if there is an error in the input. Without one, a
    557      *  partial {@link Bitmap} will be created.
    558      */
    559     public ImageDecoder setOnPartialImageListener(@Nullable OnPartialImageListener l) {
    560         return this;
    561     }
    562 
    563     /**
    564      *  Crop the output to {@code subset} of the (possibly) scaled image.
    565      *
    566      *  <p>{@code subset} must be contained within the size set by
    567      *  {@link #setResize} or the bounds of the image if setResize was not
    568      *  called. Otherwise an {@link IllegalStateException} will be thrown by
    569      *  {@link #decodeDrawable}/{@link #decodeBitmap}.</p>
    570      *
    571      *  <p>NOT intended as a replacement for
    572      *  {@link BitmapRegionDecoder#decodeRegion}. This supports all formats,
    573      *  but merely crops the output.</p>
    574      */
    575     public ImageDecoder setCrop(@Nullable Rect subset) {
    576         return this;
    577     }
    578 
    579     /**
    580      *  Set a Rect for retrieving nine patch padding.
    581      *
    582      *  If the image is a nine patch, this Rect will be set to the padding
    583      *  rectangle during decode. Otherwise it will not be modified.
    584      *
    585      *  @hide
    586      */
    587     public ImageDecoder setOutPaddingRect(@NonNull Rect outPadding) {
    588         mOutPaddingRect = outPadding;
    589         return this;
    590     }
    591 
    592     /**
    593      *  Specify whether the {@link Bitmap} should be mutable.
    594      *
    595      *  <p>By default, a {@link Bitmap} created will be immutable, but that can
    596      *  be changed with this call.</p>
    597      *
    598      *  <p>Mutable Bitmaps are incompatible with {@link #ALLOCATOR_HARDWARE},
    599      *  because {@link Bitmap.Config#HARDWARE} Bitmaps cannot be mutable.
    600      *  Attempting to combine them will throw an
    601      *  {@link java.lang.IllegalStateException}.</p>
    602      *
    603      *  <p>Mutable Bitmaps are also incompatible with {@link #decodeDrawable},
    604      *  which would require retrieving the Bitmap from the returned Drawable in
    605      *  order to modify. Attempting to decode a mutable {@link Drawable} will
    606      *  throw an {@link java.lang.IllegalStateException}.</p>
    607      */
    608     public ImageDecoder setMutable(boolean mutable) {
    609         return this;
    610     }
    611 
    612     /**
    613      *  Specify whether to potentially save RAM at the expense of quality.
    614      *
    615      *  Setting this to {@code true} may result in a {@link Bitmap} with a
    616      *  denser {@link Bitmap.Config}, depending on the image. For example, for
    617      *  an opaque {@link Bitmap}, this may result in a {@link Bitmap.Config}
    618      *  with no alpha information.
    619      */
    620     public ImageDecoder setPreferRamOverQuality(boolean preferRamOverQuality) {
    621         return this;
    622     }
    623 
    624     /**
    625      *  Specify whether to potentially treat the output as an alpha mask.
    626      *
    627      *  <p>If this is set to {@code true} and the image is encoded in a format
    628      *  with only one channel, treat that channel as alpha. Otherwise this call has
    629      *  no effect.</p>
    630      *
    631      *  <p>setAsAlphaMask is incompatible with {@link #ALLOCATOR_HARDWARE}. Trying to
    632      *  combine them will result in {@link #decodeDrawable}/
    633      *  {@link #decodeBitmap} throwing an
    634      *  {@link java.lang.IllegalStateException}.</p>
    635      */
    636     public ImageDecoder setAsAlphaMask(boolean asAlphaMask) {
    637         return this;
    638     }
    639 
    640     @Override
    641     public void close() {
    642     }
    643 
    644     /**
    645      *  Create a {@link Drawable} from a {@code Source}.
    646      *
    647      *  @param src representing the encoded image.
    648      *  @param listener for learning the {@link ImageInfo} and changing any
    649      *      default settings on the {@code ImageDecoder}. If not {@code null},
    650      *      this will be called on the same thread as {@code decodeDrawable}
    651      *      before that method returns.
    652      *  @return Drawable for displaying the image.
    653      *  @throws IOException if {@code src} is not found, is an unsupported
    654      *      format, or cannot be decoded for any reason.
    655      */
    656     @NonNull
    657     public static Drawable decodeDrawable(@NonNull Source src,
    658             @Nullable OnHeaderDecodedListener listener) throws IOException {
    659         Bitmap bitmap = decodeBitmap(src, listener);
    660         return new BitmapDrawable(src.getResources(), bitmap);
    661     }
    662 
    663     /**
    664      * See {@link #decodeDrawable(Source, OnHeaderDecodedListener)}.
    665      */
    666     @NonNull
    667     public static Drawable decodeDrawable(@NonNull Source src)
    668             throws IOException {
    669         return decodeDrawable(src, null);
    670     }
    671 
    672     /**
    673      *  Create a {@link Bitmap} from a {@code Source}.
    674      *
    675      *  @param src representing the encoded image.
    676      *  @param listener for learning the {@link ImageInfo} and changing any
    677      *      default settings on the {@code ImageDecoder}. If not {@code null},
    678      *      this will be called on the same thread as {@code decodeBitmap}
    679      *      before that method returns.
    680      *  @return Bitmap containing the image.
    681      *  @throws IOException if {@code src} is not found, is an unsupported
    682      *      format, or cannot be decoded for any reason.
    683      */
    684     @NonNull
    685     public static Bitmap decodeBitmap(@NonNull Source src,
    686             @Nullable OnHeaderDecodedListener listener) throws IOException {
    687         TypedValue value = new TypedValue();
    688         value.density = src.getDensity();
    689         ImageDecoder decoder = src.createImageDecoder();
    690         if (listener != null) {
    691             listener.onHeaderDecoded(decoder, new ImageInfo(decoder), src);
    692         }
    693         return BitmapFactory.decodeResourceStream(src.getResources(), value,
    694                 ((InputStreamSource) src).mInputStream, decoder.mOutPaddingRect, null);
    695     }
    696 
    697     /**
    698      *  See {@link #decodeBitmap(Source, OnHeaderDecodedListener)}.
    699      */
    700     @NonNull
    701     public static Bitmap decodeBitmap(@NonNull Source src) throws IOException {
    702         return decodeBitmap(src, null);
    703     }
    704 }
    705