All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.android.graphics.ImageDecoder Maven / Gradle / Ivy

Go to download

A library jar that provides APIs for Applications written for the Google Android Platform.

There is a newer version: 15-robolectric-12650502
Show newest version
/*
 * 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.
         *
         * 

Calling {@link #decodeDrawable} will return an * {@link AnimatedImageDrawable}.

*/ 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}. * *

The returned {@link Source} effectively takes ownership of the * {@link java.nio.ByteBuffer}; i.e. no other code should modify it after * this call.

* * 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. * *

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}.

* * @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. * *

This has the same effect as passing the result of * {@link #getSampledSize} to {@link #setResize(int, int)}.

* * @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 void setAllocator(@Allocator int allocator) { } /** * 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. * *

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.

* *

If set on a nine-patch image, the nine-patch data is ignored.

* *

For an animated image, the drawing commands drawn on the * {@link Canvas} will be recorded immediately and then applied to each * frame.

*/ public void setPostProcessor(@Nullable PostProcessor p) { } /** * 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 void setOnPartialImageListener(@Nullable OnPartialImageListener l) { } /** * Crop the output to {@code subset} of the (possibly) scaled image. * *

{@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}.

* *

NOT intended as a replacement for * {@link BitmapRegionDecoder#decodeRegion}. This supports all formats, * but merely crops the output.

*/ public void setCrop(@Nullable Rect subset) { } /** * 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 void setOutPaddingRect(@NonNull Rect outPadding) { mOutPaddingRect = outPadding; } /** * Specify whether the {@link Bitmap} should be mutable. * *

By default, a {@link Bitmap} created will be immutable, but that can * be changed with this call.

* *

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}.

* *

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}.

*/ 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. * *

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.

* *

setAsAlphaMask is incompatible with {@link #ALLOCATOR_HARDWARE}. Trying to * combine them will result in {@link #decodeDrawable}/ * {@link #decodeBitmap} throwing an * {@link java.lang.IllegalStateException}.

*/ 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); } public static final class DecodeException extends IOException { /** * An Exception was thrown reading the {@link Source}. */ public static final int SOURCE_EXCEPTION = 1; /** * The encoded data was incomplete. */ public static final int SOURCE_INCOMPLETE = 2; /** * The encoded data contained an error. */ public static final int SOURCE_MALFORMED_DATA = 3; @Error final int mError; @NonNull final Source mSource; DecodeException(@Error int error, @Nullable Throwable cause, @NonNull Source source) { super(errorMessage(error, cause), cause); mError = error; mSource = source; } /** * Private method called by JNI. */ @SuppressWarnings("unused") DecodeException(@Error int error, @Nullable String msg, @Nullable Throwable cause, @NonNull Source source) { super(msg + errorMessage(error, cause), cause); mError = error; mSource = source; } /** * Retrieve the reason that decoding was interrupted. * *

If the error is {@link #SOURCE_EXCEPTION}, the underlying * {@link java.lang.Throwable} can be retrieved with * {@link java.lang.Throwable#getCause}.

*/ @Error public int getError() { return mError; } /** * Retrieve the {@link Source Source} that was interrupted. * *

This can be used for equality checking to find the Source which * failed to completely decode.

*/ @NonNull public Source getSource() { return mSource; } private static String errorMessage(@Error int error, @Nullable Throwable cause) { switch (error) { case SOURCE_EXCEPTION: return "Exception in input: " + cause; case SOURCE_INCOMPLETE: return "Input was incomplete."; case SOURCE_MALFORMED_DATA: return "Input contained an error."; default: return ""; } } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy