Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit cbb60724 authored by Leon Scroggins's avatar Leon Scroggins Committed by Android (Google) Code Review
Browse files

Merge "Use ImageDecoder for BitmapDrawable"

parents b56074b3 66c6d789
Loading
Loading
Loading
Loading
+133 −7
Original line number Diff line number Diff line
@@ -33,6 +33,8 @@ import android.graphics.drawable.NinePatchDrawable;
import android.net.Uri;
import android.system.ErrnoException;
import android.system.Os;
import android.util.DisplayMetrics;
import android.util.TypedValue;

import libcore.io.IoUtils;
import dalvik.system.CloseGuard;
@@ -63,6 +65,19 @@ public final class ImageDecoder implements AutoCloseable {
        /* @hide */
        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 */
        abstract ImageDecoder createImageDecoder() throws IOException;
    };
@@ -170,26 +185,73 @@ public final class ImageDecoder implements AutoCloseable {
        return decoder;
    }

    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 {

            synchronized (this) {
                if (mInputStream == null) {
                    throw new IOException("Cannot reuse InputStreamSource");
                }
                InputStream is = mInputStream;
                mInputStream = null;
                return createFromStream(is);
            }
        }
    }

    private static class ResourceSource extends Source {
        ResourceSource(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 {
            // This is just used in order to access the underlying Asset and
            // keep it alive. FIXME: Can we skip creating this object?
            InputStream is = null;
            ImageDecoder decoder = null;
            TypedValue value = new TypedValue();
            try {
                is = mResources.openRawResource(mResId);
                is = mResources.openRawResource(mResId, value);

                if (value.density == TypedValue.DENSITY_DEFAULT) {
                    mResDensity = DisplayMetrics.DENSITY_DEFAULT;
                } else if (value.density != TypedValue.DENSITY_NONE) {
                    mResDensity = value.density;
                }

                if (!(is instanceof AssetManager.AssetInputStream)) {
                    // This should never happen.
                    throw new RuntimeException("Resource is not an asset?");
@@ -420,6 +482,22 @@ public final class ImageDecoder implements AutoCloseable {
        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);
    }

    /**
     *  Return the width and height of a given sample size.
     *
@@ -476,6 +554,10 @@ public final class ImageDecoder implements AutoCloseable {
        this.resize(dimensions.x, dimensions.y);
    }

    private boolean requestedResize() {
        return mWidth != mDesiredWidth || mHeight != mDesiredHeight;
    }

    // These need to stay in sync with ImageDecoder.cpp's Allocator enum.
    /**
     *  Use the default allocation for the pixel memory.
@@ -730,6 +812,9 @@ public final class ImageDecoder implements AutoCloseable {
                                                "Drawable!");
            }

            // this call potentially manipulates the decoder so it must be performed prior to
            // decoding the bitmap and after decode set the density on the resulting bitmap
            final int srcDensity = computeDensity(src, decoder);
            if (decoder.mAnimated) {
                // AnimatedImageDrawable calls postProcessAndRelease only if
                // mPostProcess exists.
@@ -737,7 +822,8 @@ public final class ImageDecoder implements AutoCloseable {
                        null : decoder;
                Drawable d = new AnimatedImageDrawable(decoder.mNativePtr,
                        postProcessPtr, decoder.mDesiredWidth,
                        decoder.mDesiredHeight, decoder.mCropRect,
                        decoder.mDesiredHeight, srcDensity,
                        src.computeDstDensity(), decoder.mCropRect,
                        decoder.mInputStream, decoder.mAssetFd);
                // d has taken ownership of these objects.
                decoder.mInputStream = null;
@@ -746,13 +832,15 @@ public final class ImageDecoder implements AutoCloseable {
            }

            Bitmap bm = decoder.decodeBitmap();
            Resources res = src.getResources();
            if (res == null) {
                bm.setDensity(Bitmap.DENSITY_NONE);
            }
            bm.setDensity(srcDensity);

            Resources res = src.getResources();
            byte[] np = bm.getNinePatchChunk();
            if (np != null && NinePatch.isNinePatchChunk(np)) {
                if (res != null) {
                    bm.setDensity(res.getDisplayMetrics().densityDpi);
                }

                Rect opticalInsets = new Rect();
                bm.getOpticalInsets(opticalInsets);
                Rect padding = new Rect();
@@ -799,10 +887,48 @@ public final class ImageDecoder implements AutoCloseable {
                }
            }

            return decoder.decodeBitmap();
            // this call potentially manipulates the decoder so it must be performed prior to
            // decoding the bitmap
            final int srcDensity = computeDensity(src, decoder);
            Bitmap bm = decoder.decodeBitmap();
            bm.setDensity(srcDensity);
            return bm;
        }
    }

    // This method may modify the decoder so it must be called prior to performing the decode
    private static int computeDensity(@NonNull Source src, @NonNull ImageDecoder decoder) {
        // if the caller changed the size then we treat the density as unknown
        if (decoder.requestedResize()) {
            return Bitmap.DENSITY_NONE;
        }

        // Special stuff for compatibility mode: if the target density is not
        // the same as the display density, but the resource -is- the same as
        // the display density, then don't scale it down to the target density.
        // This allows us to load the system's density-correct resources into
        // an application in compatibility mode, without scaling those down
        // to the compatibility density only to have them scaled back up when
        // drawn to the screen.
        Resources res = src.getResources();
        final int srcDensity = src.getDensity();
        if (res != null && res.getDisplayMetrics().noncompatDensityDpi == srcDensity) {
            return srcDensity;
        }

        // downscale the bitmap if the asset has a higher density than the default
        final int dstDensity = src.computeDstDensity();
        if (srcDensity != Bitmap.DENSITY_NONE && srcDensity > dstDensity) {
            float scale = (float) dstDensity / srcDensity;
            int scaledWidth = (int) (decoder.mWidth * scale + 0.5f);
            int scaledHeight = (int) (decoder.mHeight * scale + 0.5f);
            decoder.resize(scaledWidth, scaledHeight);
            return dstDensity;
        }

        return srcDensity;
    }

    private String getMimeType() {
        return nGetMimeType(mNativePtr);
    }
+16 −5
Original line number Diff line number Diff line
@@ -20,12 +20,14 @@ import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.res.AssetFileDescriptor;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.ImageDecoder;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.os.SystemClock;
import android.util.DisplayMetrics;

import libcore.io.IoUtils;
import libcore.util.NativeAllocationRegistry;
@@ -59,22 +61,31 @@ public class AnimatedImageDrawable extends Drawable implements Animatable {
     * decoder is only non-null if it has a PostProcess
     */
    public AnimatedImageDrawable(long nativeImageDecoder,
            @Nullable ImageDecoder decoder, int width, int height, Rect cropRect,
            @Nullable ImageDecoder decoder, int width, int height,
            int srcDensity, int dstDensity, Rect cropRect,
            InputStream inputStream, AssetFileDescriptor afd)
            throws IOException {
        mNativePtr = nCreate(nativeImageDecoder, decoder, width, height, cropRect);
        mInputStream = inputStream;
        mAssetFd = afd;
        width = Bitmap.scaleFromDensity(width, srcDensity, dstDensity);
        height = Bitmap.scaleFromDensity(height, srcDensity, dstDensity);

        if (cropRect == null) {
            mIntrinsicWidth  = width;
            mIntrinsicHeight = height;
        } else {
            cropRect.set(Bitmap.scaleFromDensity(cropRect.left, srcDensity, dstDensity),
                    Bitmap.scaleFromDensity(cropRect.top, srcDensity, dstDensity),
                    Bitmap.scaleFromDensity(cropRect.right, srcDensity, dstDensity),
                    Bitmap.scaleFromDensity(cropRect.bottom, srcDensity, dstDensity));
            mIntrinsicWidth  = cropRect.width();
            mIntrinsicHeight = cropRect.height();
        }

        long nativeSize = nNativeByteSize(mNativePtr);
        mNativePtr = nCreate(nativeImageDecoder, decoder, width, height, cropRect);
        mInputStream = inputStream;
        mAssetFd = afd;

        // FIXME: Use the right size for the native allocation.
        long nativeSize = 200;
        NativeAllocationRegistry registry = new NativeAllocationRegistry(
                AnimatedImageDrawable.class.getClassLoader(), nGetNativeFinalizer(), nativeSize);
        registry.registerNativeAllocation(this, mNativePtr);
+59 −26
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import android.graphics.BitmapFactory;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.ImageDecoder;
import android.graphics.Insets;
import android.graphics.Matrix;
import android.graphics.Outline;
@@ -49,6 +50,7 @@ import com.android.internal.R;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

@@ -111,7 +113,7 @@ public class BitmapDrawable extends Drawable {
     */
    @Deprecated
    public BitmapDrawable() {
        mBitmapState = new BitmapState((Bitmap) null);
        init(new BitmapState((Bitmap) null), null);
    }

    /**
@@ -124,8 +126,7 @@ public class BitmapDrawable extends Drawable {
    @SuppressWarnings("unused")
    @Deprecated
    public BitmapDrawable(Resources res) {
        mBitmapState = new BitmapState((Bitmap) null);
        mBitmapState.mTargetDensity = mTargetDensity;
        init(new BitmapState((Bitmap) null), res);
    }

    /**
@@ -135,7 +136,7 @@ public class BitmapDrawable extends Drawable {
     */
    @Deprecated
    public BitmapDrawable(Bitmap bitmap) {
        this(new BitmapState(bitmap), null);
        init(new BitmapState(bitmap), null);
    }

    /**
@@ -143,8 +144,7 @@ public class BitmapDrawable extends Drawable {
     * the display metrics of the resources.
     */
    public BitmapDrawable(Resources res, Bitmap bitmap) {
        this(new BitmapState(bitmap), res);
        mBitmapState.mTargetDensity = mTargetDensity;
        init(new BitmapState(bitmap), res);
    }

    /**
@@ -154,10 +154,7 @@ public class BitmapDrawable extends Drawable {
     */
    @Deprecated
    public BitmapDrawable(String filepath) {
        this(new BitmapState(BitmapFactory.decodeFile(filepath)), null);
        if (mBitmapState.mBitmap == null) {
            android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + filepath);
        }
        this(null, filepath);
    }

    /**
@@ -165,12 +162,23 @@ public class BitmapDrawable extends Drawable {
     */
    @SuppressWarnings("unused")
    public BitmapDrawable(Resources res, String filepath) {
        this(new BitmapState(BitmapFactory.decodeFile(filepath)), null);
        mBitmapState.mTargetDensity = mTargetDensity;
        Bitmap bitmap = null;
        try (FileInputStream stream = new FileInputStream(filepath)) {
            bitmap = ImageDecoder.decodeBitmap(ImageDecoder.createSource(res, stream),
                    (info, decoder) -> {
                decoder.setAllocator(ImageDecoder.SOFTWARE_ALLOCATOR);
            });
        } catch (Exception e) {
            /*  do nothing. This matches the behavior of BitmapFactory.decodeFile()
                If the exception happened on decode, mBitmapState.mBitmap will be null.
            */
        } finally {
            init(new BitmapState(bitmap), res);
            if (mBitmapState.mBitmap == null) {
                android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + filepath);
            }
        }
    }

    /**
     * Create a drawable by decoding a bitmap from the given input stream.
@@ -179,10 +187,7 @@ public class BitmapDrawable extends Drawable {
     */
    @Deprecated
    public BitmapDrawable(java.io.InputStream is) {
        this(new BitmapState(BitmapFactory.decodeStream(is)), null);
        if (mBitmapState.mBitmap == null) {
            android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + is);
        }
        this(null, is);
    }

    /**
@@ -190,12 +195,23 @@ public class BitmapDrawable extends Drawable {
     */
    @SuppressWarnings("unused")
    public BitmapDrawable(Resources res, java.io.InputStream is) {
        this(new BitmapState(BitmapFactory.decodeStream(is)), null);
        mBitmapState.mTargetDensity = mTargetDensity;
        Bitmap bitmap = null;
        try {
            bitmap = ImageDecoder.decodeBitmap(ImageDecoder.createSource(res, is),
                    (info, decoder) -> {
                decoder.setAllocator(ImageDecoder.SOFTWARE_ALLOCATOR);
            });
        } catch (Exception e) {
            /*  do nothing. This matches the behavior of BitmapFactory.decodeStream()
                If the exception happened on decode, mBitmapState.mBitmap will be null.
            */
        } finally {
            init(new BitmapState(bitmap), res);
            if (mBitmapState.mBitmap == null) {
                android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + is);
            }
        }
    }

    /**
     * Returns the paint used to render this drawable.
@@ -812,9 +828,19 @@ public class BitmapDrawable extends Drawable {
                }
            }

            int density = Bitmap.DENSITY_NONE;
            if (value.density == TypedValue.DENSITY_DEFAULT) {
                density = DisplayMetrics.DENSITY_DEFAULT;
            } else if (value.density != TypedValue.DENSITY_NONE) {
                density = value.density;
            }

            Bitmap bitmap = null;
            try (InputStream is = r.openRawResource(srcResId, value)) {
                bitmap = BitmapFactory.decodeResourceStream(r, value, is, null, null);
                ImageDecoder.Source source = ImageDecoder.createSource(r, is, density);
                bitmap = ImageDecoder.decodeBitmap(source, (info, decoder) -> {
                    decoder.setAllocator(ImageDecoder.SOFTWARE_ALLOCATOR);
                });
            } catch (Exception e) {
                // Do nothing and pick up the error below.
            }
@@ -1013,14 +1039,21 @@ public class BitmapDrawable extends Drawable {
        }
    }

    private BitmapDrawable(BitmapState state, Resources res) {
        init(state, res);
    }

    /**
     * The one constructor to rule them all. This is called by all public
     * The one helper to rule them all. This is called by all public & private
     * constructors to set the state and initialize local properties.
     */
    private BitmapDrawable(BitmapState state, Resources res) {
    private void init(BitmapState state, Resources res) {
        mBitmapState = state;

        updateLocalState(res);

        if (mBitmapState != null && res != null) {
            mBitmapState.mTargetDensity = mTargetDensity;
        }
    }

    /**
+38 −5
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@ import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.ImageDecoder;
import android.graphics.Insets;
import android.graphics.NinePatch;
import android.graphics.Outline;
@@ -50,11 +51,13 @@ import android.graphics.Xfermode;
import android.os.Trace;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.StateSet;
import android.util.TypedValue;
import android.util.Xml;
import android.view.View;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
@@ -1175,6 +1178,10 @@ public abstract class Drawable {
            return null;
        }

        if (opts == null) {
            return getBitmapDrawable(res, value, is);
        }

        /*  ugh. The decodeStream contract is that we have already allocated
            the pad rect, but if the bitmap does not had a ninepatch chunk,
            then the pad will be ignored. If we could change this to lazily
@@ -1207,6 +1214,33 @@ public abstract class Drawable {
        return null;
    }

    private static Drawable getBitmapDrawable(Resources res, TypedValue value, InputStream is) {
        try {
            ImageDecoder.Source source = null;
            if (value != null) {
                int density = Bitmap.DENSITY_NONE;
                if (value.density == TypedValue.DENSITY_DEFAULT) {
                    density = DisplayMetrics.DENSITY_DEFAULT;
                } else if (value.density != TypedValue.DENSITY_NONE) {
                    density = value.density;
                }
                source = ImageDecoder.createSource(res, is, density);
            } else {
                source = ImageDecoder.createSource(res, is);
            }

            return ImageDecoder.decodeDrawable(source, (info, decoder) -> {
                decoder.setAllocator(ImageDecoder.SOFTWARE_ALLOCATOR);
            });
        } catch (IOException e) {
            /*  do nothing.
                If the exception happened on decode, the drawable will be null.
            */
            Log.e("Drawable", "Unable to decode stream: " + e);
        }
        return null;
    }

    /**
     * Create a drawable from an XML document. For more information on how to
     * create resources in XML, see
@@ -1306,11 +1340,10 @@ public abstract class Drawable {
        }

        Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, pathName);
        try {
            Bitmap bm = BitmapFactory.decodeFile(pathName);
            if (bm != null) {
                return drawableFromBitmap(null, bm, null, null, null, pathName);
            }
        try (FileInputStream stream = new FileInputStream(pathName)) {
            return getBitmapDrawable(null, null, stream);
        } catch(IOException e) {
            // Do nothing; we will just return null if the FileInputStream had an error
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
        }