summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--graphics/java/android/graphics/ImageDecoder.java140
-rw-r--r--graphics/java/android/graphics/drawable/AnimatedImageDrawable.java21
-rw-r--r--graphics/java/android/graphics/drawable/BitmapDrawable.java85
-rw-r--r--graphics/java/android/graphics/drawable/Drawable.java43
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);
}