Java tutorial
/* * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ package com.facebook.imagepipeline.platform; import android.annotation.TargetApi; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.BitmapRegionDecoder; import android.graphics.ColorSpace; import android.graphics.Rect; import android.os.Build; import android.support.v4.util.Pools.SynchronizedPool; import com.facebook.common.internal.Preconditions; import com.facebook.common.internal.VisibleForTesting; import com.facebook.common.logging.FLog; import com.facebook.common.references.CloseableReference; import com.facebook.common.streams.LimitedInputStream; import com.facebook.common.streams.TailAppendingInputStream; import com.facebook.imagepipeline.bitmaps.SimpleBitmapReleaser; import com.facebook.imagepipeline.image.EncodedImage; import com.facebook.imagepipeline.memory.BitmapPool; import com.facebook.imageutils.JfifUtil; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import javax.annotation.Nullable; import javax.annotation.concurrent.ThreadSafe; /** Bitmap decoder for ART VM (Lollipop and up). */ @TargetApi(Build.VERSION_CODES.LOLLIPOP) @ThreadSafe public abstract class DefaultDecoder implements PlatformDecoder { private static final Class<?> TAG = DefaultDecoder.class; /** Size of temporary array. Value recommended by Android docs for decoding Bitmaps. */ private static final int DECODE_BUFFER_SIZE = 16 * 1024; private final BitmapPool mBitmapPool; /** * ArtPlatformImageDecoder decodes images from InputStream - to do so we need to provide temporary * buffer, otherwise framework will allocate one for us for each decode request */ @VisibleForTesting final SynchronizedPool<ByteBuffer> mDecodeBuffers; // TODO (5884402) - remove dependency on JfifUtil private static final byte[] EOI_TAIL = new byte[] { (byte) JfifUtil.MARKER_FIRST_BYTE, (byte) JfifUtil.MARKER_EOI }; public DefaultDecoder(BitmapPool bitmapPool, int maxNumThreads, SynchronizedPool decodeBuffers) { mBitmapPool = bitmapPool; mDecodeBuffers = decodeBuffers; for (int i = 0; i < maxNumThreads; i++) { mDecodeBuffers.release(ByteBuffer.allocate(DECODE_BUFFER_SIZE)); } } @Override public CloseableReference<Bitmap> decodeFromEncodedImage(EncodedImage encodedImage, Bitmap.Config bitmapConfig, @Nullable Rect regionToDecode) { return decodeFromEncodedImageWithColorSpace(encodedImage, bitmapConfig, regionToDecode, false); } @Override public CloseableReference<Bitmap> decodeJPEGFromEncodedImage(EncodedImage encodedImage, Bitmap.Config bitmapConfig, @Nullable Rect regionToDecode, int length) { return decodeJPEGFromEncodedImageWithColorSpace(encodedImage, bitmapConfig, regionToDecode, length, false); } /** * Creates a bitmap from encoded bytes. * * @param encodedImage the encoded image with a reference to the encoded bytes * @param bitmapConfig the {@link android.graphics.Bitmap.Config} used to create the decoded * Bitmap * @param regionToDecode optional image region to decode or null to decode the whole image * @param transformToSRGB whether to allow color space transformation to sRGB at load time * @return the bitmap * @exception java.lang.OutOfMemoryError if the Bitmap cannot be allocated */ @Override public CloseableReference<Bitmap> decodeFromEncodedImageWithColorSpace(EncodedImage encodedImage, Bitmap.Config bitmapConfig, @Nullable Rect regionToDecode, final boolean transformToSRGB) { final BitmapFactory.Options options = getDecodeOptionsForStream(encodedImage, bitmapConfig); boolean retryOnFail = options.inPreferredConfig != Bitmap.Config.ARGB_8888; try { return decodeFromStream(encodedImage.getInputStream(), options, regionToDecode, transformToSRGB); } catch (RuntimeException re) { if (retryOnFail) { return decodeFromEncodedImageWithColorSpace(encodedImage, Bitmap.Config.ARGB_8888, regionToDecode, transformToSRGB); } throw re; } } /** * Creates a bitmap from encoded JPEG bytes. Supports a partial JPEG image. * * @param encodedImage the encoded image with reference to the encoded bytes * @param bitmapConfig the {@link android.graphics.Bitmap.Config} used to create the decoded * Bitmap * @param regionToDecode optional image region to decode or null to decode the whole image * @param length the number of encoded bytes in the buffer * @param transformToSRGB whether to allow color space transformation to sRGB at load time * @return the bitmap * @exception java.lang.OutOfMemoryError if the Bitmap cannot be allocated */ @Override public CloseableReference<Bitmap> decodeJPEGFromEncodedImageWithColorSpace(EncodedImage encodedImage, Bitmap.Config bitmapConfig, @Nullable Rect regionToDecode, int length, final boolean transformToSRGB) { boolean isJpegComplete = encodedImage.isCompleteAt(length); final BitmapFactory.Options options = getDecodeOptionsForStream(encodedImage, bitmapConfig); InputStream jpegDataStream = encodedImage.getInputStream(); // At this point the InputStream from the encoded image should not be null since in the // pipeline,this comes from a call stack where this was checked before. Also this method needs // the InputStream to decode the image so this can't be null. Preconditions.checkNotNull(jpegDataStream); if (encodedImage.getSize() > length) { jpegDataStream = new LimitedInputStream(jpegDataStream, length); } if (!isJpegComplete) { jpegDataStream = new TailAppendingInputStream(jpegDataStream, EOI_TAIL); } boolean retryOnFail = options.inPreferredConfig != Bitmap.Config.ARGB_8888; try { return decodeFromStream(jpegDataStream, options, regionToDecode, transformToSRGB); } catch (RuntimeException re) { if (retryOnFail) { return decodeJPEGFromEncodedImageWithColorSpace(encodedImage, Bitmap.Config.ARGB_8888, regionToDecode, length, transformToSRGB); } throw re; } } /** * This method is needed because of dependency issues. * * @param inputStream the InputStream * @param options the {@link android.graphics.BitmapFactory.Options} used to decode the stream * @param regionToDecode optional image region to decode or null to decode the whole image * @return the bitmap */ protected CloseableReference<Bitmap> decodeStaticImageFromStream(InputStream inputStream, BitmapFactory.Options options, @Nullable Rect regionToDecode) { return decodeFromStream(inputStream, options, regionToDecode, false); } /** * Create a bitmap from an input stream. * * @param inputStream the InputStream * @param options the {@link android.graphics.BitmapFactory.Options} used to decode the stream * @param regionToDecode optional image region to decode or null to decode the whole image * @param transformToSRGB whether to allow color space transformation to sRGB at load time * @return the bitmap */ private CloseableReference<Bitmap> decodeFromStream(InputStream inputStream, BitmapFactory.Options options, @Nullable Rect regionToDecode, final boolean transformToSRGB) { Preconditions.checkNotNull(inputStream); int targetWidth = options.outWidth; int targetHeight = options.outHeight; if (regionToDecode != null) { targetWidth = regionToDecode.width() / options.inSampleSize; targetHeight = regionToDecode.height() / options.inSampleSize; } int sizeInBytes = getBitmapSize(targetWidth, targetHeight, options); final Bitmap bitmapToReuse = mBitmapPool.get(sizeInBytes); if (bitmapToReuse == null) { throw new NullPointerException("BitmapPool.get returned null"); } options.inBitmap = bitmapToReuse; // Performs transformation at load time to sRGB. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && transformToSRGB) { options.inPreferredColorSpace = ColorSpace.get(ColorSpace.Named.SRGB); } Bitmap decodedBitmap = null; ByteBuffer byteBuffer = mDecodeBuffers.acquire(); if (byteBuffer == null) { byteBuffer = ByteBuffer.allocate(DECODE_BUFFER_SIZE); } try { options.inTempStorage = byteBuffer.array(); if (regionToDecode != null) { BitmapRegionDecoder bitmapRegionDecoder = null; try { bitmapToReuse.reconfigure(targetWidth, targetHeight, options.inPreferredConfig); bitmapRegionDecoder = BitmapRegionDecoder.newInstance(inputStream, true); decodedBitmap = bitmapRegionDecoder.decodeRegion(regionToDecode, options); } catch (IOException e) { FLog.e(TAG, "Could not decode region %s, decoding full bitmap instead.", regionToDecode); } finally { if (bitmapRegionDecoder != null) { bitmapRegionDecoder.recycle(); } } } if (decodedBitmap == null) { decodedBitmap = BitmapFactory.decodeStream(inputStream, null, options); } } catch (IllegalArgumentException e) { mBitmapPool.release(bitmapToReuse); // This is thrown if the Bitmap options are invalid, so let's just try to decode the bitmap // as-is, which might be inefficient - but it works. try { // We need to reset the stream first inputStream.reset(); Bitmap naiveDecodedBitmap = BitmapFactory.decodeStream(inputStream); if (naiveDecodedBitmap == null) { throw e; } return CloseableReference.of(naiveDecodedBitmap, SimpleBitmapReleaser.getInstance()); } catch (IOException re) { // We throw the original exception instead since it's the one causing this workaround in the // first place. throw e; } } catch (RuntimeException re) { mBitmapPool.release(bitmapToReuse); throw re; } finally { mDecodeBuffers.release(byteBuffer); } if (bitmapToReuse != decodedBitmap) { mBitmapPool.release(bitmapToReuse); decodedBitmap.recycle(); throw new IllegalStateException(); } return CloseableReference.of(decodedBitmap, mBitmapPool); } /** * Options returned by this method are configured with mDecodeBuffer which is GuardedBy("this") */ private static BitmapFactory.Options getDecodeOptionsForStream(EncodedImage encodedImage, Bitmap.Config bitmapConfig) { final BitmapFactory.Options options = new BitmapFactory.Options(); // Sample size should ONLY be different than 1 when downsampling is enabled in the pipeline options.inSampleSize = encodedImage.getSampleSize(); options.inJustDecodeBounds = true; // fill outWidth and outHeight BitmapFactory.decodeStream(encodedImage.getInputStream(), null, options); if (options.outWidth == -1 || options.outHeight == -1) { throw new IllegalArgumentException(); } options.inJustDecodeBounds = false; options.inDither = true; options.inPreferredConfig = bitmapConfig; options.inMutable = true; return options; } public abstract int getBitmapSize(final int width, final int height, final BitmapFactory.Options options); }