| /* |
| * Copyright (C) 2017 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.graphics; |
| |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.content.ContentResolver; |
| import android.content.res.AssetManager.AssetInputStream; |
| import android.content.res.Resources; |
| import android.graphics.drawable.AnimatedImageDrawable; |
| import android.graphics.drawable.Drawable; |
| import android.graphics.drawable.BitmapDrawable; |
| import android.net.Uri; |
| import android.util.DisplayMetrics; |
| import android.util.Size; |
| import android.util.TypedValue; |
| |
| import java.nio.ByteBuffer; |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.lang.ArrayIndexOutOfBoundsException; |
| import java.lang.AutoCloseable; |
| import java.lang.NullPointerException; |
| import java.lang.annotation.Retention; |
| import static java.lang.annotation.RetentionPolicy.SOURCE; |
| |
| /** |
| * Class for decoding images as {@link Bitmap}s or {@link Drawable}s. |
| */ |
| public final class ImageDecoder implements AutoCloseable { |
| |
| /** |
| * Source of the encoded image data. |
| */ |
| public static abstract class Source { |
| private Source() {} |
| |
| /* @hide */ |
| @Nullable |
| Resources getResources() { return null; } |
| |
| /* @hide */ |
| int getDensity() { return Bitmap.DENSITY_NONE; } |
| |
| /* @hide */ |
| int computeDstDensity() { |
| Resources res = getResources(); |
| if (res == null) { |
| return Bitmap.getDefaultDensity(); |
| } |
| |
| return res.getDisplayMetrics().densityDpi; |
| } |
| |
| /* @hide */ |
| @NonNull |
| abstract ImageDecoder createImageDecoder() throws IOException; |
| }; |
| |
| private static class ByteArraySource extends Source { |
| ByteArraySource(@NonNull byte[] data, int offset, int length) { |
| mData = data; |
| mOffset = offset; |
| mLength = length; |
| }; |
| private final byte[] mData; |
| private final int mOffset; |
| private final int mLength; |
| |
| @Override |
| public ImageDecoder createImageDecoder() throws IOException { |
| return new ImageDecoder(); |
| } |
| } |
| |
| private static class ByteBufferSource extends Source { |
| ByteBufferSource(@NonNull ByteBuffer buffer) { |
| mBuffer = buffer; |
| } |
| private final ByteBuffer mBuffer; |
| |
| @Override |
| public ImageDecoder createImageDecoder() throws IOException { |
| return new ImageDecoder(); |
| } |
| } |
| |
| private static class ContentResolverSource extends Source { |
| ContentResolverSource(@NonNull ContentResolver resolver, @NonNull Uri uri) { |
| mResolver = resolver; |
| mUri = uri; |
| } |
| |
| private final ContentResolver mResolver; |
| private final Uri mUri; |
| |
| @Override |
| public ImageDecoder createImageDecoder() throws IOException { |
| return new ImageDecoder(); |
| } |
| } |
| |
| /** |
| * For backwards compatibility, this does *not* close the InputStream. |
| */ |
| private static class InputStreamSource extends Source { |
| InputStreamSource(Resources res, InputStream is, int inputDensity) { |
| if (is == null) { |
| throw new IllegalArgumentException("The InputStream cannot be null"); |
| } |
| mResources = res; |
| mInputStream = is; |
| mInputDensity = res != null ? inputDensity : Bitmap.DENSITY_NONE; |
| } |
| |
| final Resources mResources; |
| InputStream mInputStream; |
| final int mInputDensity; |
| |
| @Override |
| public Resources getResources() { return mResources; } |
| |
| @Override |
| public int getDensity() { return mInputDensity; } |
| |
| @Override |
| public ImageDecoder createImageDecoder() throws IOException { |
| return new ImageDecoder(); |
| } |
| } |
| |
| /** |
| * Takes ownership of the AssetInputStream. |
| * |
| * @hide |
| */ |
| public static class AssetInputStreamSource extends Source { |
| public AssetInputStreamSource(@NonNull AssetInputStream ais, |
| @NonNull Resources res, @NonNull TypedValue value) { |
| mAssetInputStream = ais; |
| mResources = res; |
| |
| if (value.density == TypedValue.DENSITY_DEFAULT) { |
| mDensity = DisplayMetrics.DENSITY_DEFAULT; |
| } else if (value.density != TypedValue.DENSITY_NONE) { |
| mDensity = value.density; |
| } else { |
| mDensity = Bitmap.DENSITY_NONE; |
| } |
| } |
| |
| private AssetInputStream mAssetInputStream; |
| private final Resources mResources; |
| private final int mDensity; |
| |
| @Override |
| public Resources getResources() { return mResources; } |
| |
| @Override |
| public int getDensity() { |
| return mDensity; |
| } |
| |
| @Override |
| public ImageDecoder createImageDecoder() throws IOException { |
| return new ImageDecoder(); |
| } |
| } |
| |
| private static class ResourceSource extends Source { |
| ResourceSource(@NonNull Resources res, int resId) { |
| mResources = res; |
| mResId = resId; |
| mResDensity = Bitmap.DENSITY_NONE; |
| } |
| |
| final Resources mResources; |
| final int mResId; |
| int mResDensity; |
| |
| @Override |
| public Resources getResources() { return mResources; } |
| |
| @Override |
| public int getDensity() { return mResDensity; } |
| |
| @Override |
| public ImageDecoder createImageDecoder() throws IOException { |
| return new ImageDecoder(); |
| } |
| } |
| |
| private static class FileSource extends Source { |
| FileSource(@NonNull File file) { |
| mFile = file; |
| } |
| |
| private final File mFile; |
| |
| @Override |
| public ImageDecoder createImageDecoder() throws IOException { |
| return new ImageDecoder(); |
| } |
| } |
| |
| /** |
| * Contains information about the encoded image. |
| */ |
| public static class ImageInfo { |
| private ImageDecoder mDecoder; |
| |
| private ImageInfo(@NonNull ImageDecoder decoder) { |
| mDecoder = decoder; |
| } |
| |
| /** |
| * Size of the image, without scaling or cropping. |
| */ |
| @NonNull |
| public Size getSize() { |
| return new Size(0, 0); |
| } |
| |
| /** |
| * The mimeType of the image. |
| */ |
| @NonNull |
| public String getMimeType() { |
| return ""; |
| } |
| |
| /** |
| * Whether the image is animated. |
| * |
| * <p>Calling {@link #decodeDrawable} will return an |
| * {@link AnimatedImageDrawable}.</p> |
| */ |
| public boolean isAnimated() { |
| return mDecoder.mAnimated; |
| } |
| }; |
| |
| /** |
| * Thrown if the provided data is incomplete. |
| */ |
| public static class IncompleteException extends IOException {}; |
| |
| /** |
| * Optional listener supplied to {@link #decodeDrawable} or |
| * {@link #decodeBitmap}. |
| */ |
| public interface OnHeaderDecodedListener { |
| /** |
| * Called when the header is decoded and the size is known. |
| * |
| * @param decoder allows changing the default settings of the decode. |
| * @param info Information about the encoded image. |
| * @param source that created the decoder. |
| */ |
| void onHeaderDecoded(@NonNull ImageDecoder decoder, |
| @NonNull ImageInfo info, @NonNull Source source); |
| |
| }; |
| |
| /** |
| * An Exception was thrown reading the {@link Source}. |
| */ |
| public static final int ERROR_SOURCE_EXCEPTION = 1; |
| |
| /** |
| * The encoded data was incomplete. |
| */ |
| public static final int ERROR_SOURCE_INCOMPLETE = 2; |
| |
| /** |
| * The encoded data contained an error. |
| */ |
| public static final int ERROR_SOURCE_ERROR = 3; |
| |
| @Retention(SOURCE) |
| public @interface Error {} |
| |
| /** |
| * Optional listener supplied to the ImageDecoder. |
| * |
| * Without this listener, errors will throw {@link java.io.IOException}. |
| */ |
| public interface OnPartialImageListener { |
| /** |
| * Called when there is only a partial image to display. |
| * |
| * If decoding is interrupted after having decoded a partial image, |
| * this listener lets the client know that and allows them to |
| * optionally finish the rest of the decode/creation process to create |
| * a partial {@link Drawable}/{@link Bitmap}. |
| * |
| * @param error indicating what interrupted the decode. |
| * @param source that had the error. |
| * @return True to create and return a {@link Drawable}/{@link Bitmap} |
| * with partial data. False (which is the default) to abort the |
| * decode and throw {@link java.io.IOException}. |
| */ |
| boolean onPartialImage(@Error int error, @NonNull Source source); |
| } |
| |
| private boolean mAnimated; |
| private Rect mOutPaddingRect; |
| |
| public ImageDecoder() { |
| mAnimated = true; // This is too avoid throwing an exception in AnimatedImageDrawable |
| } |
| |
| /** |
| * Create a new {@link Source} from an asset. |
| * @hide |
| * |
| * @param res the {@link Resources} object containing the image data. |
| * @param resId resource ID of the image data. |
| * // FIXME: Can be an @DrawableRes? |
| * @return a new Source object, which can be passed to |
| * {@link #decodeDrawable} or {@link #decodeBitmap}. |
| */ |
| @NonNull |
| public static Source createSource(@NonNull Resources res, int resId) |
| { |
| return new ResourceSource(res, resId); |
| } |
| |
| /** |
| * Create a new {@link Source} from a {@link android.net.Uri}. |
| * |
| * @param cr to retrieve from. |
| * @param uri of the image file. |
| * @return a new Source object, which can be passed to |
| * {@link #decodeDrawable} or {@link #decodeBitmap}. |
| */ |
| @NonNull |
| public static Source createSource(@NonNull ContentResolver cr, |
| @NonNull Uri uri) { |
| return new ContentResolverSource(cr, uri); |
| } |
| |
| /** |
| * Create a new {@link Source} from a byte array. |
| * |
| * @param data byte array of compressed image data. |
| * @param offset offset into data for where the decoder should begin |
| * parsing. |
| * @param length number of bytes, beginning at offset, to parse. |
| * @throws NullPointerException if data is null. |
| * @throws ArrayIndexOutOfBoundsException if offset and length are |
| * not within data. |
| * @hide |
| */ |
| @NonNull |
| public static Source createSource(@NonNull byte[] data, int offset, |
| int length) throws ArrayIndexOutOfBoundsException { |
| if (offset < 0 || length < 0 || offset >= data.length || |
| offset + length > data.length) { |
| throw new ArrayIndexOutOfBoundsException( |
| "invalid offset/length!"); |
| } |
| return new ByteArraySource(data, offset, length); |
| } |
| |
| /** |
| * See {@link #createSource(byte[], int, int). |
| * @hide |
| */ |
| @NonNull |
| public static Source createSource(@NonNull byte[] data) { |
| return createSource(data, 0, data.length); |
| } |
| |
| /** |
| * Create a new {@link Source} from a {@link java.nio.ByteBuffer}. |
| * |
| * <p>The returned {@link Source} effectively takes ownership of the |
| * {@link java.nio.ByteBuffer}; i.e. no other code should modify it after |
| * this call.</p> |
| * |
| * Decoding will start from {@link java.nio.ByteBuffer#position()}. The |
| * position after decoding is undefined. |
| */ |
| @NonNull |
| public static Source createSource(@NonNull ByteBuffer buffer) { |
| return new ByteBufferSource(buffer); |
| } |
| |
| /** |
| * Internal API used to generate bitmaps for use by Drawables (i.e. BitmapDrawable) |
| * @hide |
| */ |
| public static Source createSource(Resources res, InputStream is) { |
| return new InputStreamSource(res, is, Bitmap.getDefaultDensity()); |
| } |
| |
| /** |
| * Internal API used to generate bitmaps for use by Drawables (i.e. BitmapDrawable) |
| * @hide |
| */ |
| public static Source createSource(Resources res, InputStream is, int density) { |
| return new InputStreamSource(res, is, density); |
| } |
| |
| /** |
| * Create a new {@link Source} from a {@link java.io.File}. |
| */ |
| @NonNull |
| public static Source createSource(@NonNull File file) { |
| return new FileSource(file); |
| } |
| |
| /** |
| * Return the width and height of a given sample size. |
| * |
| * <p>This takes an input that functions like |
| * {@link BitmapFactory.Options#inSampleSize}. It returns a width and |
| * height that can be acheived by sampling the encoded image. Other widths |
| * and heights may be supported, but will require an additional (internal) |
| * scaling step. Such internal scaling is *not* supported with |
| * {@link #setRequireUnpremultiplied} set to {@code true}.</p> |
| * |
| * @param sampleSize Sampling rate of the encoded image. |
| * @return {@link android.util.Size} of the width and height after |
| * sampling. |
| */ |
| @NonNull |
| public Size getSampledSize(int sampleSize) { |
| return new Size(0, 0); |
| } |
| |
| // Modifiers |
| /** |
| * Resize the output to have the following size. |
| * |
| * @param width must be greater than 0. |
| * @param height must be greater than 0. |
| */ |
| public void setResize(int width, int height) { |
| } |
| |
| /** |
| * Resize based on a sample size. |
| * |
| * <p>This has the same effect as passing the result of |
| * {@link #getSampledSize} to {@link #setResize(int, int)}.</p> |
| * |
| * @param sampleSize Sampling rate of the encoded image. |
| */ |
| public void setResize(int sampleSize) { |
| } |
| |
| // These need to stay in sync with ImageDecoder.cpp's Allocator enum. |
| /** |
| * Use the default allocation for the pixel memory. |
| * |
| * Will typically result in a {@link Bitmap.Config#HARDWARE} |
| * allocation, but may be software for small images. In addition, this will |
| * switch to software when HARDWARE is incompatible, e.g. |
| * {@link #setMutable}, {@link #setAsAlphaMask}. |
| */ |
| public static final int ALLOCATOR_DEFAULT = 0; |
| |
| /** |
| * Use a software allocation for the pixel memory. |
| * |
| * Useful for drawing to a software {@link Canvas} or for |
| * accessing the pixels on the final output. |
| */ |
| public static final int ALLOCATOR_SOFTWARE = 1; |
| |
| /** |
| * Use shared memory for the pixel memory. |
| * |
| * Useful for sharing across processes. |
| */ |
| public static final int ALLOCATOR_SHARED_MEMORY = 2; |
| |
| /** |
| * Require a {@link Bitmap.Config#HARDWARE} {@link Bitmap}. |
| * |
| * When this is combined with incompatible options, like |
| * {@link #setMutable} or {@link #setAsAlphaMask}, {@link #decodeDrawable} |
| * / {@link #decodeBitmap} will throw an |
| * {@link java.lang.IllegalStateException}. |
| */ |
| public static final int ALLOCATOR_HARDWARE = 3; |
| |
| /** @hide **/ |
| @Retention(SOURCE) |
| public @interface Allocator {}; |
| |
| /** |
| * Choose the backing for the pixel memory. |
| * |
| * This is ignored for animated drawables. |
| * |
| * @param allocator Type of allocator to use. |
| */ |
| public ImageDecoder setAllocator(@Allocator int allocator) { |
| return this; |
| } |
| |
| /** |
| * Specify whether the {@link Bitmap} should have unpremultiplied pixels. |
| * |
| * By default, ImageDecoder will create a {@link Bitmap} with |
| * premultiplied pixels, which is required for drawing with the |
| * {@link android.view.View} system (i.e. to a {@link Canvas}). Calling |
| * this method with a value of {@code true} will result in |
| * {@link #decodeBitmap} returning a {@link Bitmap} with unpremultiplied |
| * pixels. See {@link Bitmap#isPremultiplied}. This is incompatible with |
| * {@link #decodeDrawable}; attempting to decode an unpremultiplied |
| * {@link Drawable} will throw an {@link java.lang.IllegalStateException}. |
| */ |
| public ImageDecoder setRequireUnpremultiplied(boolean requireUnpremultiplied) { |
| return this; |
| } |
| |
| /** |
| * Modify the image after decoding and scaling. |
| * |
| * <p>This allows adding effects prior to returning a {@link Drawable} or |
| * {@link Bitmap}. For a {@code Drawable} or an immutable {@code Bitmap}, |
| * this is the only way to process the image after decoding.</p> |
| * |
| * <p>If set on a nine-patch image, the nine-patch data is ignored.</p> |
| * |
| * <p>For an animated image, the drawing commands drawn on the |
| * {@link Canvas} will be recorded immediately and then applied to each |
| * frame.</p> |
| */ |
| public ImageDecoder setPostProcessor(@Nullable PostProcessor p) { |
| return this; |
| } |
| |
| /** |
| * Set (replace) the {@link OnPartialImageListener} on this object. |
| * |
| * Will be called if there is an error in the input. Without one, a |
| * partial {@link Bitmap} will be created. |
| */ |
| public ImageDecoder setOnPartialImageListener(@Nullable OnPartialImageListener l) { |
| return this; |
| } |
| |
| /** |
| * Crop the output to {@code subset} of the (possibly) scaled image. |
| * |
| * <p>{@code subset} must be contained within the size set by |
| * {@link #setResize} or the bounds of the image if setResize was not |
| * called. Otherwise an {@link IllegalStateException} will be thrown by |
| * {@link #decodeDrawable}/{@link #decodeBitmap}.</p> |
| * |
| * <p>NOT intended as a replacement for |
| * {@link BitmapRegionDecoder#decodeRegion}. This supports all formats, |
| * but merely crops the output.</p> |
| */ |
| public ImageDecoder setCrop(@Nullable Rect subset) { |
| return this; |
| } |
| |
| /** |
| * Set a Rect for retrieving nine patch padding. |
| * |
| * If the image is a nine patch, this Rect will be set to the padding |
| * rectangle during decode. Otherwise it will not be modified. |
| * |
| * @hide |
| */ |
| public ImageDecoder setOutPaddingRect(@NonNull Rect outPadding) { |
| mOutPaddingRect = outPadding; |
| return this; |
| } |
| |
| /** |
| * Specify whether the {@link Bitmap} should be mutable. |
| * |
| * <p>By default, a {@link Bitmap} created will be immutable, but that can |
| * be changed with this call.</p> |
| * |
| * <p>Mutable Bitmaps are incompatible with {@link #ALLOCATOR_HARDWARE}, |
| * because {@link Bitmap.Config#HARDWARE} Bitmaps cannot be mutable. |
| * Attempting to combine them will throw an |
| * {@link java.lang.IllegalStateException}.</p> |
| * |
| * <p>Mutable Bitmaps are also incompatible with {@link #decodeDrawable}, |
| * which would require retrieving the Bitmap from the returned Drawable in |
| * order to modify. Attempting to decode a mutable {@link Drawable} will |
| * throw an {@link java.lang.IllegalStateException}.</p> |
| */ |
| public ImageDecoder setMutable(boolean mutable) { |
| return this; |
| } |
| |
| /** |
| * Specify whether to potentially save RAM at the expense of quality. |
| * |
| * Setting this to {@code true} may result in a {@link Bitmap} with a |
| * denser {@link Bitmap.Config}, depending on the image. For example, for |
| * an opaque {@link Bitmap}, this may result in a {@link Bitmap.Config} |
| * with no alpha information. |
| */ |
| public ImageDecoder setPreferRamOverQuality(boolean preferRamOverQuality) { |
| return this; |
| } |
| |
| /** |
| * Specify whether to potentially treat the output as an alpha mask. |
| * |
| * <p>If this is set to {@code true} and the image is encoded in a format |
| * with only one channel, treat that channel as alpha. Otherwise this call has |
| * no effect.</p> |
| * |
| * <p>setAsAlphaMask is incompatible with {@link #ALLOCATOR_HARDWARE}. Trying to |
| * combine them will result in {@link #decodeDrawable}/ |
| * {@link #decodeBitmap} throwing an |
| * {@link java.lang.IllegalStateException}.</p> |
| */ |
| public ImageDecoder setAsAlphaMask(boolean asAlphaMask) { |
| return this; |
| } |
| |
| @Override |
| public void close() { |
| } |
| |
| /** |
| * Create a {@link Drawable} from a {@code Source}. |
| * |
| * @param src representing the encoded image. |
| * @param listener for learning the {@link ImageInfo} and changing any |
| * default settings on the {@code ImageDecoder}. If not {@code null}, |
| * this will be called on the same thread as {@code decodeDrawable} |
| * before that method returns. |
| * @return Drawable for displaying the image. |
| * @throws IOException if {@code src} is not found, is an unsupported |
| * format, or cannot be decoded for any reason. |
| */ |
| @NonNull |
| public static Drawable decodeDrawable(@NonNull Source src, |
| @Nullable OnHeaderDecodedListener listener) throws IOException { |
| Bitmap bitmap = decodeBitmap(src, listener); |
| return new BitmapDrawable(src.getResources(), bitmap); |
| } |
| |
| /** |
| * See {@link #decodeDrawable(Source, OnHeaderDecodedListener)}. |
| */ |
| @NonNull |
| public static Drawable decodeDrawable(@NonNull Source src) |
| throws IOException { |
| return decodeDrawable(src, null); |
| } |
| |
| /** |
| * Create a {@link Bitmap} from a {@code Source}. |
| * |
| * @param src representing the encoded image. |
| * @param listener for learning the {@link ImageInfo} and changing any |
| * default settings on the {@code ImageDecoder}. If not {@code null}, |
| * this will be called on the same thread as {@code decodeBitmap} |
| * before that method returns. |
| * @return Bitmap containing the image. |
| * @throws IOException if {@code src} is not found, is an unsupported |
| * format, or cannot be decoded for any reason. |
| */ |
| @NonNull |
| public static Bitmap decodeBitmap(@NonNull Source src, |
| @Nullable OnHeaderDecodedListener listener) throws IOException { |
| TypedValue value = new TypedValue(); |
| value.density = src.getDensity(); |
| ImageDecoder decoder = src.createImageDecoder(); |
| if (listener != null) { |
| listener.onHeaderDecoded(decoder, new ImageInfo(decoder), src); |
| } |
| return BitmapFactory.decodeResourceStream(src.getResources(), value, |
| ((InputStreamSource) src).mInputStream, decoder.mOutPaddingRect, null); |
| } |
| |
| /** |
| * See {@link #decodeBitmap(Source, OnHeaderDecodedListener)}. |
| */ |
| @NonNull |
| public static Bitmap decodeBitmap(@NonNull Source src) throws IOException { |
| return decodeBitmap(src, null); |
| } |
| } |