1 /*
2  * Copyright (C) 2019 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 android.system.OsConstants.SEEK_SET;
20 
21 import static org.junit.Assert.assertEquals;
22 import static org.junit.Assert.assertNotNull;
23 import static org.junit.Assert.assertNull;
24 import static org.junit.Assert.fail;
25 import static org.junit.Assume.assumeTrue;
26 
27 import android.content.ContentResolver;
28 import android.content.res.AssetManager;
29 import android.content.res.Resources;
30 import android.graphics.Bitmap;
31 import android.graphics.BitmapFactory;
32 import android.graphics.Color;
33 import android.graphics.ColorSpace;
34 import android.graphics.ColorSpace.Named;
35 import android.graphics.ImageDecoder;
36 import android.graphics.Rect;
37 import android.graphics.drawable.cts.AnimatedImageDrawableTest;
38 import android.media.MediaFormat;
39 import android.net.Uri;
40 import android.os.ParcelFileDescriptor;
41 import android.system.ErrnoException;
42 import android.system.Os;
43 import android.util.DisplayMetrics;
44 
45 import androidx.test.InstrumentationRegistry;
46 import androidx.test.filters.RequiresDevice;
47 
48 import com.android.compatibility.common.util.CddTest;
49 import com.android.compatibility.common.util.MediaUtils;
50 
51 import org.junit.Test;
52 import org.junit.runner.RunWith;
53 
54 import java.io.File;
55 import java.io.FileDescriptor;
56 import java.io.FileNotFoundException;
57 import java.io.FileOutputStream;
58 import java.io.IOException;
59 import java.io.InputStream;
60 
61 import junitparams.JUnitParamsRunner;
62 import junitparams.Parameters;
63 
64 @RunWith(JUnitParamsRunner.class)
65 public class AImageDecoderTest {
66     static {
67         System.loadLibrary("ctsgraphics_jni");
68     }
69 
getAssetManager()70     private static AssetManager getAssetManager() {
71         return InstrumentationRegistry.getTargetContext().getAssets();
72     }
73 
getResources()74     private static Resources getResources() {
75         return InstrumentationRegistry.getTargetContext().getResources();
76     }
77 
getContentResolver()78     private static ContentResolver getContentResolver() {
79         return InstrumentationRegistry.getTargetContext().getContentResolver();
80     }
81 
82     // These match the formats in the NDK.
83     // ANDROID_BITMAP_FORMAT_NONE is used by nTestDecode to signal using the default.
84     private static final int ANDROID_BITMAP_FORMAT_NONE = 0;
85     private static final int ANDROID_BITMAP_FORMAT_RGBA_8888 = 1;
86     private static final int ANDROID_BITMAP_FORMAT_RGB_565 = 4;
87     private static final int ANDROID_BITMAP_FORMAT_A_8 = 8;
88     private static final int ANDROID_BITMAP_FORMAT_RGBA_F16 = 9;
89     private static final int ANDROID_BITMAP_FORMAT_RGBA_1010102 = 10;
90 
91     @Test
testEmptyCreate()92     public void testEmptyCreate() {
93         nTestEmptyCreate();
94     }
95 
getAssetRecords()96     private static Object[] getAssetRecords() {
97         return ImageDecoderTest.getAssetRecords();
98     }
99 
getRecords()100     private static Object[] getRecords() {
101         return ImageDecoderTest.getRecords();
102     }
103 
104     // For testing all of the assets as premul and unpremul.
getAssetRecordsUnpremul()105     private static Object[] getAssetRecordsUnpremul() {
106         return Utils.crossProduct(getAssetRecords(), new Object[] { true, false });
107     }
108 
getRecordsUnpremul()109     private static Object[] getRecordsUnpremul() {
110         return Utils.crossProduct(getRecords(), new Object[] { true, false });
111     }
112 
113     // For testing all of the assets at different sample sizes.
getAssetRecordsSample()114     private static Object[] getAssetRecordsSample() {
115         return Utils.crossProduct(getAssetRecords(), new Object[] { 2, 3, 4, 8, 16 });
116     }
117 
getRecordsSample()118     private static Object[] getRecordsSample() {
119         return Utils.crossProduct(getRecords(), new Object[] { 2, 3, 4, 8, 16 });
120     }
121 
getBitMapFormatsUnpremul()122     private static Object[] getBitMapFormatsUnpremul() {
123         return Utils.crossProduct(
124             new Object[] {
125                 ANDROID_BITMAP_FORMAT_NONE,
126                 ANDROID_BITMAP_FORMAT_RGBA_1010102,
127                 ANDROID_BITMAP_FORMAT_RGB_565,
128                 ANDROID_BITMAP_FORMAT_RGBA_F16
129             },
130             new Object[] {
131                 true,
132                 false
133             });
134     }
135 
136     @Test
137     @Parameters(method = "getAssetRecords")
testNullDecoder(ImageDecoderTest.AssetRecord record)138     public void testNullDecoder(ImageDecoderTest.AssetRecord record) {
139         nTestNullDecoder(getAssetManager(), record.name);
140     }
141 
nativeDataSpace(ColorSpace cs)142     private static int nativeDataSpace(ColorSpace cs) {
143         if (cs == null) {
144             return DataSpace.ADATASPACE_UNKNOWN;
145         }
146 
147         return cs.getDataSpace();
148     }
149 
150     @Test
151     @Parameters(method = "getAssetRecords")
testCreateBuffer(ImageDecoderTest.AssetRecord record)152     public void testCreateBuffer(ImageDecoderTest.AssetRecord record) {
153         // Note: This uses an asset for simplicity, but in native it gets a
154         // buffer.
155         long asset = nOpenAsset(getAssetManager(), record.name);
156         long aimagedecoder = nCreateFromAssetBuffer(asset);
157 
158         nTestInfo(aimagedecoder, record.width, record.height, "image/png",
159                 record.isF16, nativeDataSpace(record.getColorSpace()));
160         nCloseAsset(asset);
161     }
162 
163     @Test
164     @Parameters(method = "getAssetRecords")
testCreateFd(ImageDecoderTest.AssetRecord record)165     public void testCreateFd(ImageDecoderTest.AssetRecord record) {
166         // Note: This uses an asset for simplicity, but in native it gets a
167         // file descriptor.
168         long asset = nOpenAsset(getAssetManager(), record.name);
169         long aimagedecoder = nCreateFromAssetFd(asset);
170 
171         nTestInfo(aimagedecoder, record.width, record.height, "image/png",
172                 record.isF16, nativeDataSpace(record.getColorSpace()));
173         nCloseAsset(asset);
174     }
175 
176     @Test
177     @Parameters(method = "getAssetRecords")
testCreateAsset(ImageDecoderTest.AssetRecord record)178     public void testCreateAsset(ImageDecoderTest.AssetRecord record) {
179         long asset = nOpenAsset(getAssetManager(), record.name);
180         long aimagedecoder = nCreateFromAsset(asset);
181 
182         nTestInfo(aimagedecoder, record.width, record.height, "image/png",
183                 record.isF16, nativeDataSpace(record.getColorSpace()));
184         nCloseAsset(asset);
185     }
186 
open(int resId, int offset)187     private static ParcelFileDescriptor open(int resId, int offset) throws FileNotFoundException {
188         File file = Utils.obtainFile(resId, offset);
189         assertNotNull(file);
190 
191         ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file,
192                 ParcelFileDescriptor.MODE_READ_ONLY);
193         assertNotNull(pfd);
194         return pfd;
195     }
196 
open(int resId)197     private static ParcelFileDescriptor open(int resId) throws FileNotFoundException {
198         return open(resId, 0);
199     }
200 
201     @Test
202     @Parameters(method = "getRecords")
testCreateFdResources(ImageDecoderTest.Record record)203     public void testCreateFdResources(ImageDecoderTest.Record record) throws IOException {
204         try (ParcelFileDescriptor pfd = open(record.resId)) {
205             long aimagedecoder = nCreateFromFd(pfd.getFd());
206 
207             nTestInfo(aimagedecoder, record.width, record.height, record.mimeType,
208                     false /*isF16*/, nativeDataSpace(record.colorSpace));
209         } catch (FileNotFoundException e) {
210             fail("Could not open " + Utils.getAsResourceUri(record.resId));
211         }
212     }
213 
214     @Test
215     @Parameters(method = "getRecords")
testCreateFdOffset(ImageDecoderTest.Record record)216     public void testCreateFdOffset(ImageDecoderTest.Record record) throws IOException {
217         // Use an arbitrary offset. This ensures that we rewind to the correct offset.
218         final int offset = 15;
219         try (ParcelFileDescriptor pfd = open(record.resId, offset)) {
220             FileDescriptor fd = pfd.getFileDescriptor();
221             Os.lseek(fd, offset, SEEK_SET);
222             long aimagedecoder = nCreateFromFd(pfd.getFd());
223 
224             nTestInfo(aimagedecoder, record.width, record.height, record.mimeType,
225                     false /*isF16*/, nativeDataSpace(record.colorSpace));
226         } catch (FileNotFoundException e) {
227             fail("Could not open " + Utils.getAsResourceUri(record.resId));
228         } catch (ErrnoException err) {
229             fail("Failed to seek " + Utils.getAsResourceUri(record.resId));
230         }
231     }
232 
233     @Test
testCreateIncomplete()234     public void testCreateIncomplete() {
235         String file = "green-srgb.png";
236         // This truncates the file before the IDAT.
237         nTestCreateIncomplete(getAssetManager(), file, 823);
238     }
239 
240     @Test
241     @Parameters({"shaders/tri.frag", "test_video.mp4"})
testUnsupportedFormat(String file)242     public void testUnsupportedFormat(String file) {
243         nTestCreateUnsupported(getAssetManager(), file);
244     }
245 
246     @Test
247     @Parameters(method = "getAssetRecords")
testSetFormat(ImageDecoderTest.AssetRecord record)248     public void testSetFormat(ImageDecoderTest.AssetRecord record) {
249         long asset = nOpenAsset(getAssetManager(), record.name);
250         long aimagedecoder = nCreateFromAsset(asset);
251 
252         nTestSetFormat(aimagedecoder, record.isF16, record.isGray);
253         nCloseAsset(asset);
254     }
255 
256     @Test
257     @Parameters(method = "getRecords")
testSetFormatResources(ImageDecoderTest.Record record)258     public void testSetFormatResources(ImageDecoderTest.Record record) throws IOException {
259         try (ParcelFileDescriptor pfd = open(record.resId)) {
260             long aimagedecoder = nCreateFromFd(pfd.getFd());
261 
262             nTestSetFormat(aimagedecoder, false /* isF16 */, record.isGray);
263         } catch (FileNotFoundException e) {
264             fail("Could not open " + Utils.getAsResourceUri(record.resId));
265         }
266     }
267 
268     @Test
269     @Parameters(method = "getAssetRecords")
testSetUnpremul(ImageDecoderTest.AssetRecord record)270     public void testSetUnpremul(ImageDecoderTest.AssetRecord record) {
271         long asset = nOpenAsset(getAssetManager(), record.name);
272         long aimagedecoder = nCreateFromAsset(asset);
273 
274         nTestSetUnpremul(aimagedecoder, record.hasAlpha);
275         nCloseAsset(asset);
276     }
277 
278     @Test
279     @Parameters(method = "getRecords")
testSetUnpremulResources(ImageDecoderTest.Record record)280     public void testSetUnpremulResources(ImageDecoderTest.Record record) throws IOException {
281         try (ParcelFileDescriptor pfd = open(record.resId)) {
282             long aimagedecoder = nCreateFromFd(pfd.getFd());
283 
284             nTestSetUnpremul(aimagedecoder, record.hasAlpha);
285         } catch (FileNotFoundException e) {
286             fail("Could not open " + Utils.getAsResourceUri(record.resId));
287         }
288     }
289 
290     @Test
291     @Parameters(method = "getAssetRecords")
testGetMinimumStride(ImageDecoderTest.AssetRecord record)292     public void testGetMinimumStride(ImageDecoderTest.AssetRecord record) {
293         long asset = nOpenAsset(getAssetManager(), record.name);
294         long aimagedecoder = nCreateFromAsset(asset);
295 
296         nTestGetMinimumStride(aimagedecoder, record.isF16, record.isGray);
297     }
298 
299     @Test
300     @Parameters(method = "getRecords")
testGetMinimumStrideResources(ImageDecoderTest.Record record)301     public void testGetMinimumStrideResources(ImageDecoderTest.Record record) throws IOException {
302         try (ParcelFileDescriptor pfd = open(record.resId)) {
303             long aimagedecoder = nCreateFromFd(pfd.getFd());
304 
305             nTestGetMinimumStride(aimagedecoder, false /* isF16 */, record.isGray);
306         } catch (FileNotFoundException e) {
307             fail("Could not open " + Utils.getAsResourceUri(record.resId));
308         }
309     }
310 
decode(ImageDecoder.Source src, boolean unpremul)311     private static Bitmap decode(ImageDecoder.Source src, boolean unpremul) {
312         try {
313             return ImageDecoder.decodeBitmap(src, (decoder, info, source) -> {
314                 // So we can compare pixels to the native decode.
315                 decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
316                 decoder.setUnpremultipliedRequired(unpremul);
317             });
318         } catch (IOException e) {
319             fail("Failed to decode in Java with " + e);
320             return null;
321         }
322     }
323 
decode(int resId, boolean unpremul)324     private static Bitmap decode(int resId, boolean unpremul) {
325         // This test relies on ImageDecoder *not* scaling to account for density.
326         // Temporarily change the DisplayMetrics to prevent that scaling.
327         Resources res = getResources();
328         final int originalDensity = res.getDisplayMetrics().densityDpi;
329         try {
330             res.getDisplayMetrics().densityDpi = DisplayMetrics.DENSITY_DEFAULT;
331             ImageDecoder.Source src = ImageDecoder.createSource(res, resId);
332             return decode(src, unpremul);
333         } finally {
334             res.getDisplayMetrics().densityDpi = originalDensity;
335         }
336     }
337 
338     @Test
339     @RequiresDevice
340     @Parameters(method = "getBitMapFormatsUnpremul")
testDecode10BitHeif(int bitmapFormat, boolean unpremul)341     public void testDecode10BitHeif(int bitmapFormat, boolean unpremul) throws IOException {
342         if (!MediaUtils.hasHardwareCodec(MediaFormat.MIMETYPE_VIDEO_HEVC, false)) {
343             return;
344         }
345         final int resId = R.raw.heifimage_10bit;
346         Bitmap bm = null;
347         switch (bitmapFormat) {
348             case ANDROID_BITMAP_FORMAT_NONE:
349             case ANDROID_BITMAP_FORMAT_RGBA_1010102:
350                 bm = decode(resId, unpremul);
351                 break;
352             case ANDROID_BITMAP_FORMAT_RGB_565:
353                 bm = decode(resId, Bitmap.Config.RGB_565);
354                 break;
355             case ANDROID_BITMAP_FORMAT_RGBA_F16:
356                 BitmapFactory.Options opt = new BitmapFactory.Options();
357                 opt.inPreferredConfig = Bitmap.Config.RGBA_F16;
358                 opt.inPremultiplied = !unpremul;
359                 bm = BitmapFactory.decodeStream(getResources().openRawResource(resId), null, opt);
360                 break;
361             default:
362                 fail("Unsupported Bitmap format: " + bitmapFormat);
363         }
364         assertNotNull(bm);
365 
366         try (ParcelFileDescriptor pfd = open(resId)) {
367             long aimagedecoder = nCreateFromFd(pfd.getFd());
368             nTestDecode(aimagedecoder, bitmapFormat, unpremul, bm);
369         } catch (FileNotFoundException e) {
370             fail("Could not open " + Utils.getAsResourceUri(resId));
371         }
372     }
373 
374     @Test
375     @RequiresDevice
376     @CddTest(requirements = {"5.1.5/C-0-7"})
377     @Parameters(method = "getBitMapFormatsUnpremul")
testDecode10BitAvif(int bitmapFormat, boolean unpremul)378     public void testDecode10BitAvif(int bitmapFormat, boolean unpremul) throws IOException {
379         assumeTrue("AVIF is not supported on this device, skip this test.",
380                 ImageDecoder.isMimeTypeSupported("image/avif"));
381 
382         final int resId = R.raw.avif_yuv_420_10bit;
383         Bitmap bm = null;
384         switch (bitmapFormat) {
385             case ANDROID_BITMAP_FORMAT_NONE:
386             case ANDROID_BITMAP_FORMAT_RGBA_1010102:
387                 bm = decode(resId, unpremul);
388                 break;
389             case ANDROID_BITMAP_FORMAT_RGB_565:
390                 bm = decode(resId, Bitmap.Config.RGB_565);
391                 break;
392             case ANDROID_BITMAP_FORMAT_RGBA_F16:
393                 BitmapFactory.Options opt = new BitmapFactory.Options();
394                 opt.inPreferredConfig = Bitmap.Config.RGBA_F16;
395                 opt.inPremultiplied = !unpremul;
396                 bm = BitmapFactory.decodeStream(getResources().openRawResource(resId), null, opt);
397                 break;
398             default:
399                 fail("Unsupported Bitmap format: " + bitmapFormat);
400         }
401         assertNotNull(bm);
402 
403         try (ParcelFileDescriptor pfd = open(resId)) {
404             long aimagedecoder = nCreateFromFd(pfd.getFd());
405             nTestDecode(aimagedecoder, bitmapFormat, unpremul, bm);
406         } catch (FileNotFoundException e) {
407             fail("Could not open " + Utils.getAsResourceUri(resId));
408         }
409     }
410 
411     @Test
412     @Parameters(method = "getAssetRecordsUnpremul")
testDecode(ImageDecoderTest.AssetRecord record, boolean unpremul)413     public void testDecode(ImageDecoderTest.AssetRecord record, boolean unpremul) {
414         AssetManager assets = getAssetManager();
415         ImageDecoder.Source src = ImageDecoder.createSource(assets, record.name);
416         Bitmap bm = decode(src, unpremul);
417 
418         long asset = nOpenAsset(assets, record.name);
419         long aimagedecoder = nCreateFromAsset(asset);
420 
421         nTestDecode(aimagedecoder, ANDROID_BITMAP_FORMAT_NONE, unpremul, bm);
422         nCloseAsset(asset);
423     }
424 
425     @Test
426     @Parameters(method = "getRecordsUnpremul")
testDecodeResources(ImageDecoderTest.Record record, boolean unpremul)427     public void testDecodeResources(ImageDecoderTest.Record record, boolean unpremul)
428             throws IOException {
429         Bitmap bm = decode(record.resId, unpremul);
430         try (ParcelFileDescriptor pfd = open(record.resId)) {
431             long aimagedecoder = nCreateFromFd(pfd.getFd());
432 
433             nTestDecode(aimagedecoder, ANDROID_BITMAP_FORMAT_NONE, unpremul, bm);
434         } catch (FileNotFoundException e) {
435             fail("Could not open " + Utils.getAsResourceUri(record.resId));
436         }
437     }
438 
decode(ImageDecoder.Source src, Bitmap.Config config)439     private static Bitmap decode(ImageDecoder.Source src, Bitmap.Config config) {
440         try {
441             return ImageDecoder.decodeBitmap(src, (decoder, info, source) -> {
442                 // So we can compare pixels to the native decode.
443                 decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
444                 switch (config) {
445                     case RGB_565:
446                         decoder.setMemorySizePolicy(ImageDecoder.MEMORY_POLICY_LOW_RAM);
447                         break;
448                     case ALPHA_8:
449                         decoder.setDecodeAsAlphaMaskEnabled(true);
450                         break;
451                     default:
452                         fail("Unexpected Config " + config);
453                         break;
454                 }
455             });
456         } catch (IOException e) {
457             fail("Failed to decode in Java with " + e);
458             return null;
459         }
460     }
461 
462     private static Bitmap decode(int resId, Bitmap.Config config) {
463         // This test relies on ImageDecoder *not* scaling to account for density.
464         // Temporarily change the DisplayMetrics to prevent that scaling.
465         Resources res = getResources();
466         final int originalDensity = res.getDisplayMetrics().densityDpi;
467         try {
468             res.getDisplayMetrics().densityDpi = DisplayMetrics.DENSITY_DEFAULT;
469             ImageDecoder.Source src = ImageDecoder.createSource(res, resId);
470             return decode(src, config);
471         } finally {
472             res.getDisplayMetrics().densityDpi = originalDensity;
473         }
474     }
475 
476     @Test
477     @Parameters(method = "getAssetRecords")
478     public void testDecode565(ImageDecoderTest.AssetRecord record) {
479         AssetManager assets = getAssetManager();
480         ImageDecoder.Source src = ImageDecoder.createSource(assets, record.name);
481         Bitmap bm = decode(src, Bitmap.Config.RGB_565);
482 
483         if (bm.getConfig() != Bitmap.Config.RGB_565) {
484             bm = null;
485         }
486 
487         long asset = nOpenAsset(assets, record.name);
488         long aimagedecoder = nCreateFromAsset(asset);
489 
490         nTestDecode(aimagedecoder, ANDROID_BITMAP_FORMAT_RGB_565, false, bm);
491         nCloseAsset(asset);
492     }
493 
494     @Test
495     @Parameters(method = "getRecords")
496     public void testDecode565Resources(ImageDecoderTest.Record record)
497             throws IOException {
498         Bitmap bm = decode(record.resId, Bitmap.Config.RGB_565);
499 
500         if (bm.getConfig() != Bitmap.Config.RGB_565) {
501             bm = null;
502         }
503 
504         try (ParcelFileDescriptor pfd = open(record.resId)) {
505             long aimagedecoder = nCreateFromFd(pfd.getFd());
506 
507             nTestDecode(aimagedecoder, ANDROID_BITMAP_FORMAT_RGB_565, false, bm);
508         } catch (FileNotFoundException e) {
509             fail("Could not open " + Utils.getAsResourceUri(record.resId));
510         }
511     }
512 
513     @Test
514     @Parameters("grayscale-linearSrgb.png")
515     public void testDecodeA8(String name) {
516         AssetManager assets = getAssetManager();
517         ImageDecoder.Source src = ImageDecoder.createSource(assets, name);
518         Bitmap bm = decode(src, Bitmap.Config.ALPHA_8);
519 
520         assertNotNull(bm);
521         assertNull(bm.getColorSpace());
522         assertEquals(Bitmap.Config.ALPHA_8, bm.getConfig());
523 
524         long asset = nOpenAsset(assets, name);
525         long aimagedecoder = nCreateFromAsset(asset);
526 
527         nTestDecode(aimagedecoder, ANDROID_BITMAP_FORMAT_A_8, false, bm);
528         nCloseAsset(asset);
529     }
530 
531     @Test
532     public void testDecodeA8Resources()
533             throws IOException {
534         final int resId = R.drawable.grayscale_jpg;
535         Bitmap bm = decode(resId, Bitmap.Config.ALPHA_8);
536 
537         assertNotNull(bm);
538         assertNull(bm.getColorSpace());
539         assertEquals(Bitmap.Config.ALPHA_8, bm.getConfig());
540 
541         try (ParcelFileDescriptor pfd = open(resId)) {
542             long aimagedecoder = nCreateFromFd(pfd.getFd());
543 
544             nTestDecode(aimagedecoder, ANDROID_BITMAP_FORMAT_A_8, false, bm);
545         } catch (FileNotFoundException e) {
546             fail("Could not open " + Utils.getAsResourceUri(resId));
547         }
548     }
549 
550     @Test
551     @Parameters(method = "getAssetRecordsUnpremul")
552     public void testDecodeF16(ImageDecoderTest.AssetRecord record, boolean unpremul) {
553         AssetManager assets = getAssetManager();
554 
555         // ImageDecoder doesn't allow forcing a decode to F16, so use BitmapFactory.
556         BitmapFactory.Options options = new BitmapFactory.Options();
557         options.inPreferredConfig = Bitmap.Config.RGBA_F16;
558         options.inPremultiplied = !unpremul;
559 
560         InputStream is = null;
561         try {
562             is = assets.open(record.name);
563         } catch (IOException e) {
564             fail("Failed to open " + record.name + " with " + e);
565         }
566         assertNotNull(is);
567 
568         Bitmap bm = BitmapFactory.decodeStream(is, null, options);
569         assertNotNull(bm);
570 
571         long asset = nOpenAsset(assets, record.name);
572         long aimagedecoder = nCreateFromAsset(asset);
573 
574         nTestDecode(aimagedecoder, ANDROID_BITMAP_FORMAT_RGBA_F16, unpremul, bm);
575         nCloseAsset(asset);
576     }
577 
578     @Test
579     @Parameters(method = "getRecordsUnpremul")
580     public void testDecodeF16Resources(ImageDecoderTest.Record record, boolean unpremul)
581             throws IOException {
582         // ImageDecoder doesn't allow forcing a decode to F16, so use BitmapFactory.
583         BitmapFactory.Options options = new BitmapFactory.Options();
584         options.inPreferredConfig = Bitmap.Config.RGBA_F16;
585         options.inPremultiplied = !unpremul;
586         options.inScaled = false;
587 
588         Bitmap bm = BitmapFactory.decodeResource(getResources(),
589                 record.resId, options);
590         assertNotNull(bm);
591 
592         try (ParcelFileDescriptor pfd = open(record.resId)) {
593             long aimagedecoder = nCreateFromFd(pfd.getFd());
594 
595             nTestDecode(aimagedecoder, ANDROID_BITMAP_FORMAT_RGBA_F16, unpremul, bm);
596         } catch (FileNotFoundException e) {
597             fail("Could not open " + Utils.getAsResourceUri(record.resId));
598         }
599     }
600 
601     @Test
602     @Parameters(method = "getAssetRecords")
603     public void testDecodeStride(ImageDecoderTest.AssetRecord record) {
604         long asset = nOpenAsset(getAssetManager(), record.name);
605         long aimagedecoder = nCreateFromAsset(asset);
606         nTestDecodeStride(aimagedecoder);
607         nCloseAsset(asset);
608     }
609 
610     @Test
611     @Parameters(method = "getRecords")
612     public void testDecodeStrideResources(ImageDecoderTest.Record record)
613             throws IOException {
614         try (ParcelFileDescriptor pfd = open(record.resId)) {
615             long aimagedecoder = nCreateFromFd(pfd.getFd());
616 
617             nTestDecodeStride(aimagedecoder);
618         } catch (FileNotFoundException e) {
619             fail("Could not open " + Utils.getAsResourceUri(record.resId));
620         }
621     }
622 
623     @Test
624     @Parameters(method = "getAssetRecords")
625     public void testSetTargetSize(ImageDecoderTest.AssetRecord record) {
626         long asset = nOpenAsset(getAssetManager(), record.name);
627         long aimagedecoder = nCreateFromAsset(asset);
628         nTestSetTargetSize(aimagedecoder);
629         nCloseAsset(asset);
630     }
631 
632     @Test
633     @Parameters(method = "getRecords")
634     public void testSetTargetSizeResources(ImageDecoderTest.Record record)
635             throws IOException {
636         try (ParcelFileDescriptor pfd = open(record.resId)) {
637             long aimagedecoder = nCreateFromFd(pfd.getFd());
638 
639             nTestSetTargetSize(aimagedecoder);
640         } catch (FileNotFoundException e) {
641             fail("Could not open " + Utils.getAsResourceUri(record.resId));
642         }
643     }
644 
645     private Bitmap decodeSampled(String name, ImageDecoder.Source src, int sampleSize) {
646         try {
647             return ImageDecoder.decodeBitmap(src, (decoder, info, source) -> {
648                 // So we can compare pixels to the native decode.
649                 decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
650                 decoder.setTargetSampleSize(sampleSize);
651             });
652         } catch (IOException e) {
653             fail("Failed to decode " + name + " in Java (sampleSize: "
654                     + sampleSize + ") with " + e);
655             return null;
656         }
657     }
658 
659     @Test
660     @Parameters(method = "getAssetRecordsSample")
661     public void testDecodeSampled(ImageDecoderTest.AssetRecord record, int sampleSize) {
662         AssetManager assets = getAssetManager();
663         ImageDecoder.Source src = ImageDecoder.createSource(assets, record.name);
664         Bitmap bm = decodeSampled(record.name, src, sampleSize);
665 
666         long asset = nOpenAsset(assets, record.name);
667         long aimagedecoder = nCreateFromAsset(asset);
668 
669         nTestDecodeScaled(aimagedecoder, bm);
670         nCloseAsset(asset);
671     }
672 
673     @Test
674     @Parameters(method = "getRecordsSample")
675     public void testDecodeResourceSampled(ImageDecoderTest.Record record, int sampleSize)
676             throws IOException {
677         ImageDecoder.Source src = ImageDecoder.createSource(getResources(),
678                 record.resId);
679         String name = Utils.getAsResourceUri(record.resId).toString();
680         Bitmap bm = decodeSampled(name, src, sampleSize);
681         assertNotNull(bm);
682 
683         try (ParcelFileDescriptor pfd = open(record.resId)) {
684             long aimagedecoder = nCreateFromFd(pfd.getFd());
685 
686             nTestDecodeScaled(aimagedecoder, bm);
687         } catch (FileNotFoundException e) {
688             fail("Could not open " + name + ": " + e);
689         }
690     }
691 
692     @Test
693     @Parameters(method = "getRecordsSample")
694     public void testComputeSampledSize(ImageDecoderTest.Record record, int sampleSize)
695             throws IOException {
696         if (record.mimeType.equals("image/x-adobe-dng")) {
697             // SkRawCodec does not support sampling.
698             return;
699         }
700         testComputeSampledSizeInternal(record.resId, sampleSize);
701     }
702 
703     private void testComputeSampledSizeInternal(int resId, int sampleSize)
704             throws IOException {
705         ImageDecoder.Source src = ImageDecoder.createSource(getResources(), resId);
706         String name = Utils.getAsResourceUri(resId).toString();
707         Bitmap bm = decodeSampled(name, src, sampleSize);
708         assertNotNull(bm);
709 
710         try (ParcelFileDescriptor pfd = open(resId)) {
711             long aimagedecoder = nCreateFromFd(pfd.getFd());
712 
713             nTestComputeSampledSize(aimagedecoder, bm, sampleSize);
714         } catch (FileNotFoundException e) {
715             fail("Could not open " + name + ": " + e);
716         }
717     }
718 
719     private static Object[] getExifsSample() {
720         return Utils.crossProduct(getExifImages(), new Object[] { 2, 3, 4, 8, 16 });
721     }
722 
723     @Test
724     @Parameters(method = "getExifsSample")
725     public void testComputeSampledSizeExif(int resId, int sampleSize)
726             throws IOException {
727         testComputeSampledSizeInternal(resId, sampleSize);
728     }
729 
730     private Bitmap decodeScaled(String name, ImageDecoder.Source src) {
731         try {
732             return ImageDecoder.decodeBitmap(src, (decoder, info, source) -> {
733                 // So we can compare pixels to the native decode.
734                 decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
735 
736                 // Scale to an arbitrary width and height.
737                 decoder.setTargetSize(300, 300);
738             });
739         } catch (IOException e) {
740             fail("Failed to decode " + name + " in Java (size: "
741                     + "300 x 300) with " + e);
742             return null;
743         }
744     }
745 
746     @Test
747     @Parameters(method = "getAssetRecords")
748     public void testDecodeScaled(ImageDecoderTest.AssetRecord record) {
749         AssetManager assets = getAssetManager();
750         ImageDecoder.Source src = ImageDecoder.createSource(assets, record.name);
751         Bitmap bm = decodeScaled(record.name, src);
752 
753         long asset = nOpenAsset(assets, record.name);
754         long aimagedecoder = nCreateFromAsset(asset);
755 
756         nTestDecodeScaled(aimagedecoder, bm);
757         nCloseAsset(asset);
758     }
759 
760     @Test
761     @Parameters(method = "getRecords")
762     public void testDecodeResourceScaled(ImageDecoderTest.Record record)
763             throws IOException {
764         ImageDecoder.Source src = ImageDecoder.createSource(getResources(),
765                 record.resId);
766         String name = Utils.getAsResourceUri(record.resId).toString();
767         Bitmap bm = decodeScaled(name, src);
768         assertNotNull(bm);
769 
770         try (ParcelFileDescriptor pfd = open(record.resId)) {
771             long aimagedecoder = nCreateFromFd(pfd.getFd());
772 
773             nTestDecodeScaled(aimagedecoder, bm);
774         } catch (FileNotFoundException e) {
775             fail("Could not open " + name + ": " + e);
776         }
777     }
778 
779     private Bitmap decodeScaleUp(String name, ImageDecoder.Source src) {
780         try {
781             return ImageDecoder.decodeBitmap(src, (decoder, info, source) -> {
782                 // So we can compare pixels to the native decode.
783                 decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
784 
785                 decoder.setTargetSize(info.getSize().getWidth() * 2,
786                         info.getSize().getHeight() * 2);
787             });
788         } catch (IOException e) {
789             fail("Failed to decode " + name + " in Java (scaled up) with " + e);
790             return null;
791         }
792     }
793 
794     @Test
795     @Parameters(method = "getAssetRecords")
796     public void testDecodeScaleUp(ImageDecoderTest.AssetRecord record) {
797         AssetManager assets = getAssetManager();
798         ImageDecoder.Source src = ImageDecoder.createSource(assets, record.name);
799         Bitmap bm = decodeScaleUp(record.name, src);
800 
801         long asset = nOpenAsset(assets, record.name);
802         long aimagedecoder = nCreateFromAsset(asset);
803 
804         nTestDecodeScaled(aimagedecoder, bm);
805         nCloseAsset(asset);
806     }
807 
808     @Test
809     @Parameters(method = "getRecords")
810     public void testDecodeResourceScaleUp(ImageDecoderTest.Record record)
811             throws IOException {
812         ImageDecoder.Source src = ImageDecoder.createSource(getResources(),
813                 record.resId);
814         String name = Utils.getAsResourceUri(record.resId).toString();
815         Bitmap bm = decodeScaleUp(name, src);
816         assertNotNull(bm);
817 
818         try (ParcelFileDescriptor pfd = open(record.resId)) {
819             long aimagedecoder = nCreateFromFd(pfd.getFd());
820 
821             nTestDecodeScaled(aimagedecoder, bm);
822         } catch (FileNotFoundException e) {
823             fail("Could not open " + name + ": " + e);
824         }
825     }
826 
827     @Test
828     @Parameters(method = "getAssetRecords")
829     public void testSetCrop(ImageDecoderTest.AssetRecord record) {
830         nTestSetCrop(getAssetManager(), record.name);
831     }
832 
833     private static class Cropper implements ImageDecoder.OnHeaderDecodedListener {
834         Cropper(boolean scale) {
835             mScale = scale;
836         }
837 
838         public boolean withScale() {
839             return mScale;
840         }
841 
842         public int getWidth() {
843             return mWidth;
844         }
845 
846         public int getHeight() {
847             return mHeight;
848         }
849 
850         public Rect getCropRect() {
851             return mCropRect;
852         }
853 
854         @Override
855         public void onHeaderDecoded(ImageDecoder decoder, ImageDecoder.ImageInfo info,
856                 ImageDecoder.Source source) {
857             mWidth = info.getSize().getWidth();
858             mHeight = info.getSize().getHeight();
859             if (mScale) {
860                 mWidth = 40;
861                 mHeight = 40;
862                 decoder.setTargetSize(mWidth, mHeight);
863             }
864 
865             mCropRect = new Rect(mWidth / 2, mHeight / 2, mWidth, mHeight);
866             decoder.setCrop(mCropRect);
867 
868             // So we can compare pixels to the native decode.
869             decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
870         }
871 
872         private final boolean mScale;
873         private Rect mCropRect;
874         private int mWidth;
875         private int mHeight;
876     }
877 
878     private static Bitmap decodeCropped(String name, Cropper cropper, ImageDecoder.Source src) {
879         try {
880             return ImageDecoder.decodeBitmap(src, cropper);
881         } catch (IOException e) {
882             fail("Failed to decode " + name + " in Java with "
883                     + (cropper.withScale() ? "scale and " : "a ") + "crop ("
884                     + cropper.getCropRect() + "): " + e);
885             return null;
886         }
887     }
888 
889     private static Bitmap decodeCropped(String name, Cropper cropper, int resId) {
890         // This test relies on ImageDecoder *not* scaling to account for density.
891         // Temporarily change the DisplayMetrics to prevent that scaling.
892         Resources res = getResources();
893         final int originalDensity = res.getDisplayMetrics().densityDpi;
894         try {
895             res.getDisplayMetrics().densityDpi = DisplayMetrics.DENSITY_DEFAULT;
896             ImageDecoder.Source src = ImageDecoder.createSource(res, resId);
897             return decodeCropped(name, cropper, src);
898         } finally {
899             res.getDisplayMetrics().densityDpi = originalDensity;
900         }
901     }
902 
903     @Test
904     @Parameters(method = "getAssetRecords")
905     public void testCrop(ImageDecoderTest.AssetRecord record) {
906         AssetManager assets = getAssetManager();
907         ImageDecoder.Source src = ImageDecoder.createSource(assets, record.name);
908         Cropper cropper = new Cropper(false /* scale */);
909         Bitmap bm = decodeCropped(record.name, cropper, src);
910 
911         long asset = nOpenAsset(assets, record.name);
912         long aimagedecoder = nCreateFromAsset(asset);
913 
914         Rect crop = cropper.getCropRect();
915         nTestDecodeCrop(aimagedecoder, bm, 0, 0, crop.left, crop.top, crop.right, crop.bottom);
916         nCloseAsset(asset);
917     }
918 
919     @Test
920     @Parameters(method = "getRecords")
921     public void testCropResource(ImageDecoderTest.Record record)
922             throws IOException {
923         String name = Utils.getAsResourceUri(record.resId).toString();
924         Cropper cropper = new Cropper(false /* scale */);
925         Bitmap bm = decodeCropped(name, cropper, record.resId);
926         assertNotNull(bm);
927 
928         try (ParcelFileDescriptor pfd = open(record.resId)) {
929             long aimagedecoder = nCreateFromFd(pfd.getFd());
930 
931             Rect crop = cropper.getCropRect();
932             nTestDecodeCrop(aimagedecoder, bm, 0, 0, crop.left, crop.top, crop.right, crop.bottom);
933         } catch (FileNotFoundException e) {
934             fail("Could not open " + name + ": " + e);
935         }
936     }
937 
938     @Test
939     @Parameters(method = "getAssetRecords")
940     public void testCropAndScale(ImageDecoderTest.AssetRecord record) {
941         AssetManager assets = getAssetManager();
942         ImageDecoder.Source src = ImageDecoder.createSource(assets, record.name);
943         Cropper cropper = new Cropper(true /* scale */);
944         Bitmap bm = decodeCropped(record.name, cropper, src);
945 
946         long asset = nOpenAsset(assets, record.name);
947         long aimagedecoder = nCreateFromAsset(asset);
948 
949         Rect crop = cropper.getCropRect();
950         nTestDecodeCrop(aimagedecoder, bm, cropper.getWidth(), cropper.getHeight(),
951                 crop.left, crop.top, crop.right, crop.bottom);
952         nCloseAsset(asset);
953     }
954 
955     @Test
956     @Parameters(method = "getRecords")
957     public void testCropAndScaleResource(ImageDecoderTest.Record record)
958             throws IOException {
959         ImageDecoder.Source src = ImageDecoder.createSource(getResources(),
960                 record.resId);
961         String name = Utils.getAsResourceUri(record.resId).toString();
962         Cropper cropper = new Cropper(true /* scale */);
963         Bitmap bm = decodeCropped(name, cropper, src);
964         assertNotNull(bm);
965 
966         try (ParcelFileDescriptor pfd = open(record.resId)) {
967             long aimagedecoder = nCreateFromFd(pfd.getFd());
968 
969             Rect crop = cropper.getCropRect();
970             nTestDecodeCrop(aimagedecoder, bm, cropper.getWidth(), cropper.getHeight(),
971                     crop.left, crop.top, crop.right, crop.bottom);
972         } catch (FileNotFoundException e) {
973             fail("Could not open " + name + ": " + e);
974         }
975     }
976 
977     private static Object[] getExifImages() {
978         return new Object[] {
979             R.drawable.orientation_1,
980             R.drawable.orientation_2,
981             R.drawable.orientation_3,
982             R.drawable.orientation_4,
983             R.drawable.orientation_5,
984             R.drawable.orientation_6,
985             R.drawable.orientation_7,
986             R.drawable.orientation_8,
987             R.drawable.webp_orientation1,
988             R.drawable.webp_orientation2,
989             R.drawable.webp_orientation3,
990             R.drawable.webp_orientation4,
991             R.drawable.webp_orientation5,
992             R.drawable.webp_orientation6,
993             R.drawable.webp_orientation7,
994             R.drawable.webp_orientation8,
995         };
996     }
997 
998     @Test
999     @Parameters(method = "getExifImages")
1000     public void testRespectOrientation(int resId) throws IOException {
1001         Uri uri = Utils.getAsResourceUri(resId);
1002         ImageDecoder.Source src = ImageDecoder.createSource(getContentResolver(),
1003                 uri);
1004         Bitmap bm = decode(src, false /* unpremul */);
1005         assertNotNull(bm);
1006         assertEquals(100, bm.getWidth());
1007         assertEquals(80,  bm.getHeight());
1008 
1009         // First verify that the info (and in particular, the width and height)
1010         // are correct. This uses a separate ParcelFileDescriptor/aimagedecoder
1011         // because the native methods delete the aimagedecoder.
1012         try (ParcelFileDescriptor pfd = open(resId)) {
1013             long aimagedecoder = nCreateFromFd(pfd.getFd());
1014 
1015             String mimeType = uri.toString().contains("webp") ? "image/webp" : "image/jpeg";
1016             nTestInfo(aimagedecoder, 100, 80, mimeType, false,
1017                     bm.getColorSpace().getDataSpace());
1018         } catch (FileNotFoundException e) {
1019             e.printStackTrace();
1020             fail("Could not open " + uri + " to check info");
1021         }
1022 
1023         try (ParcelFileDescriptor pfd = open(resId)) {
1024             long aimagedecoder = nCreateFromFd(pfd.getFd());
1025 
1026             nTestDecode(aimagedecoder, ANDROID_BITMAP_FORMAT_NONE, false /* unpremul */, bm);
1027         } catch (FileNotFoundException e) {
1028             e.printStackTrace();
1029             fail("Could not open " + uri);
1030         }
1031         bm.recycle();
1032     }
1033 
1034     @Test
1035     @Parameters(method = "getAssetRecords")
1036     public void testScalePlusUnpremul(ImageDecoderTest.AssetRecord record) {
1037         long asset = nOpenAsset(getAssetManager(), record.name);
1038         long aimagedecoder = nCreateFromAsset(asset);
1039 
1040         nTestScalePlusUnpremul(aimagedecoder);
1041         nCloseAsset(asset);
1042     }
1043 
1044     private static File createCompressedBitmap(int width, int height, ColorSpace colorSpace,
1045             Bitmap.CompressFormat format) {
1046         File dir = InstrumentationRegistry.getTargetContext().getFilesDir();
1047         dir.mkdirs();
1048 
1049         File file = new File(dir, colorSpace.getName());
1050         try {
1051             file.createNewFile();
1052         } catch (IOException e) {
1053             e.printStackTrace();
1054             // If the file does not exist it will be handled below.
1055         }
1056         if (!file.exists()) {
1057             fail("Failed to create new File for " + file + "!");
1058         }
1059 
1060         Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888, true,
1061                 colorSpace);
1062         bitmap.eraseColor(Color.BLUE);
1063 
1064         try (FileOutputStream fOutput = new FileOutputStream(file)) {
1065             bitmap.compress(format, 80, fOutput);
1066             return file;
1067         } catch (IOException e) {
1068             e.printStackTrace();
1069             fail("Failed to create file \"" + file + "\" with exception " + e);
1070             return null;
1071         }
1072     }
1073 
1074     private static Object[] rgbColorSpaces() {
1075         return BitmapTest.getRgbColorSpaces().toArray();
1076     }
1077 
1078     private static Object[] rgbColorSpacesAndCompressFormats() {
1079         return Utils.crossProduct(rgbColorSpaces(), Bitmap.CompressFormat.values());
1080     }
1081 
1082     String toMimeType(Bitmap.CompressFormat format) {
1083         switch (format) {
1084             case JPEG:
1085                 return "image/jpeg";
1086             case PNG:
1087                 return "image/png";
1088             case WEBP:
1089             case WEBP_LOSSY:
1090             case WEBP_LOSSLESS:
1091                 return "image/webp";
1092             default:
1093                 return "";
1094         }
1095     }
1096 
1097     @Test
1098     @Parameters(method = "rgbColorSpacesAndCompressFormats")
1099     public void testGetDataSpace(ColorSpace colorSpace, Bitmap.CompressFormat format) {
1100         if (colorSpace == ColorSpace.get(Named.EXTENDED_SRGB)
1101                 || colorSpace == ColorSpace.get(Named.LINEAR_EXTENDED_SRGB)) {
1102             // These will only be reported when the default AndroidBitmapFormat is F16.
1103             // Bitmap.compress will not compress to an image that will be decoded as F16 by default,
1104             // so these are covered by the AssetRecord tests.
1105             return;
1106         }
1107 
1108         final int width = 10;
1109         final int height = 10;
1110         File file = createCompressedBitmap(width, height, colorSpace, format);
1111         assertNotNull(file);
1112 
1113         int dataSpace = colorSpace.getDataSpace();
1114 
1115         try (ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file,
1116                 ParcelFileDescriptor.MODE_READ_ONLY)) {
1117             long aimagedecoder = nCreateFromFd(pfd.getFd());
1118             nTestInfo(aimagedecoder, width, height, toMimeType(format), false, dataSpace);
1119         } catch (IOException e) {
1120             e.printStackTrace();
1121             fail("Could not read " + file);
1122         }
1123     }
1124 
1125     private static Bitmap decode(ImageDecoder.Source src, ColorSpace colorSpace) {
1126         try {
1127             return ImageDecoder.decodeBitmap(src, (decoder, info, source) -> {
1128                 // So we can compare pixels to the native decode.
1129                 decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
1130 
1131                 decoder.setTargetColorSpace(colorSpace);
1132             });
1133         } catch (IOException e) {
1134             fail("Failed to decode in Java with " + e);
1135             return null;
1136         }
1137     }
1138 
1139     @Test
1140     @Parameters(method = "rgbColorSpaces")
1141     public void testSetDataSpace(ColorSpace colorSpace) {
1142         int dataSpace = colorSpace.getDataSpace();
1143         if (dataSpace == DataSpace.ADATASPACE_UNKNOWN) {
1144             // AImageDecoder cannot decode to these ADATASPACEs
1145             return;
1146         }
1147 
1148         String name = "translucent-green-p3.png";
1149         AssetManager assets = getAssetManager();
1150         ImageDecoder.Source src = ImageDecoder.createSource(assets, name);
1151         Bitmap bm = decode(src, colorSpace);
1152         assertEquals(colorSpace, bm.getColorSpace());
1153 
1154         long asset = nOpenAsset(assets, name);
1155         long aimagedecoder = nCreateFromAsset(asset);
1156 
1157         nTestDecode(aimagedecoder, bm, dataSpace);
1158         nCloseAsset(asset);
1159     }
1160 
1161     @Test
1162     @Parameters({ "cmyk_yellow_224_224_32.jpg", "wide_gamut_yellow_224_224_64.jpeg" })
1163     public void testNonStandardDataSpaces(String name) {
1164         AssetManager assets = getAssetManager();
1165         long asset = nOpenAsset(assets, name);
1166         long aimagedecoder = nCreateFromAsset(asset);
1167 
1168         // These images have profiles that do not map to ADataSpaces (or even SkColorSpaces).
1169         // Verify that by default, AImageDecoder will treat them as ADATASPACE_UNKNOWN.
1170         nTestInfo(aimagedecoder, 32, 32, "image/jpeg", false, DataSpace.ADATASPACE_UNKNOWN);
1171         nCloseAsset(asset);
1172     }
1173 
1174     @Test
1175     @Parameters({ "cmyk_yellow_224_224_32.jpg,#FFD8FC04",
1176                   "wide_gamut_yellow_224_224_64.jpeg,#FFE0E040" })
1177     public void testNonStandardDataSpacesDecode(String name, String color) {
1178         AssetManager assets = getAssetManager();
1179         long asset = nOpenAsset(assets, name);
1180         long aimagedecoder = nCreateFromAsset(asset);
1181 
1182         // These images are each a solid color. If we correctly do no color correction, they should
1183         // match |color|.
1184         int colorInt = Color.parseColor(color);
1185         Bitmap bm = Bitmap.createBitmap(32, 32, Bitmap.Config.ARGB_8888);
1186         bm.eraseColor(colorInt);
1187 
1188         nTestDecode(aimagedecoder, ANDROID_BITMAP_FORMAT_NONE, false /* unpremul */, bm);
1189         nCloseAsset(asset);
1190     }
1191 
1192     @Test
1193     @Parameters(method = "getAssetRecords")
1194     public void testNotAnimatedAssets(ImageDecoderTest.AssetRecord record) {
1195         long asset = nOpenAsset(getAssetManager(), record.name);
1196         long aimagedecoder = nCreateFromAsset(asset);
1197 
1198         nTestIsAnimated(aimagedecoder, false);
1199         nCloseAsset(asset);
1200     }
1201 
1202     @Test
1203     @Parameters(method = "getRecords")
1204     public void testNotAnimated(ImageDecoderTest.Record record) throws IOException {
1205         try (ParcelFileDescriptor pfd = open(record.resId)) {
1206             long aimagedecoder = nCreateFromFd(pfd.getFd());
1207 
1208             nTestIsAnimated(aimagedecoder, false);
1209         } catch (FileNotFoundException e) {
1210             fail("Could not open " + Utils.getAsResourceUri(record.resId));
1211         }
1212     }
1213 
1214     private static Object[] getAnimatedImagesPlusRepeatCounts() {
1215         return AnimatedImageDrawableTest.parametersForTestEncodedRepeats();
1216     }
1217 
1218     // Although these images have an encoded repeat count, they have only one frame,
1219     // so they are not considered animated.
1220     @Test
1221     @Parameters({"still_with_loop_count.gif", "webp_still_with_loop_count.webp"})
1222     public void testStill(String name) {
1223         long asset = nOpenAsset(getAssetManager(), name);
1224         long aimagedecoder = nCreateFromAsset(asset);
1225 
1226         nTestIsAnimated(aimagedecoder, false);
1227         nCloseAsset(asset);
1228     }
1229 
1230     @Test
1231     @Parameters(method = "getAnimatedImagesPlusRepeatCounts")
1232     public void testAnimated(int resId, int unused) throws IOException {
1233         try (ParcelFileDescriptor pfd = open(resId)) {
1234             long aimagedecoder = nCreateFromFd(pfd.getFd());
1235 
1236             nTestIsAnimated(aimagedecoder, true);
1237         } catch (FileNotFoundException e) {
1238             fail("Could not open " + Utils.getAsResourceUri(resId));
1239         }
1240     }
1241 
1242     @Test
1243     @Parameters(method = "getAnimatedImagesPlusRepeatCounts")
1244     public void testRepeatCount(int resId, int repeatCount) throws IOException {
1245         try (ParcelFileDescriptor pfd = open(resId)) {
1246             long aimagedecoder = nCreateFromFd(pfd.getFd());
1247 
1248             nTestRepeatCount(aimagedecoder, repeatCount);
1249         } catch (FileNotFoundException e) {
1250             fail("Could not open " + Utils.getAsResourceUri(resId));
1251         }
1252     }
1253 
1254     @Test
1255     @Parameters({"still_with_loop_count.gif, 1", "webp_still_with_loop_count.webp,31999"})
1256     public void testRepeatCountStill(String name, int repeatCount) {
1257         long asset = nOpenAsset(getAssetManager(), name);
1258         long aimagedecoder = nCreateFromAsset(asset);
1259 
1260         nTestRepeatCount(aimagedecoder, repeatCount);
1261         nCloseAsset(asset);
1262     }
1263 
1264     // Return a pointer to the native AAsset named |file|. Must be closed with nCloseAsset.
1265     // Throws an Exception on failure.
1266     private static native long nOpenAsset(AssetManager assets, String file);
1267     private static native void nCloseAsset(long asset);
1268 
1269     // Methods for creating and returning a pointer to an AImageDecoder. All
1270     // throw an Exception on failure.
1271     private static native long nCreateFromFd(int fd);
1272     private static native long nCreateFromAsset(long asset);
1273     private static native long nCreateFromAssetFd(long asset);
1274     private static native long nCreateFromAssetBuffer(long asset);
1275 
1276     private static native void nTestEmptyCreate();
1277     private static native void nTestNullDecoder(AssetManager assets, String file);
1278     private static native void nTestCreateIncomplete(AssetManager assets,
1279             String file, int truncatedLength);
1280     private static native void nTestCreateUnsupported(AssetManager assets, String file);
1281 
1282     // For convenience, all methods that take aimagedecoder as a parameter delete
1283     // it.
1284     private static native void nTestInfo(long aimagedecoder, int width, int height,
1285             String mimeType, boolean isF16, int dataspace);
1286     private static native void nTestSetFormat(long aimagedecoder, boolean isF16, boolean isGray);
1287     private static native void nTestSetUnpremul(long aimagedecoder, boolean hasAlpha);
1288     private static native void nTestGetMinimumStride(long aimagedecoder,
1289             boolean isF16, boolean isGray);
1290     private static native void nTestDecode(long aimagedecoder,
1291             int requestedAndroidBitmapFormat, boolean unpremul, Bitmap bitmap);
1292     private static native void nTestDecodeStride(long aimagedecoder);
1293     private static native void nTestSetTargetSize(long aimagedecoder);
1294     // Decode with the target width and height to match |bitmap|.
1295     private static native void nTestDecodeScaled(long aimagedecoder, Bitmap bitmap);
1296     private static native void nTestComputeSampledSize(long aimagedecoder, Bitmap bm,
1297             int sampleSize);
1298     private static native void nTestSetCrop(AssetManager assets, String file);
1299     // Decode and compare to |bitmap|, where they both use the specified target
1300     // size and crop rect. target size of 0 means to skip scaling.
1301     private static native void nTestDecodeCrop(long aimagedecoder,
1302             Bitmap bitmap, int targetWidth, int targetHeight,
1303             int cropLeft, int cropTop, int cropRight, int cropBottom);
1304     private static native void nTestScalePlusUnpremul(long aimagedecoder);
1305     private static native void nTestDecode(long aimagedecoder, Bitmap bm, int dataSpace);
1306     private static native void nTestIsAnimated(long aimagedecoder, boolean animated);
1307     private static native void nTestRepeatCount(long aimagedecoder, int repeatCount);
1308 }
1309