package com.bumptech.glide.load.resource.bitmap; import static com.google.common.collect.Range.closed; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.mockito.Matchers.anyInt; import static org.mockito.Mockito.when; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Build; import android.util.DisplayMetrics; import com.bumptech.glide.load.DecodeFormat; import com.bumptech.glide.load.ImageHeaderParser; import com.bumptech.glide.load.Options; import com.bumptech.glide.load.engine.Resource; import com.bumptech.glide.load.engine.bitmap_recycle.ArrayPool; import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool; import com.bumptech.glide.load.resource.bitmap.DownsamplerTest.AllocationSizeBitmap; import com.bumptech.glide.tests.Util; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Matchers; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; 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 = 19, shadows = AllocationSizeBitmap.class) public class DownsamplerTest { @Mock private BitmapPool bitmapPool; @Mock private ArrayPool byteArrayPool; private Downsampler downsampler; private Options options; private int initialSdkVersion; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); options = new Options(); DisplayMetrics displayMetrics = RuntimeEnvironment.application.getResources().getDisplayMetrics(); when(byteArrayPool.get(anyInt(), Matchers.eq(byte[].class))) .thenReturn(new byte[ArrayPool.STANDARD_BUFFER_SIZE_BYTES]); List<ImageHeaderParser> parsers = new ArrayList<ImageHeaderParser>(); parsers.add(new DefaultImageHeaderParser()); downsampler = new Downsampler(parsers, displayMetrics, bitmapPool, byteArrayPool); initialSdkVersion = Build.VERSION.SDK_INT; } @After public void tearDown() { Util.setSdkVersionInt(initialSdkVersion); } @Test public void testAlwaysArgb8888() throws IOException { Bitmap rgb565 = Bitmap.createBitmap(100, 100, Bitmap.Config.RGB_565); InputStream stream = compressBitmap(rgb565, Bitmap.CompressFormat.JPEG); options.set(Downsampler.DECODE_FORMAT, DecodeFormat.PREFER_ARGB_8888); Resource<Bitmap> result = downsampler.decode(stream, 100, 100, options); assertEquals(Bitmap.Config.ARGB_8888, result.get().getConfig()); } @Test public void testPreferRgb565() throws IOException { Bitmap rgb565 = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); InputStream stream = compressBitmap(rgb565, Bitmap.CompressFormat.JPEG); options.set(Downsampler.DECODE_FORMAT, DecodeFormat.PREFER_RGB_565); Resource<Bitmap> result = downsampler.decode(stream, 100, 100, options); assertEquals(Bitmap.Config.RGB_565, result.get().getConfig()); } @Test public void testCalculateScaling_withInvalidSourceSizes_doesNotCrash() { runScaleTest(0, 0, 100, 100, DownsampleStrategy.AT_MOST, 0, 0); runScaleTest(-1, -1, 100, 100, DownsampleStrategy.AT_MOST, -1, -1); runScaleTest(0, 0, 100, 100, DownsampleStrategy.AT_LEAST, 0, 0); runScaleTest(-1, -1, 100, 100, DownsampleStrategy.CENTER_OUTSIDE, -1, -1); } @Test public void testCalculateScaling_withAtMost() { DownsampleStrategy strategy = DownsampleStrategy.AT_MOST; runScaleTest(100, 100, 100, 100, strategy, 100, 100); runScaleTest(200, 200, 100, 100, strategy, 100, 100); runScaleTest(400, 400, 100, 100, strategy, 100, 100); runScaleTest(300, 300, 100, 100, strategy, 75, 75); runScaleTest(799, 100, 100, 100, strategy, 100, 13); runScaleTest(800, 100, 100, 100, strategy, 100, 13); runScaleTest(801, 100, 100, 100, strategy, 50, 6); runScaleTest(100, 800, 100, 100, strategy, 13, 100); runScaleTest(87, 78, 100, 100, strategy, 87, 78); } @Test public void testCalculateScaling_withAtLeast() { DownsampleStrategy strategy = DownsampleStrategy.AT_LEAST; runScaleTest(100, 100, 100, 100, strategy, 100, 100); runScaleTest(200, 200, 100, 100, strategy, 100, 100); runScaleTest(400, 400, 100, 100, strategy, 100, 100); runScaleTest(300, 300, 100, 100, strategy, 150, 150); runScaleTest(799, 100, 100, 100, strategy, 799, 100); runScaleTest(800, 100, 100, 100, strategy, 800, 100); runScaleTest(801, 100, 100, 100, strategy, 801, 100); runScaleTest(100, 800, 100, 100, strategy, 100, 800); runScaleTest(87, 78, 100, 100, strategy, 87, 78); } @Test public void testCalculateScaling_withCenterInside() { DownsampleStrategy strategy = DownsampleStrategy.FIT_CENTER; runScaleTest(100, 100, 100, 100, strategy, 100, 100); runScaleTest(200, 200, 100, 100, strategy, 100, 100); runScaleTest(400, 400, 100, 100, strategy, 100, 100); runScaleTest(300, 300, 100, 100, strategy, 100, 100); runScaleTest(799, 100, 100, 100, strategy, 100, 13); runScaleTest(800, 100, 100, 100, strategy, 100, 13); runScaleTest(801, 100, 100, 100, strategy, 100, 13); runScaleTest(100, 800, 100, 100, strategy, 13, 100); runScaleTest(87, 78, 100, 100, strategy, 100, 90); } @Test public void testCalculateScaling_withCenterOutside() { DownsampleStrategy strategy = DownsampleStrategy.CENTER_OUTSIDE; runScaleTest(100, 100, 100, 100, strategy, 100, 100); runScaleTest(200, 200, 100, 100, strategy, 100, 100); runScaleTest(400, 400, 100, 100, strategy, 100, 100); runScaleTest(300, 300, 100, 100, strategy, 100, 100); runScaleTest(799, 100, 100, 100, strategy, 799, 100); runScaleTest(800, 100, 100, 100, strategy, 800, 100); runScaleTest(801, 100, 100, 100, strategy, 801, 100); runScaleTest(100, 800, 100, 100, strategy, 100, 800); runScaleTest(87, 78, 100, 100, strategy, 112, 100); } @Test public void testCalculateScaling_withNone() { DownsampleStrategy strategy = DownsampleStrategy.NONE; runScaleTest(100, 100, 100, 100, strategy, 100, 100); runScaleTest(200, 200, 100, 100, strategy, 200, 200); runScaleTest(400, 400, 100, 100, strategy, 400, 400); runScaleTest(300, 300, 100, 100, strategy, 300, 300); runScaleTest(799, 100, 100, 100, strategy, 799, 100); runScaleTest(800, 100, 100, 100, strategy, 800, 100); runScaleTest(801, 100, 100, 100, strategy, 801, 100); runScaleTest(100, 800, 100, 100, strategy, 100, 800); runScaleTest(87, 78, 100, 100, strategy, 87, 78); } // BitmapFactory does not support downsampling wbmp files on platforms <=M. See b/27305903. @Test public void testCalculateScaling_withWbmp() { Util.setSdkVersionInt(23); DownsampleStrategy strategy = DownsampleStrategy.FIT_CENTER; BitmapFactory.Options options = new BitmapFactory.Options(); options.outMimeType = "image/vnd.wap.wbmp"; runScaleTest(100, 100, 100, 100, strategy, 100, 100, options); runScaleTest(200, 200, 100, 100, strategy, 100, 100, options); runScaleTest(400, 400, 100, 100, strategy, 100, 100, options); runScaleTest(300, 300, 100, 100, strategy, 100, 100, options); runScaleTest(799, 100, 100, 100, strategy, 100, 13, options); runScaleTest(800, 100, 100, 100, strategy, 100, 13, options); runScaleTest(801, 100, 100, 100, strategy, 100, 13, options); runScaleTest(100, 800, 100, 100, strategy, 13, 100, options); runScaleTest(87, 78, 100, 100, strategy, 100, 90, options); } private static void runScaleTest(int sourceWidth, int sourceHeight, int targetWidth, int targetHeight, DownsampleStrategy strategy, int expectedWidth, int expectedHeight) { runScaleTest(sourceWidth, sourceHeight, targetWidth, targetHeight, strategy, expectedWidth, expectedHeight, new BitmapFactory.Options()); } private static void runScaleTest(int sourceWidth, int sourceHeight, int targetWidth, int targetHeight, DownsampleStrategy strategy, int expectedWidth, int expectedHeight, BitmapFactory.Options options) { Downsampler.calculateScaling(strategy, 0, sourceWidth, sourceHeight, targetWidth, targetHeight, options); assertSize(sourceWidth, sourceHeight, expectedWidth, expectedHeight, options); } private static void assertSize(int sourceWidth, int sourceHeight, int expectedWidth, int expectedHeight, BitmapFactory.Options options) { float sampleSize = Math.max(1, options.inSampleSize); int downsampledWidth = (int) ((sourceWidth / sampleSize) + 0.5f); int downsampledHeight = (int) ((sourceHeight / sampleSize) + 0.5f); float scaleFactor = options.inScaled && options.inTargetDensity > 0 && options.inDensity > 0 ? options.inTargetDensity / (float) options.inDensity : 1f; int scaledWidth = (int) Math.ceil(downsampledWidth * scaleFactor); int scaledHeight = (int) Math.ceil(downsampledHeight * scaleFactor); assertThat(scaledWidth).isIn(closed(expectedWidth, expectedWidth + 1)); assertThat(scaledHeight).isIn(closed(expectedHeight, expectedHeight + 1)); } private InputStream compressBitmap(Bitmap bitmap, Bitmap.CompressFormat compressFormat) throws FileNotFoundException { ByteArrayOutputStream os = new ByteArrayOutputStream(); bitmap.compress(compressFormat, 100, os); return new ByteArrayInputStream(os.toByteArray()); } // Robolectric doesn't implement getAllocationByteCount correctly. @Implements(Bitmap.class) public static class AllocationSizeBitmap extends ShadowBitmap { @Implementation public int getAllocationByteCount() { return getWidth() * getHeight() * (getConfig() == Bitmap.Config.ARGB_8888 ? 4 : 2); } } }