blob: e9258f990c4994d885f6829c954df42a987b4291 [file] [log] [blame]
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.graphics.cts;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeNoException;
import static org.junit.Assume.assumeNotNull;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorSpace;
import android.graphics.ColorSpace.Named;
import android.graphics.ImageDecoder;
import android.graphics.LinearGradient;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Picture;
import android.graphics.Shader;
import android.hardware.HardwareBuffer;
import android.os.Debug;
import android.os.Parcel;
import android.os.StrictMode;
import android.util.DisplayMetrics;
import android.view.Surface;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.LargeTest;
import androidx.test.filters.SmallTest;
import com.android.compatibility.common.util.BitmapUtils;
import com.android.compatibility.common.util.ColorUtils;
import com.android.compatibility.common.util.WidgetTestUtils;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import junitparams.JUnitParamsRunner;
import junitparams.Parameters;
@SmallTest
@RunWith(JUnitParamsRunner.class)
public class BitmapTest {
// small alpha values cause color values to be pre-multiplied down, losing accuracy
private static final int PREMUL_COLOR = Color.argb(2, 255, 254, 253);
private static final int PREMUL_ROUNDED_COLOR = Color.argb(2, 255, 255, 255);
private static final int PREMUL_STORED_COLOR = Color.argb(2, 2, 2, 2);
private static final BitmapFactory.Options HARDWARE_OPTIONS = createHardwareBitmapOptions();
static {
System.loadLibrary("ctsgraphics_jni");
}
private Resources mRes;
private Bitmap mBitmap;
private BitmapFactory.Options mOptions;
public static List<ColorSpace> getRgbColorSpaces() {
List<ColorSpace> rgbColorSpaces;
rgbColorSpaces = new ArrayList<ColorSpace>();
for (ColorSpace.Named e : ColorSpace.Named.values()) {
ColorSpace cs = ColorSpace.get(e);
if (cs.getModel() != ColorSpace.Model.RGB) {
continue;
}
if (((ColorSpace.Rgb) cs).getTransferParameters() == null) {
continue;
}
rgbColorSpaces.add(cs);
}
return rgbColorSpaces;
}
@Before
public void setup() {
mRes = InstrumentationRegistry.getTargetContext().getResources();
mOptions = new BitmapFactory.Options();
mOptions.inScaled = false;
mBitmap = BitmapFactory.decodeResource(mRes, R.drawable.start, mOptions);
}
@Test(expected=IllegalStateException.class)
public void testCompressRecycled() {
mBitmap.recycle();
mBitmap.compress(CompressFormat.JPEG, 0, null);
}
@Test(expected=NullPointerException.class)
public void testCompressNullStream() {
mBitmap.compress(CompressFormat.JPEG, 0, null);
}
@Test(expected=IllegalArgumentException.class)
public void testCompressQualityTooLow() {
mBitmap.compress(CompressFormat.JPEG, -1, new ByteArrayOutputStream());
}
@Test(expected=IllegalArgumentException.class)
public void testCompressQualityTooHigh() {
mBitmap.compress(CompressFormat.JPEG, 101, new ByteArrayOutputStream());
}
private static Object[] compressFormats() {
return CompressFormat.values();
}
@Test
@Parameters(method = "compressFormats")
public void testCompress(CompressFormat format) {
assertTrue(mBitmap.compress(format, 50, new ByteArrayOutputStream()));
}
private Bitmap decodeBytes(byte[] bytes) {
ByteBuffer buffer = ByteBuffer.wrap(bytes);
ImageDecoder.Source src = ImageDecoder.createSource(buffer);
try {
return ImageDecoder.decodeBitmap(src, (decoder, info, s) -> {
decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
});
} catch (IOException e) {
fail("Failed to decode with " + e);
return null;
}
}
// There are three color components and
// each should be within a square difference of 15 * 15.
private static final int MSE_MARGIN = 3 * (15 * 15);
@Test
public void testCompressWebpLossy() {
// For qualities < 100, WEBP performs a lossy decode.
byte[] last = null;
Bitmap lastBitmap = null;
for (int quality : new int[] { 25, 50, 80, 99 }) {
ByteArrayOutputStream webp = new ByteArrayOutputStream();
assertTrue(mBitmap.compress(CompressFormat.WEBP, quality, webp));
byte[] webpCompressed = webp.toByteArray();
ByteArrayOutputStream webpLossy = new ByteArrayOutputStream();
assertTrue(mBitmap.compress(CompressFormat.WEBP_LOSSY, quality, webpLossy));
byte[] webpLossyCompressed = webpLossy.toByteArray();
assertTrue("Compression did not match at quality " + quality,
Arrays.equals(webpCompressed, webpLossyCompressed));
Bitmap result = decodeBytes(webpCompressed);
if (last != null) {
// Higher quality will generally result in a larger file.
assertTrue(webpCompressed.length > last.length);
if (!BitmapUtils.compareBitmapsMse(lastBitmap, result, MSE_MARGIN, true, false)) {
fail("Bad comparison for quality " + quality);
}
}
last = webpCompressed;
lastBitmap = result;
}
}
@Test
@Parameters({ "0", "50", "80", "99", "100" })
public void testCompressWebpLossless(int quality) {
ByteArrayOutputStream webp = new ByteArrayOutputStream();
assertTrue(mBitmap.compress(CompressFormat.WEBP_LOSSLESS, quality, webp));
byte[] webpCompressed = webp.toByteArray();
Bitmap result = decodeBytes(webpCompressed);
assertTrue("WEBP_LOSSLESS did not losslessly compress at quality " + quality,
BitmapUtils.compareBitmaps(mBitmap, result));
}
@Test
public void testCompressWebp100MeansLossless() {
ByteArrayOutputStream webp = new ByteArrayOutputStream();
assertTrue(mBitmap.compress(CompressFormat.WEBP, 100, webp));
byte[] webpCompressed = webp.toByteArray();
Bitmap result = decodeBytes(webpCompressed);
assertTrue("WEBP_LOSSLESS did not losslessly compress at quality 100",
BitmapUtils.compareBitmaps(mBitmap, result));
}
@Test
@Parameters(method = "compressFormats")
public void testCompressAlpha8Fails(CompressFormat format) {
Bitmap bitmap = Bitmap.createBitmap(1, 1, Config.ALPHA_8);
assertFalse("Incorrectly compressed ALPHA_8 to " + format,
bitmap.compress(format, 50, new ByteArrayOutputStream()));
if (format == CompressFormat.WEBP) {
// Skip the native test, since the NDK just has equivalents for
// WEBP_LOSSY and WEBP_LOSSLESS.
return;
}
byte[] storage = new byte[16 * 1024];
OutputStream stream = new ByteArrayOutputStream();
assertFalse("Incorrectly compressed ALPHA_8 with the NDK to " + format,
nCompress(bitmap, nativeCompressFormat(format), 50, stream, storage));
}
@Test(expected=IllegalStateException.class)
public void testCopyRecycled() {
mBitmap.recycle();
mBitmap.copy(Config.RGB_565, false);
}
@Test
public void testCopy() {
mBitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
Bitmap bitmap = mBitmap.copy(Config.ARGB_8888, false);
WidgetTestUtils.assertEquals(mBitmap, bitmap);
}
@Test
public void testCopyConfigs() {
Config[] supportedConfigs = new Config[] {
Config.ALPHA_8, Config.RGB_565, Config.ARGB_8888, Config.RGBA_F16,
};
for (Config src : supportedConfigs) {
for (Config dst : supportedConfigs) {
Bitmap srcBitmap = Bitmap.createBitmap(1, 1, src);
srcBitmap.eraseColor(Color.WHITE);
Bitmap dstBitmap = srcBitmap.copy(dst, false);
assertNotNull("Should support copying from " + src + " to " + dst,
dstBitmap);
if (Config.ALPHA_8 == dst || Config.ALPHA_8 == src) {
// Color will be opaque but color information will be lost.
assertEquals("Color should be black when copying from " + src + " to "
+ dst, Color.BLACK, dstBitmap.getPixel(0, 0));
} else {
assertEquals("Color should be preserved when copying from " + src + " to "
+ dst, Color.WHITE, dstBitmap.getPixel(0, 0));
}
}
}
}
@Test(expected=IllegalArgumentException.class)
public void testCopyMutableHwBitmap() {
mBitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
mBitmap.copy(Config.HARDWARE, true);
}
@Test(expected=RuntimeException.class)
public void testCopyPixelsToBufferUnsupportedBufferClass() {
final int pixSize = mBitmap.getRowBytes() * mBitmap.getHeight();
mBitmap.copyPixelsToBuffer(CharBuffer.allocate(pixSize));
}
@Test(expected=RuntimeException.class)
public void testCopyPixelsToBufferBufferTooSmall() {
final int pixSize = mBitmap.getRowBytes() * mBitmap.getHeight();
final int tooSmall = pixSize / 2;
mBitmap.copyPixelsToBuffer(ByteBuffer.allocate(tooSmall));
}
@Test
public void testCopyPixelsToBuffer() {
final int pixSize = mBitmap.getRowBytes() * mBitmap.getHeight();
ByteBuffer byteBuf = ByteBuffer.allocate(pixSize);
assertEquals(0, byteBuf.position());
mBitmap.copyPixelsToBuffer(byteBuf);
assertEquals(pixSize, byteBuf.position());
ShortBuffer shortBuf = ShortBuffer.allocate(pixSize);
assertEquals(0, shortBuf.position());
mBitmap.copyPixelsToBuffer(shortBuf);
assertEquals(pixSize >> 1, shortBuf.position());
IntBuffer intBuf1 = IntBuffer.allocate(pixSize);
assertEquals(0, intBuf1.position());
mBitmap.copyPixelsToBuffer(intBuf1);
assertEquals(pixSize >> 2, intBuf1.position());
Bitmap bitmap = Bitmap.createBitmap(mBitmap.getWidth(), mBitmap.getHeight(),
mBitmap.getConfig());
intBuf1.position(0); // copyPixelsToBuffer adjusted the position, so rewind to start
bitmap.copyPixelsFromBuffer(intBuf1);
IntBuffer intBuf2 = IntBuffer.allocate(pixSize);
bitmap.copyPixelsToBuffer(intBuf2);
assertEquals(pixSize >> 2, intBuf2.position());
assertEquals(intBuf1.position(), intBuf2.position());
int size = intBuf1.position();
intBuf1.position(0);
intBuf2.position(0);
for (int i = 0; i < size; i++) {
assertEquals("mismatching pixels at position " + i, intBuf1.get(), intBuf2.get());
}
}
@Test
public void testCreateBitmap1() {
int[] colors = createColors(100);
Bitmap bitmap = Bitmap.createBitmap(colors, 10, 10, Config.RGB_565);
assertFalse(bitmap.isMutable());
Bitmap ret = Bitmap.createBitmap(bitmap);
assertNotNull(ret);
assertFalse(ret.isMutable());
assertEquals(10, ret.getWidth());
assertEquals(10, ret.getHeight());
assertEquals(Config.RGB_565, ret.getConfig());
assertEquals(ANDROID_BITMAP_FORMAT_RGB_565, nGetFormat(ret));
}
@Test(expected=IllegalArgumentException.class)
public void testCreateBitmapNegativeX() {
Bitmap.createBitmap(mBitmap, -100, 50, 50, 200);
}
@Test
public void testCreateBitmap2() {
// special case: output bitmap is equal to the input bitmap
mBitmap = Bitmap.createBitmap(new int[100 * 100], 100, 100, Config.ARGB_8888);
assertFalse(mBitmap.isMutable()); // createBitmap w/ colors should be immutable
Bitmap ret = Bitmap.createBitmap(mBitmap, 0, 0, 100, 100);
assertNotNull(ret);
assertFalse(ret.isMutable()); // createBitmap from subset should be immutable
assertTrue(mBitmap.equals(ret));
//normal case
mBitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
ret = Bitmap.createBitmap(mBitmap, 10, 10, 50, 50);
assertNotNull(ret);
assertFalse(mBitmap.equals(ret));
assertEquals(ANDROID_BITMAP_FORMAT_RGBA_8888, nGetFormat(mBitmap));
}
@Test(expected=IllegalArgumentException.class)
public void testCreateBitmapNegativeXY() {
mBitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
// abnormal case: x and/or y less than 0
Bitmap.createBitmap(mBitmap, -1, -1, 10, 10, null, false);
}
@Test(expected=IllegalArgumentException.class)
public void testCreateBitmapNegativeWidthHeight() {
mBitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
// abnormal case: width and/or height less than 0
Bitmap.createBitmap(mBitmap, 1, 1, -10, -10, null, false);
}
@Test(expected=IllegalArgumentException.class)
public void testCreateBitmapXRegionTooWide() {
mBitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
// abnormal case: (x + width) bigger than source bitmap's width
Bitmap.createBitmap(mBitmap, 10, 10, 95, 50, null, false);
}
@Test(expected=IllegalArgumentException.class)
public void testCreateBitmapYRegionTooTall() {
mBitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
// abnormal case: (y + height) bigger than source bitmap's height
Bitmap.createBitmap(mBitmap, 10, 10, 50, 95, null, false);
}
@Test(expected=IllegalArgumentException.class)
public void testCreateMutableBitmapWithHardwareConfig() {
Bitmap.createBitmap(100, 100, Config.HARDWARE);
}
@Test
public void testCreateBitmap3() {
// special case: output bitmap is equal to the input bitmap
mBitmap = Bitmap.createBitmap(new int[100 * 100], 100, 100, Config.ARGB_8888);
Bitmap ret = Bitmap.createBitmap(mBitmap, 0, 0, 100, 100, null, false);
assertNotNull(ret);
assertFalse(ret.isMutable()); // subset should be immutable
assertTrue(mBitmap.equals(ret));
// normal case
mBitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
ret = Bitmap.createBitmap(mBitmap, 10, 10, 50, 50, new Matrix(), true);
assertTrue(ret.isMutable());
assertNotNull(ret);
assertFalse(mBitmap.equals(ret));
}
@Test
public void testCreateBitmapFromHardwareBitmap() {
Bitmap hardwareBitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot,
HARDWARE_OPTIONS);
assertEquals(Config.HARDWARE, hardwareBitmap.getConfig());
Bitmap ret = Bitmap.createBitmap(hardwareBitmap, 0, 0, 96, 96, null, false);
assertEquals(Config.HARDWARE, ret.getConfig());
assertFalse(ret.isMutable());
}
@Test
public void testCreateBitmap4() {
Bitmap ret = Bitmap.createBitmap(100, 200, Config.RGB_565);
assertNotNull(ret);
assertTrue(ret.isMutable());
assertEquals(100, ret.getWidth());
assertEquals(200, ret.getHeight());
assertEquals(Config.RGB_565, ret.getConfig());
}
private static void verify2x2BitmapContents(int[] expected, Bitmap observed) {
ColorUtils.verifyColor(expected[0], observed.getPixel(0, 0));
ColorUtils.verifyColor(expected[1], observed.getPixel(1, 0));
ColorUtils.verifyColor(expected[2], observed.getPixel(0, 1));
ColorUtils.verifyColor(expected[3], observed.getPixel(1, 1));
}
@Test
public void testCreateBitmap_matrix() {
int[] colorArray = new int[] { Color.RED, Color.GREEN, Color.BLUE, Color.BLACK };
Bitmap src = Bitmap.createBitmap(2, 2, Config.ARGB_8888);
assertTrue(src.isMutable());
src.setPixels(colorArray,0, 2, 0, 0, 2, 2);
// baseline
verify2x2BitmapContents(colorArray, src);
// null
Bitmap dst = Bitmap.createBitmap(src, 0, 0, 2, 2, null, false);
assertTrue(dst.isMutable());
verify2x2BitmapContents(colorArray, dst);
// identity matrix
Matrix matrix = new Matrix();
dst = Bitmap.createBitmap(src, 0, 0, 2, 2, matrix, false);
assertTrue(dst.isMutable());
verify2x2BitmapContents(colorArray, dst);
// big scale - only red visible
matrix.setScale(10, 10);
dst = Bitmap.createBitmap(src, 0, 0, 2, 2, matrix, false);
assertTrue(dst.isMutable());
verify2x2BitmapContents(new int[] { Color.RED, Color.RED, Color.RED, Color.RED }, dst);
// rotation
matrix.setRotate(90);
dst = Bitmap.createBitmap(src, 0, 0, 2, 2, matrix, false);
assertTrue(dst.isMutable());
verify2x2BitmapContents(
new int[] { Color.BLUE, Color.RED, Color.BLACK, Color.GREEN }, dst);
}
@Test(expected=IllegalArgumentException.class)
public void testCreateBitmapFromColorsNegativeWidthHeight() {
int[] colors = createColors(100);
// abnormal case: width and/or height less than 0
Bitmap.createBitmap(colors, 0, 100, -1, 100, Config.RGB_565);
}
@Test(expected=IllegalArgumentException.class)
public void testCreateBitmapFromColorsIllegalStride() {
int[] colors = createColors(100);
// abnormal case: stride less than width and bigger than -width
Bitmap.createBitmap(colors, 10, 10, 100, 100, Config.RGB_565);
}
@Test(expected=ArrayIndexOutOfBoundsException.class)
public void testCreateBitmapFromColorsNegativeOffset() {
int[] colors = createColors(100);
// abnormal case: offset less than 0
Bitmap.createBitmap(colors, -10, 100, 100, 100, Config.RGB_565);
}
@Test(expected=ArrayIndexOutOfBoundsException.class)
public void testCreateBitmapFromColorsOffsetTooLarge() {
int[] colors = createColors(100);
// abnormal case: (offset + width) bigger than colors' length
Bitmap.createBitmap(colors, 10, 100, 100, 100, Config.RGB_565);
}
@Test(expected=ArrayIndexOutOfBoundsException.class)
public void testCreateBitmapFromColorsScalnlineTooLarge() {
int[] colors = createColors(100);
// abnormal case: (lastScanline + width) bigger than colors' length
Bitmap.createBitmap(colors, 10, 100, 50, 100, Config.RGB_565);
}
@Test
public void testCreateBitmap6() {
int[] colors = createColors(100);
// normal case
Bitmap ret = Bitmap.createBitmap(colors, 5, 10, 10, 5, Config.RGB_565);
assertNotNull(ret);
assertFalse(ret.isMutable());
assertEquals(10, ret.getWidth());
assertEquals(5, ret.getHeight());
assertEquals(Config.RGB_565, ret.getConfig());
}
@Test
public void testCreateBitmap_displayMetrics_mutable() {
DisplayMetrics metrics =
InstrumentationRegistry.getTargetContext().getResources().getDisplayMetrics();
Bitmap bitmap;
bitmap = Bitmap.createBitmap(metrics, 10, 10, Config.ARGB_8888);
assertTrue(bitmap.isMutable());
assertEquals(metrics.densityDpi, bitmap.getDensity());
bitmap = Bitmap.createBitmap(metrics, 10, 10, Config.ARGB_8888);
assertTrue(bitmap.isMutable());
assertEquals(metrics.densityDpi, bitmap.getDensity());
bitmap = Bitmap.createBitmap(metrics, 10, 10, Config.ARGB_8888, true);
assertTrue(bitmap.isMutable());
assertEquals(metrics.densityDpi, bitmap.getDensity());
bitmap = Bitmap.createBitmap(metrics, 10, 10, Config.ARGB_8888, true, ColorSpace.get(
ColorSpace.Named.SRGB));
assertTrue(bitmap.isMutable());
assertEquals(metrics.densityDpi, bitmap.getDensity());
int[] colors = createColors(100);
bitmap = Bitmap.createBitmap(metrics, colors, 0, 10, 10, 10, Config.ARGB_8888);
assertNotNull(bitmap);
assertFalse(bitmap.isMutable());
bitmap = Bitmap.createBitmap(metrics, colors, 10, 10, Config.ARGB_8888);
assertNotNull(bitmap);
assertFalse(bitmap.isMutable());
}
@Test
public void testCreateBitmap_noDisplayMetrics_mutable() {
Bitmap bitmap;
bitmap = Bitmap.createBitmap(10, 10, Config.ARGB_8888);
assertTrue(bitmap.isMutable());
bitmap = Bitmap.createBitmap(10, 10, Config.ARGB_8888, true);
assertTrue(bitmap.isMutable());
bitmap = Bitmap.createBitmap(10, 10, Config.ARGB_8888, true, ColorSpace.get(Named.SRGB));
assertTrue(bitmap.isMutable());
}
@Test
public void testCreateBitmap_displayMetrics_immutable() {
DisplayMetrics metrics =
InstrumentationRegistry.getTargetContext().getResources().getDisplayMetrics();
int[] colors = createColors(100);
Bitmap bitmap;
bitmap = Bitmap.createBitmap(metrics, colors, 0, 10, 10, 10, Config.ARGB_8888);
assertFalse(bitmap.isMutable());
assertEquals(metrics.densityDpi, bitmap.getDensity());
bitmap = Bitmap.createBitmap(metrics, colors, 10, 10, Config.ARGB_8888);
assertFalse(bitmap.isMutable());
assertEquals(metrics.densityDpi, bitmap.getDensity());
}
@Test
public void testCreateBitmap_noDisplayMetrics_immutable() {
int[] colors = createColors(100);
Bitmap bitmap;
bitmap = Bitmap.createBitmap(colors, 0, 10, 10, 10, Config.ARGB_8888);
assertFalse(bitmap.isMutable());
bitmap = Bitmap.createBitmap(colors, 10, 10, Config.ARGB_8888);
assertFalse(bitmap.isMutable());
}
@Test
public void testCreateBitmap_Picture_immutable() {
Picture picture = new Picture();
Canvas canvas = picture.beginRecording(200, 100);
Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
p.setColor(0x88FF0000);
canvas.drawCircle(50, 50, 40, p);
p.setColor(Color.GREEN);
p.setTextSize(30);
canvas.drawText("Pictures", 60, 60, p);
picture.endRecording();
Bitmap bitmap;
bitmap = Bitmap.createBitmap(picture);
assertFalse(bitmap.isMutable());
bitmap = Bitmap.createBitmap(picture, 100, 100, Config.HARDWARE);
assertFalse(bitmap.isMutable());
assertNotNull(bitmap.getColorSpace());
bitmap = Bitmap.createBitmap(picture, 100, 100, Config.ARGB_8888);
assertFalse(bitmap.isMutable());
}
@Test
public void testCreateScaledBitmap() {
mBitmap = Bitmap.createBitmap(100, 200, Config.RGB_565);
assertTrue(mBitmap.isMutable());
Bitmap ret = Bitmap.createScaledBitmap(mBitmap, 50, 100, false);
assertNotNull(ret);
assertEquals(50, ret.getWidth());
assertEquals(100, ret.getHeight());
assertTrue(ret.isMutable());
}
@Test
public void testWrapHardwareBufferSucceeds() {
try (HardwareBuffer hwBuffer = createTestBuffer(128, 128, false)) {
Bitmap bitmap = Bitmap.wrapHardwareBuffer(hwBuffer, ColorSpace.get(Named.SRGB));
assertNotNull(bitmap);
bitmap.recycle();
}
}
@Test(expected = IllegalArgumentException.class)
public void testWrapHardwareBufferWithInvalidUsageFails() {
try (HardwareBuffer hwBuffer = HardwareBuffer.create(512, 512, HardwareBuffer.RGBA_8888, 1,
HardwareBuffer.USAGE_CPU_WRITE_RARELY)) {
Bitmap bitmap = Bitmap.wrapHardwareBuffer(hwBuffer, ColorSpace.get(Named.SRGB));
}
}
@Test(expected = IllegalArgumentException.class)
public void testWrapHardwareBufferWithRgbBufferButNonRgbColorSpaceFails() {
try (HardwareBuffer hwBuffer = HardwareBuffer.create(512, 512, HardwareBuffer.RGBA_8888, 1,
HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE)) {
Bitmap bitmap = Bitmap.wrapHardwareBuffer(hwBuffer, ColorSpace.get(Named.CIE_LAB));
}
}
@Test
public void testWrapHardwareBufferFor1010102BufferSucceeds() {
HardwareBuffer hwBufferMaybe = null;
try {
hwBufferMaybe = HardwareBuffer.create(128, 128, HardwareBuffer.RGBA_1010102, 1,
HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE);
} catch (IllegalArgumentException e) {
assumeNoException("Creating a 1010102 HW buffer was not supported", e);
}
assumeNotNull(hwBufferMaybe);
try (HardwareBuffer buffer = hwBufferMaybe) {
Bitmap bitmap = Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(Named.SRGB));
assertNotNull(bitmap);
bitmap.recycle();
}
}
private void assertMatches(HardwareBuffer hwBuffer, HardwareBuffer hwBuffer2) {
assertEquals(hwBuffer, hwBuffer2);
assertEquals(hwBuffer.hashCode(), hwBuffer2.hashCode());
assertEquals(hwBuffer.getWidth(), hwBuffer2.getWidth());
assertEquals(hwBuffer.getHeight(), hwBuffer2.getHeight());
assertEquals(hwBuffer.getFormat(), hwBuffer2.getFormat());
assertEquals(hwBuffer.getLayers(), hwBuffer2.getLayers());
assertEquals(hwBuffer.getUsage(), hwBuffer2.getUsage());
}
@Test
public void testGetHardwareBufferMatchesWrapped() {
try (HardwareBuffer hwBuffer = createTestBuffer(128, 128, false)) {
Bitmap bitmap = Bitmap.wrapHardwareBuffer(hwBuffer, ColorSpace.get(Named.SRGB));
assertNotNull(bitmap);
try (HardwareBuffer hwBuffer2 = bitmap.getHardwareBuffer()) {
assertNotNull(hwBuffer2);
assertMatches(hwBuffer, hwBuffer2);
}
bitmap.recycle();
}
}
private static Object[] parametersFor_testGetHardwareBufferConfig() {
return new Object[] {Config.ARGB_8888, Config.RGBA_F16, Config.RGB_565};
}
@Test
@Parameters(method = "parametersFor_testGetHardwareBufferConfig")
public void testGetHardwareBufferConfig(Config config) {
Bitmap bitmap = Bitmap.createBitmap(10, 10, config);
bitmap = bitmap.copy(Config.HARDWARE, false);
if (bitmap == null) {
fail("Failed to copy to HARDWARE with Config " + config);
}
try (HardwareBuffer hwBuffer = bitmap.getHardwareBuffer()) {
assertNotNull(hwBuffer);
assertEquals(hwBuffer.getWidth(), 10);
assertEquals(hwBuffer.getHeight(), 10);
}
}
@Test
public void testGetHardwareBufferTwice() {
Bitmap bitmap = Bitmap.createBitmap(10, 10, Config.ARGB_8888);
bitmap = bitmap.copy(Config.HARDWARE, false);
try (HardwareBuffer hwBuffer = bitmap.getHardwareBuffer()) {
assertNotNull(hwBuffer);
try (HardwareBuffer hwBuffer2 = bitmap.getHardwareBuffer()) {
assertNotNull(hwBuffer2);
assertMatches(hwBuffer, hwBuffer2);
}
}
}
@Test
public void testGetHardwareBufferMatches() {
Bitmap bitmap = Bitmap.createBitmap(10, 10, Config.ARGB_8888);
bitmap = bitmap.copy(Config.HARDWARE, false);
try (HardwareBuffer hwBuffer = bitmap.getHardwareBuffer()) {
HashSet<HardwareBuffer> set = new HashSet<HardwareBuffer>();
set.add(hwBuffer);
assertTrue(set.contains(bitmap.getHardwareBuffer()));
}
}
@Test(expected = IllegalStateException.class)
public void testGetHardwareBufferNonHardware() {
Bitmap bitmap = Bitmap.createBitmap(10, 10, Config.ARGB_8888);
bitmap.getHardwareBuffer();
}
@Test(expected = IllegalStateException.class)
public void testGetHardwareBufferRecycled() {
Bitmap bitmap = Bitmap.createBitmap(10, 10, Config.ARGB_8888);
bitmap = bitmap.copy(Config.HARDWARE, false);
bitmap.recycle();
bitmap.getHardwareBuffer();
}
@Test
public void testGetHardwareBufferClosed() {
HardwareBuffer hwBuffer = createTestBuffer(128, 128, false);
Bitmap bitmap = Bitmap.wrapHardwareBuffer(hwBuffer, ColorSpace.get(Named.SRGB));
assertNotNull(bitmap);
hwBuffer.close();
try (HardwareBuffer hwBuffer2 = bitmap.getHardwareBuffer()) {
assertNotNull(hwBuffer2);
assertFalse(hwBuffer2.isClosed());
assertNotEquals(hwBuffer, hwBuffer2);
}
bitmap.recycle();
}
@Test
public void testGenerationId() {
Bitmap bitmap = Bitmap.createBitmap(10, 10, Config.ARGB_8888);
int genId = bitmap.getGenerationId();
assertEquals("not expected to change", genId, bitmap.getGenerationId());
bitmap.setDensity(bitmap.getDensity() + 4);
assertEquals("not expected to change", genId, bitmap.getGenerationId());
bitmap.getPixel(0, 0);
assertEquals("not expected to change", genId, bitmap.getGenerationId());
int beforeGenId = bitmap.getGenerationId();
bitmap.eraseColor(Color.WHITE);
int afterGenId = bitmap.getGenerationId();
assertTrue("expected to increase", afterGenId > beforeGenId);
beforeGenId = bitmap.getGenerationId();
bitmap.setPixel(4, 4, Color.BLUE);
afterGenId = bitmap.getGenerationId();
assertTrue("expected to increase again", afterGenId > beforeGenId);
}
@Test
public void testDescribeContents() {
assertEquals(0, mBitmap.describeContents());
}
@Test(expected=IllegalStateException.class)
public void testEraseColorOnRecycled() {
mBitmap.recycle();
mBitmap.eraseColor(0);
}
@Test(expected = IllegalStateException.class)
public void testEraseColorLongOnRecycled() {
mBitmap.recycle();
mBitmap.eraseColor(Color.pack(0));
}
@Test(expected=IllegalStateException.class)
public void testEraseColorOnImmutable() {
mBitmap = BitmapFactory.decodeResource(mRes, R.drawable.start, mOptions);
//abnormal case: bitmap is immutable
mBitmap.eraseColor(0);
}
@Test(expected = IllegalStateException.class)
public void testEraseColorLongOnImmutable() {
mBitmap = BitmapFactory.decodeResource(mRes, R.drawable.start, mOptions);
//abnormal case: bitmap is immutable
mBitmap.eraseColor(Color.pack(0));
}
@Test
public void testEraseColor() {
// normal case
mBitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
mBitmap.eraseColor(0xffff0000);
assertEquals(0xffff0000, mBitmap.getPixel(10, 10));
assertEquals(0xffff0000, mBitmap.getPixel(50, 50));
}
@Test(expected = IllegalArgumentException.class)
public void testGetColorOOB() {
mBitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
mBitmap.getColor(-1, 0);
}
@Test(expected = IllegalArgumentException.class)
public void testGetColorOOB2() {
mBitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
mBitmap.getColor(5, -10);
}
@Test(expected = IllegalArgumentException.class)
public void testGetColorOOB3() {
mBitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
mBitmap.getColor(100, 10);
}
@Test(expected = IllegalArgumentException.class)
public void testGetColorOOB4() {
mBitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
mBitmap.getColor(99, 1000);
}
@Test(expected = IllegalStateException.class)
public void testGetColorRecycled() {
mBitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
mBitmap.recycle();
mBitmap.getColor(0, 0);
}
@Test(expected = IllegalStateException.class)
public void testGetColorHardware() {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.HARDWARE;
mBitmap = BitmapFactory.decodeResource(mRes, R.drawable.start, options);
mBitmap.getColor(50, 50);
}
private static float clamp(float f) {
return clamp(f, 0.0f, 1.0f);
}
private static float clamp(float f, float min, float max) {
return Math.min(Math.max(f, min), max);
}
@Test
public void testGetColor() {
final ColorSpace sRGB = ColorSpace.get(ColorSpace.Named.SRGB);
List<ColorSpace> rgbColorSpaces = getRgbColorSpaces();
for (Config config : new Config[] { Config.ARGB_8888, Config.RGBA_F16, Config.RGB_565 }) {
for (ColorSpace bitmapColorSpace : rgbColorSpaces) {
mBitmap = Bitmap.createBitmap(1, 1, config, /*hasAlpha*/ false,
bitmapColorSpace);
bitmapColorSpace = mBitmap.getColorSpace();
for (ColorSpace eraseColorSpace : rgbColorSpaces) {
for (long wideGamutLong : new long[] {
Color.pack(1.0f, 0.0f, 0.0f, 1.0f, eraseColorSpace),
Color.pack(0.0f, 1.0f, 0.0f, 1.0f, eraseColorSpace),
Color.pack(0.0f, 0.0f, 1.0f, 1.0f, eraseColorSpace)}) {
mBitmap.eraseColor(wideGamutLong);
Color result = mBitmap.getColor(0, 0);
if (mBitmap.getColorSpace().equals(sRGB)) {
assertEquals(mBitmap.getPixel(0, 0), result.toArgb());
}
if (eraseColorSpace.equals(bitmapColorSpace)) {
final Color wideGamutColor = Color.valueOf(wideGamutLong);
ColorUtils.verifyColor("Erasing to Bitmap's ColorSpace "
+ bitmapColorSpace, wideGamutColor, result, .001f);
} else {
Color convertedColor = Color.valueOf(
Color.convert(wideGamutLong, bitmapColorSpace));
if (mBitmap.getConfig() != Config.RGBA_F16) {
// It's possible that we have to clip to fit into the Config.
convertedColor = Color.valueOf(
clamp(convertedColor.red()),
clamp(convertedColor.green()),
clamp(convertedColor.blue()),
convertedColor.alpha(),
bitmapColorSpace);
}
ColorUtils.verifyColor("Bitmap(Config: " + mBitmap.getConfig()
+ ", ColorSpace: " + bitmapColorSpace
+ ") erasing to " + Color.valueOf(wideGamutLong),
convertedColor, result, .03f);
}
}
}
}
}
}
private static class ARGB {
public float alpha;
public float red;
public float green;
public float blue;
ARGB(float alpha, float red, float green, float blue) {
this.alpha = alpha;
this.red = red;
this.green = green;
this.blue = blue;
}
};
@Test
public void testEraseColorLong() {
List<ColorSpace> rgbColorSpaces = getRgbColorSpaces();
for (Config config : new Config[]{Config.ARGB_8888, Config.RGB_565, Config.RGBA_F16}) {
mBitmap = Bitmap.createBitmap(100, 100, config);
// pack SRGB colors into ColorLongs.
for (int color : new int[]{ Color.RED, Color.BLUE, Color.GREEN, Color.BLACK,
Color.WHITE, Color.TRANSPARENT }) {
if (config.equals(Config.RGB_565) && Float.compare(Color.alpha(color), 1.0f) != 0) {
// 565 doesn't support alpha.
continue;
}
mBitmap.eraseColor(Color.pack(color));
// The Bitmap is either SRGB or SRGBLinear (F16). getPixel(), which retrieves the
// color in SRGB, should match exactly.
ColorUtils.verifyColor("Config " + config + " mismatch at 10, 10 ",
color, mBitmap.getPixel(10, 10), 0);
ColorUtils.verifyColor("Config " + config + " mismatch at 50, 50 ",
color, mBitmap.getPixel(50, 50), 0);
}
// Use arbitrary colors in various ColorSpaces. getPixel() should approximately match
// the SRGB version of the color.
for (ARGB color : new ARGB[]{ new ARGB(1.0f, .5f, .5f, .5f),
new ARGB(1.0f, .3f, .6f, .9f),
new ARGB(0.5f, .2f, .8f, .7f) }) {
if (config.equals(Config.RGB_565) && Float.compare(color.alpha, 1.0f) != 0) {
continue;
}
int srgbColor = Color.argb(color.alpha, color.red, color.green, color.blue);
for (ColorSpace cs : rgbColorSpaces) {
long longColor = Color.convert(srgbColor, cs);
mBitmap.eraseColor(longColor);
// These tolerances were chosen by trial and error. It is expected that
// some conversions do not round-trip perfectly.
int tolerance = 1;
if (config.equals(Config.RGB_565)) {
tolerance = 4;
} else if (cs.equals(ColorSpace.get(ColorSpace.Named.SMPTE_C))) {
tolerance = 3;
}
ColorUtils.verifyColor("Config " + config + ", ColorSpace " + cs
+ ", mismatch at 10, 10 ", srgbColor, mBitmap.getPixel(10, 10),
tolerance);
ColorUtils.verifyColor("Config " + config + ", ColorSpace " + cs
+ ", mismatch at 50, 50 ", srgbColor, mBitmap.getPixel(50, 50),
tolerance);
}
}
}
}
@Test
public void testEraseColorOnP3() {
// Use a ColorLong with a different ColorSpace than the Bitmap. getPixel() should
// approximately match the SRGB version of the color.
mBitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888, true,
ColorSpace.get(ColorSpace.Named.DISPLAY_P3));
int srgbColor = Color.argb(.5f, .3f, .6f, .7f);
long acesColor = Color.convert(srgbColor, ColorSpace.get(ColorSpace.Named.ACES));
mBitmap.eraseColor(acesColor);
ColorUtils.verifyColor("Mismatch at 15, 15", srgbColor, mBitmap.getPixel(15, 15), 1);
}
@Test(expected = IllegalArgumentException.class)
public void testEraseColorXYZ() {
mBitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
mBitmap.eraseColor(Color.convert(Color.BLUE, ColorSpace.get(ColorSpace.Named.CIE_XYZ)));
}
@Test(expected = IllegalArgumentException.class)
public void testEraseColorLAB() {
mBitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
mBitmap.eraseColor(Color.convert(Color.BLUE, ColorSpace.get(ColorSpace.Named.CIE_LAB)));
}
@Test(expected = IllegalArgumentException.class)
public void testEraseColorUnknown() {
mBitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
mBitmap.eraseColor(-1L);
}
@Test(expected=IllegalStateException.class)
public void testExtractAlphaFromRecycled() {
mBitmap.recycle();
mBitmap.extractAlpha();
}
@Test
public void testExtractAlpha() {
// normal case
mBitmap = BitmapFactory.decodeResource(mRes, R.drawable.start, mOptions);
Bitmap ret = mBitmap.extractAlpha();
assertNotNull(ret);
int source = mBitmap.getPixel(10, 20);
int result = ret.getPixel(10, 20);
assertEquals(Color.alpha(source), Color.alpha(result));
assertEquals(0xFF, Color.alpha(result));
}
@Test(expected=IllegalStateException.class)
public void testExtractAlphaWithPaintAndOffsetFromRecycled() {
mBitmap.recycle();
mBitmap.extractAlpha(new Paint(), new int[]{0, 1});
}
@Test
public void testExtractAlphaWithPaintAndOffset() {
// normal case
mBitmap = BitmapFactory.decodeResource(mRes, R.drawable.start, mOptions);
Bitmap ret = mBitmap.extractAlpha(new Paint(), new int[]{0, 1});
assertNotNull(ret);
int source = mBitmap.getPixel(10, 20);
int result = ret.getPixel(10, 20);
assertEquals(Color.alpha(source), Color.alpha(result));
assertEquals(0xFF, Color.alpha(result));
}
@Test
public void testGetAllocationByteCount() {
mBitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.ALPHA_8);
int alloc = mBitmap.getAllocationByteCount();
assertEquals(mBitmap.getByteCount(), alloc);
// reconfigure same size
mBitmap.reconfigure(50, 100, Bitmap.Config.ARGB_8888);
assertEquals(mBitmap.getByteCount(), alloc);
assertEquals(mBitmap.getAllocationByteCount(), alloc);
// reconfigure different size
mBitmap.reconfigure(10, 10, Bitmap.Config.ALPHA_8);
assertEquals(mBitmap.getByteCount(), 100);
assertEquals(mBitmap.getAllocationByteCount(), alloc);
}
@Test
public void testGetConfig() {
Bitmap bm0 = Bitmap.createBitmap(100, 200, Bitmap.Config.ALPHA_8);
Bitmap bm1 = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888);
Bitmap bm2 = Bitmap.createBitmap(100, 200, Bitmap.Config.RGB_565);
Bitmap bm3 = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_4444);
assertEquals(Bitmap.Config.ALPHA_8, bm0.getConfig());
assertEquals(Bitmap.Config.ARGB_8888, bm1.getConfig());
assertEquals(Bitmap.Config.RGB_565, bm2.getConfig());
// Attempting to create a 4444 bitmap actually creates an 8888 bitmap.
assertEquals(Bitmap.Config.ARGB_8888, bm3.getConfig());
// Can't call Bitmap.createBitmap with Bitmap.Config.HARDWARE,
// because createBitmap creates mutable bitmap and hardware bitmaps are always immutable,
// so such call will throw an exception.
Bitmap hardwareBitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot,
HARDWARE_OPTIONS);
assertEquals(Bitmap.Config.HARDWARE, hardwareBitmap.getConfig());
}
@Test
public void testGetHeight() {
assertEquals(31, mBitmap.getHeight());
mBitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888);
assertEquals(200, mBitmap.getHeight());
}
@Test
public void testGetNinePatchChunk() {
assertNull(mBitmap.getNinePatchChunk());
}
@Test(expected=IllegalStateException.class)
public void testGetPixelFromRecycled() {
mBitmap.recycle();
mBitmap.getPixel(10, 16);
}
@Test(expected=IllegalArgumentException.class)
public void testGetPixelXTooLarge() {
mBitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.RGB_565);
// abnormal case: x bigger than the source bitmap's width
mBitmap.getPixel(200, 16);
}
@Test(expected=IllegalArgumentException.class)
public void testGetPixelYTooLarge() {
mBitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.RGB_565);
// abnormal case: y bigger than the source bitmap's height
mBitmap.getPixel(10, 300);
}
@Test
public void testGetPixel() {
mBitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.RGB_565);
// normal case 565
mBitmap.setPixel(10, 16, 0xFF << 24);
assertEquals(0xFF << 24, mBitmap.getPixel(10, 16));
// normal case A_8
mBitmap = Bitmap.createBitmap(10, 10, Config.ALPHA_8);
mBitmap.setPixel(5, 5, 0xFFFFFFFF);
assertEquals(0xFF000000, mBitmap.getPixel(5, 5));
mBitmap.setPixel(5, 5, 0xA8A8A8A8);
assertEquals(0xA8000000, mBitmap.getPixel(5, 5));
mBitmap.setPixel(5, 5, 0x00000000);
assertEquals(0x00000000, mBitmap.getPixel(5, 5));
mBitmap.setPixel(5, 5, 0x1F000000);
assertEquals(0x1F000000, mBitmap.getPixel(5, 5));
}
@Test
public void testGetRowBytes() {
Bitmap bm0 = Bitmap.createBitmap(100, 200, Bitmap.Config.ALPHA_8);
Bitmap bm1 = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888);
Bitmap bm2 = Bitmap.createBitmap(100, 200, Bitmap.Config.RGB_565);
Bitmap bm3 = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_4444);
assertEquals(100, bm0.getRowBytes());
assertEquals(400, bm1.getRowBytes());
assertEquals(200, bm2.getRowBytes());
// Attempting to create a 4444 bitmap actually creates an 8888 bitmap.
assertEquals(400, bm3.getRowBytes());
}
@Test
public void testGetWidth() {
assertEquals(31, mBitmap.getWidth());
mBitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888);
assertEquals(100, mBitmap.getWidth());
}
@Test
public void testHasAlpha() {
assertFalse(mBitmap.hasAlpha());
mBitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888);
assertTrue(mBitmap.hasAlpha());
}
@Test
public void testIsMutable() {
assertFalse(mBitmap.isMutable());
mBitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
assertTrue(mBitmap.isMutable());
}
@Test
public void testIsRecycled() {
assertFalse(mBitmap.isRecycled());
mBitmap.recycle();
assertTrue(mBitmap.isRecycled());
}
@Test
public void testReconfigure() {
mBitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.RGB_565);
int alloc = mBitmap.getAllocationByteCount();
// test shrinking
mBitmap.reconfigure(50, 100, Bitmap.Config.ALPHA_8);
assertEquals(mBitmap.getAllocationByteCount(), alloc);
assertEquals(mBitmap.getByteCount() * 8, alloc);
}
@Test(expected=IllegalArgumentException.class)
public void testReconfigureExpanding() {
mBitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.RGB_565);
mBitmap.reconfigure(101, 201, Bitmap.Config.ARGB_8888);
}
@Test(expected=IllegalStateException.class)
public void testReconfigureMutable() {
mBitmap = BitmapFactory.decodeResource(mRes, R.drawable.start, mOptions);
mBitmap.reconfigure(1, 1, Bitmap.Config.ALPHA_8);
}
// Used by testAlphaAndPremul.
private static Config[] CONFIGS = new Config[] { Config.ALPHA_8, Config.ARGB_4444,
Config.ARGB_8888, Config.RGB_565 };
// test that reconfigure, setHasAlpha, and setPremultiplied behave as expected with
// respect to alpha and premultiplied.
@Test
public void testAlphaAndPremul() {
boolean falseTrue[] = new boolean[] { false, true };
for (Config fromConfig : CONFIGS) {
for (Config toConfig : CONFIGS) {
for (boolean hasAlpha : falseTrue) {
for (boolean isPremul : falseTrue) {
Bitmap bitmap = Bitmap.createBitmap(10, 10, fromConfig);
// 4444 is deprecated, and will convert to 8888. No need to
// attempt a reconfigure, which will be tested when fromConfig
// is 8888.
if (fromConfig == Config.ARGB_4444) {
assertEquals(bitmap.getConfig(), Config.ARGB_8888);
break;
}
bitmap.setHasAlpha(hasAlpha);
bitmap.setPremultiplied(isPremul);
verifyAlphaAndPremul(bitmap, hasAlpha, isPremul, false);
// reconfigure to a smaller size so the function will still succeed when
// going to a Config that requires more bits.
bitmap.reconfigure(1, 1, toConfig);
if (toConfig == Config.ARGB_4444) {
assertEquals(bitmap.getConfig(), Config.ARGB_8888);
} else {
assertEquals(bitmap.getConfig(), toConfig);
}
// Check that the alpha and premultiplied state has not changed (unless
// we expected it to).
verifyAlphaAndPremul(bitmap, hasAlpha, isPremul, fromConfig == Config.RGB_565);
}
}
}
}
}
/**
* Assert that bitmap returns the appropriate values for hasAlpha() and isPremultiplied().
* @param bitmap Bitmap to check.
* @param expectedAlpha Expected return value from bitmap.hasAlpha(). Note that this is based
* on what was set, but may be different from the actual return value depending on the
* Config and convertedFrom565.
* @param expectedPremul Expected return value from bitmap.isPremultiplied(). Similar to
* expectedAlpha, this is based on what was set, but may be different from the actual
* return value depending on the Config.
* @param convertedFrom565 Whether bitmap was converted to its current Config by being
* reconfigured from RGB_565. If true, and bitmap is now a Config that supports alpha,
* hasAlpha() is expected to be true even if expectedAlpha is false.
*/
private void verifyAlphaAndPremul(Bitmap bitmap, boolean expectedAlpha, boolean expectedPremul,
boolean convertedFrom565) {
switch (bitmap.getConfig()) {
case ARGB_4444:
// This shouldn't happen, since we don't allow creating or converting
// to 4444.
assertFalse(true);
break;
case RGB_565:
assertFalse(bitmap.hasAlpha());
assertFalse(bitmap.isPremultiplied());
break;
case ALPHA_8:
// ALPHA_8 behaves mostly the same as 8888, except for premultiplied. Fall through.
case ARGB_8888:
// Since 565 is necessarily opaque, we revert to hasAlpha when switching to a type
// that can have alpha.
if (convertedFrom565) {
assertTrue(bitmap.hasAlpha());
} else {
assertEquals(bitmap.hasAlpha(), expectedAlpha);
}
if (bitmap.hasAlpha()) {
// ALPHA_8's premultiplied status is undefined.
if (bitmap.getConfig() != Config.ALPHA_8) {
assertEquals(bitmap.isPremultiplied(), expectedPremul);
}
} else {
// Opaque bitmap is never considered premultiplied.
assertFalse(bitmap.isPremultiplied());
}
break;
}
}
@Test
public void testSetColorSpace() {
// Use arbitrary colors and assign to various ColorSpaces.
for (ARGB color : new ARGB[]{ new ARGB(1.0f, .5f, .5f, .5f),
new ARGB(1.0f, .3f, .6f, .9f),
new ARGB(0.5f, .2f, .8f, .7f) }) {
int srgbColor = Color.argb(color.alpha, color.red, color.green, color.blue);
for (ColorSpace cs : getRgbColorSpaces()) {
for (Config config : new Config[] {
// F16 is tested elsewhere, since it defaults to EXTENDED_SRGB, and
// many of these calls to setColorSpace would reduce the range, resulting
// in an Exception.
Config.ARGB_8888,
Config.RGB_565,
}) {
mBitmap = Bitmap.createBitmap(10, 10, config);
mBitmap.eraseColor(srgbColor);
mBitmap.setColorSpace(cs);
ColorSpace actual = mBitmap.getColorSpace();
if (cs == ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB)) {
assertSame(ColorSpace.get(ColorSpace.Named.SRGB), actual);
} else if (cs == ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB)) {
assertSame(ColorSpace.get(ColorSpace.Named.LINEAR_SRGB), actual);
} else {
assertSame(cs, actual);
}
// This tolerance was chosen by trial and error. It is expected that
// some conversions do not round-trip perfectly.
int tolerance = 2;
Color c = Color.valueOf(color.red, color.green, color.blue, color.alpha, cs);
ColorUtils.verifyColor("Mismatch after setting the colorSpace to "
+ cs.getName(), c.convert(mBitmap.getColorSpace()),
mBitmap.getColor(5, 5), tolerance);
}
}
}
}
@Test(expected = IllegalStateException.class)
public void testSetColorSpaceRecycled() {
mBitmap = Bitmap.createBitmap(10, 10, Config.ARGB_8888);
mBitmap.recycle();
mBitmap.setColorSpace(ColorSpace.get(Named.DISPLAY_P3));
}
@Test(expected = IllegalArgumentException.class)
public void testSetColorSpaceNull() {
mBitmap = Bitmap.createBitmap(10, 10, Config.ARGB_8888);
mBitmap.setColorSpace(null);
}
@Test(expected = IllegalArgumentException.class)
public void testSetColorSpaceXYZ() {
mBitmap = Bitmap.createBitmap(10, 10, Config.ARGB_8888);
mBitmap.setColorSpace(ColorSpace.get(Named.CIE_XYZ));
}
@Test(expected = IllegalArgumentException.class)
public void testSetColorSpaceNoTransferParameters() {
mBitmap = Bitmap.createBitmap(10, 10, Config.ARGB_8888);
ColorSpace cs = new ColorSpace.Rgb("NoTransferParams",
new float[]{ 0.640f, 0.330f, 0.300f, 0.600f, 0.150f, 0.060f },
ColorSpace.ILLUMINANT_D50,
x -> Math.pow(x, 1.0f / 2.2f), x -> Math.pow(x, 2.2f),
0, 1);
mBitmap.setColorSpace(cs);
}
@Test(expected = IllegalArgumentException.class)
public void testSetColorSpaceAlpha8() {
mBitmap = Bitmap.createBitmap(10, 10, Config.ALPHA_8);
assertNull(mBitmap.getColorSpace());
mBitmap.setColorSpace(ColorSpace.get(ColorSpace.Named.SRGB));
}
@Test
public void testSetColorSpaceReducedRange() {
ColorSpace aces = ColorSpace.get(Named.ACES);
mBitmap = Bitmap.createBitmap(10, 10, Config.RGBA_F16, true, aces);
try {
mBitmap.setColorSpace(ColorSpace.get(Named.SRGB));
fail("Expected IllegalArgumentException!");
} catch (IllegalArgumentException e) {
assertSame(aces, mBitmap.getColorSpace());
}
}
@Test
public void testSetColorSpaceNotReducedRange() {
ColorSpace extended = ColorSpace.get(Named.EXTENDED_SRGB);
mBitmap = Bitmap.createBitmap(10, 10, Config.RGBA_F16, true,
extended);
mBitmap.setColorSpace(ColorSpace.get(Named.SRGB));
assertSame(mBitmap.getColorSpace(), extended);
}
@Test
public void testSetColorSpaceNotReducedRangeLinear() {
ColorSpace linearExtended = ColorSpace.get(Named.LINEAR_EXTENDED_SRGB);
mBitmap = Bitmap.createBitmap(10, 10, Config.RGBA_F16, true,
linearExtended);
mBitmap.setColorSpace(ColorSpace.get(Named.LINEAR_SRGB));
assertSame(mBitmap.getColorSpace(), linearExtended);
}
@Test
public void testSetColorSpaceIncreasedRange() {
mBitmap = Bitmap.createBitmap(10, 10, Config.RGBA_F16, true,
ColorSpace.get(Named.DISPLAY_P3));
ColorSpace linearExtended = ColorSpace.get(Named.LINEAR_EXTENDED_SRGB);
mBitmap.setColorSpace(linearExtended);
assertSame(mBitmap.getColorSpace(), linearExtended);
}
@Test
public void testSetConfig() {
mBitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.RGB_565);
int alloc = mBitmap.getAllocationByteCount();
// test shrinking
mBitmap.setConfig(Bitmap.Config.ALPHA_8);
assertEquals(mBitmap.getAllocationByteCount(), alloc);
assertEquals(mBitmap.getByteCount() * 2, alloc);
}
@Test(expected=IllegalArgumentException.class)
public void testSetConfigExpanding() {
mBitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.RGB_565);
// test expanding
mBitmap.setConfig(Bitmap.Config.ARGB_8888);
}
@Test(expected=IllegalStateException.class)
public void testSetConfigMutable() {
// test mutable
mBitmap = BitmapFactory.decodeResource(mRes, R.drawable.start, mOptions);
mBitmap.setConfig(Bitmap.Config.ALPHA_8);
}
@Test
public void testSetHeight() {
mBitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888);
int alloc = mBitmap.getAllocationByteCount();
// test shrinking
mBitmap.setHeight(100);
assertEquals(mBitmap.getAllocationByteCount(), alloc);
assertEquals(mBitmap.getByteCount() * 2, alloc);
}
@Test(expected=IllegalArgumentException.class)
public void testSetHeightExpanding() {
// test expanding
mBitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888);
mBitmap.setHeight(201);
}
@Test(expected=IllegalStateException.class)
public void testSetHeightMutable() {
// test mutable
mBitmap = BitmapFactory.decodeResource(mRes, R.drawable.start, mOptions);
mBitmap.setHeight(1);
}
@Test(expected=IllegalStateException.class)
public void testSetPixelOnRecycled() {
int color = 0xff << 24;
mBitmap.recycle();
mBitmap.setPixel(10, 16, color);
}
@Test(expected=IllegalStateException.class)
public void testSetPixelOnImmutable() {
int color = 0xff << 24;
mBitmap = BitmapFactory.decodeResource(mRes, R.drawable.start, mOptions);
mBitmap.setPixel(10, 16, color);
}
@Test(expected=IllegalArgumentException.class)
public void testSetPixelXIsTooLarge() {
int color = 0xff << 24;
mBitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.RGB_565);
// abnormal case: x bigger than the source bitmap's width
mBitmap.setPixel(200, 16, color);
}
@Test(expected=IllegalArgumentException.class)
public void testSetPixelYIsTooLarge() {
int color = 0xff << 24;
mBitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.RGB_565);
// abnormal case: y bigger than the source bitmap's height
mBitmap.setPixel(10, 300, color);
}
@Test
public void testSetPixel() {
int color = 0xff << 24;
mBitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.RGB_565);
// normal case
mBitmap.setPixel(10, 16, color);
assertEquals(color, mBitmap.getPixel(10, 16));
}
@Test(expected=IllegalStateException.class)
public void testSetPixelsOnRecycled() {
int[] colors = createColors(100);
mBitmap.recycle();
mBitmap.setPixels(colors, 0, 0, 0, 0, 0, 0);
}
@Test(expected=IllegalStateException.class)
public void testSetPixelsOnImmutable() {
int[] colors = createColors(100);
mBitmap = BitmapFactory.decodeResource(mRes, R.drawable.start, mOptions);
mBitmap.setPixels(colors, 0, 0, 0, 0, 0, 0);
}
@Test(expected=IllegalArgumentException.class)
public void testSetPixelsXYNegative() {
int[] colors = createColors(100);
mBitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
// abnormal case: x and/or y less than 0
mBitmap.setPixels(colors, 0, 0, -1, -1, 200, 16);
}
@Test(expected=IllegalArgumentException.class)
public void testSetPixelsWidthHeightNegative() {
int[] colors = createColors(100);
mBitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
// abnormal case: width and/or height less than 0
mBitmap.setPixels(colors, 0, 0, 0, 0, -1, -1);
}
@Test(expected=IllegalArgumentException.class)
public void testSetPixelsXTooHigh() {
int[] colors = createColors(100);
mBitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
// abnormal case: (x + width) bigger than the source bitmap's width
mBitmap.setPixels(colors, 0, 0, 10, 10, 95, 50);
}
@Test(expected=IllegalArgumentException.class)
public void testSetPixelsYTooHigh() {
int[] colors = createColors(100);
mBitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
// abnormal case: (y + height) bigger than the source bitmap's height
mBitmap.setPixels(colors, 0, 0, 10, 10, 50, 95);
}
@Test(expected=IllegalArgumentException.class)
public void testSetPixelsStrideIllegal() {
int[] colors = createColors(100);
mBitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
// abnormal case: stride less than width and bigger than -width
mBitmap.setPixels(colors, 0, 10, 10, 10, 50, 50);
}
@Test(expected=ArrayIndexOutOfBoundsException.class)
public void testSetPixelsOffsetNegative() {
int[] colors = createColors(100);
mBitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
// abnormal case: offset less than 0
mBitmap.setPixels(colors, -1, 50, 10, 10, 50, 50);
}
@Test(expected=ArrayIndexOutOfBoundsException.class)
public void testSetPixelsOffsetTooBig() {
int[] colors = createColors(100);
mBitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
// abnormal case: (offset + width) bigger than the length of colors
mBitmap.setPixels(colors, 60, 50, 10, 10, 50, 50);
}
@Test(expected=ArrayIndexOutOfBoundsException.class)
public void testSetPixelsLastScanlineNegative() {
int[] colors = createColors(100);
mBitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
// abnormal case: lastScanline less than 0
mBitmap.setPixels(colors, 10, -50, 10, 10, 50, 50);
}
@Test(expected=ArrayIndexOutOfBoundsException.class)
public void testSetPixelsLastScanlineTooBig() {
int[] colors = createColors(100);
mBitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
// abnormal case: (lastScanline + width) bigger than the length of colors
mBitmap.setPixels(colors, 10, 50, 10, 10, 50, 50);
}
@Test
public void testSetPixels() {
int[] colors = createColors(100 * 100);
mBitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
mBitmap.setPixels(colors, 0, 100, 0, 0, 100, 100);
int[] ret = new int[100 * 100];
mBitmap.getPixels(ret, 0, 100, 0, 0, 100, 100);
for(int i = 0; i < 10000; i++){
assertEquals(ret[i], colors[i]);
}
}
private void verifyPremultipliedBitmapConfig(Config config, boolean expectedPremul) {
Bitmap bitmap = Bitmap.createBitmap(1, 1, config);
bitmap.setPremultiplied(true);
bitmap.setPixel(0, 0, Color.TRANSPARENT);
assertTrue(bitmap.isPremultiplied() == expectedPremul);
bitmap.setHasAlpha(false);
assertFalse(bitmap.isPremultiplied());
}
@Test
public void testSetPremultipliedSimple() {
verifyPremultipliedBitmapConfig(Bitmap.Config.ALPHA_8, true);
verifyPremultipliedBitmapConfig(Bitmap.Config.RGB_565, false);
verifyPremultipliedBitmapConfig(Bitmap.Config.ARGB_4444, true);
verifyPremultipliedBitmapConfig(Bitmap.Config.ARGB_8888, true);
}
@Test
public void testSetPremultipliedData() {
// with premul, will store 2,2,2,2, so it doesn't get value correct
Bitmap bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
bitmap.setPixel(0, 0, PREMUL_COLOR);
assertEquals(bitmap.getPixel(0, 0), PREMUL_ROUNDED_COLOR);
// read premultiplied value directly
bitmap.setPremultiplied(false);
assertEquals(bitmap.getPixel(0, 0), PREMUL_STORED_COLOR);
// value can now be stored/read correctly
bitmap.setPixel(0, 0, PREMUL_COLOR);
assertEquals(bitmap.getPixel(0, 0), PREMUL_COLOR);
// verify with array methods
int testArray[] = new int[] { PREMUL_COLOR };
bitmap.setPixels(testArray, 0, 1, 0, 0, 1, 1);
bitmap.getPixels(testArray, 0, 1, 0, 0, 1, 1);
assertEquals(bitmap.getPixel(0, 0), PREMUL_COLOR);
}
@Test
public void testPremultipliedCanvas() {
Bitmap bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
bitmap.setHasAlpha(true);
bitmap.setPremultiplied(false);
assertFalse(bitmap.isPremultiplied());
Canvas c = new Canvas();
try {
c.drawBitmap(bitmap, 0, 0, null);
fail("canvas should fail with exception");
} catch (RuntimeException e) {
}
}
private int getBitmapRawInt(Bitmap bitmap) {
IntBuffer buffer = IntBuffer.allocate(1);
bitmap.copyPixelsToBuffer(buffer);
return buffer.get(0);
}
private void bitmapStoreRawInt(Bitmap bitmap, int value) {
IntBuffer buffer = IntBuffer.allocate(1);
buffer.put(0, value);
bitmap.copyPixelsFromBuffer(buffer);
}
@Test
public void testSetPremultipliedToBuffer() {
Bitmap bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
bitmap.setPixel(0, 0, PREMUL_COLOR);
int storedPremul = getBitmapRawInt(bitmap);
bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
bitmap.setPremultiplied(false);
bitmap.setPixel(0, 0, PREMUL_STORED_COLOR);
assertEquals(getBitmapRawInt(bitmap), storedPremul);
}
@Test
public void testSetPremultipliedFromBuffer() {
Bitmap bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
bitmap.setPremultiplied(false);
bitmap.setPixel(0, 0, PREMUL_COLOR);
int rawTestColor = getBitmapRawInt(bitmap);
bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
bitmap.setPremultiplied(false);
bitmapStoreRawInt(bitmap, rawTestColor);
assertEquals(bitmap.getPixel(0, 0), PREMUL_COLOR);
}
@Test
public void testSetWidth() {
mBitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888);
int alloc = mBitmap.getAllocationByteCount();
// test shrinking
mBitmap.setWidth(50);
assertEquals(mBitmap.getAllocationByteCount(), alloc);
assertEquals(mBitmap.getByteCount() * 2, alloc);
}
@Test(expected=IllegalArgumentException.class)
public void testSetWidthExpanding() {
// test expanding
mBitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888);
mBitmap.setWidth(101);
}
@Test(expected=IllegalStateException.class)
public void testSetWidthMutable() {
// test mutable
mBitmap = BitmapFactory.decodeResource(mRes, R.drawable.start, mOptions);
mBitmap.setWidth(1);
}
@Test(expected=IllegalStateException.class)
public void testWriteToParcelRecycled() {
mBitmap.recycle();
mBitmap.writeToParcel(null, 0);
}
@Test
public void testWriteToParcel() {
// abnormal case: failed to unparcel Bitmap
mBitmap = BitmapFactory.decodeResource(mRes, R.drawable.start, mOptions);
Parcel p = Parcel.obtain();
mBitmap.writeToParcel(p, 0);
try {
Bitmap.CREATOR.createFromParcel(p);
fail("shouldn't come to here");
} catch(RuntimeException e){
}
p.recycle();
// normal case
p = Parcel.obtain();
mBitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
mBitmap.writeToParcel(p, 0);
p.setDataPosition(0);
assertTrue(mBitmap.sameAs(Bitmap.CREATOR.createFromParcel(p)));
p.recycle();
}
/**
* Although not specified as required behavior, it's something that some apps appear
* to rely upon when sending bitmaps between themselves
*/
@Test
public void testWriteToParcelPreserveMutability() {
Bitmap source = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
assertTrue(source.isMutable());
Parcel p = Parcel.obtain();
source.writeToParcel(p, 0);
p.setDataPosition(0);
Bitmap result = Bitmap.CREATOR.createFromParcel(p);
p.recycle();
assertTrue(result.isMutable());
}
@Test
public void testWriteToParcelPreserveImmutability() {
// Kinda silly way to create an immutable bitmap but it works
Bitmap source = Bitmap.createBitmap(100, 100, Config.ARGB_8888)
.copy(Config.ARGB_8888, false);
assertFalse(source.isMutable());
Parcel p = Parcel.obtain();
source.writeToParcel(p, 0);
p.setDataPosition(0);
Bitmap result = Bitmap.CREATOR.createFromParcel(p);
p.recycle();
assertFalse(result.isMutable());
}
@Test
public void testWriteHwBitmapToParcel() {
mBitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot, HARDWARE_OPTIONS);
Parcel p = Parcel.obtain();
mBitmap.writeToParcel(p, 0);
p.setDataPosition(0);
Bitmap expectedBitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot);
assertTrue(expectedBitmap.sameAs(Bitmap.CREATOR.createFromParcel(p)));
p.recycle();
}
@Test
public void testParcelF16ColorSpace() {
for (ColorSpace.Named e : new ColorSpace.Named[] {
ColorSpace.Named.EXTENDED_SRGB,
ColorSpace.Named.LINEAR_EXTENDED_SRGB,
ColorSpace.Named.PRO_PHOTO_RGB,
ColorSpace.Named.DISPLAY_P3
}) {
final ColorSpace cs = ColorSpace.get(e);
Bitmap b = Bitmap.createBitmap(10, 10, Config.RGBA_F16, true, cs);
assertSame(cs, b.getColorSpace());
Parcel p = Parcel.obtain();
b.writeToParcel(p, 0);
p.setDataPosition(0);
Bitmap unparceled = Bitmap.CREATOR.createFromParcel(p);
assertSame(cs, unparceled.getColorSpace());
}
}
@Test
public void testGetScaledHeight1() {
int dummyDensity = 5;
Bitmap ret = Bitmap.createBitmap(100, 200, Config.RGB_565);
int scaledHeight = scaleFromDensity(ret.getHeight(), ret.getDensity(), dummyDensity);
assertNotNull(ret);
assertEquals(scaledHeight, ret.getScaledHeight(dummyDensity));
}
@Test
public void testGetScaledHeight2() {
Bitmap ret = Bitmap.createBitmap(100, 200, Config.RGB_565);
DisplayMetrics metrics =
InstrumentationRegistry.getTargetContext().getResources().getDisplayMetrics();
int scaledHeight = scaleFromDensity(ret.getHeight(), ret.getDensity(), metrics.densityDpi);
assertEquals(scaledHeight, ret.getScaledHeight(metrics));
}
@Test
public void testGetScaledHeight3() {
Bitmap ret = Bitmap.createBitmap(100, 200, Config.RGB_565);
Bitmap mMutableBitmap = Bitmap.createBitmap(100, 200, Config.ARGB_8888);
Canvas mCanvas = new Canvas(mMutableBitmap);
// set Density
mCanvas.setDensity(DisplayMetrics.DENSITY_HIGH);
int scaledHeight = scaleFromDensity(
ret.getHeight(), ret.getDensity(), mCanvas.getDensity());
assertEquals(scaledHeight, ret.getScaledHeight(mCanvas));
}
@Test
public void testGetScaledWidth1() {
int dummyDensity = 5;
Bitmap ret = Bitmap.createBitmap(100, 200, Config.RGB_565);
int scaledWidth = scaleFromDensity(ret.getWidth(), ret.getDensity(), dummyDensity);
assertNotNull(ret);
assertEquals(scaledWidth, ret.getScaledWidth(dummyDensity));
}
@Test
public void testGetScaledWidth2() {
Bitmap ret = Bitmap.createBitmap(100, 200, Config.RGB_565);
DisplayMetrics metrics =
InstrumentationRegistry.getTargetContext().getResources().getDisplayMetrics();
int scaledWidth = scaleFromDensity(ret.getWidth(), ret.getDensity(), metrics.densityDpi);
assertEquals(scaledWidth, ret.getScaledWidth(metrics));
}
@Test
public void testGetScaledWidth3() {
Bitmap ret = Bitmap.createBitmap(100, 200, Config.RGB_565);
Bitmap mMutableBitmap = Bitmap.createBitmap(100, 200, Config.ARGB_8888);
Canvas mCanvas = new Canvas(mMutableBitmap);
// set Density
mCanvas.setDensity(DisplayMetrics.DENSITY_HIGH);
int scaledWidth = scaleFromDensity(ret.getWidth(), ret.getDensity(), mCanvas.getDensity());
assertEquals(scaledWidth, ret.getScaledWidth(mCanvas));
}
@Test
public void testSameAs_simpleSuccess() {
Bitmap bitmap1 = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
Bitmap bitmap2 = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
bitmap1.eraseColor(Color.BLACK);
bitmap2.eraseColor(Color.BLACK);
assertTrue(bitmap1.sameAs(bitmap2));
assertTrue(bitmap2.sameAs(bitmap1));
}
@Test
public void testSameAs_simpleFail() {
Bitmap bitmap1 = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
Bitmap bitmap2 = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
bitmap1.eraseColor(Color.BLACK);
bitmap2.eraseColor(Color.BLACK);
bitmap2.setPixel(20, 10, Color.WHITE);
assertFalse(bitmap1.sameAs(bitmap2));
assertFalse(bitmap2.sameAs(bitmap1));
}
@Test
public void testSameAs_reconfigure() {
Bitmap bitmap1 = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
Bitmap bitmap2 = Bitmap.createBitmap(150, 150, Config.ARGB_8888);
bitmap2.reconfigure(100, 100, Config.ARGB_8888); // now same size, so should be same
bitmap1.eraseColor(Color.BLACK);
bitmap2.eraseColor(Color.BLACK);
assertTrue(bitmap1.sameAs(bitmap2));
assertTrue(bitmap2.sameAs(bitmap1));
}
@Test
public void testSameAs_config() {
Bitmap bitmap1 = Bitmap.createBitmap(100, 200, Config.RGB_565);
Bitmap bitmap2 = Bitmap.createBitmap(100, 200, Config.ARGB_8888);
// both bitmaps can represent black perfectly
bitmap1.eraseColor(Color.BLACK);
bitmap2.eraseColor(Color.BLACK);
// but not same due to config
assertFalse(bitmap1.sameAs(bitmap2));
assertFalse(bitmap2.sameAs(bitmap1));
}
@Test
public void testSameAs_width() {
Bitmap bitmap1 = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
Bitmap bitmap2 = Bitmap.createBitmap(101, 100, Config.ARGB_8888);
bitmap1.eraseColor(Color.BLACK);
bitmap2.eraseColor(Color.BLACK);
assertFalse(bitmap1.sameAs(bitmap2));
assertFalse(bitmap2.sameAs(bitmap1));
}
@Test
public void testSameAs_height() {
Bitmap bitmap1 = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
Bitmap bitmap2 = Bitmap.createBitmap(102, 100, Config.ARGB_8888);
bitmap1.eraseColor(Color.BLACK);
bitmap2.eraseColor(Color.BLACK);
assertFalse(bitmap1.sameAs(bitmap2));
assertFalse(bitmap2.sameAs(bitmap1));
}
@Test
public void testSameAs_opaque() {
Bitmap bitmap1 = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
Bitmap bitmap2 = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
bitmap1.eraseColor(Color.BLACK);
bitmap2.eraseColor(Color.BLACK);
bitmap1.setHasAlpha(true);
bitmap2.setHasAlpha(false);
assertFalse(bitmap1.sameAs(bitmap2));
assertFalse(bitmap2.sameAs(bitmap1));
}
@Test
public void testSameAs_hardware() {
Bitmap bitmap1 = BitmapFactory.decodeResource(mRes, R.drawable.robot, HARDWARE_OPTIONS);
Bitmap bitmap2 = BitmapFactory.decodeResource(mRes, R.drawable.robot, HARDWARE_OPTIONS);
Bitmap bitmap3 = BitmapFactory.decodeResource(mRes, R.drawable.robot);
Bitmap bitmap4 = BitmapFactory.decodeResource(mRes, R.drawable.start, HARDWARE_OPTIONS);
assertTrue(bitmap1.sameAs(bitmap2));
assertTrue(bitmap2.sameAs(bitmap1));
assertFalse(bitmap1.sameAs(bitmap3));
assertFalse(bitmap1.sameAs(bitmap4));
}
@Test
public void testSameAs_wrappedHardwareBuffer() {
try (HardwareBuffer hwBufferA = createTestBuffer(512, 512, true);
HardwareBuffer hwBufferB = createTestBuffer(512, 512, true);
HardwareBuffer hwBufferC = createTestBuffer(512, 512, true);) {
// Fill buffer C with generated data
nFillRgbaHwBuffer(hwBufferC);
// Create the test bitmaps
Bitmap bitmap1 = Bitmap.wrapHardwareBuffer(hwBufferA, ColorSpace.get(Named.SRGB));
Bitmap bitmap2 = Bitmap.wrapHardwareBuffer(hwBufferA, ColorSpace.get(Named.SRGB));
Bitmap bitmap3 = BitmapFactory.decodeResource(mRes, R.drawable.robot);
Bitmap bitmap4 = Bitmap.wrapHardwareBuffer(hwBufferB, ColorSpace.get(Named.SRGB));
Bitmap bitmap5 = Bitmap.wrapHardwareBuffer(hwBufferC, ColorSpace.get(Named.SRGB));
// Run the compare-a-thon
assertTrue(bitmap1.sameAs(bitmap2)); // SAME UNDERLYING BUFFER
assertTrue(bitmap2.sameAs(bitmap1)); // SAME UNDERLYING BUFFER
assertFalse(bitmap1.sameAs(bitmap3)); // HW vs. NON-HW
assertTrue(bitmap1.sameAs(bitmap4)); // DIFFERENT BUFFERS, SAME CONTENT
assertFalse(bitmap1.sameAs(bitmap5)); // DIFFERENT BUFFERS, DIFFERENT CONTENT
}
}
@Test(expected=IllegalStateException.class)
public void testHardwareGetPixel() {
Bitmap bitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot, HARDWARE_OPTIONS);
bitmap.getPixel(0, 0);
}
@Test(expected=IllegalStateException.class)
public void testHardwareGetPixels() {
Bitmap bitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot, HARDWARE_OPTIONS);
bitmap.getPixels(new int[5], 0, 5, 0, 0, 5, 1);
}
@Test
public void testGetConfigOnRecycled() {
Bitmap bitmap1 = BitmapFactory.decodeResource(mRes, R.drawable.robot, HARDWARE_OPTIONS);
bitmap1.recycle();
assertEquals(Config.HARDWARE, bitmap1.getConfig());
Bitmap bitmap2 = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
bitmap2.recycle();
assertEquals(Config.ARGB_8888, bitmap2.getConfig());
}
@Test(expected = IllegalStateException.class)
public void testHardwareSetWidth() {
Bitmap bitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot, HARDWARE_OPTIONS);
bitmap.setWidth(30);
}
@Test(expected = IllegalStateException.class)
public void testHardwareSetHeight() {
Bitmap bitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot, HARDWARE_OPTIONS);
bitmap.setHeight(30);
}
@Test(expected = IllegalStateException.class)
public void testHardwareSetConfig() {
Bitmap bitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot, HARDWARE_OPTIONS);
bitmap.setConfig(Config.ARGB_8888);
}
@Test(expected = IllegalStateException.class)
public void testHardwareReconfigure() {
Bitmap bitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot, HARDWARE_OPTIONS);
bitmap.reconfigure(30, 30, Config.ARGB_8888);
}
@Test(expected = IllegalStateException.class)
public void testHardwareSetPixels() {
Bitmap bitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot, HARDWARE_OPTIONS);
bitmap.setPixels(new int[10], 0, 1, 0, 0, 1, 1);
}
@Test(expected = IllegalStateException.class)
public void testHardwareSetPixel() {
Bitmap bitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot, HARDWARE_OPTIONS);
bitmap.setPixel(1, 1, 0);
}
@Test(expected = IllegalStateException.class)
public void testHardwareEraseColor() {
Bitmap bitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot, HARDWARE_OPTIONS);
bitmap.eraseColor(0);
}
@Test(expected = IllegalStateException.class)
public void testHardwareEraseColorLong() {
Bitmap bitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot, HARDWARE_OPTIONS);
bitmap.eraseColor(Color.pack(0));
}
@Test(expected = IllegalStateException.class)
public void testHardwareCopyPixelsToBuffer() {
Bitmap bitmap = BitmapFactory.decodeResource(mRes, R.drawable.start, HARDWARE_OPTIONS);
ByteBuffer byteBuf = ByteBuffer.allocate(bitmap.getRowBytes() * bitmap.getHeight());
bitmap.copyPixelsToBuffer(byteBuf);
}
@Test(expected = IllegalStateException.class)
public void testHardwareCopyPixelsFromBuffer() {
IntBuffer intBuf1 = IntBuffer.allocate(mBitmap.getRowBytes() * mBitmap.getHeight());
assertEquals(0, intBuf1.position());
mBitmap.copyPixelsToBuffer(intBuf1);
Bitmap hwBitmap = BitmapFactory.decodeResource(mRes, R.drawable.start, HARDWARE_OPTIONS);
hwBitmap.copyPixelsFromBuffer(intBuf1);
}
@Test
public void testUseMetadataAfterRecycle() {
Bitmap bitmap = Bitmap.createBitmap(10, 20, Config.RGB_565);
bitmap.recycle();
assertEquals(10, bitmap.getWidth());
assertEquals(20, bitmap.getHeight());
assertEquals(Config.RGB_565, bitmap.getConfig());
}
@Test
public void testCopyHWBitmapInStrictMode() {
strictModeTest(()->{
Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
Bitmap hwBitmap = bitmap.copy(Config.HARDWARE, false);
hwBitmap.copy(Config.ARGB_8888, false);
});
}
@Test
public void testCreateScaledFromHWInStrictMode() {
strictModeTest(()->{
Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
Bitmap hwBitmap = bitmap.copy(Config.HARDWARE, false);
Bitmap.createScaledBitmap(hwBitmap, 200, 200, false);
});
}
@Test
public void testExtractAlphaFromHWInStrictMode() {
strictModeTest(()->{
Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
Bitmap hwBitmap = bitmap.copy(Config.HARDWARE, false);
hwBitmap.extractAlpha();
});
}
@Test
public void testCompressInStrictMode() {
strictModeTest(()->{
Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
bitmap.compress(CompressFormat.JPEG, 90, new ByteArrayOutputStream());
});
}
@Test
public void testParcelHWInStrictMode() {
strictModeTest(()->{
mBitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
Bitmap hwBitmap = mBitmap.copy(Config.HARDWARE, false);
hwBitmap.writeToParcel(Parcel.obtain(), 0);
});
}
@Test
public void testSameAsFirstHWInStrictMode() {
strictModeTest(()->{
Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
Bitmap hwBitmap = bitmap.copy(Config.HARDWARE, false);
hwBitmap.sameAs(bitmap);
});
}
@Test
public void testSameAsSecondHWInStrictMode() {
strictModeTest(()->{
Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
Bitmap hwBitmap = bitmap.copy(Config.HARDWARE, false);
bitmap.sameAs(hwBitmap);
});
}
@Test
public void testNdkAccessAfterRecycle() {
Bitmap bitmap = Bitmap.createBitmap(10, 20, Config.RGB_565);
Bitmap hardware = bitmap.copy(Config.HARDWARE, false);
nValidateBitmapInfo(bitmap, 10, 20, true);
nValidateBitmapInfo(hardware, 10, 20, true);
bitmap.recycle();
hardware.recycle();
nValidateBitmapInfo(bitmap, 10, 20, true);
nValidateBitmapInfo(hardware, 10, 20, true);
nValidateNdkAccessFails(bitmap);
}
@Test
public void bitmapIsMutable() {
Bitmap b = Bitmap.createBitmap(10, 10, Config.ARGB_8888);
assertTrue("CreateBitmap w/ params should be mutable", b.isMutable());
assertTrue("CreateBitmap from bitmap should be mutable",
Bitmap.createBitmap(b).isMutable());
}
private static void runGcAndFinalizersSync() {
Runtime.getRuntime().gc();
Runtime.getRuntime().runFinalization();
final CountDownLatch fence = new CountDownLatch(1);
new Object() {
@Override
protected void finalize() throws Throwable {
try {
fence.countDown();
} finally {
super.finalize();
}
}
};
try {
do {
Runtime.getRuntime().gc();
Runtime.getRuntime().runFinalization();
} while (!fence.await(100, TimeUnit.MILLISECONDS));
} catch (InterruptedException ex) {
throw new RuntimeException(ex);
}
}
private static File sProcSelfFd = new File("/proc/self/fd");
private static int getFdCount() {
return sProcSelfFd.listFiles().length;
}
private static void assertNotLeaking(int iteration,
Debug.MemoryInfo start, Debug.MemoryInfo end) {
Debug.getMemoryInfo(end);
assertNotEquals(0, start.getTotalPss());
assertNotEquals(0, end.getTotalPss());
if (end.getTotalPss() - start.getTotalPss() > 5000 /* kB */) {
runGcAndFinalizersSync();
Debug.getMemoryInfo(end);
if (end.getTotalPss() - start.getTotalPss() > 7000 /* kB */) {
// Guarded by if so we don't continually generate garbage for the
// assertion string.
assertEquals("Memory leaked, iteration=" + iteration,
start.getTotalPss(), end.getTotalPss(),
7000 /* kb */);
}
}
}
private static void runNotLeakingTest(Runnable test) {
Debug.MemoryInfo meminfoStart = new Debug.MemoryInfo();
Debug.MemoryInfo meminfoEnd = new Debug.MemoryInfo();
int fdCount = -1;
// Do a warmup to reach steady-state memory usage
for (int i = 0; i < 50; i++) {
test.run();
}
runGcAndFinalizersSync();
Debug.getMemoryInfo(meminfoStart);
fdCount = getFdCount();
// Now run the test
for (int i = 0; i < 2000; i++) {
if (i % 100 == 5) {
assertNotLeaking(i, meminfoStart, meminfoEnd);
final int curFdCount = getFdCount();
if (curFdCount - fdCount > 10) {
fail(String.format("FDs leaked. Expected=%d, current=%d, iteration=%d",
fdCount, curFdCount, i));
}
}
test.run();
}
assertNotLeaking(2000, meminfoStart, meminfoEnd);
final int curFdCount = getFdCount();
if (curFdCount - fdCount > 10) {
fail(String.format("FDs leaked. Expected=%d, current=%d", fdCount, curFdCount));
}
}
@Test
@LargeTest
public void testHardwareBitmapNotLeaking() {
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inPreferredConfig = Config.HARDWARE;
opts.inScaled = false;
runNotLeakingTest(() -> {
Bitmap bitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot, opts);
assertNotNull(bitmap);
// Make sure nothing messed with the bitmap
assertEquals(128, bitmap.getWidth());
assertEquals(128, bitmap.getHeight());
assertEquals(Config.HARDWARE, bitmap.getConfig());
bitmap.recycle();
});
}
@Test
@LargeTest
public void testWrappedHardwareBufferBitmapNotLeaking() {
final ColorSpace colorSpace = ColorSpace.get(Named.SRGB);
try (HardwareBuffer hwBuffer = createTestBuffer(1024, 512, false)) {
runNotLeakingTest(() -> {
Bitmap bitmap = Bitmap.wrapHardwareBuffer(hwBuffer, colorSpace);
assertNotNull(bitmap);
// Make sure nothing messed with the bitmap
assertEquals(1024, bitmap.getWidth());
assertEquals(512, bitmap.getHeight());
assertEquals(Config.HARDWARE, bitmap.getConfig());
bitmap.recycle();
});
}
}
@Test
@LargeTest
public void testDrawingHardwareBitmapNotLeaking() {
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inPreferredConfig = Config.HARDWARE;
opts.inScaled = false;
RenderTarget renderTarget = RenderTarget.create();
renderTarget.setDefaultSize(128, 128);
final Surface surface = renderTarget.getSurface();
runNotLeakingTest(() -> {
Bitmap bitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot, opts);
assertNotNull(bitmap);
// Make sure nothing messed with the bitmap
assertEquals(128, bitmap.getWidth());
assertEquals(128, bitmap.getHeight());
assertEquals(Config.HARDWARE, bitmap.getConfig());
Canvas canvas = surface.lockHardwareCanvas();
canvas.drawBitmap(bitmap, 0, 0, null);
surface.unlockCanvasAndPost(canvas);
bitmap.recycle();
});
renderTarget.destroy();
}
@Test
public void testWrapHardwareBufferHoldsReference() {
Bitmap bitmap;
// Create hardware-buffer and wrap it in a Bitmap
try (HardwareBuffer hwBuffer = createTestBuffer(128, 128, true)) {
// Fill buffer with colors (x, y, 42, 255)
nFillRgbaHwBuffer(hwBuffer);
bitmap = Bitmap.wrapHardwareBuffer(hwBuffer, ColorSpace.get(Named.SRGB));
}
// Buffer is closed at this point. Ensure bitmap still works by drawing it
assertEquals(128, bitmap.getWidth());
assertEquals(128, bitmap.getHeight());
assertEquals(Config.HARDWARE, bitmap.getConfig());
// Copy bitmap to target bitmap we can read from
Bitmap dstBitmap = bitmap.copy(Config.ARGB_8888, false);
bitmap.recycle();
// Ensure that the bitmap has valid contents
int pixel = dstBitmap.getPixel(0, 0);
assertEquals(255 << 24 | 42, pixel);
dstBitmap.recycle();
}
@Test
public void testWrapHardwareBufferPreservesColors() {
try (HardwareBuffer hwBuffer = createTestBuffer(128, 128, true)) {
// Fill buffer with colors (x, y, 42, 255)
nFillRgbaHwBuffer(hwBuffer);
// Create HW bitmap from this buffer
Bitmap srcBitmap = Bitmap.wrapHardwareBuffer(hwBuffer, ColorSpace.get(Named.SRGB));
assertNotNull(srcBitmap);
// Copy it to target non-HW bitmap
Bitmap dstBitmap = srcBitmap.copy(Config.ARGB_8888, false);
srcBitmap.recycle();
// Ensure all colors are as expected (matches the nFillRgbaHwBuffer call used above).
for (int y = 0; y < 128; ++y) {
for (int x = 0; x < 128; ++x) {
int pixel = dstBitmap.getPixel(x, y);
short a = 255;
short r = (short) (x % 255);
short g = (short) (y % 255);
short b = 42;
assertEquals(a << 24 | r << 16 | g << 8 | b, pixel);
}
}
dstBitmap.recycle();
}
}
private static byte[] compressToPng(Bitmap bitmap) {
try (ByteArrayOutputStream stream = new ByteArrayOutputStream()) {
assertTrue("Failed to encode a Bitmap with Config " + bitmap.getConfig()
+ " and ColorSpace " + bitmap.getColorSpace() + "!",
bitmap.compress(CompressFormat.PNG, 100, stream));
return stream.toByteArray();
} catch (IOException e) {
fail("Failed to compress with " + e);
return null;
}
}
private static Object[] parametersForTestAsShared() {
return Utils.crossProduct(Config.values(), getRgbColorSpaces().toArray(new Object[0]));
}
@Test
@Parameters(method = "parametersForTestAsShared")
public void testAsShared(Config config, ColorSpace colorSpace) {
Bitmap original = Bitmap.createBitmap(10, 10,
config == Config.HARDWARE ? Config.ARGB_8888 : config, true /*hasAlpha*/,
colorSpace);
drawGradient(original);
if (config == Config.HARDWARE) {
original = original.copy(Config.HARDWARE, false /*mutable*/);
}
// There's no visible way to test that the memory is allocated in shared memory, but we can
// verify that the Bitmaps look the same.
Bitmap shared = original.asShared();
assertNotNull(shared);
if (config == Config.HARDWARE) {
int expectedFormat = nGetFormat(original);
assertEquals(expectedFormat, configToFormat(shared.getConfig()));
// There's no public way to look at the pixels in the HARDWARE Bitmap, but if we
// compress each as a lossless PNG, they should look identical.
byte[] origBytes = compressToPng(original);
byte[] sharedBytes = compressToPng(shared);
assertTrue(Arrays.equals(origBytes, sharedBytes));
} else {
assertSame(original.getConfig(), shared.getConfig());
assertTrue(shared.sameAs(original));
}
assertSame(original.getColorSpace(), shared.getColorSpace());
// The Bitmap is already in shared memory, so no work is done.
Bitmap shared2 = shared.asShared();
assertSame(shared, shared2);
}
@Test(expected = IllegalStateException.class)
public void testAsSharedRecycled() {
Bitmap bitmap = Bitmap.createBitmap(10, 10, Config.ARGB_8888);
bitmap.recycle();
bitmap.asShared();
}
@Test
public void testAsSharedDensity() {
DisplayMetrics metrics =
InstrumentationRegistry.getTargetContext().getResources().getDisplayMetrics();
Bitmap bitmap = Bitmap.createBitmap(10, 10, Config.ARGB_8888);
for (int density : new int[] { Bitmap.DENSITY_NONE, metrics.densityDpi,
DisplayMetrics.DENSITY_HIGH, DisplayMetrics.DENSITY_DEVICE_STABLE,
DisplayMetrics.DENSITY_MEDIUM }) {
bitmap.setDensity(density);
Bitmap shared = bitmap.asShared();
assertEquals(density, shared.getDensity());
shared.recycle();
}
}
@Test
@Parameters({"true", "false"})
public void testAsSharedImageDecoder(boolean mutable) {
Resources res = InstrumentationRegistry.getTargetContext().getResources();
ImageDecoder.Source source = ImageDecoder.createSource(res.getAssets(),
"grayscale-16bit-linearSrgb.png");
try {
Bitmap bitmap = ImageDecoder.decodeBitmap(source, (decoder, info, s) -> {
decoder.setAllocator(ImageDecoder.ALLOCATOR_SHARED_MEMORY);
if (mutable) decoder.setMutableRequired(true);
});
Bitmap shared = bitmap.asShared();
if (mutable) {
// bitmap is mutable, so asShared must make a copy.
assertNotEquals(bitmap, shared);
assertTrue(bitmap.sameAs(shared));
} else {
// bitmap is already immutable and in shared memory, so asShared will return
// itself.
assertSame(bitmap, shared);
}
} catch (IOException e) {
fail("Failed to decode with " + e);
}
}
@Test
public void testNdkFormats() {
for (ConfigToFormat pair : CONFIG_TO_FORMAT) {
Bitmap bm = Bitmap.createBitmap(10, 10, pair.config);
assertNotNull(bm);
int nativeFormat = nGetFormat(bm);
assertEquals("Config: " + pair.config, pair.format, nativeFormat);
}
}
@Test
public void testNdkFormatsHardware() {
for (ConfigToFormat pair : CONFIG_TO_FORMAT) {
Bitmap bm = Bitmap.createBitmap(10, 10, pair.config);
bm = bm.copy(Bitmap.Config.HARDWARE, false);
// ALPHA_8 may not be supported in HARDWARE.
if (bm == null) {
assertEquals(Bitmap.Config.ALPHA_8, pair.config);
continue;
}
int nativeFormat = nGetFormat(bm);
if (pair.config == Bitmap.Config.RGBA_F16) {
// It is possible the system does not support RGBA_F16 in HARDWARE.
// In that case, it will fall back to ARGB_8888.
assertTrue(nativeFormat == ANDROID_BITMAP_FORMAT_RGBA_8888
|| nativeFormat == ANDROID_BITMAP_FORMAT_RGBA_F16);
} else if (pair.config == Bitmap.Config.RGBA_1010102) {
// Devices not supporting RGBA_1010102 in hardware should fallback to ARGB_8888
assertTrue(nativeFormat == ANDROID_BITMAP_FORMAT_RGBA_8888
|| nativeFormat == ANDROID_BITMAP_FORMAT_RGBA_1010102);
} else {
assertEquals("Config: " + pair.config, pair.format, nativeFormat);
}
}
}
@Test
public void testNullBitmapNdk() {
Bitmap bitmap = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888);
nTestNullBitmap(bitmap);
}
private Object[] parametersForTestNdkInfo() {
return new Object[] {
new Object[] { Config.ALPHA_8, ANDROID_BITMAP_FORMAT_A_8 },
new Object[] { Config.ARGB_8888, ANDROID_BITMAP_FORMAT_RGBA_8888 },
new Object[] { Config.RGB_565, ANDROID_BITMAP_FORMAT_RGB_565 },
new Object[] { Config.RGBA_F16, ANDROID_BITMAP_FORMAT_RGBA_F16 },
new Object[] { Config.RGBA_1010102, ANDROID_BITMAP_FORMAT_RGBA_1010102 },
};
}
@Test
@Parameters(method = "parametersForTestNdkInfo")
public void testNdkInfo(Config config, final int expectedFormat) {
// Arbitrary width and height.
final int width = 13;
final int height = 7;
boolean[] trueFalse = new boolean[] { true, false };
for (boolean hasAlpha : trueFalse) {
for (boolean premultiplied : trueFalse) {
Bitmap bm = Bitmap.createBitmap(width, height, config, hasAlpha);
bm.setPremultiplied(premultiplied);
nTestInfo(bm, expectedFormat, width, height, bm.hasAlpha(),
bm.isPremultiplied(), false);
Bitmap hwBitmap = bm.copy(Bitmap.Config.HARDWARE, false);
if (hwBitmap == null) {
// Some devices do not support ALPHA_8 + HARDWARE.
assertEquals(Bitmap.Config.ALPHA_8, config);
} else {
// Some devices do not support (F16 | 1010102) + HARDWARE. These fall back to
// 8888. Check the HWB to confirm.
int tempExpectedFormat = expectedFormat;
if (config == Config.RGBA_F16 || config == Config.RGBA_1010102) {
HardwareBuffer buffer = hwBitmap.getHardwareBuffer();
if (buffer.getFormat() == HardwareBuffer.RGBA_8888) {
tempExpectedFormat = ANDROID_BITMAP_FORMAT_RGBA_8888;
}
}
nTestInfo(hwBitmap, tempExpectedFormat, width, height, hwBitmap.hasAlpha(),
hwBitmap.isPremultiplied(), true);
hwBitmap.recycle();
}
bm.recycle();
}
}
}
@Test
public void testNdkDataSpaceF16Extended() {
// In RGBA_F16 we force EXTENDED in these cases.
for (ColorSpace colorSpace : new ColorSpace[] {
ColorSpace.get(Named.SRGB),
ColorSpace.get(Named.EXTENDED_SRGB),
}) {
Bitmap bm = Bitmap.createBitmap(10, 10, Config.RGBA_F16, false, colorSpace);
assertNotNull(bm);
assertEquals(ColorSpace.get(Named.EXTENDED_SRGB), bm.getColorSpace());
assertEquals(DataSpace.ADATASPACE_SCRGB, nGetDataSpace(bm));
}
for (ColorSpace colorSpace : new ColorSpace[] {
ColorSpace.get(Named.LINEAR_SRGB),
ColorSpace.get(Named.LINEAR_EXTENDED_SRGB),
}) {
Bitmap bm = Bitmap.createBitmap(10, 10, Config.RGBA_F16, false, colorSpace);
assertNotNull(bm);
assertEquals(ColorSpace.get(Named.LINEAR_EXTENDED_SRGB), bm.getColorSpace());
assertEquals(DataSpace.ADATASPACE_SCRGB_LINEAR, nGetDataSpace(bm));
}
}
@Test
public void testNdkDataSpaceNonExtended() {
// In 565 and 8888, these force non-extended.
for (ColorSpace colorSpace : new ColorSpace[] {
ColorSpace.get(Named.SRGB),
ColorSpace.get(Named.EXTENDED_SRGB),
}) {
for (Config c: new Config[] { Config.ARGB_8888, Config.RGB_565 }) {
Bitmap bm = Bitmap.createBitmap(10, 10, c, false, colorSpace);
assertNotNull(bm);
assertEquals(ColorSpace.get(Named.SRGB), bm.getColorSpace());
assertEquals(DataSpace.ADATASPACE_SRGB, nGetDataSpace(bm));
}
}
for (ColorSpace colorSpace : new ColorSpace[] {
ColorSpace.get(Named.LINEAR_SRGB),
ColorSpace.get(Named.LINEAR_EXTENDED_SRGB),
}) {
for (Config c: new Config[] { Config.ARGB_8888, Config.RGB_565 }) {
Bitmap bm = Bitmap.createBitmap(10, 10, c, false, colorSpace);
assertNotNull(bm);
assertEquals(ColorSpace.get(Named.LINEAR_SRGB), bm.getColorSpace());
assertEquals(DataSpace.ADATASPACE_SRGB_LINEAR, nGetDataSpace(bm));
}
}
}
@Test
public void testNdkDataSpace() {
// DataSpace.ADATASPACEs that do not depend on the Config.
for (ColorSpace colorSpace : new ColorSpace[] {
// These have corresponding DataSpace.ADATASPACEs that are independent of the Config
ColorSpace.get(Named.DISPLAY_P3),
ColorSpace.get(Named.BT2020),
ColorSpace.get(Named.ADOBE_RGB),
ColorSpace.get(Named.BT709),
ColorSpace.get(Named.DCI_P3),
// These have no public ADATASPACE.
ColorSpace.get(Named.ACES),
ColorSpace.get(Named.ACESCG),
ColorSpace.get(Named.NTSC_1953),
ColorSpace.get(Named.PRO_PHOTO_RGB),
ColorSpace.get(Named.SMPTE_C),
}) {
for (Config c: new Config[] { Config.ARGB_8888, Config.RGB_565, Config.RGBA_F16 }) {
Bitmap bm = Bitmap.createBitmap(10, 10, c, false, colorSpace);
assertNotNull(bm);
int dataSpace = nGetDataSpace(bm);
assertEquals("Bitmap with " + c + " and " + bm.getColorSpace()
+ " has unexpected data space", DataSpace.fromColorSpace(colorSpace),
dataSpace);
}
}
}
@Test
public void testNdkDataSpaceAlpha8() {
// ALPHA_8 doesn't support ColorSpaces
Bitmap bm = Bitmap.createBitmap(10, 10, Config.ALPHA_8);
assertNotNull(bm);
assertNull(bm.getColorSpace());
int dataSpace = nGetDataSpace(bm);
assertEquals(DataSpace.ADATASPACE_UNKNOWN, dataSpace);
}
@Test
public void testNdkDataSpaceNullBitmap() {
assertEquals(DataSpace.ADATASPACE_UNKNOWN, nGetDataSpace(null));
}
private static native int nGetDataSpace(Bitmap bm);
// These match the NDK APIs.
private static final int ANDROID_BITMAP_COMPRESS_FORMAT_JPEG = 0;
private static final int ANDROID_BITMAP_COMPRESS_FORMAT_PNG = 1;
private static final int ANDROID_BITMAP_COMPRESS_FORMAT_WEBP_LOSSY = 3;
private static final int ANDROID_BITMAP_COMPRESS_FORMAT_WEBP_LOSSLESS = 4;
private int nativeCompressFormat(CompressFormat format) {
switch (format) {
case JPEG:
return ANDROID_BITMAP_COMPRESS_FORMAT_JPEG;
case PNG:
return ANDROID_BITMAP_COMPRESS_FORMAT_PNG;
case WEBP_LOSSY:
return ANDROID_BITMAP_COMPRESS_FORMAT_WEBP_LOSSY;
case WEBP_LOSSLESS:
return ANDROID_BITMAP_COMPRESS_FORMAT_WEBP_LOSSLESS;
default:
fail("format " + format + " has no corresponding native compress format!");
return -1;
}
}
private static Object[] parametersForNdkCompress() {
// Skip WEBP, which has no corresponding native compress format.
Object[] formats = new Object[] {
CompressFormat.JPEG,
CompressFormat.PNG,
CompressFormat.WEBP_LOSSY,
CompressFormat.WEBP_LOSSLESS,
};
// These are the ColorSpaces with corresponding ADataSpaces
Object[] colorSpaces = new Object[] {
ColorSpace.get(Named.SRGB),
ColorSpace.get(Named.EXTENDED_SRGB),
ColorSpace.get(Named.LINEAR_SRGB),
ColorSpace.get(Named.LINEAR_EXTENDED_SRGB),
ColorSpace.get(Named.DISPLAY_P3),
ColorSpace.get(Named.DCI_P3),
ColorSpace.get(Named.BT2020),
ColorSpace.get(Named.BT709),
ColorSpace.get(Named.ADOBE_RGB),
};
Object[] configs = new Object[] {
Config.ARGB_8888,
Config.RGB_565,
Config.RGBA_F16,
};
return crossProduct(formats, colorSpaces, configs);
}
private static Object[] crossProduct(Object[] a, Object[] b, Object[] c) {
final int length = a.length * b.length * c.length;
Object[] ret = new Object[length];
for (int i = 0; i < a.length; i++) {
for (int j = 0; j < b.length; j++) {
for (int k = 0; k < c.length; k++) {
int index = i * (b.length * c.length) + j * c.length + k;
assertNull(ret[index]);
ret[index] = new Object[] { a[i], b[j], c[k] };
}
}
}
return ret;
}
private static boolean isSrgb(ColorSpace cs) {
return cs == ColorSpace.get(Named.SRGB)
|| cs == ColorSpace.get(Named.EXTENDED_SRGB)
|| cs == ColorSpace.get(Named.LINEAR_SRGB)
|| cs == ColorSpace.get(Named.LINEAR_EXTENDED_SRGB);
}
// Helper method for populating a Bitmap with interesting pixels for comparison.
private static void drawGradient(Bitmap bitmap) {
// Use different colors and alphas.
Canvas canvas = new Canvas(bitmap);
ColorSpace cs = bitmap.getColorSpace();
if (cs == null) {
assertSame(Config.ALPHA_8, bitmap.getConfig());
cs = ColorSpace.get(ColorSpace.Named.SRGB);
}
long color0 = Color.pack(0, 0, 1, 1, cs);
long color1 = Color.pack(1, 0, 0, 0, cs);
LinearGradient gradient = new LinearGradient(0, 0, 10, 10, color0, color1,
Shader.TileMode.CLAMP);
Paint paint = new Paint();
paint.setShader(gradient);
canvas.drawPaint(paint);
}
@Test
@Parameters(method = "parametersForNdkCompress")
public void testNdkCompress(CompressFormat format, ColorSpace cs, Config config)
throws IOException {
// Verify that ndk compress behaves the same as Bitmap#compress
Bitmap bitmap = Bitmap.createBitmap(10, 10, config, true /* hasAlpha */, cs);
assertNotNull(bitmap);
{
drawGradient(bitmap);
}
byte[] storage = new byte[16 * 1024];
for (int quality : new int[] { 50, 80, 100 }) {
byte[] expected = null;
try (ByteArrayOutputStream stream = new ByteArrayOutputStream()) {
assertTrue("Failed to encode a Bitmap with " + cs + " to " + format + " at quality "
+ quality + " from Java API", bitmap.compress(format, quality, stream));
expected = stream.toByteArray();
}
try (ByteArrayOutputStream stream = new ByteArrayOutputStream()) {
boolean success = nCompress(bitmap, nativeCompressFormat(format),
quality, stream, storage);
assertTrue("Failed to encode pixels with " + cs + " to " + format + " at quality "
+ quality + " from NDK API", success);
byte[] actual = stream.toByteArray();
if (isSrgb(cs)) {
if (!Arrays.equals(expected, actual)) {
fail("NDK compression did not match for " + cs + " and format " + format
+ " at quality " + quality);
}
} else {
// The byte arrays will match exactly for SRGB and its variants, because those
// are treated specially. For the others, there are some small differences
// between Skia's and ColorSpace's values that result in the ICC profiles being
// written slightly differently. They should still look the same, though.
Bitmap expectedBitmap = decodeBytes(expected);
Bitmap actualBitmap = decodeBytes(actual);
boolean matched = BitmapUtils.compareBitmapsMse(expectedBitmap, actualBitmap,
5, true, false);
expectedBitmap.recycle();
actualBitmap.recycle();
assertTrue("NDK compression did not match for " + cs + " and format " + format
+ " at quality " + quality, matched);
}
}
}
}
@Test
public void testNdkCompressBadParameter() throws IOException {
try (ByteArrayOutputStream stream = new ByteArrayOutputStream()) {
nTestNdkCompressBadParameter(mBitmap, stream, new byte[16 * 1024]);
}
}
private static native boolean nCompress(Bitmap bitmap, int format, int quality,
OutputStream stream, byte[] storage);
private static native void nTestNdkCompressBadParameter(Bitmap bitmap,
OutputStream stream, byte[] storage);
private void strictModeTest(Runnable runnable) {
StrictMode.ThreadPolicy originalPolicy = StrictMode.getThreadPolicy();
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectCustomSlowCalls().penaltyDeath().build());
try {
runnable.run();
fail("Shouldn't reach it");
} catch (RuntimeException expected){
// expect to receive StrictModeViolation
} finally {
StrictMode.setThreadPolicy(originalPolicy);
}
}
private static native void nValidateBitmapInfo(Bitmap bitmap, int width, int height,
boolean is565);
private static native void nValidateNdkAccessFails(Bitmap bitmap);
private static native void nFillRgbaHwBuffer(HardwareBuffer hwBuffer);
private static native void nTestNullBitmap(Bitmap bitmap);
private static final int ANDROID_BITMAP_FORMAT_NONE = 0;
static final int ANDROID_BITMAP_FORMAT_RGBA_8888 = 1;
private static final int ANDROID_BITMAP_FORMAT_RGB_565 = 4;
private static final int ANDROID_BITMAP_FORMAT_A_8 = 8;
private static final int ANDROID_BITMAP_FORMAT_RGBA_F16 = 9;
private static final int ANDROID_BITMAP_FORMAT_RGBA_1010102 = 10;
private static class ConfigToFormat {
public final Config config;
public final int format;
ConfigToFormat(Config c, int f) {
this.config = c;
this.format = f;
}
}
private static int configToFormat(Config config) {
for (ConfigToFormat pair : CONFIG_TO_FORMAT) {
if (config == pair.config) {
return pair.format;
}
}
return ANDROID_BITMAP_FORMAT_NONE;
}
private static final ConfigToFormat[] CONFIG_TO_FORMAT = new ConfigToFormat[] {
new ConfigToFormat(Bitmap.Config.ARGB_8888, ANDROID_BITMAP_FORMAT_RGBA_8888),
// ARGB_4444 is deprecated, and createBitmap converts to 8888.
new ConfigToFormat(Bitmap.Config.ARGB_4444, ANDROID_BITMAP_FORMAT_RGBA_8888),
new ConfigToFormat(Bitmap.Config.RGB_565, ANDROID_BITMAP_FORMAT_RGB_565),
new ConfigToFormat(Bitmap.Config.ALPHA_8, ANDROID_BITMAP_FORMAT_A_8),
new ConfigToFormat(Bitmap.Config.RGBA_F16, ANDROID_BITMAP_FORMAT_RGBA_F16),
new ConfigToFormat(Bitmap.Config.RGBA_1010102, ANDROID_BITMAP_FORMAT_RGBA_1010102),
};
static native int nGetFormat(Bitmap bitmap);
private static native void nTestInfo(Bitmap bm, int androidBitmapFormat, int width, int height,
boolean hasAlpha, boolean premultiplied, boolean hardware);
private static HardwareBuffer createTestBuffer(int width, int height, boolean cpuAccess) {
long usage = HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE;
if (cpuAccess) {
usage |= HardwareBuffer.USAGE_CPU_WRITE_RARELY;
}
// We can assume that RGBA_8888 format is supported for every platform.
HardwareBuffer hwBuffer = HardwareBuffer.create(width, height, HardwareBuffer.RGBA_8888,
1, usage);
return hwBuffer;
}
private static int scaleFromDensity(int size, int sdensity, int tdensity) {
if (sdensity == Bitmap.DENSITY_NONE || sdensity == tdensity) {
return size;
}
// Scale by tdensity / sdensity, rounding up.
return ((size * tdensity) + (sdensity >> 1)) / sdensity;
}
private static int[] createColors(int size) {
int[] colors = new int[size];
for (int i = 0; i < size; i++) {
colors[i] = (0xFF << 24) | (i << 16) | (i << 8) | i;
}
return colors;
}
private static BitmapFactory.Options createHardwareBitmapOptions() {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Config.HARDWARE;
return options;
}
@Test
public void testCopyAlpha8ToHardware() {
Bitmap bm = Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8);
assertNotNull(bm);
Bitmap hwBitmap = bm.copy(Bitmap.Config.HARDWARE, false /* mutable */);
// Some devices may not support ALPHA_8 + HARDWARE
if (hwBitmap != null) {
assertNull(hwBitmap.getColorSpace());
}
bm.recycle();
}
}