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);
}
}
}