| /* |
| * 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_SET; |
| |
| 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; |
| import android.content.res.Resources; |
| import android.graphics.drawable.AnimatedImageDrawable; |
| import android.graphics.drawable.Drawable; |
| import android.graphics.drawable.BitmapDrawable; |
| import android.graphics.drawable.NinePatchDrawable; |
| import android.net.Uri; |
| import android.system.ErrnoException; |
| import android.system.Os; |
| |
| import libcore.io.IoUtils; |
| import dalvik.system.CloseGuard; |
| |
| import java.nio.ByteBuffer; |
| import java.io.FileDescriptor; |
| import java.io.FileInputStream; |
| import java.io.FileNotFoundException; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.lang.ArrayIndexOutOfBoundsException; |
| import java.lang.AutoCloseable; |
| import java.lang.NullPointerException; |
| import java.lang.RuntimeException; |
| import java.lang.annotation.Retention; |
| import static java.lang.annotation.RetentionPolicy.SOURCE; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| |
| /** |
| * Class for decoding images as {@link Bitmap}s or {@link Drawable}s. |
| * @hide |
| */ |
| public final class ImageDecoder implements AutoCloseable { |
| /** |
| * Source of the encoded image data. |
| */ |
| public static abstract class Source { |
| /* @hide */ |
| Resources getResources() { return null; } |
| |
| /* @hide */ |
| abstract ImageDecoder createImageDecoder() throws IOException; |
| }; |
| |
| private static class ByteArraySource extends Source { |
| ByteArraySource(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(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(ContentResolver resolver, Uri uri) { |
| mResolver = resolver; |
| mUri = uri; |
| } |
| |
| private final ContentResolver mResolver; |
| private final Uri mUri; |
| |
| @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); |
| } |
| |
| 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)); |
| } |
| } finally { |
| if (decoder == null) { |
| IoUtils.closeQuietly(assetFd); |
| } else { |
| decoder.mAssetFd = assetFd; |
| } |
| } |
| return decoder; |
| } |
| } |
| |
| private static ImageDecoder createFromStream(InputStream is) throws IOException { |
| // Arbitrary size matches BitmapFactory. |
| byte[] storage = new byte[16 * 1024]; |
| ImageDecoder decoder = null; |
| try { |
| decoder = nCreate(is, storage); |
| } finally { |
| if (decoder == null) { |
| IoUtils.closeQuietly(is); |
| } else { |
| decoder.mInputStream = is; |
| decoder.mTempStorage = storage; |
| } |
| } |
| |
| return decoder; |
| } |
| |
| private static class ResourceSource extends Source { |
| ResourceSource(Resources res, int resId) { |
| mResources = res; |
| mResId = resId; |
| } |
| |
| final Resources mResources; |
| final int mResId; |
| |
| @Override |
| public Resources getResources() { return mResources; } |
| |
| @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; |
| try { |
| is = mResources.openRawResource(mResId); |
| if (!(is instanceof AssetManager.AssetInputStream)) { |
| // This should never happen. |
| throw new RuntimeException("Resource is not an asset?"); |
| } |
| long asset = ((AssetManager.AssetInputStream) is).getNativeAsset(); |
| decoder = nCreate(asset); |
| } finally { |
| if (decoder == null) { |
| IoUtils.closeQuietly(is); |
| } else { |
| decoder.mInputStream = is; |
| } |
| } |
| return decoder; |
| } |
| } |
| |
| /** |
| * Contains information about the encoded image. |
| */ |
| public static class ImageInfo { |
| /** |
| * Width of the image, without scaling or cropping. |
| */ |
| public final int width; |
| |
| /** |
| * Height of the image, without scaling or cropping. |
| */ |
| public final int height; |
| |
| /* @hide */ |
| ImageDecoder decoder; |
| |
| /* @hide */ |
| ImageInfo(ImageDecoder decoder) { |
| this.width = decoder.mWidth; |
| this.height = decoder.mHeight; |
| this.decoder = decoder; |
| } |
| |
| /** |
| * The mimeType of the image, if known. |
| */ |
| public String getMimeType() { |
| return decoder.getMimeType(); |
| } |
| }; |
| |
| /** |
| * Used if the provided data is incomplete. |
| * |
| * May be thrown if there is nothing to display. |
| * |
| * If supplied to onPartialImage, there may be a correct partial image to |
| * display. |
| */ |
| public static class IncompleteException extends IOException {}; |
| |
| /** |
| * Used if the provided data is corrupt. |
| * |
| * May be thrown if there is nothing to display. |
| * |
| * If supplied to onPartialImage, there may be a correct partial image to |
| * display. |
| */ |
| public static class CorruptException 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 info Information about the encoded image. |
| * @param decoder allows changing the default settings of the decode. |
| */ |
| public void onHeaderDecoded(ImageInfo info, ImageDecoder decoder); |
| |
| }; |
| |
| /** |
| * Optional listener supplied to the ImageDecoder. |
| */ |
| public static interface OnPartialImageListener { |
| /** |
| * Called when there is only a partial image to display. |
| * |
| * If the input is incomplete or contains an error, this listener lets |
| * the client know that and allows them to optionally bypass the rest |
| * of the decode/creation process. |
| * |
| * @param e IOException containing information about the error that |
| * interrupted the decode. |
| * @return True (which is the default) to create and return a |
| * {@link Drawable}/{@link Bitmap} with partial data. False to |
| * abort the decode and throw the {@link java.io.IOException}. |
| */ |
| public boolean onPartialImage(IOException e); |
| }; |
| |
| // 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 = DEFAULT_ALLOCATOR; |
| private boolean mRequireUnpremultiplied = false; |
| private boolean mMutable = false; |
| private boolean mPreferRamOverQuality = false; |
| private boolean mAsAlphaMask = false; |
| private Rect mCropRect; |
| |
| private PostProcess mPostProcess; |
| private OnPartialImageListener mOnPartialImageListener; |
| |
| // Objects for interacting with the input. |
| private InputStream mInputStream; |
| 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(); |
| } |
| |
| close(); |
| } finally { |
| super.finalize(); |
| } |
| } |
| |
| /** |
| * Create a new {@link Source} from an asset. |
| * |
| * @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); |
| } |
| |
| /** |
| * 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. |
| */ |
| 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). |
| */ |
| public static Source createSource(@NonNull byte[] data) { |
| return createSource(data, 0, data.length); |
| } |
| |
| /** |
| * Create a new {@link Source} from a {@link java.nio.ByteBuffer}. |
| * |
| * The returned {@link Source} effectively takes ownership of the |
| * {@link java.nio.ByteBuffer}; i.e. no other code should modify it after |
| * this call. |
| * |
| * Decoding will start from {@link java.nio.ByteBuffer#position()}. |
| */ |
| public static Source createSource(ByteBuffer buffer) { |
| return new ByteBufferSource(buffer); |
| } |
| |
| /** |
| * Return the width and height of a given sample size. |
| * |
| * 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 #requireUnpremultiplied}. |
| * |
| * @param sampleSize Sampling rate of the encoded image. |
| * @return Point {@link Point#x} and {@link Point#y} correspond to the |
| * width and height after sampling. |
| */ |
| public Point 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. |
| */ |
| public void resize(int width, int height) { |
| if (width <= 0 || height <= 0) { |
| throw new IllegalArgumentException("Dimensions must be positive! " |
| + "provided (" + width + ", " + height + ")"); |
| } |
| |
| mDesiredWidth = width; |
| mDesiredHeight = height; |
| } |
| |
| /** |
| * Resize based on a sample size. |
| * |
| * This has the same effect as passing the result of |
| * {@link #getSampledSize} to {@link #resize(int, int)}. |
| * |
| * @param sampleSize Sampling rate of the encoded image. |
| */ |
| public void resize(int sampleSize) { |
| Point dimensions = this.getSampledSize(sampleSize); |
| this.resize(dimensions.x, dimensions.y); |
| } |
| |
| // 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 #setAsAlphaMask}. |
| */ |
| public static final int DEFAULT_ALLOCATOR = 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 SOFTWARE_ALLOCATOR = 1; |
| |
| /** |
| * Use shared memory for the pixel memory. |
| * |
| * Useful for sharing across processes. |
| */ |
| public static final int SHARED_MEMORY_ALLOCATOR = 2; |
| |
| /** |
| * Require a {@link Bitmap.Config#HARDWARE} {@link Bitmap}. |
| * |
| * This will throw an {@link java.lang.IllegalStateException} when combined |
| * with incompatible options, like {@link #setMutable} or |
| * {@link #setAsAlphaMask}. |
| */ |
| public static final int HARDWARE_ALLOCATOR = 3; |
| |
| /** @hide **/ |
| @Retention(SOURCE) |
| @IntDef({ DEFAULT_ALLOCATOR, SOFTWARE_ALLOCATOR, SHARED_MEMORY_ALLOCATOR, |
| HARDWARE_ALLOCATOR }) |
| public @interface Allocator {}; |
| |
| /** |
| * Choose the backing for the pixel memory. |
| * |
| * This is ignored for animated drawables. |
| * |
| * TODO: Allow accessing the backing from the Bitmap. |
| * |
| * @param allocator Type of allocator to use. |
| */ |
| public void setAllocator(@Allocator int allocator) { |
| if (allocator < DEFAULT_ALLOCATOR || allocator > HARDWARE_ALLOCATOR) { |
| throw new IllegalArgumentException("invalid allocator " + allocator); |
| } |
| mAllocator = allocator; |
| } |
| |
| /** |
| * Create a {@link Bitmap} with unpremultiplied pixels. |
| * |
| * 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 will result in {@link #decodeBitmap} returning a |
| * {@link Bitmap} with unpremultiplied pixels. See |
| * {@link Bitmap#isPremultiplied}. Incompatible with |
| * {@link #decodeDrawable}; attempting to decode an unpremultiplied |
| * {@link Drawable} will throw an {@link java.lang.IllegalStateException}. |
| */ |
| public void requireUnpremultiplied() { |
| mRequireUnpremultiplied = true; |
| } |
| |
| /** |
| * Modify the image after decoding and scaling. |
| * |
| * 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. |
| * |
| * If set on a nine-patch image, the nine-patch data is ignored. |
| * |
| * For an animated image, the drawing commands drawn on the {@link Canvas} |
| * will be recorded immediately and then applied to each frame. |
| */ |
| public void setPostProcess(PostProcess p) { |
| mPostProcess = p; |
| } |
| |
| /** |
| * Set (replace) the {@link OnPartialImageListener} on this object. |
| * |
| * Will be called if there is an error in the input. Without one, a |
| * partial {@link Bitmap} will be created. |
| */ |
| public void setOnPartialImageListener(OnPartialImageListener l) { |
| mOnPartialImageListener = l; |
| } |
| |
| /** |
| * Crop the output to {@code subset} of the (possibly) scaled image. |
| * |
| * {@code subset} must be contained within the size set by {@link #resize} |
| * or the bounds of the image if resize was not called. Otherwise an |
| * {@link IllegalStateException} will be thrown. |
| * |
| * NOT intended as a replacement for |
| * {@link BitmapRegionDecoder#decodeRegion}. This supports all formats, |
| * but merely crops the output. |
| */ |
| public void crop(Rect subset) { |
| mCropRect = subset; |
| } |
| |
| /** |
| * Create a mutable {@link Bitmap}. |
| * |
| * By default, a {@link Bitmap} created will be immutable, but that can be |
| * changed with this call. |
| * |
| * Incompatible with {@link #HARDWARE_ALLOCATOR}, because |
| * {@link Bitmap.Config#HARDWARE} Bitmaps cannot be mutable. Attempting to |
| * combine them will throw an {@link java.lang.IllegalStateException}. |
| * |
| * 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} |
| */ |
| public void setMutable() { |
| mMutable = true; |
| } |
| |
| /** |
| * Potentially save RAM at the expense of quality. |
| * |
| * This may result in a {@link Bitmap} with a denser {@link Bitmap.Config}, |
| * depending on the image. For example, for an opaque {@link Bitmap}, this |
| * may result in a {@link Bitmap.Config} with no alpha information. |
| */ |
| public void setPreferRamOverQuality() { |
| mPreferRamOverQuality = true; |
| } |
| |
| /** |
| * Potentially treat the output as an alpha mask. |
| * |
| * If the image is encoded in a format with only one channel, treat that |
| * channel as alpha. Otherwise this call has no effect. |
| * |
| * Incompatible with {@link #HARDWARE_ALLOCATOR}. Trying to combine them |
| * will throw an {@link java.lang.IllegalStateException}. |
| */ |
| public void setAsAlphaMask() { |
| mAsAlphaMask = true; |
| } |
| |
| @Override |
| public void close() { |
| mCloseGuard.close(); |
| if (!mClosed.compareAndSet(false, true)) { |
| return; |
| } |
| nClose(mNativePtr); |
| mNativePtr = 0; |
| |
| 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 == HARDWARE_ALLOCATOR) { |
| if (mMutable) { |
| throw new IllegalStateException("Cannot make mutable HARDWARE Bitmap!"); |
| } |
| if (mAsAlphaMask) { |
| throw new IllegalStateException("Cannot make HARDWARE Alpha mask Bitmap!"); |
| } |
| } |
| |
| if (mPostProcess != 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 + ")"); |
| } |
| } |
| |
| private Bitmap decodeBitmap() throws IOException { |
| checkState(); |
| // nDecodeBitmap calls postProcessAndRelease only if mPostProcess |
| // exists. |
| ImageDecoder postProcessPtr = mPostProcess == null ? null : this; |
| return nDecodeBitmap(mNativePtr, mOnPartialImageListener, |
| postProcessPtr, mDesiredWidth, mDesiredHeight, mCropRect, |
| mMutable, mAllocator, mRequireUnpremultiplied, |
| mPreferRamOverQuality, mAsAlphaMask); |
| |
| } |
| |
| /** |
| * 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}. If not {@code null}, |
| * 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, |
| @Nullable OnHeaderDecodedListener listener) throws IOException { |
| try (ImageDecoder decoder = src.createImageDecoder()) { |
| if (listener != null) { |
| ImageInfo info = new ImageInfo(decoder); |
| try { |
| listener.onHeaderDecoded(info, decoder); |
| } finally { |
| info.decoder = null; |
| } |
| } |
| |
| 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!"); |
| } |
| |
| if (decoder.mAnimated) { |
| // AnimatedImageDrawable calls postProcessAndRelease only if |
| // mPostProcess exists. |
| ImageDecoder postProcessPtr = decoder.mPostProcess == null ? |
| null : decoder; |
| Drawable d = new AnimatedImageDrawable(decoder.mNativePtr, |
| postProcessPtr, decoder.mDesiredWidth, |
| decoder.mDesiredHeight, decoder.mCropRect, |
| decoder.mInputStream, decoder.mAssetFd); |
| // d has taken ownership of these objects. |
| decoder.mInputStream = null; |
| decoder.mAssetFd = null; |
| return d; |
| } |
| |
| Bitmap bm = decoder.decodeBitmap(); |
| Resources res = src.getResources(); |
| if (res == null) { |
| bm.setDensity(Bitmap.DENSITY_NONE); |
| } |
| |
| byte[] np = bm.getNinePatchChunk(); |
| if (np != null && NinePatch.isNinePatchChunk(np)) { |
| Rect opticalInsets = new Rect(); |
| bm.getOpticalInsets(opticalInsets); |
| Rect 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 decodeDrawable(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}. If not {@code null}, |
| * 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, |
| @Nullable OnHeaderDecodedListener listener) throws IOException { |
| try (ImageDecoder decoder = src.createImageDecoder()) { |
| if (listener != null) { |
| ImageInfo info = new ImageInfo(decoder); |
| try { |
| listener.onHeaderDecoded(info, decoder); |
| } finally { |
| info.decoder = null; |
| } |
| } |
| |
| return decoder.decodeBitmap(); |
| } |
| } |
| |
| private String getMimeType() { |
| return nGetMimeType(mNativePtr); |
| } |
| |
| /** |
| * See {@link #decodeBitmap(Source, OnHeaderDecodedListener)}. |
| */ |
| @NonNull |
| public static Bitmap decodeBitmap(@NonNull Source src) throws IOException { |
| return decodeBitmap(src, null); |
| } |
| |
| /** |
| * Private method called by JNI. |
| */ |
| @SuppressWarnings("unused") |
| private int postProcessAndRelease(@NonNull Canvas canvas, int width, int height) { |
| try { |
| return mPostProcess.postProcess(canvas, width, height); |
| } finally { |
| canvas.release(); |
| } |
| } |
| |
| 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); |
| private static native ImageDecoder nCreate(FileDescriptor fd) throws IOException; |
| @NonNull |
| private static native Bitmap nDecodeBitmap(long nativePtr, |
| OnPartialImageListener listener, |
| @Nullable ImageDecoder decoder, // Only used if mPostProcess != null |
| int width, int height, |
| Rect cropRect, boolean mutable, |
| int allocator, boolean requireUnpremul, |
| boolean preferRamOverQuality, boolean asAlphaMask) |
| throws IOException; |
| private static native Point nGetSampledSize(long nativePtr, |
| int sampleSize); |
| private static native void nGetPadding(long nativePtr, Rect outRect); |
| private static native void nClose(long nativePtr); |
| private static native String nGetMimeType(long nativePtr); |
| } |