/*
* 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.platform;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Build;
import com.facebook.common.internal.Preconditions;
import com.facebook.common.internal.Throwables;
import com.facebook.common.memory.PooledByteBuffer;
import com.facebook.common.references.CloseableReference;
import com.facebook.imagepipeline.common.TooManyBitmapsException;
import com.facebook.imagepipeline.image.EncodedImage;
import com.facebook.imagepipeline.memory.BitmapCounter;
import com.facebook.imagepipeline.memory.BitmapCounterProvider;
import com.facebook.imagepipeline.nativecode.Bitmaps;
import com.facebook.imageutils.JfifUtil;
/**
* Base class for bitmap decodes for Dalvik VM (Gingerbread to KitKat).
*/
abstract class DalvikPurgeableDecoder implements PlatformDecoder {
protected static final byte[] EOI = new byte[] {
(byte) JfifUtil.MARKER_FIRST_BYTE, (byte) JfifUtil.MARKER_EOI };
private final BitmapCounter mUnpooledBitmapsCounter;
DalvikPurgeableDecoder() {
mUnpooledBitmapsCounter = BitmapCounterProvider.get();
}
/**
* Creates a bitmap from encoded bytes.
*
* @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
* @return the bitmap
* @throws TooManyBitmapsException if the pool is full
* @throws java.lang.OutOfMemoryError if the Bitmap cannot be allocated
*/
@Override
public CloseableReference<Bitmap> decodeFromEncodedImage(
final EncodedImage encodedImage,
Bitmap.Config bitmapConfig) {
BitmapFactory.Options options = getBitmapFactoryOptions(
encodedImage.getSampleSize(),
bitmapConfig);
CloseableReference<PooledByteBuffer> bytesRef = encodedImage.getByteBufferRef();
Preconditions.checkNotNull(bytesRef);
try {
Bitmap bitmap = decodeByteArrayAsPurgeable(bytesRef, options);
return pinBitmap(bitmap);
} finally {
CloseableReference.closeSafely(bytesRef);
}
}
/**
* Creates a bitmap from encoded JPEG bytes. Supports a partial JPEG image.
*
* @param encodedImage the encoded image with reference to the encoded bytes
* @param length the number of encoded bytes in the buffer
* @param bitmapConfig the {@link android.graphics.Bitmap.Config}
* used to create the decoded Bitmap
* @return the bitmap
* @throws TooManyBitmapsException if the pool is full
* @throws java.lang.OutOfMemoryError if the Bitmap cannot be allocated
*/
@Override
public CloseableReference<Bitmap> decodeJPEGFromEncodedImage(
final EncodedImage encodedImage,
Bitmap.Config bitmapConfig,
int length) {
BitmapFactory.Options options = getBitmapFactoryOptions(
encodedImage.getSampleSize(),
bitmapConfig);
final CloseableReference<PooledByteBuffer> bytesRef = encodedImage.getByteBufferRef();
Preconditions.checkNotNull(bytesRef);
try {
Bitmap bitmap = decodeJPEGByteArrayAsPurgeable(bytesRef, length, options);
return pinBitmap(bitmap);
} finally {
CloseableReference.closeSafely(bytesRef);
}
}
/**
* Decodes a byteArray into a purgeable bitmap
*
* @param bytesRef the byte buffer that contains the encoded bytes
* @param options the options passed to the BitmapFactory
* @return
*/
abstract Bitmap decodeByteArrayAsPurgeable(
CloseableReference<PooledByteBuffer> bytesRef,
BitmapFactory.Options options);
/**
* Decodes a byteArray containing jpeg encoded bytes into a purgeable bitmap
*
* <p> Adds a JFIF End-Of-Image marker if needed before decoding.
*
* @param bytesRef the byte buffer that contains the encoded bytes
* @param length the number of encoded bytes in the buffer
* @param options the options passed to the BitmapFactory
* @return
*/
abstract Bitmap decodeJPEGByteArrayAsPurgeable(
CloseableReference<PooledByteBuffer> bytesRef,
int length,
BitmapFactory.Options options);
private static BitmapFactory.Options getBitmapFactoryOptions(
int sampleSize,
Bitmap.Config bitmapConfig) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inDither = true; // known to improve picture quality at low cost
options.inPreferredConfig = bitmapConfig;
// Decode the image into a 'purgeable' bitmap that lives on the ashmem heap
options.inPurgeable = true;
// Enable copy of of bitmap to enable purgeable decoding by filedescriptor
options.inInputShareable = true;
// Sample size should ONLY be different than 1 when downsampling is enabled in the pipeline
options.inSampleSize = sampleSize;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
options.inMutable = true; // no known perf difference; allows postprocessing to work
}
return options;
}
protected static boolean endsWithEOI(CloseableReference<PooledByteBuffer> bytesRef, int length) {
PooledByteBuffer buffer = bytesRef.get();
return length >= 2 &&
buffer.read(length - 2) == (byte) JfifUtil.MARKER_FIRST_BYTE &&
buffer.read(length - 1) == (byte) JfifUtil.MARKER_EOI;
}
/**
* Pins the bitmap
*/
public CloseableReference<Bitmap> pinBitmap(Bitmap bitmap) {
try {
// Real decoding happens here - if the image was corrupted, this will throw an exception
Bitmaps.pinBitmap(bitmap);
} catch (Exception e) {
bitmap.recycle();
throw Throwables.propagate(e);
}
if (!mUnpooledBitmapsCounter.increase(bitmap)) {
bitmap.recycle();
throw new TooManyBitmapsException();
}
return CloseableReference.of(bitmap, mUnpooledBitmapsCounter.getReleaser());
}
}