• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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