1 /* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.graphics.cts; 18 19 import static org.junit.Assert.assertEquals; 20 import static org.junit.Assert.assertNotNull; 21 import static org.junit.Assert.assertNull; 22 import static org.junit.Assert.assertSame; 23 import static org.junit.Assert.assertTrue; 24 import static org.junit.Assert.fail; 25 import static org.testng.Assert.assertThrows; 26 27 import android.content.res.Resources; 28 import android.graphics.Bitmap; 29 import android.graphics.Bitmap.Config; 30 import android.graphics.BitmapFactory; 31 import android.graphics.BitmapFactory.Options; 32 import android.graphics.BitmapRegionDecoder; 33 import android.graphics.Canvas; 34 import android.graphics.ColorSpace; 35 import android.graphics.Rect; 36 import android.hardware.HardwareBuffer; 37 import android.media.MediaFormat; 38 import android.os.ParcelFileDescriptor; 39 import android.platform.test.annotations.DisabledOnRavenwood; 40 41 import androidx.test.InstrumentationRegistry; 42 import androidx.test.filters.LargeTest; 43 import androidx.test.filters.SmallTest; 44 import androidx.test.runner.AndroidJUnit4; 45 46 import com.android.compatibility.common.util.BitmapUtils; 47 import com.android.compatibility.common.util.MediaUtils; 48 49 import org.junit.After; 50 import org.junit.Before; 51 import org.junit.Test; 52 import org.junit.runner.RunWith; 53 54 import java.io.ByteArrayOutputStream; 55 import java.io.File; 56 import java.io.FileDescriptor; 57 import java.io.FileInputStream; 58 import java.io.FileNotFoundException; 59 import java.io.FileOutputStream; 60 import java.io.IOException; 61 import java.io.InputStream; 62 import java.util.ArrayList; 63 64 @SmallTest 65 @RunWith(AndroidJUnit4.class) 66 public class BitmapRegionDecoderTest { 67 // The test images, including baseline JPEGs and progressive JPEGs, a PNG, 68 // a WEBP, a GIF and a BMP. 69 private static final int[] RES_IDS = new int[] { 70 R.drawable.baseline_jpeg, R.drawable.progressive_jpeg, 71 R.drawable.baseline_restart_jpeg, 72 R.drawable.progressive_restart_jpeg, 73 R.drawable.png_test, R.drawable.webp_test, 74 R.drawable.gif_test, R.drawable.bmp_test 75 }; 76 private static final String[] NAMES_TEMP_FILES = new String[] { 77 "baseline_temp.jpg", "progressive_temp.jpg", "baseline_restart_temp.jpg", 78 "progressive_restart_temp.jpg", "png_temp.png", "webp_temp.webp", 79 "gif_temp.gif", "bmp_temp.bmp" 80 }; 81 82 // Do not change the order! 83 private static final String[] ASSET_NAMES = { 84 "blue-16bit-srgb.png", 85 "green-p3.png", 86 "red-adobergb.png", 87 "green-srgb.png", 88 "blue-16bit-prophoto.png", 89 }; 90 private static final ColorSpace.Named[][] ASSET_COLOR_SPACES = { 91 // inPreferredConfig = ARGB_8888 92 { 93 ColorSpace.Named.EXTENDED_SRGB, // This 16 bit PNG is decoded to F16. 94 ColorSpace.Named.DISPLAY_P3, 95 ColorSpace.Named.ADOBE_RGB, 96 ColorSpace.Named.SRGB, 97 ColorSpace.Named.PRO_PHOTO_RGB, 98 }, 99 // inPreferredConfig = RGB_565 100 { 101 ColorSpace.Named.SRGB, 102 ColorSpace.Named.DISPLAY_P3, 103 ColorSpace.Named.ADOBE_RGB, 104 ColorSpace.Named.SRGB, 105 ColorSpace.Named.PRO_PHOTO_RGB, 106 } 107 }; 108 109 // The width and height of the above image. 110 // -1 denotes that the image format is not supported by BitmapRegionDecoder 111 private static final int WIDTHS[] = new int[] { 112 1280, 1280, 1280, 1280, 640, 640, -1, -1}; 113 private static final int HEIGHTS[] = new int[] {960, 960, 960, 960, 480, 480, -1, -1}; 114 115 // The number of test images, format of which is supported by BitmapRegionDecoder 116 private static final int NUM_TEST_IMAGES = 6; 117 118 private static final int TILE_SIZE = 256; 119 private static final int SMALL_TILE_SIZE = 16; 120 121 // Configurations for BitmapFactory.Options 122 private static final Config[] COLOR_CONFIGS = new Config[] {Config.ARGB_8888, 123 Config.RGB_565}; 124 private static final int[] SAMPLESIZES = new int[] {1, 4}; 125 126 // We allow a certain degree of discrepancy between the tile-based decoding 127 // result and the regular decoding result, because the two decoders may have 128 // different implementations. The allowable discrepancy is set to a mean 129 // square error of 3 * (1 * 1) among the RGB values. 130 private static final int MSE_MARGIN = 3 * (1 * 1); 131 132 // MSE margin for WebP Region-Decoding for 'Config.RGB_565' is little bigger. 133 private static final int MSE_MARGIN_WEB_P_CONFIG_RGB_565 = 8; 134 135 private ArrayList<File> mFilesCreated = new ArrayList<>(NAMES_TEMP_FILES.length); 136 137 private Resources mRes; 138 139 @Before setup()140 public void setup() { 141 mRes = InstrumentationRegistry.getTargetContext().getResources(); 142 } 143 144 @After teardown()145 public void teardown() { 146 for (File file : mFilesCreated) { 147 file.delete(); 148 } 149 } 150 151 @Test testNewInstanceInputStream()152 public void testNewInstanceInputStream() throws IOException { 153 for (int i = 0; i < RES_IDS.length; ++i) { 154 InputStream is = obtainInputStream(RES_IDS[i]); 155 try { 156 BitmapRegionDecoder decoder = 157 BitmapRegionDecoder.newInstance(is); 158 assertEquals(WIDTHS[i], decoder.getWidth()); 159 assertEquals(HEIGHTS[i], decoder.getHeight()); 160 } catch (IOException e) { 161 assertEquals(WIDTHS[i], -1); 162 assertEquals(HEIGHTS[i], -1); 163 } finally { 164 if (is != null) { 165 is.close(); 166 } 167 } 168 } 169 } 170 171 @Test testNewInstanceByteArray()172 public void testNewInstanceByteArray() throws IOException { 173 for (int i = 0; i < RES_IDS.length; ++i) { 174 byte[] imageData = obtainByteArray(RES_IDS[i]); 175 try { 176 BitmapRegionDecoder decoder = BitmapRegionDecoder 177 .newInstance(imageData, 0, imageData.length); 178 assertEquals(WIDTHS[i], decoder.getWidth()); 179 assertEquals(HEIGHTS[i], decoder.getHeight()); 180 } catch (IOException e) { 181 assertEquals(WIDTHS[i], -1); 182 assertEquals(HEIGHTS[i], -1); 183 } 184 } 185 } 186 187 @Test testNewInstanceStringAndFileDescriptor()188 public void testNewInstanceStringAndFileDescriptor() throws IOException { 189 for (int i = 0; i < RES_IDS.length; ++i) { 190 String filepath = obtainPath(i); 191 ParcelFileDescriptor pfd = obtainParcelDescriptor(filepath); 192 try { 193 BitmapRegionDecoder decoder1 = 194 BitmapRegionDecoder.newInstance(filepath); 195 assertEquals(WIDTHS[i], decoder1.getWidth()); 196 assertEquals(HEIGHTS[i], decoder1.getHeight()); 197 198 BitmapRegionDecoder decoder2 = 199 BitmapRegionDecoder.newInstance(pfd); 200 assertEquals(WIDTHS[i], decoder2.getWidth()); 201 assertEquals(HEIGHTS[i], decoder2.getHeight()); 202 } catch (IOException e) { 203 assertEquals(WIDTHS[i], -1); 204 assertEquals(HEIGHTS[i], -1); 205 } 206 } 207 } 208 209 @LargeTest 210 @Test testDecodeRegionInputStream()211 public void testDecodeRegionInputStream() throws IOException { 212 Options opts = new BitmapFactory.Options(); 213 for (int i = 0; i < NUM_TEST_IMAGES; ++i) { 214 for (int j = 0; j < SAMPLESIZES.length; ++j) { 215 for (int k = 0; k < COLOR_CONFIGS.length; ++k) { 216 opts.inSampleSize = SAMPLESIZES[j]; 217 opts.inPreferredConfig = COLOR_CONFIGS[k]; 218 219 InputStream is1 = obtainInputStream(RES_IDS[i]); 220 BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is1); 221 InputStream is2 = obtainInputStream(RES_IDS[i]); 222 Bitmap wholeImage = BitmapFactory.decodeStream(is2, null, opts); 223 224 if (RES_IDS[i] == R.drawable.webp_test && COLOR_CONFIGS[k] == Config.RGB_565) { 225 compareRegionByRegion(decoder, opts, MSE_MARGIN_WEB_P_CONFIG_RGB_565, 226 wholeImage); 227 } else { 228 compareRegionByRegion(decoder, opts, MSE_MARGIN, wholeImage); 229 } 230 wholeImage.recycle(); 231 } 232 } 233 } 234 } 235 236 @LargeTest 237 @Test testDecodeRegionInputStreamInBitmap()238 public void testDecodeRegionInputStreamInBitmap() throws IOException { 239 Options opts = new BitmapFactory.Options(); 240 for (int i = 0; i < NUM_TEST_IMAGES; ++i) { 241 for (int j = 0; j < SAMPLESIZES.length; ++j) { 242 for (int k = 0; k < COLOR_CONFIGS.length; ++k) { 243 opts.inSampleSize = SAMPLESIZES[j]; 244 opts.inPreferredConfig = COLOR_CONFIGS[k]; 245 opts.inBitmap = null; 246 247 InputStream is1 = obtainInputStream(RES_IDS[i]); 248 BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is1); 249 InputStream is2 = obtainInputStream(RES_IDS[i]); 250 Bitmap wholeImage = BitmapFactory.decodeStream(is2, null, opts); 251 252 // setting inBitmap enables several checks within compareRegionByRegion 253 opts.inBitmap = Bitmap.createBitmap( 254 wholeImage.getWidth(), wholeImage.getHeight(), opts.inPreferredConfig); 255 256 if (RES_IDS[i] == R.drawable.webp_test && COLOR_CONFIGS[k] == Config.RGB_565) { 257 compareRegionByRegion(decoder, opts, MSE_MARGIN_WEB_P_CONFIG_RGB_565, 258 wholeImage); 259 } else { 260 compareRegionByRegion(decoder, opts, MSE_MARGIN, wholeImage); 261 } 262 wholeImage.recycle(); 263 } 264 } 265 } 266 } 267 268 @LargeTest 269 @Test testDecodeRegionByteArray()270 public void testDecodeRegionByteArray() throws IOException { 271 Options opts = new BitmapFactory.Options(); 272 for (int i = 0; i < NUM_TEST_IMAGES; ++i) { 273 for (int j = 0; j < SAMPLESIZES.length; ++j) { 274 for (int k = 0; k < COLOR_CONFIGS.length; ++k) { 275 opts.inSampleSize = SAMPLESIZES[j]; 276 opts.inPreferredConfig = COLOR_CONFIGS[k]; 277 278 byte[] imageData = obtainByteArray(RES_IDS[i]); 279 BitmapRegionDecoder decoder = BitmapRegionDecoder 280 .newInstance(imageData, 0, imageData.length); 281 Bitmap wholeImage = BitmapFactory.decodeByteArray(imageData, 282 0, imageData.length, opts); 283 284 if (RES_IDS[i] == R.drawable.webp_test && COLOR_CONFIGS[k] == Config.RGB_565) { 285 compareRegionByRegion(decoder, opts, MSE_MARGIN_WEB_P_CONFIG_RGB_565, 286 wholeImage); 287 } else { 288 compareRegionByRegion(decoder, opts, MSE_MARGIN, wholeImage); 289 } 290 wholeImage.recycle(); 291 } 292 } 293 } 294 } 295 296 @LargeTest 297 @Test testDecodeRegionStringAndFileDescriptor()298 public void testDecodeRegionStringAndFileDescriptor() throws IOException { 299 Options opts = new BitmapFactory.Options(); 300 for (int i = 0; i < NUM_TEST_IMAGES; ++i) { 301 String filepath = obtainPath(i); 302 for (int j = 0; j < SAMPLESIZES.length; ++j) { 303 for (int k = 0; k < COLOR_CONFIGS.length; ++k) { 304 opts.inSampleSize = SAMPLESIZES[j]; 305 opts.inPreferredConfig = COLOR_CONFIGS[k]; 306 307 BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(filepath); 308 Bitmap wholeImage = BitmapFactory.decodeFile(filepath, opts); 309 if (RES_IDS[i] == R.drawable.webp_test && COLOR_CONFIGS[k] == Config.RGB_565) { 310 compareRegionByRegion(decoder, opts, MSE_MARGIN_WEB_P_CONFIG_RGB_565, 311 wholeImage); 312 } else { 313 compareRegionByRegion(decoder, opts, MSE_MARGIN, wholeImage); 314 } 315 316 ParcelFileDescriptor pfd1 = obtainParcelDescriptor(filepath); 317 decoder = BitmapRegionDecoder.newInstance(pfd1); 318 if (RES_IDS[i] == R.drawable.webp_test && COLOR_CONFIGS[k] == Config.RGB_565) { 319 compareRegionByRegion(decoder, opts, MSE_MARGIN_WEB_P_CONFIG_RGB_565, 320 wholeImage); 321 } else { 322 compareRegionByRegion(decoder, opts, MSE_MARGIN, wholeImage); 323 } 324 wholeImage.recycle(); 325 } 326 } 327 } 328 } 329 330 @Test testRecycle()331 public void testRecycle() throws IOException { 332 InputStream is = obtainInputStream(RES_IDS[0]); 333 BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is); 334 decoder.recycle(); 335 assertTrue(decoder.isRecycled()); 336 try { 337 decoder.getWidth(); 338 fail("Should throw an exception!"); 339 } catch (Exception e) { 340 } 341 342 try { 343 decoder.getHeight(); 344 fail("Should throw an exception!"); 345 } catch (Exception e) { 346 } 347 348 Rect rect = new Rect(0, 0, WIDTHS[0], HEIGHTS[0]); 349 BitmapFactory.Options opts = new BitmapFactory.Options(); 350 try { 351 decoder.decodeRegion(rect, opts); 352 fail("Should throw an exception!"); 353 } catch (Exception e) { 354 } 355 } 356 357 // The documentation for BitmapRegionDecoder guarantees that, when reusing a 358 // bitmap, "the provided Bitmap's width, height, and Bitmap.Config will not 359 // be changed". If the inBitmap is too small, decoded content will be 360 // clipped into inBitmap. Here we test that: 361 // (1) If inBitmap is specified, it is always used. 362 // (2) The width, height, and Config of inBitmap are never changed. 363 // (3) All of the pixels decoded into inBitmap exactly match the pixels 364 // of a decode where inBitmap is NULL. 365 @LargeTest 366 @Test testInBitmapReuse()367 public void testInBitmapReuse() throws IOException { 368 Options defaultOpts = new BitmapFactory.Options(); 369 Options reuseOpts = new BitmapFactory.Options(); 370 Rect subset = new Rect(0, 0, TILE_SIZE, TILE_SIZE); 371 372 for (int i = 0; i < NUM_TEST_IMAGES; i++) { 373 InputStream is = obtainInputStream(RES_IDS[i]); 374 BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is); 375 for (int j = 0; j < SAMPLESIZES.length; j++) { 376 int sampleSize = SAMPLESIZES[j]; 377 defaultOpts.inSampleSize = sampleSize; 378 reuseOpts.inSampleSize = sampleSize; 379 380 // We don't need to worry about rounding here because sampleSize 381 // divides evenly into TILE_SIZE. 382 assertEquals(0, TILE_SIZE % sampleSize); 383 int scaledDim = TILE_SIZE / sampleSize; 384 int chunkSize = scaledDim / 2; 385 for (int k = 0; k < COLOR_CONFIGS.length; k++) { 386 Config config = COLOR_CONFIGS[k]; 387 defaultOpts.inPreferredConfig = config; 388 reuseOpts.inPreferredConfig = config; 389 390 // For both the width and the height of inBitmap, we test three 391 // interesting cases: 392 // (1) inBitmap dimension is smaller than scaledDim. The decoded 393 // pixels that fit inside inBitmap should exactly match the 394 // corresponding decoded pixels from the same region decode, 395 // performed without an inBitmap. The pixels that do not fit 396 // inside inBitmap should be clipped. 397 // (2) inBitmap dimension matches scaledDim. After the decode, 398 // the pixels and dimensions of inBitmap should exactly match 399 // those of the result bitmap of the same region decode, 400 // performed without an inBitmap. 401 // (3) inBitmap dimension is larger than scaledDim. After the 402 // decode, inBitmap should contain decoded pixels for the 403 // entire region, exactly matching the decoded pixels 404 // produced when inBitmap is not specified. The additional 405 // pixels in inBitmap are left the same as before the decode. 406 for (int w = chunkSize; w <= 3 * chunkSize; w += chunkSize) { 407 for (int h = chunkSize; h <= 3 * chunkSize; h += chunkSize) { 408 // Decode reusing inBitmap. 409 reuseOpts.inBitmap = Bitmap.createBitmap(w, h, config); 410 Bitmap reuseResult = decoder.decodeRegion(subset, reuseOpts); 411 assertSame(reuseOpts.inBitmap, reuseResult); 412 assertEquals(reuseResult.getWidth(), w); 413 assertEquals(reuseResult.getHeight(), h); 414 assertEquals(reuseResult.getConfig(), config); 415 416 // Decode into a new bitmap. 417 Bitmap defaultResult = decoder.decodeRegion(subset, defaultOpts); 418 assertEquals(defaultResult.getWidth(), scaledDim); 419 assertEquals(defaultResult.getHeight(), scaledDim); 420 421 // Ensure that the decoded pixels of reuseResult and defaultResult 422 // are identical. 423 int cropWidth = Math.min(w, scaledDim); 424 int cropHeight = Math.min(h, scaledDim); 425 Rect crop = new Rect(0 ,0, cropWidth, cropHeight); 426 Bitmap reuseCropped = cropBitmap(reuseResult, crop); 427 Bitmap defaultCropped = cropBitmap(defaultResult, crop); 428 BitmapUtils.assertBitmapsMse(reuseCropped, defaultCropped, 0, true, 429 false); 430 } 431 } 432 } 433 } 434 } 435 } 436 437 @Test 438 @DisabledOnRavenwood(blockedBy = HardwareBuffer.class) testDecodeHardwareBitmap()439 public void testDecodeHardwareBitmap() throws IOException { 440 BitmapFactory.Options options = new BitmapFactory.Options(); 441 options.inPreferredConfig = Bitmap.Config.HARDWARE; 442 InputStream is = obtainInputStream(RES_IDS[0]); 443 BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is); 444 Bitmap hardwareBitmap = decoder.decodeRegion(new Rect(0, 0, 10, 10), options); 445 assertNotNull(hardwareBitmap); 446 // Test that checks that correct bitmap was obtained is in uirendering/HardwareBitmapTests 447 assertEquals(Config.HARDWARE, hardwareBitmap.getConfig()); 448 } 449 450 @Test testOutColorType()451 public void testOutColorType() throws IOException { 452 Options opts = new BitmapFactory.Options(); 453 for (int i = 0; i < NUM_TEST_IMAGES; ++i) { 454 for (int j = 0; j < SAMPLESIZES.length; ++j) { 455 for (int k = 0; k < COLOR_CONFIGS.length; ++k) { 456 opts.inSampleSize = SAMPLESIZES[j]; 457 opts.inPreferredConfig = COLOR_CONFIGS[k]; 458 459 InputStream is1 = obtainInputStream(RES_IDS[i]); 460 BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is1); 461 Bitmap region = decoder.decodeRegion( 462 new Rect(0, 0, TILE_SIZE, TILE_SIZE), opts); 463 decoder.recycle(); 464 465 assertSame(opts.inPreferredConfig, opts.outConfig); 466 assertSame(opts.outConfig, region.getConfig()); 467 region.recycle(); 468 } 469 } 470 } 471 } 472 473 @Test testOutColorSpace()474 public void testOutColorSpace() throws IOException { 475 Options opts = new BitmapFactory.Options(); 476 for (int i = 0; i < ASSET_NAMES.length; i++) { 477 for (int j = 0; j < SAMPLESIZES.length; ++j) { 478 for (int k = 0; k < COLOR_CONFIGS.length; ++k) { 479 opts.inPreferredConfig = COLOR_CONFIGS[k]; 480 481 String assetName = ASSET_NAMES[i]; 482 InputStream is1 = obtainInputStream(assetName); 483 BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is1); 484 Bitmap region = decoder.decodeRegion( 485 new Rect(0, 0, SMALL_TILE_SIZE, SMALL_TILE_SIZE), opts); 486 decoder.recycle(); 487 488 ColorSpace expected = ColorSpace.get(ASSET_COLOR_SPACES[k][i]); 489 assertSame(expected, opts.outColorSpace); 490 assertSame(expected, region.getColorSpace()); 491 region.recycle(); 492 } 493 } 494 } 495 } 496 497 @Test testReusedColorSpace()498 public void testReusedColorSpace() throws IOException { 499 Bitmap b = Bitmap.createBitmap(SMALL_TILE_SIZE, SMALL_TILE_SIZE, Config.ARGB_8888, 500 false, ColorSpace.get(ColorSpace.Named.ADOBE_RGB)); 501 502 Options opts = new BitmapFactory.Options(); 503 opts.inBitmap = b; 504 505 // sRGB 506 BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance( 507 obtainInputStream(ASSET_NAMES[3])); 508 Bitmap region = decoder.decodeRegion( 509 new Rect(0, 0, SMALL_TILE_SIZE, SMALL_TILE_SIZE), opts); 510 decoder.recycle(); 511 512 assertEquals(ColorSpace.get(ColorSpace.Named.SRGB), region.getColorSpace()); 513 514 // DisplayP3 515 decoder = BitmapRegionDecoder.newInstance(obtainInputStream(ASSET_NAMES[1])); 516 region = decoder.decodeRegion(new Rect(0, 0, SMALL_TILE_SIZE, SMALL_TILE_SIZE), opts); 517 decoder.recycle(); 518 519 assertEquals(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), region.getColorSpace()); 520 } 521 522 @Test testReusedColorSpaceCropped()523 public void testReusedColorSpaceCropped() throws IOException { 524 Bitmap b = Bitmap.createBitmap(SMALL_TILE_SIZE, SMALL_TILE_SIZE, Config.ARGB_8888, 525 false, ColorSpace.get(ColorSpace.Named.ADOBE_RGB)); 526 527 Options opts = new BitmapFactory.Options(); 528 opts.inBitmap = b; 529 530 // sRGB 531 BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance( 532 obtainInputStream(ASSET_NAMES[3])); 533 Bitmap region = decoder.decodeRegion( 534 new Rect(0, 0, TILE_SIZE, TILE_SIZE), opts); 535 decoder.recycle(); 536 537 assertSame(b, region); 538 assertEquals(ColorSpace.get(ColorSpace.Named.SRGB), region.getColorSpace()); 539 540 // DisplayP3 541 decoder = BitmapRegionDecoder.newInstance(obtainInputStream(ASSET_NAMES[1])); 542 region = decoder.decodeRegion(new Rect(0, 0, TILE_SIZE, TILE_SIZE), opts); 543 decoder.recycle(); 544 545 assertSame(b, region); 546 assertEquals(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), region.getColorSpace()); 547 } 548 549 @Test testInColorSpace()550 public void testInColorSpace() throws IOException { 551 Options opts = new BitmapFactory.Options(); 552 for (int i = 0; i < NUM_TEST_IMAGES; ++i) { 553 for (int j = 0; j < SAMPLESIZES.length; ++j) { 554 opts.inSampleSize = SAMPLESIZES[j]; 555 opts.inPreferredColorSpace = ColorSpace.get(ColorSpace.Named.DISPLAY_P3); 556 557 InputStream is1 = obtainInputStream(RES_IDS[i]); 558 BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is1); 559 Bitmap region = decoder.decodeRegion(new Rect(0, 0, TILE_SIZE, TILE_SIZE), opts); 560 decoder.recycle(); 561 562 assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), opts.outColorSpace); 563 assertSame(opts.outColorSpace, region.getColorSpace()); 564 region.recycle(); 565 } 566 } 567 } 568 569 @Test testInColorSpaceRGBA16F()570 public void testInColorSpaceRGBA16F() throws IOException { 571 Options opts = new BitmapFactory.Options(); 572 opts.inPreferredColorSpace = ColorSpace.get(ColorSpace.Named.ADOBE_RGB); 573 574 InputStream is1 = obtainInputStream(ASSET_NAMES[0]); 575 BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is1); 576 Bitmap region = decoder.decodeRegion(new Rect(0, 0, SMALL_TILE_SIZE, SMALL_TILE_SIZE), opts); 577 decoder.recycle(); 578 579 assertSame(ColorSpace.get(ColorSpace.Named.ADOBE_RGB), region.getColorSpace()); 580 region.recycle(); 581 } 582 583 @Test testInColorSpace565()584 public void testInColorSpace565() throws IOException { 585 Options opts = new BitmapFactory.Options(); 586 opts.inPreferredConfig = Config.RGB_565; 587 opts.inPreferredColorSpace = ColorSpace.get(ColorSpace.Named.ADOBE_RGB); 588 589 InputStream is1 = obtainInputStream(ASSET_NAMES[1]); // Display P3 590 BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is1); 591 Bitmap region = decoder.decodeRegion(new Rect(0, 0, SMALL_TILE_SIZE, SMALL_TILE_SIZE), opts); 592 decoder.recycle(); 593 594 assertSame(ColorSpace.get(ColorSpace.Named.ADOBE_RGB), region.getColorSpace()); 595 region.recycle(); 596 } 597 598 @Test testF16WithInBitmap()599 public void testF16WithInBitmap() throws IOException { 600 // This image normally decodes to F16, but if there is an inBitmap, 601 // decoding will match the Config of that Bitmap. 602 InputStream is = obtainInputStream(ASSET_NAMES[0]); // F16 603 BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is); 604 605 Options opts = new BitmapFactory.Options(); 606 for (Bitmap.Config config : new Bitmap.Config[] { 607 null, // Do not use inBitmap 608 Bitmap.Config.ARGB_8888, 609 Bitmap.Config.RGB_565}) { 610 Bitmap.Config expected = config; 611 if (expected == null) { 612 expected = Bitmap.Config.RGBA_F16; 613 opts.inBitmap = null; 614 } else { 615 opts.inBitmap = Bitmap.createBitmap(decoder.getWidth(), 616 decoder.getHeight(), config); 617 } 618 Bitmap result = decoder.decodeRegion(new Rect(0, 0, decoder.getWidth(), 619 decoder.getHeight()), opts); 620 assertNotNull(result); 621 assertEquals(expected, result.getConfig()); 622 } 623 } 624 625 @Test(expected = IllegalArgumentException.class) testInColorSpaceNotRgb()626 public void testInColorSpaceNotRgb() throws IOException { 627 Options opts = new BitmapFactory.Options(); 628 opts.inPreferredColorSpace = ColorSpace.get(ColorSpace.Named.CIE_LAB); 629 InputStream is1 = obtainInputStream(RES_IDS[0]); 630 BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is1); 631 Bitmap region = decoder.decodeRegion(new Rect(0, 0, TILE_SIZE, TILE_SIZE), opts); 632 } 633 634 @Test(expected = IllegalArgumentException.class) testInColorSpaceNoTransferParameters()635 public void testInColorSpaceNoTransferParameters() throws IOException { 636 Options opts = new BitmapFactory.Options(); 637 opts.inPreferredColorSpace = new ColorSpace.Rgb("NoTransferParams", 638 new float[]{ 0.640f, 0.330f, 0.300f, 0.600f, 0.150f, 0.060f }, 639 ColorSpace.ILLUMINANT_D50, 640 x -> Math.pow(x, 1.0f / 2.2f), x -> Math.pow(x, 2.2f), 641 0, 1); 642 InputStream is1 = obtainInputStream(RES_IDS[0]); 643 BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is1); 644 Bitmap region = decoder.decodeRegion(new Rect(0, 0, TILE_SIZE, TILE_SIZE), opts); 645 } 646 647 @Test(expected = IllegalArgumentException.class) 648 @DisabledOnRavenwood(blockedBy = HardwareBuffer.class) testHardwareBitmapIn()649 public void testHardwareBitmapIn() throws IOException { 650 Options opts = new BitmapFactory.Options(); 651 Bitmap bitmap = Bitmap.createBitmap(TILE_SIZE, TILE_SIZE, Config.ARGB_8888) 652 .copy(Config.HARDWARE, false); 653 opts.inBitmap = bitmap; 654 InputStream is = obtainInputStream(RES_IDS[0]); 655 BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is); 656 decoder.decodeRegion(new Rect(0, 0, TILE_SIZE, TILE_SIZE), opts); 657 } 658 659 @Test testRecycledBitmapIn()660 public void testRecycledBitmapIn() throws IOException { 661 Options opts = new BitmapFactory.Options(); 662 Bitmap bitmap = Bitmap.createBitmap(TILE_SIZE, TILE_SIZE, Config.ARGB_8888); 663 bitmap.recycle(); 664 665 opts.inBitmap = bitmap; 666 InputStream is = obtainInputStream(RES_IDS[0]); 667 BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is); 668 assertThrows(IllegalArgumentException.class, () -> { 669 decoder.decodeRegion(new Rect(0, 0, TILE_SIZE, TILE_SIZE), opts); 670 }); 671 } 672 673 @Test 674 @DisabledOnRavenwood(blockedBy = MediaUtils.class) testHeif()675 public void testHeif() throws IOException { 676 if (!MediaUtils.hasDecoder(MediaFormat.MIMETYPE_VIDEO_HEVC)) { 677 // HEIF support is optional when HEVC decoder is not supported. 678 return; 679 } 680 InputStream is = obtainInputStream(R.raw.heifwriter_input); 681 BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is); 682 Bitmap region = decoder.decodeRegion(new Rect(0, 0, TILE_SIZE, TILE_SIZE), null); 683 assertNotNull(region); 684 685 // Prior to a fix, this crashed in native code. 686 Bitmap full = decoder.decodeRegion(new Rect(0, 0, decoder.getWidth(), decoder.getHeight()), 687 null); 688 assertNotNull(full); 689 } 690 691 @Test(expected = NullPointerException.class) testNullParcelFileDescriptor()692 public void testNullParcelFileDescriptor() throws IOException { 693 ParcelFileDescriptor pfd = null; 694 BitmapRegionDecoder.newInstance(pfd); 695 } 696 697 @Test(expected = NullPointerException.class) testNullFileDescriptor()698 public void testNullFileDescriptor() throws IOException { 699 FileDescriptor fd = null; 700 BitmapRegionDecoder.newInstance(fd, false); 701 } 702 703 @Test testNullInputStream()704 public void testNullInputStream() throws IOException { 705 InputStream is = null; 706 assertNull(BitmapRegionDecoder.newInstance(is)); 707 } 708 709 @Test(expected = NullPointerException.class) testNullPathName()710 public void testNullPathName() throws IOException { 711 String pathName = null; 712 BitmapRegionDecoder.newInstance(pathName); 713 } 714 715 @Test(expected = IOException.class) testEmptyPathName()716 public void testEmptyPathName() throws IOException { 717 String pathName = ""; 718 BitmapRegionDecoder.newInstance(pathName); 719 } 720 721 @Test(expected = NullPointerException.class) testNullByteArray()722 public void testNullByteArray() throws IOException { 723 byte[] data = null; 724 BitmapRegionDecoder.newInstance(data, 0, 0); 725 } 726 727 @Test(expected = ArrayIndexOutOfBoundsException.class) testNegativeOffset()728 public void testNegativeOffset() throws IOException { 729 byte[] data = new byte[10]; 730 BitmapRegionDecoder.newInstance(data, -1, 10); 731 } 732 733 @Test(expected = ArrayIndexOutOfBoundsException.class) testNegativeLength()734 public void testNegativeLength() throws IOException { 735 byte[] data = new byte[10]; 736 BitmapRegionDecoder.newInstance(data, 0, -10); 737 } 738 739 @Test(expected = ArrayIndexOutOfBoundsException.class) testTooLong()740 public void testTooLong() throws IOException { 741 byte[] data = new byte[10]; 742 BitmapRegionDecoder.newInstance(data, 1, 10); 743 } 744 745 @Test(expected = IOException.class) testEmptyByteArray()746 public void testEmptyByteArray() throws IOException { 747 byte[] data = new byte[0]; 748 BitmapRegionDecoder.newInstance(data, 0, 0); 749 } 750 751 @Test(expected = IOException.class) testEmptyInputStream()752 public void testEmptyInputStream() throws IOException { 753 InputStream is = new InputStream() { 754 @Override 755 public int read() { 756 return -1; 757 } 758 }; 759 BitmapRegionDecoder.newInstance(is); 760 } 761 createEmptyFile()762 private static File createEmptyFile() throws IOException { 763 File dir = InstrumentationRegistry.getTargetContext().getFilesDir(); 764 dir.mkdirs(); 765 return File.createTempFile("emptyFile", "tmp", dir); 766 } 767 768 @Test testEmptyFile()769 public void testEmptyFile() throws IOException { 770 File file = createEmptyFile(); 771 String pathName = file.getAbsolutePath(); 772 assertThrows(IOException.class, () -> { 773 BitmapRegionDecoder.newInstance(pathName); 774 }); 775 file.delete(); 776 } 777 778 @Test testEmptyFileDescriptor()779 public void testEmptyFileDescriptor() throws IOException { 780 File file = createEmptyFile(); 781 FileInputStream fileStream = new FileInputStream(file); 782 FileDescriptor fd = fileStream.getFD(); 783 assertThrows(IOException.class, () -> { 784 BitmapRegionDecoder.newInstance(fd, false); 785 }); 786 file.delete(); 787 } 788 789 @Test testEmptyParcelFileDescriptor()790 public void testEmptyParcelFileDescriptor() throws IOException, FileNotFoundException { 791 File file = createEmptyFile(); 792 ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file, 793 ParcelFileDescriptor.MODE_READ_ONLY); 794 assertThrows(IOException.class, () -> { 795 BitmapRegionDecoder.newInstance(pfd); 796 }); 797 file.delete(); 798 } 799 800 @Test(expected = IOException.class) testInvalidFileDescriptor()801 public void testInvalidFileDescriptor() throws IOException { 802 BitmapRegionDecoder.newInstance(new FileDescriptor(), false); 803 } 804 compareRegionByRegion(BitmapRegionDecoder decoder, Options opts, int mseMargin, Bitmap wholeImage)805 private void compareRegionByRegion(BitmapRegionDecoder decoder, 806 Options opts, int mseMargin, Bitmap wholeImage) { 807 int width = decoder.getWidth(); 808 int height = decoder.getHeight(); 809 Rect rect = new Rect(0, 0, width, height); 810 int numCols = (width + TILE_SIZE - 1) / TILE_SIZE; 811 int numRows = (height + TILE_SIZE - 1) / TILE_SIZE; 812 Bitmap actual; 813 Bitmap expected; 814 815 for (int i = 0; i < numCols; ++i) { 816 for (int j = 0; j < numRows; ++j) { 817 Rect rect1 = new Rect(i * TILE_SIZE, j * TILE_SIZE, 818 (i + 1) * TILE_SIZE, (j + 1) * TILE_SIZE); 819 rect1.intersect(rect); 820 actual = decoder.decodeRegion(rect1, opts); 821 int left = rect1.left / opts.inSampleSize; 822 int top = rect1.top / opts.inSampleSize; 823 if (opts.inBitmap != null) { 824 // bitmap reuse path - ensure reuse worked 825 assertSame(opts.inBitmap, actual); 826 int currentWidth = rect1.width() / opts.inSampleSize; 827 int currentHeight = rect1.height() / opts.inSampleSize; 828 Rect actualRect = new Rect(0, 0, currentWidth, currentHeight); 829 // crop 'actual' to the size to be tested (and avoid recycling inBitmap) 830 actual = cropBitmap(actual, actualRect); 831 } 832 Rect expectedRect = new Rect(left, top, left + actual.getWidth(), 833 top + actual.getHeight()); 834 expected = cropBitmap(wholeImage, expectedRect); 835 BitmapUtils.assertBitmapsMse(expected, actual, mseMargin, true, false); 836 actual.recycle(); 837 expected.recycle(); 838 } 839 } 840 } 841 cropBitmap(Bitmap wholeImage, Rect rect)842 private static Bitmap cropBitmap(Bitmap wholeImage, Rect rect) { 843 Bitmap cropped = Bitmap.createBitmap(rect.width(), rect.height(), 844 wholeImage.getConfig()); 845 Canvas canvas = new Canvas(cropped); 846 Rect dst = new Rect(0, 0, rect.width(), rect.height()); 847 canvas.drawBitmap(wholeImage, rect, dst, null); 848 return cropped; 849 } 850 obtainInputStream(int resId)851 private InputStream obtainInputStream(int resId) { 852 return mRes.openRawResource(resId); 853 } 854 obtainInputStream(String assetName)855 private InputStream obtainInputStream(String assetName) throws IOException { 856 return mRes.getAssets().open(assetName); 857 } 858 obtainByteArray(int resId)859 private byte[] obtainByteArray(int resId) throws IOException { 860 InputStream is = obtainInputStream(resId); 861 ByteArrayOutputStream os = new ByteArrayOutputStream(); 862 byte[] buffer = new byte[1024]; 863 int readLength; 864 while ((readLength = is.read(buffer)) != -1) { 865 os.write(buffer, 0, readLength); 866 } 867 byte[] data = os.toByteArray(); 868 is.close(); 869 os.close(); 870 return data; 871 } 872 obtainPath(int idx)873 private String obtainPath(int idx) throws IOException { 874 File dir = InstrumentationRegistry.getTargetContext().getFilesDir(); 875 dir.mkdirs(); 876 File file = new File(dir, NAMES_TEMP_FILES[idx]); 877 InputStream is = obtainInputStream(RES_IDS[idx]); 878 FileOutputStream fOutput = new FileOutputStream(file); 879 mFilesCreated.add(file); 880 byte[] dataBuffer = new byte[1024]; 881 int readLength = 0; 882 while ((readLength = is.read(dataBuffer)) != -1) { 883 fOutput.write(dataBuffer, 0, readLength); 884 } 885 is.close(); 886 fOutput.close(); 887 return (file.getPath()); 888 } 889 obtainParcelDescriptor(String path)890 private static ParcelFileDescriptor obtainParcelDescriptor(String path) 891 throws IOException { 892 File file = new File(path); 893 return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); 894 } 895 } 896