提交 2c259f53 编写于 作者: S Sam Judd

Set transIndex for GIFs with transparent pixels.

Fixes #201.
上级 3640e349
...@@ -246,19 +246,10 @@ public class GifDrawableTest { ...@@ -246,19 +246,10 @@ public class GifDrawableTest {
} }
@Test @Test
public void testGetOpacityReturnsTransparentIfDecoderHasTransparency() { public void testGetOpacityReturnsTransparent() {
when(gifDecoder.isTransparent()).thenReturn(true);
assertEquals(PixelFormat.TRANSPARENT, drawable.getOpacity()); assertEquals(PixelFormat.TRANSPARENT, drawable.getOpacity());
} }
@Test
public void testGetOpacityReturnsOpaqueIfDecoderDoesNotHaveTransparency() {
when(gifDecoder.isTransparent()).thenReturn(false);
assertEquals(PixelFormat.OPAQUE, drawable.getOpacity());
}
@Test @Test
public void testReturnsFrameCountFromDecoder() { public void testReturnsFrameCountFromDecoder() {
int expected = 4; int expected = 4;
......
...@@ -14,6 +14,6 @@ class GifBitmapProvider implements GifDecoder.BitmapProvider { ...@@ -14,6 +14,6 @@ class GifBitmapProvider implements GifDecoder.BitmapProvider {
@Override @Override
public Bitmap obtain(int width, int height, Bitmap.Config config) { public Bitmap obtain(int width, int height, Bitmap.Config config) {
return bitmapPool.get(width, height, config); return bitmapPool.getDirty(width, height, config);
} }
} }
...@@ -197,7 +197,8 @@ public class GifDrawable extends GlideDrawable implements GifFrameManager.FrameC ...@@ -197,7 +197,8 @@ public class GifDrawable extends GlideDrawable implements GifFrameManager.FrameC
@Override @Override
public int getOpacity() { public int getOpacity() {
return decoder.isTransparent() ? PixelFormat.TRANSPARENT : PixelFormat.OPAQUE; // We can't tell, so default to transparent to be safe.
return PixelFormat.TRANSPARENT;
} }
@TargetApi(Build.VERSION_CODES.HONEYCOMB) @TargetApi(Build.VERSION_CODES.HONEYCOMB)
......
...@@ -5,26 +5,17 @@ import android.graphics.Bitmap; ...@@ -5,26 +5,17 @@ import android.graphics.Bitmap;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.os.SystemClock; import android.os.SystemClock;
import com.bumptech.glide.Glide; import com.bumptech.glide.Glide;
import com.bumptech.glide.gifdecoder.GifDecoder; import com.bumptech.glide.gifdecoder.GifDecoder;
import com.bumptech.glide.load.Encoder; import com.bumptech.glide.load.Encoder;
import com.bumptech.glide.load.ResourceDecoder;
import com.bumptech.glide.load.ResourceEncoder;
import com.bumptech.glide.load.Transformation; import com.bumptech.glide.load.Transformation;
import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool; import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
import com.bumptech.glide.load.engine.cache.MemorySizeCalculator; import com.bumptech.glide.load.engine.cache.MemorySizeCalculator;
import com.bumptech.glide.load.resource.NullDecoder;
import com.bumptech.glide.load.resource.NullEncoder; import com.bumptech.glide.load.resource.NullEncoder;
import com.bumptech.glide.load.resource.NullResourceEncoder;
import com.bumptech.glide.load.resource.bitmap.BitmapEncoder;
import com.bumptech.glide.load.resource.bitmap.StreamBitmapDecoder;
import com.bumptech.glide.load.resource.file.FileToStreamDecoder;
import com.bumptech.glide.request.animation.GlideAnimation; import com.bumptech.glide.request.animation.GlideAnimation;
import com.bumptech.glide.request.target.SimpleTarget; import com.bumptech.glide.request.target.SimpleTarget;
import com.bumptech.glide.util.Util;
import java.io.File;
class GifFrameManager { class GifFrameManager {
/** 60fps is {@value #MIN_FRAME_DELAY}ms per frame. */ /** 60fps is {@value #MIN_FRAME_DELAY}ms per frame. */
...@@ -32,10 +23,8 @@ class GifFrameManager { ...@@ -32,10 +23,8 @@ class GifFrameManager {
private final MemorySizeCalculator calculator; private final MemorySizeCalculator calculator;
private final GifFrameModelLoader frameLoader; private final GifFrameModelLoader frameLoader;
private final GifFrameResourceDecoder frameResourceDecoder; private final GifFrameResourceDecoder frameResourceDecoder;
private final ResourceDecoder<File, Bitmap> cacheDecoder;
private final GifDecoder decoder; private final GifDecoder decoder;
private final Handler mainHandler; private final Handler mainHandler;
private final ResourceEncoder<Bitmap> encoder;
private final Context context; private final Context context;
private final Encoder<GifDecoder> sourceEncoder; private final Encoder<GifDecoder> sourceEncoder;
private final Transformation<Bitmap>[] transformation; private final Transformation<Bitmap>[] transformation;
...@@ -69,23 +58,11 @@ class GifFrameManager { ...@@ -69,23 +58,11 @@ class GifFrameManager {
this.transformation = new Transformation[] {transformation}; this.transformation = new Transformation[] {transformation};
this.targetWidth = targetWidth; this.targetWidth = targetWidth;
this.targetHeight = targetHeight; this.targetHeight = targetHeight;
this.totalFrameSize = frameWidth * frameHeight * (decoder.isTransparent() ? 4 : 2); this.totalFrameSize = Util.getBitmapByteSize(frameWidth, frameHeight, Bitmap.Config.ARGB_8888);
this.calculator = new MemorySizeCalculator(context); this.calculator = new MemorySizeCalculator(context);
this.frameLoader = new GifFrameModelLoader(); this.frameLoader = new GifFrameModelLoader();
this.sourceEncoder = NullEncoder.get(); this.sourceEncoder = NullEncoder.get();
if (!decoder.isTransparent()) {
// For non transparent gifs, we can beat the performance of our gif decoder for each frame by decoding jpegs
// from disk.
this.cacheDecoder = new FileToStreamDecoder<Bitmap>(new StreamBitmapDecoder(context));
this.encoder = new BitmapEncoder();
} else {
// For transparent gifs, we would have to encode as pngs which is actually slower than our gif decoder so we
// avoid writing frames to the disk cache entirely.
this.cacheDecoder = NullDecoder.get();
this.encoder = NullResourceEncoder.get();
}
} }
Transformation<Bitmap> getTransformation() { Transformation<Bitmap> getTransformation() {
...@@ -95,17 +72,9 @@ class GifFrameManager { ...@@ -95,17 +72,9 @@ class GifFrameManager {
public void getNextFrame(FrameCallback cb) { public void getNextFrame(FrameCallback cb) {
decoder.advance(); decoder.advance();
/**
* Note - Using the disk cache can potentially cause frames to be decoded incorrectly because the decoder is
* sequential. If earlier frames are evicted for some reason, later ones may then not be decoded correctly.
*/
// We don't want to blow out the entire memory cache with frames of gifs, so try to set some // We don't want to blow out the entire memory cache with frames of gifs, so try to set some
// maximum size beyond which we will always just decode one frame at a time. // maximum size beyond which we will always just decode one frame at a time.
boolean skipCache = totalFrameSize > calculator.getMemoryCacheSize() / 2; boolean skipCache = totalFrameSize > calculator.getMemoryCacheSize() / 2;
// We can decode non transparent (cached as jpegs) frames more quickly from cache, but transparent
// (cached as png) frames more quickly from the gif data.
boolean skipDiskCache = decoder.isTransparent();
long targetTime = SystemClock.uptimeMillis() + Math.max(MIN_FRAME_DELAY, decoder.getNextDelay()); long targetTime = SystemClock.uptimeMillis() + Math.max(MIN_FRAME_DELAY, decoder.getNextDelay());
next = new DelayTarget(cb, targetTime); next = new DelayTarget(cb, targetTime);
...@@ -117,11 +86,9 @@ class GifFrameManager { ...@@ -117,11 +86,9 @@ class GifFrameManager {
.as(Bitmap.class) .as(Bitmap.class)
.sourceEncoder(sourceEncoder) .sourceEncoder(sourceEncoder)
.decoder(frameResourceDecoder) .decoder(frameResourceDecoder)
.cacheDecoder(cacheDecoder)
.encoder(encoder)
.transform(transformation) .transform(transformation)
.skipMemoryCache(skipCache) .skipMemoryCache(skipCache)
.diskCacheStrategy(skipDiskCache ? DiskCacheStrategy.NONE : DiskCacheStrategy.RESULT) .diskCacheStrategy(DiskCacheStrategy.NONE)
.into(next); .into(next);
} }
......
...@@ -24,8 +24,9 @@ package com.bumptech.glide.gifdecoder; ...@@ -24,8 +24,9 @@ package com.bumptech.glide.gifdecoder;
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
import android.annotation.TargetApi;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.Color; import android.os.Build;
import android.util.Log; import android.util.Log;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
...@@ -144,10 +145,6 @@ public class GifDecoder { ...@@ -144,10 +145,6 @@ public class GifDecoder {
return header.height; return header.height;
} }
public boolean isTransparent() {
return header.isTransparent;
}
public byte[] getData() { public byte[] getData() {
return data; return data;
} }
...@@ -323,6 +320,7 @@ public class GifDecoder { ...@@ -323,6 +320,7 @@ public class GifDecoder {
rawData.rewind(); rawData.rewind();
rawData.order(ByteOrder.LITTLE_ENDIAN); rawData.order(ByteOrder.LITTLE_ENDIAN);
// No point in specially saving an old frame if we're never going to use it. // No point in specially saving an old frame if we're never going to use it.
savePrevious = false; savePrevious = false;
for (GifFrame frame : header.frames) { for (GifFrame frame : header.frames) {
...@@ -458,7 +456,7 @@ public class GifDecoder { ...@@ -458,7 +456,7 @@ public class GifDecoder {
} }
} }
//Copy pixels into previous image // Copy pixels into previous image
if (savePrevious && currentFrame.dispose == DISPOSAL_UNSPECIFIED || currentFrame.dispose == DISPOSAL_NONE) { if (savePrevious && currentFrame.dispose == DISPOSAL_UNSPECIFIED || currentFrame.dispose == DISPOSAL_NONE) {
if (previousImage == null) { if (previousImage == null) {
previousImage = getNextBitmap(); previousImage = getNextBitmap();
...@@ -631,8 +629,10 @@ public class GifDecoder { ...@@ -631,8 +629,10 @@ public class GifDecoder {
} }
private Bitmap.Config getPreferredConfig() { private Bitmap.Config getPreferredConfig() {
if (config == Bitmap.Config.RGB_565 && !header.isTransparent) { // We can't tell if a gif has transparency to decode a partial frame on top of a previous frame, or if the final
return Bitmap.Config.RGB_565; // frame will actually have transparent pixels, so we must always use a format that supports transparency.
if (config == Bitmap.Config.RGB_565 || config == Bitmap.Config.ARGB_4444) {
return Bitmap.Config.ARGB_4444;
} else { } else {
return Bitmap.Config.ARGB_8888; return Bitmap.Config.ARGB_8888;
} }
...@@ -643,10 +643,15 @@ public class GifDecoder { ...@@ -643,10 +643,15 @@ public class GifDecoder {
Bitmap result = bitmapProvider.obtain(header.width, header.height, targetConfig); Bitmap result = bitmapProvider.obtain(header.width, header.height, targetConfig);
if (result == null) { if (result == null) {
result = Bitmap.createBitmap(header.width, header.height, targetConfig); result = Bitmap.createBitmap(header.width, header.height, targetConfig);
} else {
// If we're reusing a bitmap it may have other things drawn in it which we need to remove.
result.eraseColor(Color.TRANSPARENT);
} }
setAlpha(result);
return result; return result;
} }
@TargetApi(12)
private static void setAlpha(Bitmap bitmap) {
if (Build.VERSION.SDK_INT >= 12) {
bitmap.setHasAlpha(true);
}
}
} }
...@@ -34,7 +34,6 @@ public class GifHeader { ...@@ -34,7 +34,6 @@ public class GifHeader {
int pixelAspect; int pixelAspect;
//TODO: this is set both during reading the header and while decoding frames... //TODO: this is set both during reading the header and while decoding frames...
int bgColor; int bgColor;
boolean isTransparent;
int loopCount; int loopCount;
public int getHeight() { public int getHeight() {
......
...@@ -143,7 +143,6 @@ public class GifHeaderParser { ...@@ -143,7 +143,6 @@ public class GifHeaderParser {
header.currentFrame.dispose = 1; header.currentFrame.dispose = 1;
} }
header.currentFrame.transparency = (packed & 1) != 0; header.currentFrame.transparency = (packed & 1) != 0;
header.isTransparent |= header.currentFrame.transparency;
// Delay in milliseconds. // Delay in milliseconds.
header.currentFrame.delay = readShort() * 10; header.currentFrame.delay = readShort() * 10;
// Transparent color index // Transparent color index
......
...@@ -36,45 +36,47 @@ import java.io.OutputStream; ...@@ -36,45 +36,47 @@ import java.io.OutputStream;
public class AnimatedGifEncoder { public class AnimatedGifEncoder {
protected int width; // image size private int width; // image size
protected int height; private int height;
protected Integer transparent = null; // transparent color if given private Integer transparent = null; // transparent color if given
protected int transIndex; // transparent index in color table private int transIndex; // transparent index in color table
protected int repeat = -1; // no repeat private int repeat = -1; // no repeat
protected int delay = 0; // frame delay (hundredths) private int delay = 0; // frame delay (hundredths)
protected boolean started = false; // ready to output frames private boolean started = false; // ready to output frames
protected OutputStream out; private OutputStream out;
protected Bitmap image; // current frame private Bitmap image; // current frame
protected byte[] pixels; // BGR byte array from frame private byte[] pixels; // BGR byte array from frame
protected byte[] indexedPixels; // converted frame indexed to palette private byte[] indexedPixels; // converted frame indexed to palette
protected int colorDepth; // number of bit planes private int colorDepth; // number of bit planes
protected byte[] colorTab; // RGB palette private byte[] colorTab; // RGB palette
protected boolean[] usedEntry = new boolean[256]; // active palette entries private boolean[] usedEntry = new boolean[256]; // active palette entries
protected int palSize = 7; // color table size (bits-1) private int palSize = 7; // color table size (bits-1)
protected int dispose = -1; // disposal code (-1 = use default) private int dispose = -1; // disposal code (-1 = use default)
protected boolean closeStream = false; // close stream when finished private boolean closeStream = false; // close stream when finished
protected boolean firstFrame = true; private boolean firstFrame = true;
protected boolean sizeSet = false; // if false, get size from first frame private boolean sizeSet = false; // if false, get size from first frame
protected int sample = 10; // default sample interval for quantizer private int sample = 10; // default sample interval for quantizer
private boolean hasTransparentPixels;
/** /**
* Sets the delay time between each frame, or changes it for subsequent frames * Sets the delay time between each frame, or changes it for subsequent frames
...@@ -300,7 +302,7 @@ public class AnimatedGifEncoder { ...@@ -300,7 +302,7 @@ public class AnimatedGifEncoder {
/** /**
* Analyzes image colors and creates color map. * Analyzes image colors and creates color map.
*/ */
protected void analyzePixels() { private void analyzePixels() {
int len = pixels.length; int len = pixels.length;
int nPix = len / 3; int nPix = len / 3;
indexedPixels = new byte[nPix]; indexedPixels = new byte[nPix];
...@@ -327,6 +329,8 @@ public class AnimatedGifEncoder { ...@@ -327,6 +329,8 @@ public class AnimatedGifEncoder {
// get closest match to transparent color if specified // get closest match to transparent color if specified
if (transparent != null) { if (transparent != null) {
transIndex = findClosest(transparent); transIndex = findClosest(transparent);
} else if (hasTransparentPixels) {
transIndex = findClosest(Color.TRANSPARENT);
} }
} }
...@@ -334,7 +338,7 @@ public class AnimatedGifEncoder { ...@@ -334,7 +338,7 @@ public class AnimatedGifEncoder {
* Returns index of palette color closest to c * Returns index of palette color closest to c
* *
*/ */
protected int findClosest(int color) { private int findClosest(int color) {
if (colorTab == null) if (colorTab == null)
return -1; return -1;
int r = Color.red(color); int r = Color.red(color);
...@@ -361,7 +365,7 @@ public class AnimatedGifEncoder { ...@@ -361,7 +365,7 @@ public class AnimatedGifEncoder {
/** /**
* Extracts image pixels into byte array "pixels" * Extracts image pixels into byte array "pixels"
*/ */
protected void getImagePixels() { private void getImagePixels() {
int w = image.getWidth(); int w = image.getWidth();
int h = image.getHeight(); int h = image.getHeight();
...@@ -379,7 +383,11 @@ public class AnimatedGifEncoder { ...@@ -379,7 +383,11 @@ public class AnimatedGifEncoder {
pixels = new byte[pixelsInt.length * 3]; pixels = new byte[pixelsInt.length * 3];
int pixelsIndex = 0; int pixelsIndex = 0;
hasTransparentPixels = false;
for (final int pixel : pixelsInt) { for (final int pixel : pixelsInt) {
if (pixel == Color.TRANSPARENT) {
hasTransparentPixels = true;
}
pixels[pixelsIndex++] = (byte) (pixel & 0xFF); pixels[pixelsIndex++] = (byte) (pixel & 0xFF);
pixels[pixelsIndex++] = (byte) ((pixel >> 8) & 0xFF); pixels[pixelsIndex++] = (byte) ((pixel >> 8) & 0xFF);
pixels[pixelsIndex++] = (byte) ((pixel >> 16) & 0xFF); pixels[pixelsIndex++] = (byte) ((pixel >> 16) & 0xFF);
...@@ -389,12 +397,12 @@ public class AnimatedGifEncoder { ...@@ -389,12 +397,12 @@ public class AnimatedGifEncoder {
/** /**
* Writes Graphic Control Extension * Writes Graphic Control Extension
*/ */
protected void writeGraphicCtrlExt() throws IOException { private void writeGraphicCtrlExt() throws IOException {
out.write(0x21); // extension introducer out.write(0x21); // extension introducer
out.write(0xf9); // GCE label out.write(0xf9); // GCE label
out.write(4); // data block size out.write(4); // data block size
int transp, disp; int transp, disp;
if (transparent == null) { if (transparent == null && !hasTransparentPixels) {
transp = 0; transp = 0;
disp = 0; // dispose = no action disp = 0; // dispose = no action
} else { } else {
...@@ -420,7 +428,7 @@ public class AnimatedGifEncoder { ...@@ -420,7 +428,7 @@ public class AnimatedGifEncoder {
/** /**
* Writes Image Descriptor * Writes Image Descriptor
*/ */
protected void writeImageDesc() throws IOException { private void writeImageDesc() throws IOException {
out.write(0x2c); // image separator out.write(0x2c); // image separator
writeShort(0); // image position x,y = 0,0 writeShort(0); // image position x,y = 0,0
writeShort(0); writeShort(0);
...@@ -443,7 +451,7 @@ public class AnimatedGifEncoder { ...@@ -443,7 +451,7 @@ public class AnimatedGifEncoder {
/** /**
* Writes Logical Screen Descriptor * Writes Logical Screen Descriptor
*/ */
protected void writeLSD() throws IOException { private void writeLSD() throws IOException {
// logical screen size // logical screen size
writeShort(width); writeShort(width);
writeShort(height); writeShort(height);
...@@ -460,7 +468,7 @@ public class AnimatedGifEncoder { ...@@ -460,7 +468,7 @@ public class AnimatedGifEncoder {
/** /**
* Writes Netscape application extension to define repeat count. * Writes Netscape application extension to define repeat count.
*/ */
protected void writeNetscapeExt() throws IOException { private void writeNetscapeExt() throws IOException {
out.write(0x21); // extension introducer out.write(0x21); // extension introducer
out.write(0xff); // app extension label out.write(0xff); // app extension label
out.write(11); // block size out.write(11); // block size
...@@ -474,7 +482,7 @@ public class AnimatedGifEncoder { ...@@ -474,7 +482,7 @@ public class AnimatedGifEncoder {
/** /**
* Writes color table * Writes color table
*/ */
protected void writePalette() throws IOException { private void writePalette() throws IOException {
out.write(colorTab, 0, colorTab.length); out.write(colorTab, 0, colorTab.length);
int n = (3 * 256) - colorTab.length; int n = (3 * 256) - colorTab.length;
for (int i = 0; i < n; i++) { for (int i = 0; i < n; i++) {
...@@ -485,7 +493,7 @@ public class AnimatedGifEncoder { ...@@ -485,7 +493,7 @@ public class AnimatedGifEncoder {
/** /**
* Encodes and writes pixel data * Encodes and writes pixel data
*/ */
protected void writePixels() throws IOException { private void writePixels() throws IOException {
LZWEncoder encoder = new LZWEncoder(width, height, indexedPixels, colorDepth); LZWEncoder encoder = new LZWEncoder(width, height, indexedPixels, colorDepth);
encoder.encode(out); encoder.encode(out);
} }
...@@ -493,7 +501,7 @@ public class AnimatedGifEncoder { ...@@ -493,7 +501,7 @@ public class AnimatedGifEncoder {
/** /**
* Write 16-bit value to output stream, LSB first * Write 16-bit value to output stream, LSB first
*/ */
protected void writeShort(int value) throws IOException { private void writeShort(int value) throws IOException {
out.write(value & 0xff); out.write(value & 0xff);
out.write((value >> 8) & 0xff); out.write((value >> 8) & 0xff);
} }
...@@ -501,7 +509,7 @@ public class AnimatedGifEncoder { ...@@ -501,7 +509,7 @@ public class AnimatedGifEncoder {
/** /**
* Writes string to output stream * Writes string to output stream
*/ */
protected void writeString(String s) throws IOException { private void writeString(String s) throws IOException {
for (int i = 0; i < s.length(); i++) { for (int i = 0; i < s.length(); i++) {
out.write((byte) s.charAt(i)); out.write((byte) s.charAt(i));
} }
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册