| /* |
| * 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.assumeTrue; |
| import static org.testng.Assert.assertThrows; |
| |
| 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.BitmapFactory.Options; |
| import android.graphics.Color; |
| import android.graphics.Rect; |
| import android.media.MediaCodecInfo; |
| import android.media.MediaCodecList; |
| import android.media.MediaFormat; |
| import android.os.Build; |
| import android.os.Parcel; |
| import android.os.ParcelFileDescriptor; |
| import android.platform.test.annotations.LargeTest; |
| import android.platform.test.annotations.RequiresDevice; |
| import android.system.ErrnoException; |
| import android.system.Os; |
| import android.util.DisplayMetrics; |
| import android.util.TypedValue; |
| |
| import androidx.test.InstrumentationRegistry; |
| import androidx.test.filters.SmallTest; |
| |
| import com.android.compatibility.common.util.ApiLevelUtil; |
| import com.android.compatibility.common.util.BitmapUtils; |
| import com.android.compatibility.common.util.CddTest; |
| import com.android.compatibility.common.util.MediaUtils; |
| |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.ByteArrayOutputStream; |
| import java.io.File; |
| import java.io.FileDescriptor; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.RandomAccessFile; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.concurrent.CountDownLatch; |
| |
| import junitparams.JUnitParamsRunner; |
| import junitparams.Parameters; |
| |
| @SmallTest |
| @RunWith(JUnitParamsRunner.class) |
| public class BitmapFactoryTest { |
| // height and width of start.jpg |
| private static final int START_HEIGHT = 31; |
| private static final int START_WIDTH = 31; |
| |
| static class TestImage { |
| TestImage(int id, int width, int height) { |
| this.id = id; |
| this.width = width; |
| this.height = height; |
| } |
| public final int id; |
| public final int width; |
| public final int height; |
| } |
| |
| private Object[] testImages() { |
| ArrayList<Object> testImages = new ArrayList<>(Arrays.asList(new Object[] { |
| new TestImage(R.drawable.baseline_jpeg, 1280, 960), |
| new TestImage(R.drawable.png_test, 640, 480), |
| new TestImage(R.drawable.gif_test, 320, 240), |
| new TestImage(R.drawable.bmp_test, 320, 240), |
| new TestImage(R.drawable.webp_test, 640, 480) |
| })); |
| if (MediaUtils.hasDecoder(MediaFormat.MIMETYPE_VIDEO_HEVC)) { |
| // HEIF support is optional when HEVC decoder is not supported. |
| testImages.add(new TestImage(R.raw.heifwriter_input, 1920, 1080)); |
| } |
| return testImages.toArray(new Object[] {}); |
| } |
| |
| private static final int[] RAW_COLORS = new int[] { |
| // raw data from R.drawable.premul_data |
| Color.argb(255, 0, 0, 0), |
| Color.argb(128, 255, 0, 0), |
| Color.argb(128, 25, 26, 27), |
| Color.argb(2, 255, 254, 253), |
| }; |
| |
| private static final int[] DEPREMUL_COLORS = new int[] { |
| // data from R.drawable.premul_data, after premultiplied store + un-premultiplied load |
| Color.argb(255, 0, 0, 0), |
| Color.argb(128, 255, 0, 0), |
| Color.argb(128, 26, 26, 28), |
| Color.argb(2, 255, 255, 255), |
| }; |
| |
| private Resources mRes; |
| // opt for non-null |
| private BitmapFactory.Options mOpt1; |
| // opt for null |
| private BitmapFactory.Options mOpt2; |
| private int mDefaultDensity; |
| private int mTargetDensity; |
| |
| @Before |
| public void setup() { |
| mRes = InstrumentationRegistry.getTargetContext().getResources(); |
| mDefaultDensity = DisplayMetrics.DENSITY_DEFAULT; |
| mTargetDensity = mRes.getDisplayMetrics().densityDpi; |
| |
| mOpt1 = new BitmapFactory.Options(); |
| mOpt1.inScaled = false; |
| mOpt2 = new BitmapFactory.Options(); |
| mOpt2.inScaled = false; |
| mOpt2.inJustDecodeBounds = true; |
| } |
| |
| @Test |
| public void testConstructor() { |
| new BitmapFactory(); |
| } |
| |
| @Test |
| public void testDecodeResource1() { |
| Bitmap b = BitmapFactory.decodeResource(mRes, R.drawable.start, |
| mOpt1); |
| assertNotNull(b); |
| // Test the bitmap size |
| assertEquals(START_HEIGHT, b.getHeight()); |
| assertEquals(START_WIDTH, b.getWidth()); |
| // Test if no bitmap |
| assertNull(BitmapFactory.decodeResource(mRes, R.drawable.start, mOpt2)); |
| } |
| |
| @Test |
| public void testDecodeResource2() { |
| Bitmap b = BitmapFactory.decodeResource(mRes, R.drawable.start); |
| assertNotNull(b); |
| // Test the bitmap size |
| assertEquals(START_HEIGHT * mTargetDensity / mDefaultDensity, b.getHeight(), 1.1); |
| assertEquals(START_WIDTH * mTargetDensity / mDefaultDensity, b.getWidth(), 1.1); |
| } |
| |
| @Test |
| public void testDecodeResourceStream() { |
| InputStream is = obtainInputStream(); |
| Rect r = new Rect(1, 1, 1, 1); |
| TypedValue value = new TypedValue(); |
| Bitmap b = BitmapFactory.decodeResourceStream(mRes, value, is, r, mOpt1); |
| assertNotNull(b); |
| // Test the bitmap size |
| assertEquals(START_HEIGHT, b.getHeight()); |
| assertEquals(START_WIDTH, b.getWidth()); |
| } |
| |
| @Test |
| public void testDecodeByteArray1() { |
| byte[] array = obtainArray(); |
| Bitmap b = BitmapFactory.decodeByteArray(array, 0, array.length, mOpt1); |
| assertNotNull(b); |
| // Test the bitmap size |
| assertEquals(START_HEIGHT, b.getHeight()); |
| assertEquals(START_WIDTH, b.getWidth()); |
| // Test if no bitmap |
| assertNull(BitmapFactory.decodeByteArray(array, 0, array.length, mOpt2)); |
| } |
| |
| @Test |
| public void testDecodeByteArray2() { |
| byte[] array = obtainArray(); |
| Bitmap b = BitmapFactory.decodeByteArray(array, 0, array.length); |
| assertNotNull(b); |
| // Test the bitmap size |
| assertEquals(START_HEIGHT, b.getHeight()); |
| assertEquals(START_WIDTH, b.getWidth()); |
| } |
| |
| @Test |
| public void testDecodeStream1() { |
| InputStream is = obtainInputStream(); |
| Rect r = new Rect(1, 1, 1, 1); |
| Bitmap b = BitmapFactory.decodeStream(is, r, mOpt1); |
| assertNotNull(b); |
| // Test the bitmap size |
| assertEquals(START_HEIGHT, b.getHeight()); |
| assertEquals(START_WIDTH, b.getWidth()); |
| // Test if no bitmap |
| assertNull(BitmapFactory.decodeStream(is, r, mOpt2)); |
| } |
| |
| @Test |
| public void testDecodeStream2() { |
| InputStream is = obtainInputStream(); |
| Bitmap b = BitmapFactory.decodeStream(is); |
| assertNotNull(b); |
| // Test the bitmap size |
| assertEquals(START_HEIGHT, b.getHeight()); |
| assertEquals(START_WIDTH, b.getWidth()); |
| } |
| |
| @Test |
| @Parameters(method = "testImages") |
| public void testDecodeStream3(TestImage testImage) { |
| InputStream is = obtainInputStream(testImage.id); |
| Bitmap b = BitmapFactory.decodeStream(is); |
| assertNotNull(b); |
| // Test the bitmap size |
| assertEquals(testImage.width, b.getWidth()); |
| assertEquals(testImage.height, b.getHeight()); |
| } |
| |
| private Object[] paramsForWebpDecodeEncode() { |
| return new Object[] { |
| new Object[] {Config.ARGB_8888, 16}, |
| new Object[] {Config.RGB_565, 49} |
| }; |
| } |
| |
| private Bitmap decodeOpaqueImage(int resId, BitmapFactory.Options options) { |
| return decodeOpaqueImage(obtainInputStream(resId), options); |
| } |
| |
| private Bitmap decodeOpaqueImage(InputStream stream, BitmapFactory.Options options) { |
| Bitmap bitmap = BitmapFactory.decodeStream(stream, null, options); |
| assertNotNull(bitmap); |
| assertFalse(bitmap.isPremultiplied()); |
| assertFalse(bitmap.hasAlpha()); |
| return bitmap; |
| } |
| |
| @Test |
| @Parameters(method = "paramsForWebpDecodeEncode") |
| public void testWebpStreamDecode(Config config, int tolerance) { |
| BitmapFactory.Options options = new BitmapFactory.Options(); |
| options.inPreferredConfig = config; |
| |
| // Decode the PNG & WebP test images. The WebP test image has been encoded from PNG test |
| // image and should have same similar (within some error-tolerance) Bitmap data. |
| Bitmap bPng = decodeOpaqueImage(R.drawable.png_test, options); |
| assertEquals(bPng.getConfig(), config); |
| Bitmap bWebp = decodeOpaqueImage(R.drawable.webp_test, options); |
| BitmapUtils.assertBitmapsMse(bPng, bWebp, tolerance, true, bPng.isPremultiplied()); |
| } |
| |
| @Test |
| @Parameters(method = "paramsForWebpDecodeEncode") |
| public void testWebpStreamEncode(Config config, int tolerance) { |
| BitmapFactory.Options options = new BitmapFactory.Options(); |
| options.inPreferredConfig = config; |
| |
| Bitmap bPng = decodeOpaqueImage(R.drawable.png_test, options); |
| assertEquals(bPng.getConfig(), config); |
| |
| // Compress the PNG image to WebP format (Quality=90) and decode it back. |
| // This will test end-to-end WebP encoding and decoding. |
| ByteArrayOutputStream oStreamWebp = new ByteArrayOutputStream(); |
| assertTrue(bPng.compress(CompressFormat.WEBP, 90, oStreamWebp)); |
| InputStream iStreamWebp = new ByteArrayInputStream(oStreamWebp.toByteArray()); |
| Bitmap bWebp2 = decodeOpaqueImage(iStreamWebp, options); |
| BitmapUtils.assertBitmapsMse(bPng, bWebp2, tolerance, true, bPng.isPremultiplied()); |
| } |
| |
| @Test |
| public void testDecodeStream5() { |
| final int tolerance = 72; |
| BitmapFactory.Options options = new BitmapFactory.Options(); |
| options.inPreferredConfig = Config.ARGB_8888; |
| |
| // Decode the PNG & WebP (google_logo) images. The WebP image has |
| // been encoded from PNG image. |
| InputStream iStreamPng = obtainInputStream(R.drawable.google_logo_1); |
| Bitmap bPng = BitmapFactory.decodeStream(iStreamPng, null, options); |
| assertNotNull(bPng); |
| assertEquals(bPng.getConfig(), Config.ARGB_8888); |
| assertTrue(bPng.isPremultiplied()); |
| assertTrue(bPng.hasAlpha()); |
| |
| // Decode the corresponding WebP (transparent) image (google_logo_2.webp). |
| InputStream iStreamWebP1 = obtainInputStream(R.drawable.google_logo_2); |
| Bitmap bWebP1 = BitmapFactory.decodeStream(iStreamWebP1, null, options); |
| assertNotNull(bWebP1); |
| assertEquals(bWebP1.getConfig(), Config.ARGB_8888); |
| assertTrue(bWebP1.isPremultiplied()); |
| assertTrue(bWebP1.hasAlpha()); |
| BitmapUtils.assertBitmapsMse(bPng, bWebP1, tolerance, true, bPng.isPremultiplied()); |
| |
| // Compress the PNG image to WebP format (Quality=90) and decode it back. |
| // This will test end-to-end WebP encoding and decoding. |
| ByteArrayOutputStream oStreamWebp = new ByteArrayOutputStream(); |
| assertTrue(bPng.compress(CompressFormat.WEBP, 90, oStreamWebp)); |
| InputStream iStreamWebp2 = new ByteArrayInputStream(oStreamWebp.toByteArray()); |
| Bitmap bWebP2 = BitmapFactory.decodeStream(iStreamWebp2, null, options); |
| assertNotNull(bWebP2); |
| assertEquals(bWebP2.getConfig(), Config.ARGB_8888); |
| assertTrue(bWebP2.isPremultiplied()); |
| assertTrue(bWebP2.hasAlpha()); |
| BitmapUtils.assertBitmapsMse(bPng, bWebP2, tolerance, true, bPng.isPremultiplied()); |
| } |
| |
| @Test |
| public void testDecodeFileDescriptor1() throws IOException { |
| ParcelFileDescriptor pfd = obtainParcelDescriptor(obtainPath()); |
| FileDescriptor input = pfd.getFileDescriptor(); |
| Rect r = new Rect(1, 1, 1, 1); |
| Bitmap b = BitmapFactory.decodeFileDescriptor(input, r, mOpt1); |
| assertNotNull(b); |
| // Test the bitmap size |
| assertEquals(START_HEIGHT, b.getHeight()); |
| assertEquals(START_WIDTH, b.getWidth()); |
| // Test if no bitmap |
| assertNull(BitmapFactory.decodeFileDescriptor(input, r, mOpt2)); |
| } |
| |
| @Test |
| public void testDecodeFileDescriptor2() throws IOException { |
| ParcelFileDescriptor pfd = obtainParcelDescriptor(obtainPath()); |
| FileDescriptor input = pfd.getFileDescriptor(); |
| Bitmap b = BitmapFactory.decodeFileDescriptor(input); |
| assertNotNull(b); |
| // Test the bitmap size |
| assertEquals(START_HEIGHT, b.getHeight()); |
| assertEquals(START_WIDTH, b.getWidth()); |
| } |
| |
| |
| // TODO: Better parameterize this and split it up. |
| @Test |
| @Parameters(method = "testImages") |
| public void testDecodeFileDescriptor3(TestImage testImage) throws IOException { |
| // Arbitrary offsets to use. If the offset of the FD matches the offset of the image, |
| // decoding should succeed, but if they do not match, decoding should fail. |
| final long[] actual_offsets = new long[] { 0, 17 }; |
| for (int j = 0; j < actual_offsets.length; ++j) { |
| // FIXME: The purgeable test should attempt to purge the memory |
| // to force a re-decode. |
| for (boolean purgeable : new boolean[] { false, true }) { |
| BitmapFactory.Options opts = new BitmapFactory.Options(); |
| opts.inPurgeable = purgeable; |
| opts.inInputShareable = purgeable; |
| |
| long actualOffset = actual_offsets[j]; |
| String path = Utils.obtainPath(testImage.id, actualOffset); |
| RandomAccessFile file = new RandomAccessFile(path, "r"); |
| FileDescriptor fd = file.getFD(); |
| assertTrue(fd.valid()); |
| |
| // Set the offset to ACTUAL_OFFSET |
| file.seek(actualOffset); |
| assertEquals(file.getFilePointer(), actualOffset); |
| |
| // Now decode. This should be successful and leave the offset |
| // unchanged. |
| Bitmap b = BitmapFactory.decodeFileDescriptor(fd, null, opts); |
| assertNotNull(b); |
| assertEquals(file.getFilePointer(), actualOffset); |
| |
| // Now use the other offset. It should fail to decode, and |
| // the offset should remain unchanged. |
| long otherOffset = actual_offsets[(j + 1) % actual_offsets.length]; |
| assertFalse(otherOffset == actualOffset); |
| file.seek(otherOffset); |
| assertEquals(file.getFilePointer(), otherOffset); |
| |
| b = BitmapFactory.decodeFileDescriptor(fd, null, opts); |
| assertNull(b); |
| assertEquals(file.getFilePointer(), otherOffset); |
| } |
| } |
| } |
| |
| @Test |
| public void testDecodeFile1() throws IOException { |
| Bitmap b = BitmapFactory.decodeFile(obtainPath(), mOpt1); |
| assertNotNull(b); |
| // Test the bitmap size |
| assertEquals(START_HEIGHT, b.getHeight()); |
| assertEquals(START_WIDTH, b.getWidth()); |
| // Test if no bitmap |
| assertNull(BitmapFactory.decodeFile(obtainPath(), mOpt2)); |
| } |
| |
| @Test |
| public void testDecodeFile2() throws IOException { |
| Bitmap b = BitmapFactory.decodeFile(obtainPath()); |
| assertNotNull(b); |
| // Test the bitmap size |
| assertEquals(START_HEIGHT, b.getHeight()); |
| assertEquals(START_WIDTH, b.getWidth()); |
| } |
| |
| @Test |
| public void testDecodeReuseBasic() { |
| BitmapFactory.Options options = new BitmapFactory.Options(); |
| options.inMutable = true; |
| options.inSampleSize = 0; // treated as 1 |
| options.inScaled = false; |
| Bitmap start = BitmapFactory.decodeResource(mRes, R.drawable.start, options); |
| int originalSize = start.getByteCount(); |
| assertEquals(originalSize, start.getAllocationByteCount()); |
| |
| options.inBitmap = start; |
| options.inMutable = false; // will be overridden by non-null inBitmap |
| options.inSampleSize = -42; // treated as 1 |
| Bitmap pass = BitmapFactory.decodeResource(mRes, R.drawable.pass, options); |
| |
| assertEquals(originalSize, pass.getByteCount()); |
| assertEquals(originalSize, pass.getAllocationByteCount()); |
| assertSame(start, pass); |
| assertTrue(pass.isMutable()); |
| } |
| |
| @Test |
| public void testDecodeReuseAttempt() { |
| // BitmapFactory "silently" ignores an immutable inBitmap. (It does print a log message.) |
| BitmapFactory.Options options = new BitmapFactory.Options(); |
| options.inMutable = false; |
| |
| Bitmap start = BitmapFactory.decodeResource(mRes, R.drawable.start, options); |
| assertFalse(start.isMutable()); |
| |
| options.inBitmap = start; |
| Bitmap pass = BitmapFactory.decodeResource(mRes, R.drawable.pass, options); |
| assertNotNull(pass); |
| assertNotEquals(start, pass); |
| } |
| |
| @Test |
| public void testDecodeReuseRecycled() { |
| BitmapFactory.Options options = new BitmapFactory.Options(); |
| options.inMutable = true; |
| |
| Bitmap start = BitmapFactory.decodeResource(mRes, R.drawable.start, options); |
| assertNotNull(start); |
| start.recycle(); |
| |
| options.inBitmap = start; |
| |
| assertThrows(IllegalArgumentException.class, () -> { |
| BitmapFactory.decodeResource(mRes, R.drawable.pass, options); |
| }); |
| } |
| |
| @Test |
| public void testDecodeReuseHardware() { |
| BitmapFactory.Options options = new BitmapFactory.Options(); |
| options.inPreferredConfig = Bitmap.Config.HARDWARE; |
| |
| Bitmap start = BitmapFactory.decodeResource(mRes, R.drawable.start, options); |
| assertNotNull(start); |
| |
| options.inBitmap = start; |
| |
| assertThrows(IllegalArgumentException.class, () -> { |
| BitmapFactory.decodeResource(mRes, R.drawable.pass, options); |
| }); |
| } |
| |
| /** |
| * Create bitmap sized to load unscaled resources: start, pass, and alpha |
| */ |
| private Bitmap createBitmapForReuse(int pixelCount) { |
| Bitmap bitmap = Bitmap.createBitmap(pixelCount, 1, Config.ARGB_8888); |
| bitmap.eraseColor(Color.BLACK); |
| bitmap.setHasAlpha(false); |
| return bitmap; |
| } |
| |
| /** |
| * Decode resource with ResId into reuse bitmap without scaling, verifying expected hasAlpha |
| */ |
| private void decodeResourceWithReuse(Bitmap reuse, int resId, boolean hasAlpha) { |
| BitmapFactory.Options options = new BitmapFactory.Options(); |
| options.inMutable = true; |
| options.inSampleSize = 1; |
| options.inScaled = false; |
| options.inBitmap = reuse; |
| Bitmap output = BitmapFactory.decodeResource(mRes, resId, options); |
| assertSame(reuse, output); |
| assertEquals(output.hasAlpha(), hasAlpha); |
| } |
| |
| @Test |
| public void testDecodeReuseHasAlpha() { |
| final int bitmapSize = 31; // size in pixels of start, pass, and alpha resources |
| final int pixelCount = bitmapSize * bitmapSize; |
| |
| // Test reuse, hasAlpha false and true |
| Bitmap bitmap = createBitmapForReuse(pixelCount); |
| decodeResourceWithReuse(bitmap, R.drawable.start, false); |
| decodeResourceWithReuse(bitmap, R.drawable.alpha, true); |
| |
| // Test pre-reconfigure, hasAlpha false and true |
| bitmap = createBitmapForReuse(pixelCount); |
| bitmap.reconfigure(bitmapSize, bitmapSize, Config.ARGB_8888); |
| bitmap.setHasAlpha(true); |
| decodeResourceWithReuse(bitmap, R.drawable.start, false); |
| |
| bitmap = createBitmapForReuse(pixelCount); |
| bitmap.reconfigure(bitmapSize, bitmapSize, Config.ARGB_8888); |
| decodeResourceWithReuse(bitmap, R.drawable.alpha, true); |
| } |
| |
| @Test |
| @Parameters(method = "testImages") |
| public void testDecodeReuseFormats(TestImage testImage) { |
| // reuse should support all image formats |
| Bitmap reuseBuffer = Bitmap.createBitmap(1000000, 1, Bitmap.Config.ALPHA_8); |
| |
| BitmapFactory.Options options = new BitmapFactory.Options(); |
| options.inBitmap = reuseBuffer; |
| options.inSampleSize = 4; |
| options.inScaled = false; |
| Bitmap decoded = BitmapFactory.decodeResource(mRes, testImage.id, options); |
| assertSame(reuseBuffer, decoded); |
| } |
| |
| @Test |
| public void testDecodeReuseFailure() { |
| BitmapFactory.Options options = new BitmapFactory.Options(); |
| options.inMutable = true; |
| options.inScaled = false; |
| options.inSampleSize = 4; |
| Bitmap reduced = BitmapFactory.decodeResource(mRes, R.drawable.robot, options); |
| |
| options.inBitmap = reduced; |
| options.inSampleSize = 1; |
| try { |
| BitmapFactory.decodeResource(mRes, R.drawable.robot, options); |
| fail("should throw exception due to lack of space"); |
| } catch (IllegalArgumentException e) { |
| } |
| } |
| |
| @Test |
| public void testDecodeReuseScaling() { |
| BitmapFactory.Options options = new BitmapFactory.Options(); |
| options.inMutable = true; |
| options.inScaled = false; |
| Bitmap original = BitmapFactory.decodeResource(mRes, R.drawable.robot, options); |
| int originalSize = original.getByteCount(); |
| assertEquals(originalSize, original.getAllocationByteCount()); |
| |
| options.inBitmap = original; |
| options.inSampleSize = 4; |
| Bitmap reduced = BitmapFactory.decodeResource(mRes, R.drawable.robot, options); |
| |
| assertSame(original, reduced); |
| assertEquals(originalSize, reduced.getAllocationByteCount()); |
| assertEquals(originalSize, reduced.getByteCount() * 16); |
| } |
| |
| @Test |
| public void testDecodeReuseDoubleScaling() { |
| BitmapFactory.Options options = new BitmapFactory.Options(); |
| options.inMutable = true; |
| options.inScaled = false; |
| options.inSampleSize = 1; |
| Bitmap original = BitmapFactory.decodeResource(mRes, R.drawable.robot, options); |
| int originalSize = original.getByteCount(); |
| |
| // Verify that inSampleSize and density scaling both work with reuse concurrently |
| options.inBitmap = original; |
| options.inScaled = true; |
| options.inSampleSize = 2; |
| options.inDensity = 2; |
| options.inTargetDensity = 4; |
| Bitmap doubleScaled = BitmapFactory.decodeResource(mRes, R.drawable.robot, options); |
| |
| assertSame(original, doubleScaled); |
| assertEquals(4, doubleScaled.getDensity()); |
| assertEquals(originalSize, doubleScaled.getByteCount()); |
| } |
| |
| @Test |
| public void testDecodeReuseEquivalentScaling() { |
| BitmapFactory.Options options = new BitmapFactory.Options(); |
| options.inMutable = true; |
| options.inScaled = true; |
| options.inDensity = 4; |
| options.inTargetDensity = 2; |
| Bitmap densityReduced = BitmapFactory.decodeResource(mRes, R.drawable.robot, options); |
| assertEquals(2, densityReduced.getDensity()); |
| options.inBitmap = densityReduced; |
| options.inDensity = 0; |
| options.inScaled = false; |
| options.inSampleSize = 2; |
| Bitmap scaleReduced = BitmapFactory.decodeResource(mRes, R.drawable.robot, options); |
| // verify that density isn't incorrectly carried over during bitmap reuse |
| assertFalse(densityReduced.getDensity() == 2); |
| assertFalse(densityReduced.getDensity() == 0); |
| assertSame(densityReduced, scaleReduced); |
| } |
| |
| @Test |
| public void testDecodePremultipliedDefault() { |
| Bitmap simplePremul = BitmapFactory.decodeResource(mRes, R.drawable.premul_data); |
| assertTrue(simplePremul.isPremultiplied()); |
| } |
| |
| @Test |
| public void testDecodePremultipliedData() { |
| BitmapFactory.Options options = new BitmapFactory.Options(); |
| options.inScaled = false; |
| Bitmap premul = BitmapFactory.decodeResource(mRes, R.drawable.premul_data, options); |
| options.inPremultiplied = false; |
| Bitmap unpremul = BitmapFactory.decodeResource(mRes, R.drawable.premul_data, options); |
| assertEquals(premul.getConfig(), Bitmap.Config.ARGB_8888); |
| assertEquals(unpremul.getConfig(), Bitmap.Config.ARGB_8888); |
| assertTrue(premul.getHeight() == 1 && unpremul.getHeight() == 1); |
| assertTrue(premul.getWidth() == unpremul.getWidth() && |
| DEPREMUL_COLORS.length == RAW_COLORS.length && |
| premul.getWidth() == DEPREMUL_COLORS.length); |
| |
| // verify pixel data - unpremul should have raw values, premul will have rounding errors |
| for (int i = 0; i < premul.getWidth(); i++) { |
| assertEquals(premul.getPixel(i, 0), DEPREMUL_COLORS[i]); |
| assertEquals(unpremul.getPixel(i, 0), RAW_COLORS[i]); |
| } |
| } |
| |
| @Test |
| public void testDecodeInPurgeableAllocationCount() { |
| BitmapFactory.Options options = new BitmapFactory.Options(); |
| options.inSampleSize = 1; |
| options.inJustDecodeBounds = false; |
| options.inPurgeable = true; |
| options.inInputShareable = false; |
| byte[] array = obtainArray(); |
| Bitmap purgeableBitmap = BitmapFactory.decodeByteArray(array, 0, array.length, options); |
| assertFalse(purgeableBitmap.getAllocationByteCount() == 0); |
| } |
| |
| private int mDefaultCreationDensity; |
| private void verifyScaled(Bitmap b) { |
| assertEquals(b.getWidth(), START_WIDTH * 2); |
| assertEquals(b.getDensity(), 2); |
| } |
| |
| private void verifyUnscaled(Bitmap b) { |
| assertEquals(b.getWidth(), START_WIDTH); |
| assertEquals(b.getDensity(), mDefaultCreationDensity); |
| } |
| |
| @Test |
| public void testDecodeScaling() { |
| BitmapFactory.Options defaultOpt = new BitmapFactory.Options(); |
| |
| BitmapFactory.Options unscaledOpt = new BitmapFactory.Options(); |
| unscaledOpt.inScaled = false; |
| |
| BitmapFactory.Options scaledOpt = new BitmapFactory.Options(); |
| scaledOpt.inScaled = true; |
| scaledOpt.inDensity = 1; |
| scaledOpt.inTargetDensity = 2; |
| |
| mDefaultCreationDensity = Bitmap.createBitmap(1, 1, Config.ARGB_8888).getDensity(); |
| |
| byte[] bytes = obtainArray(); |
| |
| verifyUnscaled(BitmapFactory.decodeByteArray(bytes, 0, bytes.length)); |
| verifyUnscaled(BitmapFactory.decodeByteArray(bytes, 0, bytes.length, null)); |
| verifyUnscaled(BitmapFactory.decodeByteArray(bytes, 0, bytes.length, unscaledOpt)); |
| verifyUnscaled(BitmapFactory.decodeByteArray(bytes, 0, bytes.length, defaultOpt)); |
| |
| verifyUnscaled(BitmapFactory.decodeStream(obtainInputStream())); |
| verifyUnscaled(BitmapFactory.decodeStream(obtainInputStream(), null, null)); |
| verifyUnscaled(BitmapFactory.decodeStream(obtainInputStream(), null, unscaledOpt)); |
| verifyUnscaled(BitmapFactory.decodeStream(obtainInputStream(), null, defaultOpt)); |
| |
| // scaling should only occur if Options are passed with inScaled=true |
| verifyScaled(BitmapFactory.decodeByteArray(bytes, 0, bytes.length, scaledOpt)); |
| verifyScaled(BitmapFactory.decodeStream(obtainInputStream(), null, scaledOpt)); |
| } |
| |
| @Test |
| public void testParcel() { |
| BitmapFactory.Options opts = new BitmapFactory.Options(); |
| opts.inScaled = false; |
| Bitmap b = BitmapFactory.decodeResource(mRes, R.drawable.gif_test, opts); |
| assertNotNull(b); |
| |
| Parcel p = Parcel.obtain(); |
| b.writeToParcel(p, 0); |
| |
| p.setDataPosition(0); |
| Bitmap b2 = Bitmap.CREATOR.createFromParcel(p); |
| assertTrue(BitmapUtils.compareBitmaps(b, b2)); |
| |
| ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
| assertTrue(b2.compress(Bitmap.CompressFormat.JPEG, 50, baos)); |
| } |
| |
| @Test |
| public void testConfigs() { |
| // The output Config of a BitmapFactory decode depends on the request from the |
| // client and the properties of the image to be decoded. |
| // |
| // Options.inPreferredConfig = Config.ARGB_8888 |
| // This is the default value of inPreferredConfig. In this case, the image |
| // will always be decoded to Config.ARGB_8888. |
| // Options.inPreferredConfig = Config.RGB_565 |
| // If the encoded image is opaque, we will decode to Config.RGB_565, |
| // otherwise we will decode to whichever color type is the most natural match |
| // for the encoded data. |
| // Options.inPreferredConfig = Config.ARGB_4444 |
| // This is deprecated and will always decode to Config.ARGB_8888. |
| // Options.inPreferredConfig = Config.ALPHA_8 |
| // If the encoded image is gray, we will decode to 8-bit grayscale values |
| // and indicate that the output bitmap is Config.ALPHA_8. This is somewhat |
| // misleading because the image is really opaque and grayscale, but we are |
| // labeling each pixel as if it is a translucency (alpha) value. If the |
| // encoded image is not gray, we will decode to whichever color type is the |
| // most natural match for the encoded data. |
| // Options.inPreferredConfig = null |
| // We will decode to whichever Config is the most natural match with the |
| // encoded data. This could be ALPHA_8 (gray) or ARGB_8888. |
| // |
| // This test ensures that images are decoded to the intended Config and that the |
| // decodes match regardless of the Config. |
| decodeConfigs(R.drawable.alpha, 31, 31, true, false); |
| decodeConfigs(R.drawable.baseline_jpeg, 1280, 960, false, false); |
| decodeConfigs(R.drawable.bmp_test, 320, 240, false, false); |
| decodeConfigs(R.drawable.scaled2, 6, 8, false, false); |
| decodeConfigs(R.drawable.grayscale_jpg, 128, 128, false, true); |
| decodeConfigs(R.drawable.grayscale_png, 128, 128, false, true); |
| } |
| |
| @Test(expected=IllegalArgumentException.class) |
| public void testMutableHardwareInDecodeResource() { |
| Options options = new Options(); |
| options.inMutable = true; |
| options.inPreferredConfig = Config.HARDWARE; |
| BitmapFactory.decodeResource(mRes, R.drawable.alpha, options); |
| } |
| |
| @Test(expected=IllegalArgumentException.class) |
| public void testMutableHardwareInDecodeByteArray() { |
| Options options = new Options(); |
| options.inMutable = true; |
| options.inPreferredConfig = Config.HARDWARE; |
| BitmapFactory.decodeByteArray(new byte[100], 1, 20, options); |
| } |
| |
| @Test(expected=IllegalArgumentException.class) |
| public void testMutableHardwareInDecodeFile() { |
| Options options = new Options(); |
| options.inMutable = true; |
| options.inPreferredConfig = Config.HARDWARE; |
| BitmapFactory.decodeFile("barely/care.jpg", options); |
| } |
| |
| @Test(expected=IllegalArgumentException.class) |
| public void testMutableHardwareInDecodeFileDescriptor() { |
| Options options = new Options(); |
| options.inMutable = true; |
| options.inPreferredConfig = Config.HARDWARE; |
| BitmapFactory.decodeFileDescriptor(null, new Rect(), options); |
| } |
| |
| @Test(expected=IllegalArgumentException.class) |
| public void testMutableHardwareInDecodeResourceStream() { |
| Options options = new Options(); |
| options.inMutable = true; |
| options.inPreferredConfig = Config.HARDWARE; |
| TypedValue value = new TypedValue(); |
| BitmapFactory.decodeResourceStream(mRes, value, |
| new ByteArrayInputStream(new byte[20]), new Rect(), options); |
| } |
| |
| @Test |
| public void testDecodeHardwareBitmap() { |
| BitmapFactory.Options options = new BitmapFactory.Options(); |
| options.inPreferredConfig = Bitmap.Config.HARDWARE; |
| Bitmap hardwareBitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot, options); |
| assertNotNull(hardwareBitmap); |
| // Test that checks that correct bitmap was obtained is in uirendering/HardwareBitmapTests |
| assertEquals(Config.HARDWARE, hardwareBitmap.getConfig()); |
| } |
| |
| @Test |
| public void testJpegInfiniteLoop() { |
| BitmapFactory.Options options = new BitmapFactory.Options(); |
| options.inSampleSize = 19; |
| Bitmap bm = BitmapFactory.decodeResource(mRes, R.raw.b78329453, options); |
| assertNotNull(bm); |
| } |
| |
| private static final class DNG { |
| public final int resId; |
| public final int width; |
| public final int height; |
| |
| DNG(int resId, int width, int height) { |
| this.resId = resId; |
| this.width = width; |
| this.height = height; |
| } |
| } |
| |
| @Test |
| @CddTest(requirement = "5.1.5/C-0-6") |
| @Parameters(method = "parametersForTestDng") |
| @LargeTest |
| public void testDng(DNG dng) { |
| byte[] bytes = ImageDecoderTest.getAsByteArray(dng.resId); |
| // No scaling |
| Bitmap bm = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, mOpt1); |
| assertNotNull(bm); |
| assertEquals(dng.width, bm.getWidth()); |
| assertEquals(dng.height, bm.getHeight()); |
| } |
| |
| private Object[] parametersForTestDng() { |
| return new Object[]{ |
| new DNG(R.raw.sample_1mp, 600, 338), |
| new DNG(R.raw.sample_arw, 1616, 1080), |
| new DNG(R.raw.sample_cr2, 2304, 1536), |
| new DNG(R.raw.sample_nef, 4608, 3072), |
| new DNG(R.raw.sample_nrw, 4000, 3000), |
| new DNG(R.raw.sample_orf, 3200, 2400), |
| new DNG(R.raw.sample_pef, 4928, 3264), |
| new DNG(R.raw.sample_raf, 2048, 1536), |
| new DNG(R.raw.sample_rw2, 1920, 1440), |
| new DNG(R.raw.sample_srw, 5472, 3648), |
| }; |
| } |
| |
| @Test |
| public void testDecodePngFromPipe() { |
| // This test verifies that we can send a PNG over a pipe and |
| // successfully decode it. This behavior worked in N, so this |
| // verifies that do not break it for backwards compatibility. |
| // This was already not supported for the other Bitmap.CompressFormats |
| // (JPEG and WEBP), so we do not test those. |
| Bitmap source = Bitmap.createBitmap(100, 100, Config.ARGB_8888); |
| source.eraseColor(Color.RED); |
| try { |
| Bitmap result = sendOverPipe(source, CompressFormat.PNG); |
| assertTrue(source.sameAs(result)); |
| } catch (Exception e) { |
| fail(e.toString()); |
| } |
| } |
| |
| private Bitmap sendOverPipe(Bitmap source, CompressFormat format) |
| throws IOException, ErrnoException, InterruptedException { |
| FileDescriptor[] pipeFds = Os.pipe(); |
| final FileDescriptor readFd = pipeFds[0]; |
| final FileDescriptor writeFd = pipeFds[1]; |
| final Throwable[] compressErrors = new Throwable[1]; |
| final CountDownLatch writeFinished = new CountDownLatch(1); |
| final CountDownLatch readFinished = new CountDownLatch(1); |
| final Bitmap[] decodedResult = new Bitmap[1]; |
| Thread writeThread = new Thread() { |
| @Override |
| public void run() { |
| try { |
| FileOutputStream output = new FileOutputStream(writeFd); |
| source.compress(format, 100, output); |
| output.close(); |
| } catch (Throwable t) { |
| compressErrors[0] = t; |
| // Try closing the FD to unblock the test thread |
| try { |
| Os.close(writeFd); |
| } catch (Throwable ignore) {} |
| } finally { |
| writeFinished.countDown(); |
| } |
| } |
| }; |
| Thread readThread = new Thread() { |
| @Override |
| public void run() { |
| decodedResult[0] = BitmapFactory.decodeFileDescriptor(readFd); |
| } |
| }; |
| writeThread.start(); |
| readThread.start(); |
| writeThread.join(1000); |
| readThread.join(1000); |
| assertFalse(writeThread.isAlive()); |
| if (compressErrors[0] != null) { |
| fail(compressErrors[0].toString()); |
| } |
| if (readThread.isAlive()) { |
| // Test failure, try to clean up |
| Os.close(writeFd); |
| readThread.join(500); |
| fail("Read timed out"); |
| } |
| assertValidFd("readFd", readFd); |
| assertValidFd("writeFd", writeFd); |
| Os.close(readFd); |
| Os.close(writeFd); |
| return decodedResult[0]; |
| } |
| |
| private static void assertValidFd(String name, FileDescriptor fd) { |
| try { |
| assertTrue(fd.valid()); |
| // Hacky check to test that the underlying FD is still valid without using |
| // the private fcntlVoid to do F_GETFD |
| Os.close(Os.dup(fd)); |
| } catch (ErrnoException ex) { |
| fail(name + " is invalid: " + ex.getMessage()); |
| } |
| } |
| |
| private void decodeConfigs(int id, int width, int height, boolean hasAlpha, boolean isGray) { |
| Options opts = new BitmapFactory.Options(); |
| opts.inScaled = false; |
| assertEquals(Config.ARGB_8888, opts.inPreferredConfig); |
| Bitmap reference = BitmapFactory.decodeResource(mRes, id, opts); |
| assertNotNull(reference); |
| assertEquals(width, reference.getWidth()); |
| assertEquals(height, reference.getHeight()); |
| assertEquals(Config.ARGB_8888, reference.getConfig()); |
| |
| opts.inPreferredConfig = Config.ARGB_4444; |
| Bitmap argb4444 = BitmapFactory.decodeResource(mRes, id, opts); |
| assertNotNull(argb4444); |
| assertEquals(width, argb4444.getWidth()); |
| assertEquals(height, argb4444.getHeight()); |
| // ARGB_4444 is deprecated and we should decode to ARGB_8888. |
| assertEquals(Config.ARGB_8888, argb4444.getConfig()); |
| assertTrue(BitmapUtils.compareBitmaps(reference, argb4444)); |
| |
| opts.inPreferredConfig = Config.RGB_565; |
| Bitmap rgb565 = BitmapFactory.decodeResource(mRes, id, opts); |
| assertNotNull(rgb565); |
| assertEquals(width, rgb565.getWidth()); |
| assertEquals(height, rgb565.getHeight()); |
| if (!hasAlpha) { |
| assertEquals(Config.RGB_565, rgb565.getConfig()); |
| // Convert the RGB_565 bitmap to ARGB_8888 and test that it is similar to |
| // the reference. We lose information when decoding to 565, so there must |
| // be some tolerance. The tolerance is intentionally loose to allow us some |
| // flexibility regarding if we dither and how we color convert. |
| BitmapUtils.assertBitmapsMse(reference, rgb565.copy(Config.ARGB_8888, false), 30, true, |
| true); |
| } |
| |
| opts.inPreferredConfig = Config.ALPHA_8; |
| Bitmap alpha8 = BitmapFactory.decodeResource(mRes, id, opts); |
| assertNotNull(alpha8); |
| assertEquals(width, reference.getWidth()); |
| assertEquals(height, reference.getHeight()); |
| if (isGray) { |
| assertEquals(Config.ALPHA_8, alpha8.getConfig()); |
| // Convert the ALPHA_8 bitmap to ARGB_8888 and test that it is identical to |
| // the reference. We must do this manually because we are abusing ALPHA_8 |
| // in order to represent grayscale. |
| assertTrue(BitmapUtils.compareBitmaps(reference, grayToARGB(alpha8))); |
| assertNull(alpha8.getColorSpace()); |
| } |
| |
| // Setting inPreferredConfig to nullptr will cause the default Config to be |
| // selected, which in this case is ARGB_8888. |
| opts.inPreferredConfig = null; |
| Bitmap defaultBitmap = BitmapFactory.decodeResource(mRes, id, opts); |
| assertNotNull(defaultBitmap); |
| assertEquals(width, defaultBitmap.getWidth()); |
| assertEquals(height, defaultBitmap.getHeight()); |
| assertEquals(Config.ARGB_8888, defaultBitmap.getConfig()); |
| assertTrue(BitmapUtils.compareBitmaps(reference, defaultBitmap)); |
| } |
| |
| private static Bitmap grayToARGB(Bitmap gray) { |
| Bitmap argb = Bitmap.createBitmap(gray.getWidth(), gray.getHeight(), Config.ARGB_8888); |
| for (int y = 0; y < argb.getHeight(); y++) { |
| for (int x = 0; x < argb.getWidth(); x++) { |
| int grayByte = Color.alpha(gray.getPixel(x, y)); |
| argb.setPixel(x, y, Color.rgb(grayByte, grayByte, grayByte)); |
| } |
| } |
| return argb; |
| } |
| |
| @Test |
| @RequiresDevice |
| public void testDecode10BitHEIFTo10BitBitmap() { |
| assumeTrue( |
| "Test needs Android T.", ApiLevelUtil.isFirstApiAtLeast(Build.VERSION_CODES.TIRAMISU)); |
| assumeTrue("No 10-bit HEVC decoder, skip the test.", has10BitHEVCDecoder()); |
| |
| BitmapFactory.Options opt = new BitmapFactory.Options(); |
| opt.inPreferredConfig = Config.RGBA_1010102; |
| Bitmap bm = BitmapFactory.decodeStream(obtainInputStream(R.raw.heifimage_10bit), null, opt); |
| assertNotNull(bm); |
| assertEquals(4096, bm.getWidth()); |
| assertEquals(3072, bm.getHeight()); |
| assertEquals(Config.RGBA_1010102, bm.getConfig()); |
| } |
| |
| @Test |
| @RequiresDevice |
| public void testDecode10BitHEIFTo8BitBitmap() { |
| assumeTrue( |
| "Test needs Android T.", ApiLevelUtil.isFirstApiAtLeast(Build.VERSION_CODES.TIRAMISU)); |
| assumeTrue("No 10-bit HEVC decoder, skip the test.", has10BitHEVCDecoder()); |
| |
| BitmapFactory.Options opt = new BitmapFactory.Options(); |
| opt.inPreferredConfig = Config.ARGB_8888; |
| Bitmap bm1 = |
| BitmapFactory.decodeStream(obtainInputStream(R.raw.heifimage_10bit), null, opt); |
| Bitmap bm2 = BitmapFactory.decodeStream(obtainInputStream(R.raw.heifimage_10bit)); |
| assertNotNull(bm1); |
| assertEquals(4096, bm1.getWidth()); |
| assertEquals(3072, bm1.getHeight()); |
| assertEquals(Config.RGBA_1010102, bm1.getConfig()); |
| assertNotNull(bm2); |
| assertEquals(4096, bm2.getWidth()); |
| assertEquals(3072, bm2.getHeight()); |
| assertEquals(Config.RGBA_1010102, bm2.getConfig()); |
| } |
| |
| @Test |
| @RequiresDevice |
| public void testDecode8BitHEIFTo10BitBitmap() { |
| if (!MediaUtils.hasDecoder(MediaFormat.MIMETYPE_VIDEO_HEVC)) { |
| return; |
| } |
| BitmapFactory.Options opt = new BitmapFactory.Options(); |
| opt.inPreferredConfig = Config.RGBA_1010102; |
| Bitmap bm1 = |
| BitmapFactory.decodeStream(obtainInputStream(R.raw.heifwriter_input), null, opt); |
| Bitmap bm2 = BitmapFactory.decodeStream(obtainInputStream(R.raw.heifwriter_input)); |
| assertNotNull(bm1); |
| assertEquals(1920, bm1.getWidth()); |
| assertEquals(1080, bm1.getHeight()); |
| assertEquals(Config.ARGB_8888, bm1.getConfig()); |
| assertNotNull(bm2); |
| assertEquals(1920, bm2.getWidth()); |
| assertEquals(1080, bm2.getHeight()); |
| assertEquals(Config.ARGB_8888, bm2.getConfig()); |
| } |
| |
| private byte[] obtainArray() { |
| ByteArrayOutputStream stm = new ByteArrayOutputStream(); |
| Options opt = new BitmapFactory.Options(); |
| opt.inScaled = false; |
| Bitmap bitmap = BitmapFactory.decodeResource(mRes, R.drawable.start, opt); |
| bitmap.compress(Bitmap.CompressFormat.JPEG, 0, stm); |
| return(stm.toByteArray()); |
| } |
| |
| private InputStream obtainInputStream() { |
| return mRes.openRawResource(R.drawable.start); |
| } |
| |
| private InputStream obtainInputStream(int resId) { |
| return mRes.openRawResource(resId); |
| } |
| |
| private static ParcelFileDescriptor obtainParcelDescriptor(String path) throws IOException { |
| File file = new File(path); |
| return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); |
| } |
| |
| private String obtainPath() throws IOException { |
| return Utils.obtainPath(R.drawable.start, 0); |
| } |
| |
| private static boolean has10BitHEVCDecoder() { |
| MediaFormat format = new MediaFormat(); |
| format.setString(MediaFormat.KEY_MIME, "video/hevc"); |
| format.setInteger( |
| MediaFormat.KEY_PROFILE, MediaCodecInfo.CodecProfileLevel.HEVCProfileMain10); |
| format.setInteger( |
| MediaFormat.KEY_LEVEL, MediaCodecInfo.CodecProfileLevel.HEVCMainTierLevel5); |
| |
| MediaCodecList mcl = new MediaCodecList(MediaCodecList.ALL_CODECS); |
| if (mcl.findDecoderForFormat(format) == null) { |
| return false; |
| } |
| return true; |
| } |
| } |