1package com.bumptech.glide.load.resource.gif;
2
3import android.content.Context;
4import android.graphics.Bitmap;
5import android.util.Log;
6
7import com.bumptech.glide.Glide;
8import com.bumptech.glide.gifdecoder.GifDecoder;
9import com.bumptech.glide.gifdecoder.GifHeader;
10import com.bumptech.glide.gifdecoder.GifHeaderParser;
11import com.bumptech.glide.load.ResourceDecoder;
12import com.bumptech.glide.load.Transformation;
13import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
14import com.bumptech.glide.load.resource.UnitTransformation;
15import com.bumptech.glide.util.Util;
16
17import java.io.ByteArrayOutputStream;
18import java.io.IOException;
19import java.io.InputStream;
20import java.util.Queue;
21
22/**
23 * An {@link com.bumptech.glide.load.ResourceDecoder} that decodes
24 * {@link com.bumptech.glide.load.resource.gif.GifDrawable} from {@link java.io.InputStream} data.
25 */
26public class GifResourceDecoder implements ResourceDecoder<InputStream, GifDrawable> {
27    private static final String TAG = "GifResourceDecoder";
28    private static final GifHeaderParserPool PARSER_POOL = new GifHeaderParserPool();
29    private static final GifDecoderPool DECODER_POOL = new GifDecoderPool();
30
31    private final Context context;
32    private final GifHeaderParserPool parserPool;
33    private final BitmapPool bitmapPool;
34    private final GifDecoderPool decoderPool;
35    private final GifBitmapProvider provider;
36
37    public GifResourceDecoder(Context context) {
38        this(context, Glide.get(context).getBitmapPool());
39    }
40
41    public GifResourceDecoder(Context context, BitmapPool bitmapPool) {
42        this(context, bitmapPool, PARSER_POOL, DECODER_POOL);
43    }
44
45    // Visible for testing.
46    GifResourceDecoder(Context context, BitmapPool bitmapPool, GifHeaderParserPool parserPool,
47            GifDecoderPool decoderPool) {
48        this.context = context;
49        this.bitmapPool = bitmapPool;
50        this.decoderPool = decoderPool;
51        this.provider = new GifBitmapProvider(bitmapPool);
52        this.parserPool = parserPool;
53    }
54
55    @Override
56    public GifDrawableResource decode(InputStream source, int width, int height) {
57        byte[] data = inputStreamToBytes(source);
58        final GifHeaderParser parser = parserPool.obtain(data);
59        final GifDecoder decoder = decoderPool.obtain(provider);
60        try {
61            return decode(data, width, height, parser, decoder);
62        } finally {
63            parserPool.release(parser);
64            decoderPool.release(decoder);
65        }
66    }
67
68    private GifDrawableResource decode(byte[] data, int width, int height, GifHeaderParser parser, GifDecoder decoder) {
69        final GifHeader header = parser.parseHeader();
70        if (header.getNumFrames() <= 0 || header.getStatus() != GifDecoder.STATUS_OK) {
71            // If we couldn't decode the GIF, we will end up with a frame count of 0.
72            return null;
73        }
74
75        Bitmap firstFrame = decodeFirstFrame(decoder, header, data);
76        if (firstFrame == null) {
77            return null;
78        }
79
80        Transformation<Bitmap> unitTransformation = UnitTransformation.get();
81
82        GifDrawable gifDrawable = new GifDrawable(context, provider, bitmapPool, unitTransformation, width, height,
83                header, data, firstFrame);
84
85        return new GifDrawableResource(gifDrawable);
86    }
87
88    private Bitmap decodeFirstFrame(GifDecoder decoder, GifHeader header, byte[] data) {
89        decoder.setData(header, data);
90        decoder.advance();
91        return decoder.getNextFrame();
92    }
93
94    @Override
95    public String getId() {
96        return "";
97    }
98
99    private static byte[] inputStreamToBytes(InputStream is) {
100        final int bufferSize = 16384;
101        ByteArrayOutputStream buffer = new ByteArrayOutputStream(bufferSize);
102        try {
103            int nRead;
104            byte[] data = new byte[bufferSize];
105            while ((nRead = is.read(data)) != -1) {
106                buffer.write(data, 0, nRead);
107            }
108            buffer.flush();
109        } catch (IOException e) {
110            Log.w(TAG, "Error reading data from stream", e);
111        }
112        //TODO the returned byte[] may be partial if an IOException was thrown from read
113        return buffer.toByteArray();
114    }
115
116    // Visible for testing.
117    static class GifDecoderPool {
118        private final Queue<GifDecoder> pool = Util.createQueue(0);
119
120        public synchronized GifDecoder obtain(GifDecoder.BitmapProvider bitmapProvider) {
121            GifDecoder result = pool.poll();
122            if (result == null) {
123                result = new GifDecoder(bitmapProvider);
124            }
125            return result;
126        }
127
128        public synchronized void release(GifDecoder decoder) {
129            decoder.clear();
130            pool.offer(decoder);
131        }
132    }
133
134    // Visible for testing.
135    static class GifHeaderParserPool {
136        private final Queue<GifHeaderParser> pool = Util.createQueue(0);
137
138        public synchronized GifHeaderParser obtain(byte[] data) {
139            GifHeaderParser result = pool.poll();
140            if (result == null) {
141                result = new GifHeaderParser();
142            }
143            return result.setData(data);
144        }
145
146        public synchronized void release(GifHeaderParser parser) {
147            parser.clear();
148            pool.offer(parser);
149        }
150    }
151}
152