/* * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ package com.facebook.imagepipeline.bitmaps; import javax.annotation.concurrent.GuardedBy; import java.io.IOException; import java.io.InputStream; import android.annotation.TargetApi; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Build; import com.facebook.common.references.CloseableReference; import com.facebook.common.streams.LimitedInputStream; import com.facebook.common.streams.TailAppendingInputStream; import com.facebook.imagepipeline.memory.BitmapPool; import com.facebook.imagepipeline.memory.PooledByteBuffer; import com.facebook.imagepipeline.nativecode.Bitmaps; import com.facebook.imageutils.JfifUtil; /** * Bitmap factory for ART VM (Lollipop and up). */ @TargetApi(Build.VERSION_CODES.LOLLIPOP) public class ArtBitmapFactory { /** * 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 */ @GuardedBy("this") private final byte[] mDecodeBuffer = new byte[DECODE_BUFFER_SIZE]; // TODO (5884402) - remove dependency on JfifUtil private static final byte[] EOI_TAIL = new byte[] { (byte) JfifUtil.MARKER_FIRST_BYTE, (byte) JfifUtil.MARKER_EOI}; public ArtBitmapFactory(BitmapPool bitmapPool) { mBitmapPool = bitmapPool; } /** * Creates a bitmap of the specified width and height. * * @param width the width of the bitmap * @param height the height of the bitmap * @return a reference to the bitmap * @throws java.lang.OutOfMemoryError if the Bitmap cannot be allocated */ CloseableReference<Bitmap> createBitmap(int width, int height) { Bitmap bitmap = mBitmapPool.get(width * height); Bitmaps.reconfigureBitmap(bitmap, width, height); return CloseableReference.of(bitmap, mBitmapPool); } /** * Creates a bitmap from encoded bytes. * * @param pooledByteBufferRef the reference to the encoded bytes * @return the bitmap * @throws java.lang.OutOfMemoryError if the Bitmap cannot be allocated */ CloseableReference<Bitmap> decodeFromPooledByteBuffer( CloseableReference<PooledByteBuffer> pooledByteBufferRef) { return doDecodeStaticImage(pooledByteBufferRef.get().getStream()); } /** * Creates a bitmap from encoded JPEG bytes. Supports a partial JPEG image. * * @param pooledByteBufferRef the reference to the encoded bytes * @param length the number of encoded bytes in the buffer * @return the bitmap * @throws java.lang.OutOfMemoryError if the Bitmap cannot be allocated */ CloseableReference<Bitmap> decodeJPEGFromPooledByteBuffer( CloseableReference<PooledByteBuffer> pooledByteBufferRef, int length) { final PooledByteBuffer pooledByteBuffer = pooledByteBufferRef.get(); final InputStream jpegBufferInputStream = pooledByteBuffer.getStream(); jpegBufferInputStream.mark(Integer.MAX_VALUE); boolean isJpegComplete; try { jpegBufferInputStream.skip(length - 2); isJpegComplete = (jpegBufferInputStream.read() == JfifUtil.MARKER_FIRST_BYTE) && (jpegBufferInputStream.read() == JfifUtil.MARKER_EOI); jpegBufferInputStream.reset(); } catch (IOException ioe) { throw new RuntimeException(ioe); } InputStream jpegDataStream = jpegBufferInputStream; if (pooledByteBuffer.size() > length) { jpegDataStream = new LimitedInputStream(jpegDataStream, length); } if (!isJpegComplete) { jpegDataStream = new TailAppendingInputStream(jpegDataStream, EOI_TAIL); } return doDecodeStaticImage(jpegDataStream); } private CloseableReference<Bitmap> doDecodeStaticImage(InputStream inputStream) { inputStream.mark(Integer.MAX_VALUE); final BitmapFactory.Options options = getDecodeOptionsForStream(inputStream); try { inputStream.reset(); } catch (IOException ioe) { throw new RuntimeException(ioe); } final Bitmap bitmapToReuse = mBitmapPool.get(options.outHeight * options.outWidth); if (bitmapToReuse == null) { throw new NullPointerException("BitmapPool.get returned null"); } options.inBitmap = bitmapToReuse; Bitmap decodedBitmap; try { decodedBitmap = BitmapFactory.decodeStream(inputStream, null, options); } catch (RuntimeException re) { mBitmapPool.release(bitmapToReuse); throw re; } 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 BitmapFactory.Options getDecodeOptionsForStream(InputStream inputStream) { final BitmapFactory.Options options = new BitmapFactory.Options(); options.inTempStorage = mDecodeBuffer; options.inJustDecodeBounds = true; // fill outWidth and outHeight BitmapFactory.decodeStream(inputStream, null, options); if (options.outWidth == -1 || options.outHeight == -1) { throw new IllegalArgumentException(); } options.inJustDecodeBounds = false; options.inDither = true; options.inPreferredConfig = Bitmaps.BITMAP_CONFIG; options.inMutable = true; return options; } }