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 package android.graphics.cts;
17 
18 import static org.junit.Assert.assertEquals;
19 import static org.junit.Assert.assertNotNull;
20 import static org.junit.Assert.assertTrue;
21 import static org.junit.Assert.fail;
22 
23 import android.content.res.Resources;
24 import android.graphics.Bitmap;
25 import android.graphics.BitmapFactory;
26 import android.graphics.Canvas;
27 import android.graphics.Color;
28 import android.graphics.ColorSpace;
29 import android.graphics.ImageFormat;
30 import android.graphics.Rect;
31 import android.graphics.YuvImage;
32 import android.platform.test.annotations.RequiresFlagsEnabled;
33 import android.media.ExifInterface;
34 import android.util.Log;
35 
36 import androidx.test.InstrumentationRegistry;
37 import androidx.test.filters.SmallTest;
38 import androidx.test.runner.AndroidJUnit4;
39 
40 import com.android.compatibility.common.util.ApiTest;
41 import com.android.compatibility.common.util.BitmapUtils;
42 import com.android.graphics.flags.Flags;
43 
44 import org.junit.Test;
45 import org.junit.runner.RunWith;
46 
47 import java.io.ByteArrayInputStream;
48 import java.io.ByteArrayOutputStream;
49 import java.io.IOException;
50 import java.nio.file.Files;
51 import java.nio.file.Paths;
52 import java.util.Arrays;
53 
54 @SmallTest
55 @RunWith(AndroidJUnit4.class)
56 public class YuvImageTest {
57     // Coefficients are taken from jcolor.c in libjpeg.
58     private static final int CSHIFT = 16;
59     private static final int CYR = 19595;
60     private static final int CYG = 38470;
61     private static final int CYB = 7471;
62     private static final int CUR = -11059;
63     private static final int CUG = -21709;
64     private static final int CUB = 32768;
65     private static final int CVR = 32768;
66     private static final int CVG = -27439;
67     private static final int CVB = -5329;
68 
69     private static final String TAG = "YuvImageTest";
70 
71     private static final int[] FORMATS = { ImageFormat.NV21, ImageFormat.YUY2,
72                                            ImageFormat.YCBCR_P010, ImageFormat.YUV_420_888 };
73     private static final int[] JPEG_FORMATS = { ImageFormat.NV21, ImageFormat.YUY2 };
74 
75     private static final int WIDTH = 256;
76     private static final int HEIGHT = 128;
77 
78     private static final int[] RECT_WIDTHS = { 128, 124, 123 };
79     private static final int[] RECT_HEIGHTS = { 64, 60, 59 };
80 
81     // Various rectangles:
82     // mRects[0] : a normal one.
83     // mRects[1] : same size to that of mRects[1], but its left-top point is shifted
84     // mRects[2] : sides are not multiples of 16
85     // mRects[3] : the left-top point is at an odd position
86     private static final Rect[] RECTS = { new Rect(0, 0, 0 + RECT_WIDTHS[0],  0 + RECT_HEIGHTS[0]),
87             new Rect(10, 10, 10 + RECT_WIDTHS[0], 10 + RECT_HEIGHTS[0]),
88             new Rect(0, 0, 0 + RECT_WIDTHS[1], 0 + RECT_HEIGHTS[1]),
89             new Rect(11, 11, 11 + RECT_WIDTHS[1], 11 + RECT_HEIGHTS[1]) };
90 
91     // Two rectangles of same size but at different positions
92     private static final Rect[] RECTS_SHIFTED = { RECTS[0], RECTS[1] };
93 
94     // A rect whose side lengths are odd.
95     private static final Rect RECT_ODD_SIDES = new Rect(10, 10, 10 + RECT_WIDTHS[2],
96             10 + RECT_HEIGHTS[2]);
97 
98     private static final int[] PADDINGS = { 0, 32 };
99 
100     // There are three color components and
101     // each should be within a square difference of 15 * 15.
102     private static final int MSE_MARGIN = 3 * (15 * 15);
103 
104     private Bitmap[] mTestBitmaps = new Bitmap[1];
105 
106     @Test
testYuvImage()107     public void testYuvImage() {
108         int width = 100;
109         int height = 100;
110         byte[] yuv = new byte[width * height * 2];
111         YuvImage image;
112 
113         // normal case: test that the required formats are all supported
114         for (int i = 0; i < FORMATS.length; ++i) {
115             try {
116                 new YuvImage(yuv, FORMATS[i], width, height, null);
117             } catch (Exception e) {
118                 Log.e(TAG, "unexpected exception", e);
119                 fail("unexpected exception");
120             }
121         }
122 
123         // normal case: test that default strides are returned correctly
124         for (int i = 0; i < FORMATS.length; ++i) {
125             int[] expected = null;
126             int[] actual = null;
127             switch (FORMATS[i]) {
128                 case ImageFormat.NV21:
129                     expected = new int[]{width, width};
130                     break;
131                 case ImageFormat.YCBCR_P010:
132                     expected = new int[]{width * 2, width * 2};
133                     break;
134                 case ImageFormat.YUV_420_888:
135                     expected = new int[]{width, (width + 1) / 2, (width + 1) / 2};
136                     break;
137                 case ImageFormat.YUY2:
138                     expected = new int[]{width * 2};
139                     break;
140                 default:
141                     // We shouldn't hit here.
142             }
143 
144             try {
145                 image = new YuvImage(yuv, FORMATS[i], width, height, null);
146                 actual = image.getStrides();
147                 assertTrue("default strides not calculated correctly",
148                         Arrays.equals(expected, actual));
149             } catch (Exception e) {
150                 Log.e(TAG, "unexpected exception", e);
151                 fail("unexpected exception");
152             }
153         }
154 
155         // abnormal case: pass a null ColorSpace, should throw IllegalArgumentException
156         try{
157             image = new YuvImage(yuv, FORMATS[0], width, height, null, null);
158             fail("not catching hdr empty");
159         } catch (IllegalArgumentException e){
160             // expected
161         }
162     }
163 
164     @Test(expected=IllegalArgumentException.class)
testYuvImageNegativeWidth()165     public void testYuvImageNegativeWidth(){
166         new YuvImage(new byte[100 * 100 * 2], FORMATS[0], -1, 100, null);
167     }
168 
169     @Test(expected=IllegalArgumentException.class)
testYuvImageNegativeHeight()170     public void testYuvImageNegativeHeight(){
171         new YuvImage(new byte[100 * 100 * 2], FORMATS[0], 100, -1, null);
172     }
173 
174     @Test(expected=IllegalArgumentException.class)
testYuvImageNullArray()175     public void testYuvImageNullArray(){
176         new YuvImage(null, FORMATS[0], 100, 100, null);
177    }
178 
179     @Test
testCompressYuvToJpeg()180     public void testCompressYuvToJpeg() {
181         generateTestBitmaps(WIDTH, HEIGHT);
182 
183         // test if handling compression parameters correctly
184         verifyParameters();
185 
186         // test various cases by varying
187         // <ImageFormat, Bitmap, HasPaddings, Rect>
188         for (int i = 0; i < JPEG_FORMATS.length; ++i) {
189             for (int j = 0; j < mTestBitmaps.length; ++j) {
190                 for (int k = 0; k < PADDINGS.length; ++k) {
191                     YuvImage image = generateYuvImage(JPEG_FORMATS[i],
192                         mTestBitmaps[j], PADDINGS[k], null);
193                     for (int l = 0; l < RECTS.length; ++l) {
194 
195                         // test compressing the same rect in
196                         // mTestBitmaps[j] and image.
197                         compressRects(mTestBitmaps[j], image,
198                                 RECTS[l], RECTS[l]);
199                     }
200 
201                     // test compressing different rects in
202                     // mTestBitmap[j] and image.
203                     compressRects(mTestBitmaps[j], image, RECTS_SHIFTED[0],
204                             RECTS_SHIFTED[1]);
205 
206                     // test compressing a rect whose side lengths are odd.
207                     compressOddRect(mTestBitmaps[j], image, RECT_ODD_SIDES);
208                 }
209             }
210         }
211 
212     }
213 
214     @Test
testGetHeight()215     public void testGetHeight() {
216         generateTestBitmaps(WIDTH, HEIGHT);
217         YuvImage image = generateYuvImage(ImageFormat.YUY2, mTestBitmaps[0], 0, null);
218         assertEquals(mTestBitmaps[0].getHeight(), image.getHeight());
219         assertEquals(mTestBitmaps[0].getWidth(), image.getWidth());
220     }
221 
222     @Test
testGetColorSpace()223     public void testGetColorSpace() {
224         generateTestBitmaps(WIDTH, HEIGHT);
225         ColorSpace defaultColorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
226         ColorSpace expectedColorSpace = ColorSpace.get(ColorSpace.Named.BT709);
227         YuvImage imageSRGB = generateYuvImage(ImageFormat.YUY2, mTestBitmaps[0], 0, null);
228         YuvImage imageBT709 = generateYuvImage(
229                 ImageFormat.YUY2, mTestBitmaps[0], 0, expectedColorSpace);
230         assertEquals(defaultColorSpace, imageSRGB.getColorSpace());
231         assertEquals(expectedColorSpace, imageBT709.getColorSpace());
232     }
233 
234     @Test
testGetYuvData()235     public void testGetYuvData() {
236         generateTestBitmaps(WIDTH, HEIGHT);
237         int width = mTestBitmaps[0].getWidth();
238         int height = mTestBitmaps[0].getHeight();
239         int stride = width;
240         int[] argb = new int[stride * height];
241         mTestBitmaps[0].getPixels(argb, 0, stride, 0, 0, width, height);
242         byte[] yuv = convertArgbsToYuvs(argb, stride, height, ImageFormat.NV21);
243         int[] strides = new int[] {
244                 stride, stride
245         };
246         YuvImage image = new YuvImage(yuv, ImageFormat.NV21, width, height, strides);
247         assertEquals(yuv, image.getYuvData());
248     }
249 
250     @Test
testGetYuvFormat()251     public void testGetYuvFormat() {
252         generateTestBitmaps(WIDTH, HEIGHT);
253         YuvImage image = generateYuvImage(ImageFormat.YUY2, mTestBitmaps[0], 0, null);
254         assertEquals(ImageFormat.YUY2, image.getYuvFormat());
255     }
256 
257     @Test
testCompressYuvToJpegRWithBadInputs()258     public void testCompressYuvToJpegRWithBadInputs() {
259         String hdrInput = Utils.obtainPath(R.raw.raw_p010_image, 0);
260         String sdrInput = Utils.obtainPath(R.raw.raw_yuv420_image, 0);
261 
262         final int width = 1280;
263         final int height = 720;
264 
265         ByteArrayOutputStream stream = new ByteArrayOutputStream();
266 
267         try {
268             byte[] emptyData = new byte[0];
269             byte[] hdrData = Files.readAllBytes(Paths.get(hdrInput));
270             byte[] sdrData = Files.readAllBytes(Paths.get(sdrInput));
271 
272             YuvImage emptyHdr = new YuvImage(
273                     emptyData, ImageFormat.YCBCR_P010, width, height, null);
274             YuvImage emptySdr = new YuvImage(
275                     emptyData, ImageFormat.YUV_420_888, width, height, null);
276             YuvImage supportedHdr = new YuvImage(
277                     hdrData, ImageFormat.YCBCR_P010, width, height, null);
278             YuvImage supportedSdr = new YuvImage(
279                     sdrData, ImageFormat.YUV_420_888, width, height, null);
280             YuvImage unsupportedHdr = new YuvImage(
281                     hdrData, ImageFormat.YUV_420_888, width, height, null);
282             YuvImage unsupportedSdr = new YuvImage(
283                     sdrData, ImageFormat.NV21, width, height, null);
284             YuvImage sdrWithDifferentRes = new YuvImage(
285                     sdrData, ImageFormat.YUV_420_888, 720, 480, null);
286             YuvImage hdrWithNotSupportedColorSpace = new YuvImage(
287                     hdrData, ImageFormat.YCBCR_P010, 720, 480, null,
288                     ColorSpace.get(ColorSpace.Named.CIE_LAB));
289             YuvImage sdrWithNotSupportedColorSpace = new YuvImage(
290                     sdrData, ImageFormat.YUV_420_888, 720, 480, null,
291                     ColorSpace.get(ColorSpace.Named.BT709));
292 
293             // abnormal case: hdr is empty
294             try{
295                 emptyHdr.compressToJpegR(supportedSdr, 90, stream);
296                 fail("not catching hdr empty");
297             } catch (IllegalArgumentException e){
298                 // expected
299             }
300 
301             // abnormal case: sdr is empty
302             try{
303                 supportedHdr.compressToJpegR(emptySdr, 90, stream);
304                 fail("not catching sdr empty");
305             } catch (IllegalArgumentException e){
306                 // expected
307             }
308 
309             // abnormal case: sdr is unsupported color format
310             try{
311                 supportedHdr.compressToJpegR(unsupportedSdr, 90, stream);
312                 fail("not catching sdr is unsupported color format");
313             } catch (IllegalArgumentException e){
314                 // expected
315             }
316 
317             // abnormal case: sdr is null
318             try{
319                 supportedHdr.compressToJpegR(null, 90, stream);
320                 fail("not catching sdr is null");
321             } catch (IllegalArgumentException e){
322                 // expected
323             }
324 
325             // abnormal case: quality < 0
326             try{
327                 supportedHdr.compressToJpegR(supportedSdr, -1, stream);
328                 fail("not catching illegal compression quality");
329             } catch (IllegalArgumentException e){
330                 // expected
331             }
332 
333             // abnormal case: quality > 100
334             try{
335                 supportedHdr.compressToJpegR(supportedSdr, 101, stream);
336                 fail("not catching illegal compression quality");
337             } catch (IllegalArgumentException e){
338                 // expected
339             }
340 
341             // abnormal case: stream is null
342             try{
343                 supportedHdr.compressToJpegR(supportedSdr, 90, null);
344                 fail("not catching null stream");
345             } catch (IllegalArgumentException e){
346                 // expected
347             }
348 
349             // abnormal case: resolution mismatch
350             try{
351                 supportedHdr.compressToJpegR(sdrWithDifferentRes, 90, stream);
352                 fail("not catching resolution mismatch");
353             } catch (IllegalArgumentException e){
354                 // expected
355             }
356 
357             // abnormal case: not supported color space
358             try{
359                 supportedHdr.compressToJpegR(sdrWithNotSupportedColorSpace, 90, stream);
360                 fail("not catching resolution mismatch");
361             } catch (IllegalArgumentException e){
362                 // expected
363             }
364             try{
365                 hdrWithNotSupportedColorSpace.compressToJpegR(supportedSdr, 90, stream);
366                 fail("not catching resolution mismatch");
367             } catch (IllegalArgumentException e){
368                 // expected
369             }
370         } catch (Exception e) {
371             Log.e(TAG, "unexpected exception", e);
372             fail("unexpected exception");
373         }
374     }
375 
376     @Test
testCompressYuvToJpegR()377     public void testCompressYuvToJpegR() {
378         String hdrInput = Utils.obtainPath(R.raw.raw_p010_image, 0);
379         String sdrInput = Utils.obtainPath(R.raw.raw_yuv420_image, 0);
380 
381         final int width = 1280;
382         final int height = 720;
383 
384         ByteArrayOutputStream stream = new ByteArrayOutputStream();
385 
386         try {
387             byte[] hdrData = Files.readAllBytes(Paths.get(hdrInput));
388             byte[] sdrData = Files.readAllBytes(Paths.get(sdrInput));
389 
390             YuvImage hdrImage = new YuvImage(hdrData, ImageFormat.YCBCR_P010,
391                                              width, height, null,
392                                              ColorSpace.get(ColorSpace.Named.BT2020_HLG));
393             YuvImage sdrImage = new YuvImage(sdrData, ImageFormat.YUV_420_888,
394                                              width, height, null);
395 
396             assertTrue("Fail in JPEG/R compression.",
397                     hdrImage.compressToJpegR(sdrImage, 90, stream));
398             byte[] jpegRData = stream.toByteArray();
399             Bitmap decoded = BitmapFactory.decodeByteArray(jpegRData, 0, jpegRData.length);
400             assertNotNull(decoded);
401         } catch (Exception e) {
402             Log.e(TAG, "unexpected exception", e);
403             fail("unexpected exception");
404         }
405     }
406 
407     @ApiTest(apis = {"android.graphics.YuvImage#compressToJpegR"})
408     @RequiresFlagsEnabled(Flags.FLAG_YUV_IMAGE_COMPRESS_TO_ULTRA_HDR)
409     @Test
testCompressYuvToJpegRWithExif()410     public void testCompressYuvToJpegRWithExif() {
411         String hdrInput = Utils.obtainPath(R.raw.raw_p010_image, 0);
412         String sdrInput = Utils.obtainPath(R.raw.raw_yuv420_image, 0);
413         String exifInput = Utils.obtainPath(R.raw.sample_exif, 0);
414 
415         final int width = 1280;
416         final int height = 720;
417 
418         ByteArrayOutputStream stream = new ByteArrayOutputStream();
419 
420         try {
421             byte[] hdrData = Files.readAllBytes(Paths.get(hdrInput));
422             byte[] sdrData = Files.readAllBytes(Paths.get(sdrInput));
423             byte[] exifData = Files.readAllBytes(Paths.get(exifInput));
424 
425             YuvImage hdrImage = new YuvImage(hdrData, ImageFormat.YCBCR_P010,
426                 width, height, null,
427                 ColorSpace.get(ColorSpace.Named.BT2020_HLG));
428             YuvImage sdrImage = new YuvImage(sdrData, ImageFormat.YUV_420_888,
429                 width, height, null);
430 
431             assertTrue("Fail in JPEG/R compression.",
432                     hdrImage.compressToJpegR(sdrImage, 90, stream, exifData));
433             byte[] jpegRData = stream.toByteArray();
434             Bitmap decoded = BitmapFactory.decodeByteArray(jpegRData, 0, jpegRData.length);
435             assertNotNull(decoded);
436 
437             ExifInterface exifInterface = new ExifInterface(
438                     new ByteArrayInputStream(stream.toByteArray()));
439             assertEquals(exifInterface.getAttribute(ExifInterface.TAG_IMAGE_WIDTH),
440                     String.valueOf(width));
441             assertEquals(exifInterface.getAttribute(ExifInterface.TAG_IMAGE_LENGTH),
442                     String.valueOf(height));
443             assertEquals(exifInterface.getAttribute(ExifInterface.TAG_DATETIME),
444                     "2022:11:22 23:05:32");
445             assertEquals(exifInterface.getAttribute(ExifInterface.TAG_MAKE),
446                     "Google");
447             assertEquals(exifInterface.getAttribute(ExifInterface.TAG_MODEL),
448                     "Cuttlefish GMS x86_64");
449         } catch (Exception e) {
450             Log.e(TAG, "unexpected exception", e);
451             fail("unexpected exception");
452         }
453     }
454 
455     @Test
testCompressYuvToJpegRWithStrides()456     public void testCompressYuvToJpegRWithStrides() throws Exception {
457         String hdrInput = Utils.obtainPath(R.raw.raw_p010_image, 0);
458         String sdrInput = Utils.obtainPath(R.raw.raw_yuv420_image, 0);
459 
460         final int stride = 1280;
461         final int padding = 20;
462         final int width = stride - padding;
463         final int height = 720;
464 
465         ByteArrayOutputStream stream = new ByteArrayOutputStream();
466 
467         byte[] hdrData = Files.readAllBytes(Paths.get(hdrInput));
468         byte[] sdrData = Files.readAllBytes(Paths.get(sdrInput));
469 
470         YuvImage hdrImage = new YuvImage(hdrData, ImageFormat.YCBCR_P010,
471                 width, height, new int[] {stride * 2, stride * 2},
472                 ColorSpace.get(ColorSpace.Named.BT2020_HLG));
473         YuvImage sdrImage = new YuvImage(sdrData, ImageFormat.YUV_420_888,
474                 width, height, new int[] {stride, stride / 2, stride / 2});
475 
476         assertTrue("Fail in JPEG/R compression.",
477                 hdrImage.compressToJpegR(sdrImage, 90, stream));
478         byte[] jpegRData = stream.toByteArray();
479         Bitmap decoded = BitmapFactory.decodeByteArray(jpegRData, 0, jpegRData.length);
480         assertNotNull(decoded);
481         assertEquals(decoded.getWidth(), width);
482     }
483 
generateTestBitmaps(int width, int height)484     private void generateTestBitmaps(int width, int height) {
485         Bitmap dst = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
486         Canvas c = new Canvas(dst);
487 
488         // mTestBitmap[0] = scaled testimage.jpg
489         Resources res = InstrumentationRegistry.getTargetContext().getResources();
490         Bitmap src = BitmapFactory.decodeResource(res, R.drawable.testimage);
491         c.drawBitmap(src, null, new Rect(0, 0, WIDTH, HEIGHT), null);
492         mTestBitmaps[0] = dst;
493     }
494 
495     // Generate YuvImage based on the content in bitmap. If paddings > 0, the
496     // strides of YuvImage are calculated by adding paddings to bitmap.getWidth().
497     // If colorSpace is null, this method will return a YuvImage with the default
498     // ColorSpace (SRGB). Otherwise, it will return a YuvImage with configured ColorSpace.
generateYuvImage(int format, Bitmap bitmap, int paddings, ColorSpace colorSpace)499     private YuvImage generateYuvImage(int format, Bitmap bitmap, int paddings,
500                 ColorSpace colorSpace) {
501         int width = bitmap.getWidth();
502         int height = bitmap.getHeight();
503 
504         int stride = width + paddings;
505 
506         YuvImage image = null;
507         int[] argb = new int [stride * height];
508         bitmap.getPixels(argb, 0, stride, 0, 0, width, height);
509         byte[] yuv = convertArgbsToYuvs(argb, stride, height, format);
510 
511         int[] strides = null;
512         if (format == ImageFormat.NV21) {
513             strides = new int[] {stride, stride};
514         } else if (format == ImageFormat.YUY2) {
515            strides = new int[] {stride * 2};
516         }
517 
518         if (colorSpace != null) {
519             image = new YuvImage(yuv, format, width, height, strides, colorSpace);
520         } else {
521             image = new YuvImage(yuv, format, width, height, strides);
522         }
523         return image;
524     }
525 
526     // Compress rect1 in testBitmap and rect2 in image.
527     // Then, compare the two results to check their MSE.
compressRects(Bitmap testBitmap, YuvImage image, Rect rect1, Rect rect2)528     private void compressRects(Bitmap testBitmap, YuvImage image,
529             Rect rect1, Rect rect2) {
530         Bitmap expected = null;
531         Bitmap actual = null;
532         boolean sameRect = rect1.equals(rect2) ? true : false;
533 
534         Rect actualRect = new Rect(rect2);
535         actual = compressDecompress(image, actualRect);
536 
537         Rect expectedRect = sameRect ? actualRect : rect1;
538         expected = Bitmap.createBitmap(testBitmap, expectedRect.left, expectedRect.top,
539                 expectedRect.width(), expectedRect.height());
540         BitmapUtils.assertBitmapsMse(expected, actual, MSE_MARGIN, sameRect, false);
541     }
542 
543     // Compress rect in image.
544     // If side lengths of rect are odd, the rect might be modified by image,
545     // We use the modified one to get pixels from testBitmap.
compressOddRect(Bitmap testBitmap, YuvImage image, Rect rect)546     private void compressOddRect(Bitmap testBitmap, YuvImage image,
547             Rect rect) {
548         Bitmap expected = null;
549         Bitmap actual = null;
550         actual = compressDecompress(image, rect);
551 
552         Rect newRect = rect;
553         expected = Bitmap.createBitmap(testBitmap, newRect.left, newRect.top,
554               newRect.width(), newRect.height());
555 
556         BitmapUtils.assertBitmapsMse(expected, actual, MSE_MARGIN, true, false);
557     }
558 
559     // Compress rect in image to a jpeg and then decode the jpeg to a bitmap.
compressDecompress(YuvImage image, Rect rect)560     private Bitmap compressDecompress(YuvImage image, Rect rect) {
561         Bitmap result = null;
562         ByteArrayOutputStream stream = new ByteArrayOutputStream();
563         try {
564             boolean rt = image.compressToJpeg(rect, 90, stream);
565             assertTrue("fail in compression", rt);
566             byte[] jpegData = stream.toByteArray();
567             result = BitmapFactory.decodeByteArray(jpegData, 0,
568                     jpegData.length);
569         } catch (Exception e){
570             Log.e(TAG, "unexpected exception", e);
571             fail("unexpected exception");
572         }
573         return result;
574     }
575 
convertArgbsToYuvs(int[] argb, int width, int height, int format)576     private byte[] convertArgbsToYuvs(int[] argb, int width, int height,
577             int format) {
578         byte[] yuv = new byte[width * height *
579                 ImageFormat.getBitsPerPixel(format)];
580         if (format == ImageFormat.NV21) {
581             int vuStart = width * height;
582             byte[] yuvColor = new byte[3];
583             for (int row = 0; row < height; ++row) {
584                 for (int col = 0; col < width; ++col) {
585                     int idx = row * width + col;
586                     argb2yuv(argb[idx], yuvColor);
587                     yuv[idx] = yuvColor[0];
588                     if ((row & 1) == 0 && (col & 1) == 0) {
589                         int offset = row / 2 * width + col / 2 * 2;
590                         yuv[vuStart + offset] = yuvColor[2];
591                         yuv[vuStart + offset + 1] = yuvColor[1];
592                     }
593                 }
594             }
595         } else if (format == ImageFormat.YUY2) {
596             byte[] yuvColor0 = new byte[3];
597             byte[] yuvColor1 = new byte[3];
598             for (int row = 0; row < height; ++row) {
599                 for (int col = 0; col < width; col += 2) {
600                     int idx = row * width + col;
601                     argb2yuv(argb[idx], yuvColor0);
602                     argb2yuv(argb[idx + 1], yuvColor1);
603                     int offset = idx / 2 * 4;
604                     yuv[offset] = yuvColor0[0];
605                     yuv[offset + 1] = yuvColor0[1];
606                     yuv[offset + 2] = yuvColor1[0];
607                     yuv[offset + 3] = yuvColor0[2];
608                 }
609             }
610         }
611 
612         return yuv;
613     }
614 
argb2yuv(int argb, byte[] yuv)615     private void argb2yuv(int argb, byte[] yuv) {
616         int r = Color.red(argb);
617         int g = Color.green(argb);
618         int b = Color.blue(argb);
619         yuv[0] = (byte) ((CYR * r + CYG * g + CYB * b) >> CSHIFT);
620         yuv[1] = (byte) (((CUR * r + CUG * g + CUB * b) >> CSHIFT) + 128);
621         yuv[2] = (byte) (((CVR * r + CVG * g + CVB * b) >> CSHIFT) + 128);
622     }
623 
verifyParameters()624     private void verifyParameters() {
625         int format = ImageFormat.NV21;
626         int[] argb = new int[WIDTH * HEIGHT];
627         mTestBitmaps[0].getPixels(argb, 0, WIDTH, 0, 0, WIDTH, HEIGHT);
628         byte[] yuv = convertArgbsToYuvs(argb, WIDTH, HEIGHT, format);
629 
630         YuvImage image = new YuvImage(yuv, format, WIDTH, HEIGHT, null);
631         ByteArrayOutputStream stream = new ByteArrayOutputStream();
632 
633         // abnormal case: quality > 100
634         try{
635             Rect rect = new Rect(0, 0, WIDTH, HEIGHT);
636             image.compressToJpeg(rect, 101, stream);
637             fail("not catching illegal compression quality");
638         } catch (IllegalArgumentException e){
639             // expected
640         }
641 
642         // abnormal case: quality < 0
643         try{
644             Rect rect = new Rect(0, 0, WIDTH, HEIGHT);
645             image.compressToJpeg(rect, -1, stream);
646             fail("not catching illegal compression quality");
647         } catch (IllegalArgumentException e){
648             // expected
649         }
650 
651         // abnormal case: stream is null
652         try {
653             Rect rect = new Rect(0, 0, WIDTH, HEIGHT);
654             image.compressToJpeg(rect, 80, null);
655             fail("not catching null stream");
656         } catch (IllegalArgumentException e){
657             // expected
658         }
659 
660         // abnormal case: rectangle not within the whole image
661         try {
662             Rect rect = new Rect(10, 0, WIDTH, HEIGHT + 5);
663             image.compressToJpeg(rect, 80, stream);
664             fail("not catching illegal rectangular region");
665         } catch (IllegalArgumentException e){
666             // expected
667         }
668 
669         // abnormal case: unsupported color space
670         try {
671             Rect rect = new Rect(0, 0, WIDTH, HEIGHT);
672             YuvImage image2 = new YuvImage(yuv, format, WIDTH, HEIGHT, null,
673                     ColorSpace.get(ColorSpace.Named.BT709));
674             image2.compressToJpeg(rect, 80, stream);
675             fail("not catching illegal color Space");
676         } catch (IllegalArgumentException e){
677             // expected
678         }
679     }
680 }
681