package com.bumptech.glide.load.resource.bitmap;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.media.ExifInterface;
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
import com.bumptech.glide.tests.Util;
import com.bumptech.glide.util.Preconditions;
import com.google.common.collect.Range;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.Shadows;
import org.robolectric.annotation.Config;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.shadows.ShadowBitmap;
@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE, sdk = 18, shadows = {
TransformationUtilsTest.AlphaShadowBitmap.class })
public class TransformationUtilsTest {
@Mock BitmapPool bitmapPool;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
when(bitmapPool.get(anyInt(), anyInt(), any(Bitmap.Config.class)))
.thenAnswer(new Util.CreateBitmap());
}
@Test
public void testFitCenterWithWideBitmap() {
final int maxSide = 500;
Bitmap wide = Bitmap.createBitmap(2000, 100, Bitmap.Config.ARGB_8888);
Bitmap transformed = TransformationUtils.fitCenter(bitmapPool, wide, maxSide, maxSide);
assertHasOriginalAspectRatio(wide, transformed);
assertBitmapFitsExactlyWithinBounds(maxSide, transformed);
}
@Test
public void testFitCenterWithSmallWideBitmap() {
final int maxSide = 500;
Bitmap smallWide = Bitmap.createBitmap(400, 40, Bitmap.Config.ARGB_8888);
Bitmap transformed = TransformationUtils.fitCenter(bitmapPool, smallWide, maxSide, maxSide);
assertHasOriginalAspectRatio(smallWide, transformed);
assertBitmapFitsExactlyWithinBounds(maxSide, transformed);
}
@Test
public void testFitCenterWithTallBitmap() {
final int maxSide = 500;
Bitmap tall = Bitmap.createBitmap(65, 3000, Bitmap.Config.ARGB_8888);
Bitmap transformed = TransformationUtils.fitCenter(bitmapPool, tall, maxSide, maxSide);
assertHasOriginalAspectRatio(tall, transformed);
assertBitmapFitsExactlyWithinBounds(maxSide, transformed);
}
@Test
public void testFitCenterWithSmallTallBitmap() {
final int maxSide = 500;
Bitmap smallTall = Bitmap.createBitmap(10, 400, Bitmap.Config.ARGB_8888);
Bitmap transformed = TransformationUtils.fitCenter(bitmapPool, smallTall, maxSide, maxSide);
assertHasOriginalAspectRatio(smallTall, transformed);
assertBitmapFitsExactlyWithinBounds(maxSide, transformed);
}
@Test
public void testFitCenterWithSquareBitmap() {
final int maxSide = 500;
Bitmap square = Bitmap.createBitmap(600, 600, Bitmap.Config.ARGB_8888);
Bitmap transformed = TransformationUtils.fitCenter(bitmapPool, square, maxSide, maxSide);
assertHasOriginalAspectRatio(square, transformed);
assertBitmapFitsExactlyWithinBounds(maxSide, transformed);
}
@Test
public void testFitCenterWithTooSmallSquareBitmap() {
final int maxSide = 500;
Bitmap smallSquare = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
Bitmap transformed = TransformationUtils.fitCenter(bitmapPool, smallSquare, maxSide, maxSide);
assertHasOriginalAspectRatio(smallSquare, transformed);
assertBitmapFitsExactlyWithinBounds(maxSide, transformed);
}
// Test for Issue #195.
@Test
public void testFitCenterUsesFloorInsteadOfRoundingForOutputBitmapSize() {
Bitmap toTransform = Bitmap.createBitmap(1230, 1640, Bitmap.Config.RGB_565);
Bitmap transformed = TransformationUtils.fitCenter(bitmapPool, toTransform, 1075, 1366);
assertEquals(1024, transformed.getWidth());
assertEquals(1366, transformed.getHeight());
}
@Test
public void testFitCenterReturnsGivenBitmapIfGivenBitmapMatchesExactly() {
Bitmap toFit = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_4444);
Bitmap transformed = TransformationUtils.fitCenter(bitmapPool, toFit, toFit.getWidth(),
toFit.getHeight());
assertTrue(toFit == transformed);
}
@Test
public void testFitCenterReturnsGivenBitmapIfGivenBitmapWidthMatchesExactly() {
Bitmap toFit = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_4444);
Bitmap transformed =
TransformationUtils.fitCenter(bitmapPool, toFit, toFit.getWidth(), toFit.getHeight() * 2);
assertTrue(toFit == transformed);
}
@Test
public void testFitCenterReturnsGivenBitmapIfGivenBitmapHeightMatchesExactly() {
Bitmap toFit = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_4444);
Bitmap transformed =
TransformationUtils.fitCenter(bitmapPool, toFit, toFit.getWidth() * 2, toFit.getHeight());
assertTrue(toFit == transformed);
}
@Test
public void testCenterCropReturnsGivenBitmapIfGivenBitmapExactlyMatchesGivenDimensions() {
Bitmap toCrop = Bitmap.createBitmap(200, 300, Bitmap.Config.ARGB_8888);
Bitmap transformed = TransformationUtils
.centerCrop(bitmapPool, toCrop, toCrop.getWidth(), toCrop.getHeight());
// Robolectric incorrectly implements equals() for Bitmaps, we want the original object not
// just an equivalent.
assertTrue(toCrop == transformed);
}
@Test
public void testFitCenterHandlesBitmapsWithNullConfigs() {
Bitmap toFit = Bitmap.createBitmap(100, 100, Bitmap.Config.RGB_565);
Shadows.shadowOf(toFit).setConfig(null);
Bitmap transformed = TransformationUtils.fitCenter(bitmapPool, toFit, 50, 50);
assertEquals(Bitmap.Config.ARGB_8888, transformed.getConfig());
}
@Test
public void testCenterCropSetsOutBitmapToHaveAlphaIfInBitmapHasAlphaAndOutBitmapIsReused() {
Bitmap toTransform = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
Bitmap toReuse = Bitmap.createBitmap(50, 50, Bitmap.Config.ARGB_8888);
reset(bitmapPool);
when(bitmapPool.get(eq(50), eq(50), eq(Bitmap.Config.ARGB_8888)))
.thenReturn(toReuse);
toReuse.setHasAlpha(false);
toTransform.setHasAlpha(true);
Bitmap result = TransformationUtils.centerCrop(bitmapPool, toTransform, toReuse.getWidth(),
toReuse.getHeight());
assertEquals(toReuse, result);
assertTrue(result.hasAlpha());
}
@Test
public void
testCenterCropSetsOutBitmapToNotHaveAlphaIfInBitmapDoesNotHaveAlphaAndOutBitmapIsReused() {
Bitmap toTransform = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
Bitmap toReuse = Bitmap.createBitmap(50, 50, Bitmap.Config.ARGB_8888);
reset(bitmapPool);
when(bitmapPool.get(eq(50), eq(50), eq(Bitmap.Config.ARGB_8888))).thenReturn(toReuse);
toReuse.setHasAlpha(true);
toTransform.setHasAlpha(false);
Bitmap result = TransformationUtils.centerCrop(bitmapPool, toTransform, toReuse.getWidth(),
toReuse.getHeight());
assertEquals(toReuse, result);
assertFalse(result.hasAlpha());
}
@Test
public void testCenterCropSetsOutBitmapToHaveAlphaIfInBitmapHasAlpha() {
Bitmap toTransform = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
toTransform.setHasAlpha(true);
Bitmap result = TransformationUtils.centerCrop(bitmapPool, toTransform,
toTransform.getWidth() / 2, toTransform.getHeight() / 2);
assertTrue(result.hasAlpha());
}
@Test
public void testCenterCropHandlesBitmapsWithNullConfigs() {
Bitmap toTransform = Bitmap.createBitmap(100, 100, Bitmap.Config.RGB_565);
Shadows.shadowOf(toTransform).setConfig(null);
Bitmap transformed = TransformationUtils.centerCrop(bitmapPool, toTransform, 50, 50);
assertEquals(Bitmap.Config.ARGB_8888, transformed.getConfig());
}
@Test
public void testCenterCropSetsOutBitmapToNotHaveAlphaIfInBitmapDoesNotHaveAlpha() {
Bitmap toTransform = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
toTransform.setHasAlpha(false);
Bitmap result = TransformationUtils.centerCrop(bitmapPool, toTransform,
toTransform.getWidth() / 2, toTransform.getHeight() / 2);
assertFalse(result.hasAlpha());
}
@Test
public void testFitCenterSetsOutBitmapToHaveAlphaIfInBitmapHasAlphaAndOutBitmapIsReused() {
Bitmap toTransform = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
Bitmap toReuse = Bitmap.createBitmap(50, 50, Bitmap.Config.ARGB_8888);
reset(bitmapPool);
when(bitmapPool.get(eq(toReuse.getWidth()), eq(toReuse.getHeight()), eq(toReuse.getConfig())))
.thenReturn(toReuse);
toReuse.setHasAlpha(false);
toTransform.setHasAlpha(true);
Bitmap result = TransformationUtils.fitCenter(bitmapPool, toTransform, toReuse.getWidth(),
toReuse.getHeight());
assertEquals(toReuse, result);
assertTrue(result.hasAlpha());
}
@Test
public void
testFitCenterSetsOutBitmapToNotHaveAlphaIfInBitmapDoesNotHaveAlphaAndOutBitmapIsReused() {
Bitmap toTransform = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
Bitmap toReuse = Bitmap.createBitmap(50, 50, Bitmap.Config.ARGB_8888);
reset(bitmapPool);
when(bitmapPool.get(eq(toReuse.getWidth()), eq(toReuse.getHeight()), eq(toReuse.getConfig())))
.thenReturn(toReuse);
toReuse.setHasAlpha(true);
toTransform.setHasAlpha(false);
Bitmap result = TransformationUtils.fitCenter(bitmapPool, toTransform, toReuse.getWidth(),
toReuse.getHeight());
assertEquals(toReuse, result);
assertFalse(result.hasAlpha());
}
@Test
public void testFitCenterSetsOutBitmapToHaveAlphaIfInBitmapHasAlpha() {
Bitmap toTransform = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
toTransform.setHasAlpha(true);
Bitmap result = TransformationUtils.fitCenter(bitmapPool, toTransform,
toTransform.getWidth() / 2, toTransform.getHeight() / 2);
assertTrue(result.hasAlpha());
}
@Test
public void testFitCenterSetsOutBitmapToNotHaveAlphaIfInBitmapDoesNotHaveAlpha() {
Bitmap toTransform = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
toTransform.setHasAlpha(false);
Bitmap result = TransformationUtils.fitCenter(bitmapPool, toTransform,
toTransform.getWidth() / 2, toTransform.getHeight() / 2);
assertFalse(result.hasAlpha());
}
private static void assertHasOriginalAspectRatio(Bitmap original, Bitmap transformed) {
double originalAspectRatio = (double) original.getWidth() / (double) original.getHeight();
double transformedAspectRatio =
(double) transformed.getWidth() / (double) transformed.getHeight();
assertThat(transformedAspectRatio)
.isIn(Range.open(originalAspectRatio - 0.05f, originalAspectRatio + 0.05f));
}
private static void assertBitmapFitsExactlyWithinBounds(int maxSide, Bitmap bitmap) {
final int width = bitmap.getWidth();
final int height = bitmap.getHeight();
assertThat(width).isIn(Range.atMost(maxSide));
assertThat(height).isIn(Range.atMost(maxSide));
assertTrue("one side must match maxSide", width == maxSide || height == maxSide);
}
@Test
public void testGetExifOrientationDegrees() {
assertEquals(0,
TransformationUtils.getExifOrientationDegrees(ExifInterface.ORIENTATION_NORMAL));
assertEquals(90,
TransformationUtils.getExifOrientationDegrees(ExifInterface.ORIENTATION_TRANSPOSE));
assertEquals(90,
TransformationUtils.getExifOrientationDegrees(ExifInterface.ORIENTATION_ROTATE_90));
assertEquals(180,
TransformationUtils.getExifOrientationDegrees(ExifInterface.ORIENTATION_ROTATE_180));
assertEquals(180,
TransformationUtils.getExifOrientationDegrees(ExifInterface.ORIENTATION_FLIP_VERTICAL));
assertEquals(270,
TransformationUtils.getExifOrientationDegrees(ExifInterface.ORIENTATION_TRANSVERSE));
assertEquals(270,
TransformationUtils.getExifOrientationDegrees(ExifInterface.ORIENTATION_ROTATE_270));
}
@Test
public void testRotateImage() {
Bitmap toRotate = Bitmap.createBitmap(2, 2, Bitmap.Config.ARGB_8888);
Bitmap zero = TransformationUtils.rotateImage(toRotate, 0);
assertTrue(toRotate == zero);
Bitmap ninety = TransformationUtils.rotateImage(toRotate, 90);
assertThat(Shadows.shadowOf(ninety).getDescription()).contains("rotate=90.0");
assertEquals(toRotate.getWidth(), toRotate.getHeight());
}
@Test
public void testRotateImageExifReturnsGivenBitmapIfRotationIsNormal() {
Bitmap toRotate = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_4444);
// Use assertTrue because Robolectric incorrectly implements equality for Bitmaps. We want
// not just an identical Bitmap, but our original Bitmap object back.
Bitmap rotated =
TransformationUtils.rotateImageExif(bitmapPool, toRotate, ExifInterface.ORIENTATION_NORMAL);
assertTrue(toRotate == rotated);
}
@Test
public void testRotateImageExifReturnsGivenBitmapIfRotationIsUndefined() {
Bitmap toRotate = Bitmap.createBitmap(100, 100, Bitmap.Config.RGB_565);
// Use assertTrue because Robolectric incorrectly implements equality for Bitmaps. We want
// not just an identical Bitmap, but our original Bitmap object back.
Bitmap rotated = TransformationUtils.rotateImageExif(bitmapPool, toRotate,
ExifInterface.ORIENTATION_UNDEFINED);
assertTrue(toRotate == rotated);
}
@Test
public void testRotateImageExifReturnsGivenBitmapIfOrientationIsInvalid() {
Bitmap toRotate = Bitmap.createBitmap(200, 100, Bitmap.Config.ARGB_8888);
// Use assertTrue because Robolectric incorrectly implements equality for Bitmaps. We want
// not just an identical Bitmap, but our original Bitmap object back.
Bitmap rotated = TransformationUtils.rotateImageExif(bitmapPool, toRotate, -1);
assertTrue(toRotate == rotated);
}
@Test
public void testRotateImageExifHandlesBitmapsWithNullConfigs() {
Bitmap toRotate = Bitmap.createBitmap(100, 100, Bitmap.Config.RGB_565);
Shadows.shadowOf(toRotate).setConfig(null);
Bitmap rotated = TransformationUtils.rotateImageExif(bitmapPool, toRotate,
ExifInterface.ORIENTATION_ROTATE_180);
assertEquals(Bitmap.Config.ARGB_8888, rotated.getConfig());
}
@Test
public void testInitializeMatrixSetsScaleIfFlipHorizontal() {
Matrix matrix = mock(Matrix.class);
TransformationUtils
.initializeMatrixForRotation(ExifInterface.ORIENTATION_FLIP_HORIZONTAL, matrix);
verify(matrix).setScale(-1, 1);
}
@Test
public void testInitializeMatrixSetsScaleAndRotateIfFlipVertical() {
Matrix matrix = mock(Matrix.class);
TransformationUtils
.initializeMatrixForRotation(ExifInterface.ORIENTATION_FLIP_VERTICAL, matrix);
verify(matrix).setRotate(180);
verify(matrix).postScale(-1, 1);
}
@Test
public void testInitializeMatrixSetsScaleAndRotateIfTranspose() {
Matrix matrix = mock(Matrix.class);
TransformationUtils.initializeMatrixForRotation(ExifInterface.ORIENTATION_TRANSPOSE, matrix);
verify(matrix).setRotate(90);
verify(matrix).postScale(-1, 1);
}
@Test
public void testInitializeMatrixSetsScaleAndRotateIfTransverse() {
Matrix matrix = mock(Matrix.class);
TransformationUtils.initializeMatrixForRotation(ExifInterface.ORIENTATION_TRANSVERSE, matrix);
verify(matrix).setRotate(-90);
verify(matrix).postScale(-1, 1);
}
@Test
public void testInitializeMatrixSetsRotateOnRotation() {
Matrix matrix = mock(Matrix.class);
TransformationUtils.initializeMatrixForRotation(ExifInterface.ORIENTATION_ROTATE_90, matrix);
verify(matrix).setRotate(90);
TransformationUtils.initializeMatrixForRotation(ExifInterface.ORIENTATION_ROTATE_180, matrix);
verify(matrix).setRotate(180);
TransformationUtils.initializeMatrixForRotation(ExifInterface.ORIENTATION_ROTATE_270, matrix);
verify(matrix).setRotate(-90);
}
@Implements(Bitmap.class)
public static class AlphaShadowBitmap extends ShadowBitmap {
@Implementation
public static Bitmap createBitmap(int width, int height, Bitmap.Config config) {
// Robolectric doesn't match the framework behavior with null configs, so we have to do so
// here.
Preconditions.checkNotNull("Config must not be null");
return ShadowBitmap.createBitmap(width, height, config);
}
}
}