1 package com.bumptech.glide.load.resource.gif; 2 3 import android.graphics.Bitmap; 4 import android.util.Log; 5 6 import com.bumptech.glide.gifdecoder.GifDecoder; 7 import com.bumptech.glide.gifdecoder.GifHeader; 8 import com.bumptech.glide.gifdecoder.GifHeaderParser; 9 import com.bumptech.glide.gifencoder.AnimatedGifEncoder; 10 import com.bumptech.glide.load.ResourceEncoder; 11 import com.bumptech.glide.load.Transformation; 12 import com.bumptech.glide.load.engine.Resource; 13 import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool; 14 import com.bumptech.glide.load.resource.UnitTransformation; 15 import com.bumptech.glide.load.resource.bitmap.BitmapResource; 16 import com.bumptech.glide.util.LogTime; 17 18 import java.io.IOException; 19 import java.io.OutputStream; 20 21 /** 22 * An {@link com.bumptech.glide.load.ResourceEncoder} that can write 23 * {@link com.bumptech.glide.load.resource.gif.GifDrawable} to cache. 24 */ 25 public class GifResourceEncoder implements ResourceEncoder<GifDrawable> { 26 private static final Factory FACTORY = new Factory(); 27 private static final String TAG = "GifEncoder"; 28 private final GifDecoder.BitmapProvider provider; 29 private final BitmapPool bitmapPool; 30 private final Factory factory; 31 32 public GifResourceEncoder(BitmapPool bitmapPool) { 33 this(bitmapPool, FACTORY); 34 } 35 36 // Visible for testing. 37 GifResourceEncoder(BitmapPool bitmapPool, Factory factory) { 38 this.bitmapPool = bitmapPool; 39 provider = new GifBitmapProvider(bitmapPool); 40 this.factory = factory; 41 } 42 43 @Override 44 public boolean encode(Resource<GifDrawable> resource, OutputStream os) { 45 long startTime = LogTime.getLogTime(); 46 47 GifDrawable drawable = resource.get(); 48 Transformation<Bitmap> transformation = drawable.getFrameTransformation(); 49 if (transformation instanceof UnitTransformation) { 50 return writeDataDirect(drawable.getData(), os); 51 } 52 53 GifDecoder decoder = decodeHeaders(drawable.getData()); 54 55 AnimatedGifEncoder encoder = factory.buildEncoder(); 56 if (!encoder.start(os)) { 57 return false; 58 } 59 60 for (int i = 0; i < decoder.getFrameCount(); i++) { 61 Bitmap currentFrame = decoder.getNextFrame(); 62 Resource<Bitmap> transformedResource = getTransformedFrame(currentFrame, transformation, drawable); 63 try { 64 if (!encoder.addFrame(transformedResource.get())) { 65 return false; 66 } 67 int currentFrameIndex = decoder.getCurrentFrameIndex(); 68 int delay = decoder.getDelay(currentFrameIndex); 69 encoder.setDelay(delay); 70 71 decoder.advance(); 72 } finally { 73 transformedResource.recycle(); 74 } 75 } 76 77 boolean result = encoder.finish(); 78 79 if (Log.isLoggable(TAG, Log.VERBOSE)) { 80 Log.v(TAG, "Encoded gif with " + decoder.getFrameCount() + " frames and " + drawable.getData().length 81 + " bytes in " + LogTime.getElapsedMillis(startTime) + " ms"); 82 } 83 84 return result; 85 } 86 87 private boolean writeDataDirect(byte[] data, OutputStream os) { 88 boolean success = true; 89 try { 90 os.write(data); 91 } catch (IOException e) { 92 if (Log.isLoggable(TAG, Log.DEBUG)) { 93 Log.d(TAG, "Failed to write data to output stream in GifResourceEncoder", e); 94 } 95 success = false; 96 } 97 return success; 98 } 99 100 private GifDecoder decodeHeaders(byte[] data) { 101 GifHeaderParser parser = factory.buildParser(); 102 parser.setData(data); 103 GifHeader header = parser.parseHeader(); 104 105 GifDecoder decoder = factory.buildDecoder(provider); 106 decoder.setData(header, data); 107 decoder.advance(); 108 109 return decoder; 110 } 111 112 private Resource<Bitmap> getTransformedFrame(Bitmap currentFrame, Transformation<Bitmap> transformation, 113 GifDrawable drawable) { 114 // TODO: what if current frame is null? 115 Resource<Bitmap> bitmapResource = factory.buildFrameResource(currentFrame, bitmapPool); 116 Resource<Bitmap> transformedResource = transformation.transform(bitmapResource, 117 drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); 118 if (!bitmapResource.equals(transformedResource)) { 119 bitmapResource.recycle(); 120 } 121 return transformedResource; 122 } 123 124 @Override 125 public String getId() { 126 return ""; 127 } 128 129 // Visible for testing. 130 static class Factory { 131 132 public GifDecoder buildDecoder(GifDecoder.BitmapProvider bitmapProvider) { 133 return new GifDecoder(bitmapProvider); 134 } 135 136 public GifHeaderParser buildParser() { 137 return new GifHeaderParser(); 138 } 139 140 public AnimatedGifEncoder buildEncoder() { 141 return new AnimatedGifEncoder(); 142 } 143 144 public Resource<Bitmap> buildFrameResource(Bitmap bitmap, BitmapPool bitmapPool) { 145 return new BitmapResource(bitmap, bitmapPool); 146 } 147 } 148 } 149