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

Commit 5aec67ca authored by Stan Iliev's avatar Stan Iliev Committed by Leon Scroggins III
Browse files

Use ImageDecoder for NinePatchDrawable and BitmapDrawable

Don't scale NinePatchDrawable if bitmap has no density.
Fixed bugs around density and input streams.

Update PointerIcon to account for the fact that BitmapDrawable no longer
scales its Bitmap up at decode time. PointerIcon now handles the
scaling. This is necessary because PointerIcon never draws its Bitmap.
Instead, native code uses the Bitmap's internal SkBitmap without
accounting for density.

Test: Ran CTS:
- CtsUiRenderingTestCases
- CtsGraphicsTestCases
- CtsViewTestCases

Change-Id: I030b4bb89c66b0102ccea2d85f5271197558d14e
parent 34b58512
Loading
Loading
Loading
Loading
+35 −2
Original line number Diff line number Diff line
@@ -23,6 +23,10 @@ import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
@@ -396,6 +400,33 @@ public final class PointerIcon implements Parcelable {
        return true;
    }

    /**
     *  Get the Bitmap from the Drawable.
     *
     *  If the Bitmap needed to be scaled up to account for density, BitmapDrawable
     *  handles this at draw time. But this class doesn't actually draw the Bitmap;
     *  it is just a holder for native code to access its SkBitmap. So this needs to
     *  get a version that is scaled to account for density.
     */
    private Bitmap getBitmapFromDrawable(BitmapDrawable bitmapDrawable) {
        Bitmap bitmap = bitmapDrawable.getBitmap();
        final int scaledWidth  = bitmapDrawable.getIntrinsicWidth();
        final int scaledHeight = bitmapDrawable.getIntrinsicHeight();
        if (scaledWidth == bitmap.getWidth() && scaledHeight == bitmap.getHeight()) {
            return bitmap;
        }

        Rect src = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
        RectF dst = new RectF(0, 0, scaledWidth, scaledHeight);

        Bitmap scaled = Bitmap.createBitmap(scaledWidth, scaledHeight, bitmap.getConfig());
        Canvas canvas = new Canvas(scaled);
        Paint paint = new Paint();
        paint.setFilterBitmap(true);
        canvas.drawBitmap(bitmap, src, dst, paint);
        return scaled;
    }

    private void loadResource(Context context, Resources resources, @XmlRes int resourceId) {
        final XmlResourceParser parser = resources.getXml(resourceId);
        final int bitmapRes;
@@ -452,7 +483,8 @@ public final class PointerIcon implements Parcelable {
                                + "is different. All frames should have the exact same size and "
                                + "share the same hotspot.");
                    }
                    mBitmapFrames[i - 1] = ((BitmapDrawable)drawableFrame).getBitmap();
                    BitmapDrawable bitmapDrawableFrame = (BitmapDrawable) drawableFrame;
                    mBitmapFrames[i - 1] = getBitmapFromDrawable(bitmapDrawableFrame);
                }
            }
        }
@@ -461,7 +493,8 @@ public final class PointerIcon implements Parcelable {
                    + "refer to a bitmap drawable.");
        }

        final Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
        BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
        final Bitmap bitmap = getBitmapFromDrawable(bitmapDrawable);
        validateHotSpot(bitmap, hotSpotX, hotSpotY);
        // Set the properties now that we have successfully loaded the icon.
        mBitmap = bitmap;
+26 −2
Original line number Diff line number Diff line
@@ -438,6 +438,7 @@ public final class ImageDecoder implements AutoCloseable {
    private boolean mPreferRamOverQuality = false;
    private boolean mAsAlphaMask = false;
    private Rect    mCropRect;
    private Rect    mOutPaddingRect;
    private Source  mSource;

    private PostProcessor          mPostProcessor;
@@ -764,6 +765,18 @@ public final class ImageDecoder implements AutoCloseable {
        mCropRect = 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.
     *
@@ -875,7 +888,6 @@ public final class ImageDecoder implements AutoCloseable {
                postProcessPtr, mDesiredWidth, mDesiredHeight, mCropRect,
                mMutable, mAllocator, mRequireUnpremultiplied,
                mPreferRamOverQuality, mAsAlphaMask);

    }

    private void callHeaderDecoded(@Nullable OnHeaderDecodedListener listener,
@@ -948,7 +960,10 @@ public final class ImageDecoder implements AutoCloseable {
            if (np != null && NinePatch.isNinePatchChunk(np)) {
                Rect opticalInsets = new Rect();
                bm.getOpticalInsets(opticalInsets);
                Rect padding = new Rect();
                Rect padding = decoder.mOutPaddingRect;
                if (padding == null) {
                    padding = new Rect();
                }
                nGetPadding(decoder.mNativePtr, padding);
                return new NinePatchDrawable(res, bm, np, padding,
                        opticalInsets, null);
@@ -991,6 +1006,15 @@ public final class ImageDecoder implements AutoCloseable {
            final int srcDensity = computeDensity(src, decoder);
            Bitmap bm = decoder.decodeBitmap();
            bm.setDensity(srcDensity);

            Rect padding = decoder.mOutPaddingRect;
            if (padding != null) {
                byte[] np = bm.getNinePatchChunk();
                if (np != null && NinePatch.isNinePatchChunk(np)) {
                    nGetPadding(decoder.mNativePtr, padding);
                }
            }

            return bm;
        }
    }
+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", "ChainingConstructorIgnoresParameter" })
    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),
                    (decoder, info, src) -> {
                decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
            });
        } 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", "ChainingConstructorIgnoresParameter" })
    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),
                    (decoder, info, src) -> {
                decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
            });
        } 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, (decoder, info, src) -> {
                    decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
                });
            } 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, (decoder, info, src) -> {
                decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
            });
        } 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);
        }
+14 −7
Original line number Diff line number Diff line
@@ -24,9 +24,9 @@ import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.ImageDecoder;
import android.graphics.Insets;
import android.graphics.NinePatch;
import android.graphics.Outline;
@@ -211,7 +211,8 @@ public class NinePatchDrawable extends Drawable {
            restoreAlpha = -1;
        }

        final boolean needsDensityScaling = canvas.getDensity() == 0;
        final boolean needsDensityScaling = canvas.getDensity() == 0
                && Bitmap.DENSITY_NONE != state.mNinePatch.getDensity();
        if (needsDensityScaling) {
            restoreToCount = restoreToCount >= 0 ? restoreToCount : canvas.save();

@@ -421,10 +422,6 @@ public class NinePatchDrawable extends Drawable {

        final int srcResId = a.getResourceId(R.styleable.NinePatchDrawable_src, 0);
        if (srcResId != 0) {
            final BitmapFactory.Options options = new BitmapFactory.Options();
            options.inDither = !state.mDither;
            options.inScreenDensity = r.getDisplayMetrics().noncompatDensityDpi;

            final Rect padding = new Rect();
            final Rect opticalInsets = new Rect();
            Bitmap bitmap = null;
@@ -433,7 +430,17 @@ public class NinePatchDrawable extends Drawable {
                final TypedValue value = new TypedValue();
                final InputStream is = r.openRawResource(srcResId, value);

                bitmap = BitmapFactory.decodeResourceStream(r, value, is, padding, options);
                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;
                }
                ImageDecoder.Source source = ImageDecoder.createSource(r, is, density);
                bitmap = ImageDecoder.decodeBitmap(source, (decoder, info, src) -> {
                    decoder.setOutPaddingRect(padding);
                    decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
                });

                is.close();
            } catch (IOException e) {