Bug: 73788928 Test: I1606cbb4e71579160ffaef12c1ed738fad882cd1 This will allow Kotlin developers to treat the setters as properties. Part of the motivation for the rename is that "getAsAlphaMask" sounds like it returns an alpha mask itself. Rename both to _etDecodeAsAlphaMask. Change-Id: I8f9b04f8381840490b662c3bcd37a95442af8110
1319 lines
45 KiB
Java
1319 lines
45 KiB
Java
/*
|
|
* 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 static android.system.OsConstants.SEEK_CUR;
|
|
import static android.system.OsConstants.SEEK_SET;
|
|
|
|
import static java.lang.annotation.RetentionPolicy.SOURCE;
|
|
|
|
import android.annotation.IntDef;
|
|
import android.annotation.NonNull;
|
|
import android.annotation.Nullable;
|
|
import android.annotation.RawRes;
|
|
import android.content.ContentResolver;
|
|
import android.content.res.AssetFileDescriptor;
|
|
import android.content.res.AssetManager.AssetInputStream;
|
|
import android.content.res.Resources;
|
|
import android.graphics.drawable.AnimatedImageDrawable;
|
|
import android.graphics.drawable.BitmapDrawable;
|
|
import android.graphics.drawable.Drawable;
|
|
import android.graphics.drawable.NinePatchDrawable;
|
|
import android.net.Uri;
|
|
import android.system.ErrnoException;
|
|
import android.system.Os;
|
|
import android.util.DisplayMetrics;
|
|
import android.util.Size;
|
|
import android.util.TypedValue;
|
|
|
|
import dalvik.system.CloseGuard;
|
|
|
|
import libcore.io.IoUtils;
|
|
|
|
import java.io.File;
|
|
import java.io.FileDescriptor;
|
|
import java.io.FileInputStream;
|
|
import java.io.FileNotFoundException;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.lang.annotation.Retention;
|
|
import java.nio.ByteBuffer;
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
|
|
|
/**
|
|
* 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 */
|
|
final 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 nCreate(mData, mOffset, mLength);
|
|
}
|
|
}
|
|
|
|
private static class ByteBufferSource extends Source {
|
|
ByteBufferSource(@NonNull ByteBuffer buffer) {
|
|
mBuffer = buffer;
|
|
}
|
|
private final ByteBuffer mBuffer;
|
|
|
|
@Override
|
|
public ImageDecoder createImageDecoder() throws IOException {
|
|
if (!mBuffer.isDirect() && mBuffer.hasArray()) {
|
|
int offset = mBuffer.arrayOffset() + mBuffer.position();
|
|
int length = mBuffer.limit() - mBuffer.position();
|
|
return nCreate(mBuffer.array(), offset, length);
|
|
}
|
|
return nCreate(mBuffer, mBuffer.position(), mBuffer.limit());
|
|
}
|
|
}
|
|
|
|
private static class ContentResolverSource extends Source {
|
|
ContentResolverSource(@NonNull ContentResolver resolver, @NonNull Uri uri,
|
|
@Nullable Resources res) {
|
|
mResolver = resolver;
|
|
mUri = uri;
|
|
mResources = res;
|
|
}
|
|
|
|
private final ContentResolver mResolver;
|
|
private final Uri mUri;
|
|
private final Resources mResources;
|
|
|
|
@Nullable
|
|
Resources getResources() { return mResources; }
|
|
|
|
@Override
|
|
public ImageDecoder createImageDecoder() throws IOException {
|
|
AssetFileDescriptor assetFd = null;
|
|
try {
|
|
if (mUri.getScheme() == ContentResolver.SCHEME_CONTENT) {
|
|
assetFd = mResolver.openTypedAssetFileDescriptor(mUri,
|
|
"image/*", null);
|
|
} else {
|
|
assetFd = mResolver.openAssetFileDescriptor(mUri, "r");
|
|
}
|
|
} catch (FileNotFoundException e) {
|
|
// Some images cannot be opened as AssetFileDescriptors (e.g.
|
|
// bmp, ico). Open them as InputStreams.
|
|
InputStream is = mResolver.openInputStream(mUri);
|
|
if (is == null) {
|
|
throw new FileNotFoundException(mUri.toString());
|
|
}
|
|
|
|
return createFromStream(is, true);
|
|
}
|
|
|
|
final FileDescriptor fd = assetFd.getFileDescriptor();
|
|
final long offset = assetFd.getStartOffset();
|
|
|
|
ImageDecoder decoder = null;
|
|
try {
|
|
try {
|
|
Os.lseek(fd, offset, SEEK_SET);
|
|
decoder = nCreate(fd);
|
|
} catch (ErrnoException e) {
|
|
decoder = createFromStream(new FileInputStream(fd), true);
|
|
}
|
|
} finally {
|
|
if (decoder == null) {
|
|
IoUtils.closeQuietly(assetFd);
|
|
} else {
|
|
decoder.mAssetFd = assetFd;
|
|
}
|
|
}
|
|
return decoder;
|
|
}
|
|
}
|
|
|
|
@NonNull
|
|
private static ImageDecoder createFromFile(@NonNull File file) throws IOException {
|
|
FileInputStream stream = new FileInputStream(file);
|
|
FileDescriptor fd = stream.getFD();
|
|
try {
|
|
Os.lseek(fd, 0, SEEK_CUR);
|
|
} catch (ErrnoException e) {
|
|
return createFromStream(stream, true);
|
|
}
|
|
|
|
ImageDecoder decoder = null;
|
|
try {
|
|
decoder = nCreate(fd);
|
|
} finally {
|
|
if (decoder == null) {
|
|
IoUtils.closeQuietly(stream);
|
|
} else {
|
|
decoder.mInputStream = stream;
|
|
decoder.mOwnsInputStream = true;
|
|
}
|
|
}
|
|
return decoder;
|
|
}
|
|
|
|
@NonNull
|
|
private static ImageDecoder createFromStream(@NonNull InputStream is,
|
|
boolean closeInputStream) throws IOException {
|
|
// Arbitrary size matches BitmapFactory.
|
|
byte[] storage = new byte[16 * 1024];
|
|
ImageDecoder decoder = null;
|
|
try {
|
|
decoder = nCreate(is, storage);
|
|
} finally {
|
|
if (decoder == null) {
|
|
if (closeInputStream) {
|
|
IoUtils.closeQuietly(is);
|
|
}
|
|
} else {
|
|
decoder.mInputStream = is;
|
|
decoder.mOwnsInputStream = closeInputStream;
|
|
decoder.mTempStorage = storage;
|
|
}
|
|
}
|
|
|
|
return decoder;
|
|
}
|
|
|
|
/**
|
|
* 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 {
|
|
|
|
synchronized (this) {
|
|
if (mInputStream == null) {
|
|
throw new IOException("Cannot reuse InputStreamSource");
|
|
}
|
|
InputStream is = mInputStream;
|
|
mInputStream = null;
|
|
return createFromStream(is, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 {
|
|
ImageDecoder decoder = null;
|
|
synchronized (this) {
|
|
if (mAssetInputStream == null) {
|
|
throw new IOException("Cannot reuse AssetInputStreamSource");
|
|
}
|
|
AssetInputStream ais = mAssetInputStream;
|
|
mAssetInputStream = null;
|
|
try {
|
|
long asset = ais.getNativeAsset();
|
|
decoder = nCreate(asset);
|
|
} finally {
|
|
if (decoder == null) {
|
|
IoUtils.closeQuietly(ais);
|
|
} else {
|
|
decoder.mInputStream = ais;
|
|
decoder.mOwnsInputStream = true;
|
|
}
|
|
}
|
|
return decoder;
|
|
}
|
|
}
|
|
}
|
|
|
|
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 {
|
|
// 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, value);
|
|
|
|
if (value.density == TypedValue.DENSITY_DEFAULT) {
|
|
mResDensity = DisplayMetrics.DENSITY_DEFAULT;
|
|
} else if (value.density != TypedValue.DENSITY_NONE) {
|
|
mResDensity = value.density;
|
|
}
|
|
|
|
long asset = ((AssetInputStream) is).getNativeAsset();
|
|
decoder = nCreate(asset);
|
|
} finally {
|
|
if (decoder == null) {
|
|
IoUtils.closeQuietly(is);
|
|
} else {
|
|
decoder.mInputStream = is;
|
|
decoder.mOwnsInputStream = true;
|
|
}
|
|
}
|
|
return decoder;
|
|
}
|
|
}
|
|
|
|
private static class FileSource extends Source {
|
|
FileSource(@NonNull File file) {
|
|
mFile = file;
|
|
}
|
|
|
|
private final File mFile;
|
|
|
|
@Override
|
|
public ImageDecoder createImageDecoder() throws IOException {
|
|
return createFromFile(mFile);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Contains information about the encoded image.
|
|
*/
|
|
public static class ImageInfo {
|
|
private final Size mSize;
|
|
private ImageDecoder mDecoder;
|
|
|
|
private ImageInfo(@NonNull ImageDecoder decoder) {
|
|
mSize = new Size(decoder.mWidth, decoder.mHeight);
|
|
mDecoder = decoder;
|
|
}
|
|
|
|
/**
|
|
* Size of the image, without scaling or cropping.
|
|
*/
|
|
@NonNull
|
|
public Size getSize() {
|
|
return mSize;
|
|
}
|
|
|
|
/**
|
|
* The mimeType of the image.
|
|
*/
|
|
@NonNull
|
|
public String getMimeType() {
|
|
return mDecoder.getMimeType();
|
|
}
|
|
|
|
/**
|
|
* Whether the image is animated.
|
|
*
|
|
* <p>Calling {@link #decodeDrawable} will return an
|
|
* {@link AnimatedImageDrawable}.</p>
|
|
*/
|
|
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 static 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.
|
|
*/
|
|
public 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)
|
|
@IntDef({ ERROR_SOURCE_EXCEPTION, ERROR_SOURCE_INCOMPLETE, ERROR_SOURCE_ERROR })
|
|
public @interface Error {};
|
|
|
|
/**
|
|
* Optional listener supplied to the ImageDecoder.
|
|
*
|
|
* Without this listener, errors will throw {@link java.io.IOException}.
|
|
*/
|
|
public static 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}.
|
|
*/
|
|
public boolean onPartialImage(@Error int error, @NonNull Source source);
|
|
};
|
|
|
|
// Fields
|
|
private long mNativePtr;
|
|
private final int mWidth;
|
|
private final int mHeight;
|
|
private final boolean mAnimated;
|
|
|
|
private int mDesiredWidth;
|
|
private int mDesiredHeight;
|
|
private int mAllocator = ALLOCATOR_DEFAULT;
|
|
private boolean mRequireUnpremultiplied = false;
|
|
private boolean mMutable = false;
|
|
private boolean mConserveMemory = false;
|
|
private boolean mDecodeAsAlphaMask = false;
|
|
private Rect mCropRect;
|
|
private Rect mOutPaddingRect;
|
|
private Source mSource;
|
|
|
|
private PostProcessor mPostProcessor;
|
|
private OnPartialImageListener mOnPartialImageListener;
|
|
|
|
// Objects for interacting with the input.
|
|
private InputStream mInputStream;
|
|
private boolean mOwnsInputStream;
|
|
private byte[] mTempStorage;
|
|
private AssetFileDescriptor mAssetFd;
|
|
private final AtomicBoolean mClosed = new AtomicBoolean();
|
|
private final CloseGuard mCloseGuard = CloseGuard.get();
|
|
|
|
/**
|
|
* Private constructor called by JNI. {@link #close} must be
|
|
* called after decoding to delete native resources.
|
|
*/
|
|
@SuppressWarnings("unused")
|
|
private ImageDecoder(long nativePtr, int width, int height,
|
|
boolean animated) {
|
|
mNativePtr = nativePtr;
|
|
mWidth = width;
|
|
mHeight = height;
|
|
mDesiredWidth = width;
|
|
mDesiredHeight = height;
|
|
mAnimated = animated;
|
|
mCloseGuard.open("close");
|
|
}
|
|
|
|
@Override
|
|
protected void finalize() throws Throwable {
|
|
try {
|
|
if (mCloseGuard != null) {
|
|
mCloseGuard.warnIfOpen();
|
|
}
|
|
|
|
// Avoid closing these in finalizer.
|
|
mInputStream = null;
|
|
mAssetFd = null;
|
|
|
|
close();
|
|
} finally {
|
|
super.finalize();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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, @RawRes 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, null);
|
|
}
|
|
|
|
/**
|
|
* Provide Resources for density scaling.
|
|
*
|
|
* @hide
|
|
*/
|
|
@NonNull
|
|
public static Source createSource(@NonNull ContentResolver cr,
|
|
@NonNull Uri uri, @Nullable Resources res) {
|
|
return new ContentResolverSource(cr, uri, res);
|
|
}
|
|
|
|
/**
|
|
* 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 (data == null) {
|
|
throw new NullPointerException("null byte[] in createSource!");
|
|
}
|
|
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}.
|
|
*
|
|
* <p>Decoding will start from {@link java.nio.ByteBuffer#position()}. The
|
|
* position of {@code buffer} will not be affected.</p>
|
|
*
|
|
* <p>Note: If this {@code Source} is passed to {@link #decodeDrawable}, and
|
|
* the encoded image is animated, the returned {@link AnimatedImageDrawable}
|
|
* will continue reading from the {@code buffer}, so its contents must not
|
|
* be modified, even after the {@code AnimatedImageDrawable} is returned.
|
|
* {@code buffer}'s contents should never be modified during decode.</p>
|
|
*/
|
|
@NonNull
|
|
public static Source createSource(@NonNull ByteBuffer buffer) {
|
|
return new ByteBufferSource(buffer.slice());
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*
|
|
* <p>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}.</p>
|
|
*
|
|
* @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) {
|
|
if (sampleSize <= 0) {
|
|
throw new IllegalArgumentException("sampleSize must be positive! "
|
|
+ "provided " + sampleSize);
|
|
}
|
|
if (mNativePtr == 0) {
|
|
throw new IllegalStateException("ImageDecoder is closed!");
|
|
}
|
|
|
|
return nGetSampledSize(mNativePtr, sampleSize);
|
|
}
|
|
|
|
// Modifiers
|
|
/**
|
|
* Resize the output to have the following size.
|
|
*
|
|
* @param width must be greater than 0.
|
|
* @param height must be greater than 0.
|
|
* @return this object for chaining.
|
|
*/
|
|
public ImageDecoder setResize(int width, int height) {
|
|
if (width <= 0 || height <= 0) {
|
|
throw new IllegalArgumentException("Dimensions must be positive! "
|
|
+ "provided (" + width + ", " + height + ")");
|
|
}
|
|
|
|
mDesiredWidth = width;
|
|
mDesiredHeight = height;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Resize based on a sample size.
|
|
*
|
|
* <p>This has the same effect as passing the result of
|
|
* {@link #getSampledSize} to {@link #setResize(int, int)}.</p>
|
|
*
|
|
* @param sampleSize Sampling rate of the encoded image.
|
|
* @return this object for chaining.
|
|
*/
|
|
public ImageDecoder setResize(int sampleSize) {
|
|
Size size = this.getSampledSize(sampleSize);
|
|
return this.setResize(size.getWidth(), size.getHeight());
|
|
}
|
|
|
|
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.
|
|
*
|
|
* 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 #setDecodeAsAlphaMask}.
|
|
*/
|
|
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 #setDecodeAsAlphaMask}, {@link #decodeDrawable}
|
|
* / {@link #decodeBitmap} will throw an
|
|
* {@link java.lang.IllegalStateException}.
|
|
*/
|
|
public static final int ALLOCATOR_HARDWARE = 3;
|
|
|
|
/** @hide **/
|
|
@Retention(SOURCE)
|
|
@IntDef(value = { ALLOCATOR_DEFAULT, ALLOCATOR_SOFTWARE,
|
|
ALLOCATOR_SHARED_MEMORY, ALLOCATOR_HARDWARE },
|
|
prefix = {"ALLOCATOR_"})
|
|
public @interface Allocator {};
|
|
|
|
/**
|
|
* Choose the backing for the pixel memory.
|
|
*
|
|
* This is ignored for animated drawables.
|
|
*
|
|
* @param allocator Type of allocator to use.
|
|
* @return this object for chaining.
|
|
*/
|
|
public ImageDecoder setAllocator(@Allocator int allocator) {
|
|
if (allocator < ALLOCATOR_DEFAULT || allocator > ALLOCATOR_HARDWARE) {
|
|
throw new IllegalArgumentException("invalid allocator " + allocator);
|
|
}
|
|
mAllocator = allocator;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Return the allocator for the pixel memory.
|
|
*/
|
|
@Allocator
|
|
public int getAllocator() {
|
|
return mAllocator;
|
|
}
|
|
|
|
/**
|
|
* Specify whether the {@link Bitmap} should have unpremultiplied pixels.
|
|
*
|
|
* <p>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}.
|
|
* </p>
|
|
*
|
|
* @return this object for chaining.
|
|
*/
|
|
public ImageDecoder setRequireUnpremultiplied(boolean requireUnpremultiplied) {
|
|
mRequireUnpremultiplied = requireUnpremultiplied;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Return whether the {@link Bitmap} will have unpremultiplied pixels.
|
|
*/
|
|
public boolean getRequireUnpremultiplied() {
|
|
return mRequireUnpremultiplied;
|
|
}
|
|
|
|
/**
|
|
* Modify the image after decoding and scaling.
|
|
*
|
|
* <p>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.</p>
|
|
*
|
|
* <p>If set on a nine-patch image, the nine-patch data is ignored.</p>
|
|
*
|
|
* <p>For an animated image, the drawing commands drawn on the
|
|
* {@link Canvas} will be recorded immediately and then applied to each
|
|
* frame.</p>
|
|
*
|
|
* @return this object for chaining.
|
|
*/
|
|
public ImageDecoder setPostProcessor(@Nullable PostProcessor p) {
|
|
mPostProcessor = p;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Return the {@link PostProcessor} currently set.
|
|
*/
|
|
@Nullable
|
|
public PostProcessor getPostProcessor() {
|
|
return mPostProcessor;
|
|
}
|
|
|
|
/**
|
|
* Set (replace) the {@link OnPartialImageListener} on this object.
|
|
*
|
|
* <p>Will be called if there is an error in the input. Without one, an
|
|
* error will result in an Exception being thrown.</p>
|
|
*
|
|
* @return this object for chaining.
|
|
*/
|
|
public ImageDecoder setOnPartialImageListener(@Nullable OnPartialImageListener l) {
|
|
mOnPartialImageListener = l;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Return the {@link OnPartialImageListener} currently set.
|
|
*/
|
|
@Nullable
|
|
public OnPartialImageListener getOnPartialImageListener() {
|
|
return mOnPartialImageListener;
|
|
}
|
|
|
|
/**
|
|
* Crop the output to {@code subset} of the (possibly) scaled image.
|
|
*
|
|
* <p>{@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}.</p>
|
|
*
|
|
* <p>NOT intended as a replacement for
|
|
* {@link BitmapRegionDecoder#decodeRegion}. This supports all formats,
|
|
* but merely crops the output.</p>
|
|
*
|
|
* @return this object for chaining.
|
|
*/
|
|
public ImageDecoder setCrop(@Nullable Rect subset) {
|
|
mCropRect = subset;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Return the cropping rectangle, if set.
|
|
*/
|
|
@Nullable
|
|
public Rect getCrop() {
|
|
return mCropRect;
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*
|
|
* @return this object for chaining.
|
|
*
|
|
* @hide
|
|
*/
|
|
public ImageDecoder setOutPaddingRect(@NonNull Rect outPadding) {
|
|
mOutPaddingRect = outPadding;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Specify whether the {@link Bitmap} should be mutable.
|
|
*
|
|
* <p>By default, a {@link Bitmap} created will be immutable, but that can
|
|
* be changed with this call.</p>
|
|
*
|
|
* <p>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}.</p>
|
|
*
|
|
* <p>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}.</p>
|
|
*
|
|
* @return this object for chaining.
|
|
*/
|
|
public ImageDecoder setMutable(boolean mutable) {
|
|
mMutable = mutable;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Return whether the {@link Bitmap} will be mutable.
|
|
*/
|
|
public boolean getMutable() {
|
|
return mMutable;
|
|
}
|
|
|
|
/**
|
|
* Specify whether to potentially save RAM at the expense of quality.
|
|
*
|
|
* <p>Setting this to {@code true} may result in a {@link Bitmap} with a
|
|
* denser {@link Bitmap.Config}, depending on the image. For example, an
|
|
* opaque {@link Bitmap} with 8 bits or precision for each of its red,
|
|
* green and blue components would decode to
|
|
* {@link Bitmap.Config#ARGB_8888} by default, but setting this to
|
|
* {@code true} will result in decoding to {@link Bitmap.Config#RGB_565}.
|
|
* This necessarily lowers the quality of the output, but saves half
|
|
* the memory used.</p>
|
|
*
|
|
* @return this object for chaining.
|
|
*/
|
|
public ImageDecoder setConserveMemory(boolean conserveMemory) {
|
|
mConserveMemory = conserveMemory;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Return whether this object will try to save RAM at the expense of quality.
|
|
*
|
|
* <p>This returns whether {@link #setConserveMemory} was set to {@code true}.
|
|
* It may still return {@code true} even if the {@code ImageDecoder} does not
|
|
* have a way to save RAM at the expense of quality for this image.</p>
|
|
*/
|
|
public boolean getConserveMemory() {
|
|
return mConserveMemory;
|
|
}
|
|
|
|
/**
|
|
* Specify whether to potentially treat the output as an alpha mask.
|
|
*
|
|
* <p>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.</p>
|
|
*
|
|
* <p>setDecodeAsAlphaMask is incompatible with {@link #ALLOCATOR_HARDWARE}. Trying to
|
|
* combine them will result in {@link #decodeDrawable}/
|
|
* {@link #decodeBitmap} throwing an
|
|
* {@link java.lang.IllegalStateException}.</p>
|
|
*
|
|
* @return this object for chaining.
|
|
*/
|
|
public ImageDecoder setDecodeAsAlphaMask(boolean decodeAsAlphaMask) {
|
|
mDecodeAsAlphaMask = decodeAsAlphaMask;
|
|
return this;
|
|
}
|
|
|
|
/** @removed
|
|
* @deprecated Call {@link #setDecodeAsAlphaMask} instead.
|
|
*/
|
|
@java.lang.Deprecated
|
|
public ImageDecoder setAsAlphaMask(boolean asAlphaMask) {
|
|
return this.setDecodeAsAlphaMask(asAlphaMask);
|
|
}
|
|
|
|
/**
|
|
* Return whether to treat single channel input as alpha.
|
|
*
|
|
* <p>This returns whether {@link #setDecodeAsAlphaMask} was set to {@code true}.
|
|
* It may still return {@code true} even if the image has more than one
|
|
* channel and therefore will not be treated as an alpha mask.</p>
|
|
*/
|
|
public boolean getDecodeAsAlphaMask() {
|
|
return mDecodeAsAlphaMask;
|
|
}
|
|
|
|
/** @removed
|
|
* @deprecated Call {@link #getDecodeAsAlphaMask} instead.
|
|
*/
|
|
@java.lang.Deprecated
|
|
public boolean getAsAlphaMask() {
|
|
return this.getDecodeAsAlphaMask();
|
|
}
|
|
|
|
@Override
|
|
public void close() {
|
|
mCloseGuard.close();
|
|
if (!mClosed.compareAndSet(false, true)) {
|
|
return;
|
|
}
|
|
nClose(mNativePtr);
|
|
mNativePtr = 0;
|
|
|
|
if (mOwnsInputStream) {
|
|
IoUtils.closeQuietly(mInputStream);
|
|
}
|
|
IoUtils.closeQuietly(mAssetFd);
|
|
|
|
mInputStream = null;
|
|
mAssetFd = null;
|
|
mTempStorage = null;
|
|
}
|
|
|
|
private void checkState() {
|
|
if (mNativePtr == 0) {
|
|
throw new IllegalStateException("Cannot use closed ImageDecoder!");
|
|
}
|
|
|
|
checkSubset(mDesiredWidth, mDesiredHeight, mCropRect);
|
|
|
|
if (mAllocator == ALLOCATOR_HARDWARE) {
|
|
if (mMutable) {
|
|
throw new IllegalStateException("Cannot make mutable HARDWARE Bitmap!");
|
|
}
|
|
if (mDecodeAsAlphaMask) {
|
|
throw new IllegalStateException("Cannot make HARDWARE Alpha mask Bitmap!");
|
|
}
|
|
}
|
|
|
|
if (mPostProcessor != null && mRequireUnpremultiplied) {
|
|
throw new IllegalStateException("Cannot draw to unpremultiplied pixels!");
|
|
}
|
|
}
|
|
|
|
private static void checkSubset(int width, int height, Rect r) {
|
|
if (r == null) {
|
|
return;
|
|
}
|
|
if (r.left < 0 || r.top < 0 || r.right > width || r.bottom > height) {
|
|
throw new IllegalStateException("Subset " + r + " not contained by "
|
|
+ "scaled image bounds: (" + width + " x " + height + ")");
|
|
}
|
|
}
|
|
|
|
@NonNull
|
|
private Bitmap decodeBitmapInternal() throws IOException {
|
|
checkState();
|
|
// nDecodeBitmap calls onPartialImage only if mOnPartialImageListener
|
|
// exists
|
|
ImageDecoder partialImagePtr = mOnPartialImageListener == null ? null : this;
|
|
// nDecodeBitmap calls postProcessAndRelease only if mPostProcessor
|
|
// exists.
|
|
ImageDecoder postProcessPtr = mPostProcessor == null ? null : this;
|
|
return nDecodeBitmap(mNativePtr, partialImagePtr,
|
|
postProcessPtr, mDesiredWidth, mDesiredHeight, mCropRect,
|
|
mMutable, mAllocator, mRequireUnpremultiplied,
|
|
mConserveMemory, mDecodeAsAlphaMask);
|
|
}
|
|
|
|
private void callHeaderDecoded(@Nullable OnHeaderDecodedListener listener,
|
|
@NonNull Source src) {
|
|
if (listener != null) {
|
|
ImageInfo info = new ImageInfo(this);
|
|
try {
|
|
listener.onHeaderDecoded(this, info, src);
|
|
} finally {
|
|
info.mDecoder = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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}. 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,
|
|
@NonNull OnHeaderDecodedListener listener) throws IOException {
|
|
if (listener == null) {
|
|
throw new IllegalArgumentException("listener cannot be null! "
|
|
+ "Use decodeDrawable(Source) to not have a listener");
|
|
}
|
|
return decodeDrawableImpl(src, listener);
|
|
}
|
|
|
|
@NonNull
|
|
private static Drawable decodeDrawableImpl(@NonNull Source src,
|
|
@Nullable OnHeaderDecodedListener listener) throws IOException {
|
|
try (ImageDecoder decoder = src.createImageDecoder()) {
|
|
decoder.mSource = src;
|
|
decoder.callHeaderDecoded(listener, src);
|
|
|
|
if (decoder.mRequireUnpremultiplied) {
|
|
// Though this could be supported (ignored) for opaque images,
|
|
// it seems better to always report this error.
|
|
throw new IllegalStateException("Cannot decode a Drawable " +
|
|
"with unpremultiplied pixels!");
|
|
}
|
|
|
|
if (decoder.mMutable) {
|
|
throw new IllegalStateException("Cannot decode a mutable " +
|
|
"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
|
|
// mPostProcessor exists.
|
|
ImageDecoder postProcessPtr = decoder.mPostProcessor == null ?
|
|
null : decoder;
|
|
Drawable d = new AnimatedImageDrawable(decoder.mNativePtr,
|
|
postProcessPtr, decoder.mDesiredWidth,
|
|
decoder.mDesiredHeight, srcDensity,
|
|
src.computeDstDensity(), decoder.mCropRect,
|
|
decoder.mInputStream, decoder.mAssetFd);
|
|
// d has taken ownership of these objects.
|
|
decoder.mInputStream = null;
|
|
decoder.mAssetFd = null;
|
|
return d;
|
|
}
|
|
|
|
Bitmap bm = decoder.decodeBitmapInternal();
|
|
bm.setDensity(srcDensity);
|
|
|
|
Resources res = src.getResources();
|
|
byte[] np = bm.getNinePatchChunk();
|
|
if (np != null && NinePatch.isNinePatchChunk(np)) {
|
|
Rect opticalInsets = new Rect();
|
|
bm.getOpticalInsets(opticalInsets);
|
|
Rect padding = decoder.mOutPaddingRect;
|
|
if (padding == null) {
|
|
padding = new Rect();
|
|
}
|
|
nGetPadding(decoder.mNativePtr, padding);
|
|
return new NinePatchDrawable(res, bm, np, padding,
|
|
opticalInsets, null);
|
|
}
|
|
|
|
return new BitmapDrawable(res, bm);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* See {@link #decodeDrawable(Source, OnHeaderDecodedListener)}.
|
|
*/
|
|
@NonNull
|
|
public static Drawable decodeDrawable(@NonNull Source src)
|
|
throws IOException {
|
|
return decodeDrawableImpl(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}. 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,
|
|
@NonNull OnHeaderDecodedListener listener) throws IOException {
|
|
if (listener == null) {
|
|
throw new IllegalArgumentException("listener cannot be null! "
|
|
+ "Use decodeBitmap(Source) to not have a listener");
|
|
}
|
|
return decodeBitmapImpl(src, listener);
|
|
}
|
|
|
|
@NonNull
|
|
private static Bitmap decodeBitmapImpl(@NonNull Source src,
|
|
@Nullable OnHeaderDecodedListener listener) throws IOException {
|
|
try (ImageDecoder decoder = src.createImageDecoder()) {
|
|
decoder.mSource = src;
|
|
decoder.callHeaderDecoded(listener, src);
|
|
|
|
// 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.decodeBitmapInternal();
|
|
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;
|
|
}
|
|
}
|
|
|
|
// 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.setResize(scaledWidth, scaledHeight);
|
|
return dstDensity;
|
|
}
|
|
|
|
return srcDensity;
|
|
}
|
|
|
|
@NonNull
|
|
private String getMimeType() {
|
|
return nGetMimeType(mNativePtr);
|
|
}
|
|
|
|
/**
|
|
* See {@link #decodeBitmap(Source, OnHeaderDecodedListener)}.
|
|
*/
|
|
@NonNull
|
|
public static Bitmap decodeBitmap(@NonNull Source src) throws IOException {
|
|
return decodeBitmapImpl(src, null);
|
|
}
|
|
|
|
/**
|
|
* Private method called by JNI.
|
|
*/
|
|
@SuppressWarnings("unused")
|
|
private int postProcessAndRelease(@NonNull Canvas canvas) {
|
|
try {
|
|
return mPostProcessor.onPostProcess(canvas);
|
|
} finally {
|
|
canvas.release();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Private method called by JNI.
|
|
*/
|
|
@SuppressWarnings("unused")
|
|
private boolean onPartialImage(@Error int error) {
|
|
return mOnPartialImageListener.onPartialImage(error, mSource);
|
|
}
|
|
|
|
private static native ImageDecoder nCreate(long asset) throws IOException;
|
|
private static native ImageDecoder nCreate(ByteBuffer buffer,
|
|
int position,
|
|
int limit) throws IOException;
|
|
private static native ImageDecoder nCreate(byte[] data, int offset,
|
|
int length) throws IOException;
|
|
private static native ImageDecoder nCreate(InputStream is, byte[] storage);
|
|
// The fd must be seekable.
|
|
private static native ImageDecoder nCreate(FileDescriptor fd) throws IOException;
|
|
@NonNull
|
|
private static native Bitmap nDecodeBitmap(long nativePtr,
|
|
@Nullable ImageDecoder partialImageListener,
|
|
@Nullable ImageDecoder postProcessor,
|
|
int width, int height,
|
|
@Nullable Rect cropRect, boolean mutable,
|
|
int allocator, boolean requireUnpremul,
|
|
boolean conserveMemory, boolean decodeAsAlphaMask)
|
|
throws IOException;
|
|
private static native Size nGetSampledSize(long nativePtr,
|
|
int sampleSize);
|
|
private static native void nGetPadding(long nativePtr, @NonNull Rect outRect);
|
|
private static native void nClose(long nativePtr);
|
|
private static native String nGetMimeType(long nativePtr);
|
|
}
|