Skip to content

Commit

Permalink
Support tint list on GifDrawable.
Browse files Browse the repository at this point in the history
The Java doc of `Drawable#setColorFilter(ColorFilter)` says that "non-null
color filter disables tint.".
https://developer.android.com/reference/android/graphics/drawable/Drawable#setColorFilter(android.graphics.ColorFilter)

This change sets the tint color filter by `draw()` to align with the
document. This behavior is the same as the `ColorDrawable`.
  • Loading branch information
sumita12 committed Dec 4, 2023
1 parent 3bbc390 commit 6d23472
Show file tree
Hide file tree
Showing 2 changed files with 107 additions and 8 deletions.
Expand Up @@ -3,12 +3,16 @@
import static com.bumptech.glide.gifdecoder.GifDecoder.TOTAL_ITERATION_COUNT_FOREVER;

import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.Rect;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
Expand Down Expand Up @@ -66,6 +70,9 @@ public class GifDrawable extends Drawable

private boolean applyGravity;
private Paint paint;
private ColorStateList tint;
private PorterDuff.Mode tintMode;
private ColorFilter tintFilter;
private Rect destRect;

/** Callbacks to notify loop completion of a gif, where the loop count is explicitly specified. */
Expand Down Expand Up @@ -288,7 +295,17 @@ public void draw(@NonNull Canvas canvas) {
}

Bitmap currentFrame = state.frameLoader.getCurrentFrame();
canvas.drawBitmap(currentFrame, null, getDestRect(), getPaint());
Paint paint = getPaint();
ColorFilter colorFilter = paint.getColorFilter();
if (colorFilter != null || tintFilter == null) {
// ColorFilter disables tint list. See Drawable#setColorFilter().
canvas.drawBitmap(currentFrame, null, getDestRect(), paint);
} else {
// Temporary set a tint filter then restore.
paint.setColorFilter(tintFilter);
canvas.drawBitmap(currentFrame, null, getDestRect(), paint);
paint.setColorFilter(colorFilter);
}
}

@Override
Expand All @@ -298,7 +315,47 @@ public void setAlpha(int i) {

@Override
public void setColorFilter(ColorFilter colorFilter) {
getPaint().setColorFilter(colorFilter);
if (getColorFilter() != colorFilter) {
getPaint().setColorFilter(colorFilter);
invalidateSelf();
}
}

@Override
public ColorFilter getColorFilter() {
return getPaint().getColorFilter();
}

@Override
public void setTintList(ColorStateList tint) {
this.tint = tint;
updateTintFilter();
invalidateSelf();
}

@Override
public void setTintMode(PorterDuff.Mode tintMode) {
this.tintMode = tintMode;
updateTintFilter();
invalidateSelf();
}

@Override
protected boolean onStateChange(int[] stateSet) {
if (tint != null && tintMode != null) {
updateTintFilter();
return true;
}
return false;
}

private void updateTintFilter() {
if (tint != null && tintMode != null) {
int color = tint.getColorForState(getState(), Color.TRANSPARENT);
tintFilter = new PorterDuffColorFilter(color, tintMode);
} else {
tintFilter = null;
}
}

private Rect getDestRect() {
Expand Down
Expand Up @@ -6,6 +6,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.ArgumentMatchers.isNull;
Expand All @@ -16,6 +17,7 @@
import static org.mockito.Mockito.when;

import android.app.Application;
import android.content.res.ColorStateList;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
Expand All @@ -40,7 +42,6 @@
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
Expand Down Expand Up @@ -568,12 +569,53 @@ public void testSetAlphaSetsAlphaOnPaint() {
public void testSetColorFilterSetsColorFilterOnPaint() {
ColorFilter colorFilter = new PorterDuffColorFilter(Color.RED, Mode.ADD);
drawable.setColorFilter(colorFilter);
verify(paint).setColorFilter(eq(colorFilter));
}

@Config(sdk = Build.VERSION_CODES.LOLLIPOP)
@Test
public void testDrawSetsTintListColorFilterOnPaint() {
ColorStateList tint =
new ColorStateList(
new int[][] {new int[] {android.R.attr.state_pressed}, new int[0]},
new int[] {Color.RED, Color.GREEN});
drawable.setTintList(tint);
drawable.setTintMode(Mode.ADD);
when(paint.getColorFilter()).thenReturn(null);
drawable.draw(new Canvas());

// draw() temporary sets tint filter then restore.
verify(paint).setColorFilter(eq(new PorterDuffColorFilter(Color.GREEN, Mode.ADD)));
verify(paint).setColorFilter(null);

assertThat(drawable.setState(new int[] {android.R.attr.state_pressed})).isTrue();
drawable.draw(new Canvas());

// Pressed state. draw() temporary sets a red color filter.
verify(paint).setColorFilter(eq(new PorterDuffColorFilter(Color.RED, Mode.ADD)));
verify(paint, times(2)).setColorFilter(null);
}

@Config(sdk = Build.VERSION_CODES.LOLLIPOP)
@Test
public void testDrawUsesColorFilterInsteadOfTintList() {
ColorStateList tint =
new ColorStateList(
new int[][] {new int[] {android.R.attr.state_pressed}, new int[0]},
new int[] {Color.RED, Color.GREEN});
drawable.setTintList(tint);
drawable.setTintMode(Mode.ADD);
ColorFilter colorFilter = new PorterDuffColorFilter(Color.BLUE, Mode.ADD);
drawable.setColorFilter(colorFilter);
verify(paint).setColorFilter(eq(colorFilter));
when(paint.getColorFilter()).thenReturn(colorFilter);

drawable.draw(new Canvas());
drawable.onStateChange(new int[] {android.R.attr.state_pressed});
drawable.draw(new Canvas());

// Use ArgumentCaptor instead of eq() due to b/73121412 where ShadowPorterDuffColorFilter.equals
// uses a method that can't be found (PorterDuffColorFilter.getColor).
ArgumentCaptor<ColorFilter> captor = ArgumentCaptor.forClass(ColorFilter.class);
verify(paint).setColorFilter(captor.capture());
assertThat(captor.getValue()).isSameInstanceAs(colorFilter);
// ColorFilter disables tint list, so draw() should not invoke setColorFilter() any more.
verify(paint).setColorFilter(any());
}

@Test
Expand Down

0 comments on commit 6d23472

Please sign in to comment.