diff options
4 files changed, 246 insertions, 43 deletions
diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java index 05dadc97a5ba..4d715d1cb9ea 100644 --- a/graphics/java/android/graphics/ImageDecoder.java +++ b/graphics/java/android/graphics/ImageDecoder.java @@ -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; @@ -64,6 +66,19 @@ public final class ImageDecoder implements AutoCloseable { 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?"); @@ -421,6 +483,22 @@ public final class ImageDecoder implements AutoCloseable { } /** + * 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. * * This takes an input that functions like @@ -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,8 +887,46 @@ 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() { diff --git a/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java index ce3bd9af73b6..da170c0fae24 100644 --- a/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java +++ b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java @@ -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); diff --git a/graphics/java/android/graphics/drawable/BitmapDrawable.java b/graphics/java/android/graphics/drawable/BitmapDrawable.java index e3740e3cf284..f74c39d84bce 100644 --- a/graphics/java/android/graphics/drawable/BitmapDrawable.java +++ b/graphics/java/android/graphics/drawable/BitmapDrawable.java @@ -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,10 +162,21 @@ public class BitmapDrawable extends Drawable { */ @SuppressWarnings("unused") public BitmapDrawable(Resources res, String filepath) { - this(new BitmapState(BitmapFactory.decodeFile(filepath)), null); - mBitmapState.mTargetDensity = mTargetDensity; - if (mBitmapState.mBitmap == null) { - android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + filepath); + 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); + } } } @@ -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,10 +195,21 @@ 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; - if (mBitmapState.mBitmap == null) { - android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + is); + 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); + } } } @@ -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; + } } /** diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java index f17cd768c386..291b0a0be4f4 100644 --- a/graphics/java/android/graphics/drawable/Drawable.java +++ b/graphics/java/android/graphics/drawable/Drawable.java @@ -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); } |