blob: 18b198a2a44197f743744387d3f10875bf511fa4 [file] [log] [blame]
package com.bumptech.glide.load.resource.gifbitmap;
import android.graphics.Bitmap;
import com.bumptech.glide.load.ResourceDecoder;
import com.bumptech.glide.load.engine.Resource;
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
import com.bumptech.glide.load.model.ImageVideoWrapper;
import com.bumptech.glide.load.resource.bitmap.BitmapResource;
import com.bumptech.glide.load.resource.bitmap.ImageHeaderParser;
import com.bumptech.glide.load.resource.bitmap.RecyclableBufferedInputStream;
import com.bumptech.glide.load.resource.gif.GifDrawable;
import com.bumptech.glide.util.ByteArrayPool;
import java.io.IOException;
import java.io.InputStream;
/**
* An {@link ResourceDecoder} that can decode either an {@link Bitmap} or an {@link GifDrawable}
* from an {@link InputStream} or a {@link android.os.ParcelFileDescriptor ParcelFileDescriptor}.
*/
public class GifBitmapWrapperResourceDecoder implements ResourceDecoder<ImageVideoWrapper, GifBitmapWrapper> {
private static final ImageTypeParser DEFAULT_PARSER = new ImageTypeParser();
private static final BufferedStreamFactory DEFAULT_STREAM_FACTORY = new BufferedStreamFactory();
// 2048 is rather arbitrary, for most well formatted image types we only need 32 bytes.
// Visible for testing.
static final int MARK_LIMIT_BYTES = 2048;
private final ResourceDecoder<ImageVideoWrapper, Bitmap> bitmapDecoder;
private final ResourceDecoder<InputStream, GifDrawable> gifDecoder;
private final BitmapPool bitmapPool;
private final ImageTypeParser parser;
private final BufferedStreamFactory streamFactory;
private String id;
public GifBitmapWrapperResourceDecoder(ResourceDecoder<ImageVideoWrapper, Bitmap> bitmapDecoder,
ResourceDecoder<InputStream, GifDrawable> gifDecoder, BitmapPool bitmapPool) {
this(bitmapDecoder, gifDecoder, bitmapPool, DEFAULT_PARSER, DEFAULT_STREAM_FACTORY);
}
// Visible for testing.
GifBitmapWrapperResourceDecoder(ResourceDecoder<ImageVideoWrapper, Bitmap> bitmapDecoder,
ResourceDecoder<InputStream, GifDrawable> gifDecoder, BitmapPool bitmapPool, ImageTypeParser parser,
BufferedStreamFactory streamFactory) {
this.bitmapDecoder = bitmapDecoder;
this.gifDecoder = gifDecoder;
this.bitmapPool = bitmapPool;
this.parser = parser;
this.streamFactory = streamFactory;
}
@SuppressWarnings("resource")
// @see ResourceDecoder.decode
@Override
public Resource<GifBitmapWrapper> decode(ImageVideoWrapper source, int width, int height) throws IOException {
ByteArrayPool pool = ByteArrayPool.get();
byte[] tempBytes = pool.getBytes();
GifBitmapWrapper wrapper = null;
try {
wrapper = decode(source, width, height, tempBytes);
} finally {
pool.releaseBytes(tempBytes);
}
return wrapper != null ? new GifBitmapWrapperResource(wrapper) : null;
}
private GifBitmapWrapper decode(ImageVideoWrapper source, int width, int height, byte[] bytes) throws IOException {
final GifBitmapWrapper result;
if (source.getStream() != null) {
result = decodeStream(source, width, height, bytes);
} else {
result = decodeBitmapWrapper(source, width, height);
}
return result;
}
private GifBitmapWrapper decodeStream(ImageVideoWrapper source, int width, int height, byte[] bytes)
throws IOException {
InputStream bis = streamFactory.build(source.getStream(), bytes);
bis.mark(MARK_LIMIT_BYTES);
ImageHeaderParser.ImageType type = parser.parse(bis);
bis.reset();
GifBitmapWrapper result = null;
if (type == ImageHeaderParser.ImageType.GIF) {
result = decodeGifWrapper(bis, width, height);
}
// Decoding the gif may fail even if the type matches.
if (result == null) {
// We can only reset the buffered InputStream, so to start from the beginning of the stream, we need to
// pass in a new source containing the buffered stream rather than the original stream.
ImageVideoWrapper forBitmapDecoder = new ImageVideoWrapper(bis, source.getFileDescriptor());
result = decodeBitmapWrapper(forBitmapDecoder, width, height);
}
return result;
}
private GifBitmapWrapper decodeGifWrapper(InputStream bis, int width, int height) throws IOException {
GifBitmapWrapper result = null;
Resource<GifDrawable> gifResource = gifDecoder.decode(bis, width, height);
if (gifResource != null) {
GifDrawable drawable = gifResource.get();
// We can more efficiently hold Bitmaps in memory, so for static GIFs, try to return Bitmaps
// instead. Returning a Bitmap incurs the cost of allocating the GifDrawable as well as the normal
// Bitmap allocation, but since we can encode the Bitmap out as a JPEG, future decodes will be
// efficient.
if (drawable.getFrameCount() > 1) {
result = new GifBitmapWrapper(null /*bitmapResource*/, gifResource);
} else {
Resource<Bitmap> bitmapResource = new BitmapResource(drawable.getFirstFrame(), bitmapPool);
result = new GifBitmapWrapper(bitmapResource, null /*gifResource*/);
}
}
return result;
}
private GifBitmapWrapper decodeBitmapWrapper(ImageVideoWrapper toDecode, int width, int height) throws IOException {
GifBitmapWrapper result = null;
Resource<Bitmap> bitmapResource = bitmapDecoder.decode(toDecode, width, height);
if (bitmapResource != null) {
result = new GifBitmapWrapper(bitmapResource, null);
}
return result;
}
@Override
public String getId() {
if (id == null) {
id = gifDecoder.getId() + bitmapDecoder.getId();
}
return id;
}
// Visible for testing.
static class BufferedStreamFactory {
public InputStream build(InputStream is, byte[] buffer) {
return new RecyclableBufferedInputStream(is, buffer);
}
}
// Visible for testing.
static class ImageTypeParser {
public ImageHeaderParser.ImageType parse(InputStream is) throws IOException {
return new ImageHeaderParser(is).getType();
}
}
}