Home | History | Annotate | Download | only in cts
      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.assertFalse;
     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.ImageFormat;
     29 import android.graphics.Rect;
     30 import android.graphics.YuvImage;
     31 import android.util.Log;
     32 
     33 import androidx.test.InstrumentationRegistry;
     34 import androidx.test.filters.SmallTest;
     35 import androidx.test.runner.AndroidJUnit4;
     36 
     37 import org.junit.Test;
     38 import org.junit.runner.RunWith;
     39 
     40 import java.io.ByteArrayOutputStream;
     41 import java.util.Arrays;
     42 
     43 @SmallTest
     44 @RunWith(AndroidJUnit4.class)
     45 public class YuvImageTest {
     46     // Coefficients are taken from jcolor.c in libjpeg.
     47     private static final int CSHIFT = 16;
     48     private static final int CYR = 19595;
     49     private static final int CYG = 38470;
     50     private static final int CYB = 7471;
     51     private static final int CUR = -11059;
     52     private static final int CUG = -21709;
     53     private static final int CUB = 32768;
     54     private static final int CVR = 32768;
     55     private static final int CVG = -27439;
     56     private static final int CVB = -5329;
     57 
     58     private static final String TAG = "YuvImageTest";
     59 
     60     private static final int[] FORMATS = { ImageFormat.NV21, ImageFormat.YUY2 };
     61 
     62     private static final int WIDTH = 256;
     63     private static final int HEIGHT = 128;
     64 
     65     private static final int[] RECT_WIDTHS = { 128, 124, 123 };
     66     private static final int[] RECT_HEIGHTS = { 64, 60, 59 };
     67 
     68     // Various rectangles:
     69     // mRects[0] : a normal one.
     70     // mRects[1] : same size to that of mRects[1], but its left-top point is shifted
     71     // mRects[2] : sides are not multiples of 16
     72     // mRects[3] : the left-top point is at an odd position
     73     private static final Rect[] RECTS = { new Rect(0, 0, 0 + RECT_WIDTHS[0],  0 + RECT_HEIGHTS[0]),
     74             new Rect(10, 10, 10 + RECT_WIDTHS[0], 10 + RECT_HEIGHTS[0]),
     75             new Rect(0, 0, 0 + RECT_WIDTHS[1], 0 + RECT_HEIGHTS[1]),
     76             new Rect(11, 11, 11 + RECT_WIDTHS[1], 11 + RECT_HEIGHTS[1]) };
     77 
     78     // Two rectangles of same size but at different positions
     79     private static final Rect[] RECTS_SHIFTED = { RECTS[0], RECTS[1] };
     80 
     81     // A rect whose side lengths are odd.
     82     private static final Rect RECT_ODD_SIDES = new Rect(10, 10, 10 + RECT_WIDTHS[2],
     83             10 + RECT_HEIGHTS[2]);
     84 
     85     private static final int[] PADDINGS = { 0, 32 };
     86 
     87     // There are three color components and
     88     // each should be within a square difference of 15 * 15.
     89     private static final int MSE_MARGIN = 3 * (15 * 15);
     90 
     91     private Bitmap[] mTestBitmaps = new Bitmap[1];
     92 
     93     @Test
     94     public void testYuvImage() {
     95         int width = 100;
     96         int height = 100;
     97         byte[] yuv = new byte[width * height * 2];
     98         YuvImage image;
     99 
    100         // normal case: test that the required formats are all supported
    101         for (int i = 0; i < FORMATS.length; ++i) {
    102             try {
    103                 new YuvImage(yuv, FORMATS[i], width, height, null);
    104             } catch (Exception e) {
    105                 Log.e(TAG, "unexpected exception", e);
    106                 fail("unexpected exception");
    107             }
    108         }
    109 
    110         // normal case: test that default strides are returned correctly
    111         for (int i = 0; i < FORMATS.length; ++i) {
    112             int[] expected = null;
    113             int[] actual = null;
    114             if (FORMATS[i] == ImageFormat.NV21) {
    115                 expected = new int[]{width, width};
    116             } else if (FORMATS[i] == ImageFormat.YUY2) {
    117                 expected = new int[]{width * 2};
    118             }
    119 
    120             try {
    121                 image = new YuvImage(yuv, FORMATS[i], width, height, null);
    122                 actual = image.getStrides();
    123                 assertTrue("default strides not calculated correctly",
    124                         Arrays.equals(expected, actual));
    125             } catch (Exception e) {
    126                 Log.e(TAG, "unexpected exception", e);
    127                 fail("unexpected exception");
    128             }
    129         }
    130     }
    131 
    132     @Test(expected=IllegalArgumentException.class)
    133     public void testYuvImageNegativeWidth(){
    134         new YuvImage(new byte[100 * 100 * 2], FORMATS[0], -1, 100, null);
    135     }
    136 
    137     @Test(expected=IllegalArgumentException.class)
    138     public void testYuvImageNegativeHeight(){
    139         new YuvImage(new byte[100 * 100 * 2], FORMATS[0], 100, -1, null);
    140     }
    141 
    142     @Test(expected=IllegalArgumentException.class)
    143     public void testYuvImageNullArray(){
    144         new YuvImage(null, FORMATS[0], 100, 100, null);
    145    }
    146 
    147     @Test
    148     public void testCompressYuvToJpeg() {
    149         generateTestBitmaps(WIDTH, HEIGHT);
    150 
    151         // test if handling compression parameters correctly
    152         verifyParameters();
    153 
    154         // test various cases by varing
    155         // <ImageFormat, Bitmap, HasPaddings, Rect>
    156         for (int i = 0; i < FORMATS.length; ++i) {
    157             for (int j = 0; j < mTestBitmaps.length; ++j) {
    158                 for (int k = 0; k < PADDINGS.length; ++k) {
    159                     YuvImage image = generateYuvImage(FORMATS[i],
    160                         mTestBitmaps[j], PADDINGS[k]);
    161                     for (int l = 0; l < RECTS.length; ++l) {
    162 
    163                         // test compressing the same rect in
    164                         // mTestBitmaps[j] and image.
    165                         compressRects(mTestBitmaps[j], image,
    166                                 RECTS[l], RECTS[l]);
    167                     }
    168 
    169                     // test compressing different rects in
    170                     // mTestBitmap[j] and image.
    171                     compressRects(mTestBitmaps[j], image, RECTS_SHIFTED[0],
    172                             RECTS_SHIFTED[1]);
    173 
    174                     // test compressing a rect whose side lengths are odd.
    175                     compressOddRect(mTestBitmaps[j], image, RECT_ODD_SIDES);
    176                 }
    177             }
    178         }
    179 
    180     }
    181 
    182     @Test
    183     public void testGetHeight() {
    184         generateTestBitmaps(WIDTH, HEIGHT);
    185         YuvImage image = generateYuvImage(ImageFormat.YUY2, mTestBitmaps[0], 0);
    186         assertEquals(mTestBitmaps[0].getHeight(), image.getHeight());
    187         assertEquals(mTestBitmaps[0].getWidth(), image.getWidth());
    188     }
    189 
    190     @Test
    191     public void testGetYuvData() {
    192         generateTestBitmaps(WIDTH, HEIGHT);
    193         int width = mTestBitmaps[0].getWidth();
    194         int height = mTestBitmaps[0].getHeight();
    195         int stride = width;
    196         int[] argb = new int[stride * height];
    197         mTestBitmaps[0].getPixels(argb, 0, stride, 0, 0, width, height);
    198         byte[] yuv = convertArgbsToYuvs(argb, stride, height, ImageFormat.NV21);
    199         int[] strides = new int[] {
    200                 stride, stride
    201         };
    202         YuvImage image = new YuvImage(yuv, ImageFormat.NV21, width, height, strides);
    203         assertEquals(yuv, image.getYuvData());
    204     }
    205 
    206     @Test
    207     public void testGetYuvFormat() {
    208         generateTestBitmaps(WIDTH, HEIGHT);
    209         YuvImage image = generateYuvImage(ImageFormat.YUY2, mTestBitmaps[0], 0);
    210         assertEquals(ImageFormat.YUY2, image.getYuvFormat());
    211     }
    212 
    213     private void generateTestBitmaps(int width, int height) {
    214         Bitmap dst = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
    215         Canvas c = new Canvas(dst);
    216 
    217         // mTestBitmap[0] = scaled testimage.jpg
    218         Resources res = InstrumentationRegistry.getTargetContext().getResources();
    219         Bitmap src = BitmapFactory.decodeResource(res, R.drawable.testimage);
    220         c.drawBitmap(src, null, new Rect(0, 0, WIDTH, HEIGHT), null);
    221         mTestBitmaps[0] = dst;
    222     }
    223 
    224     // Generate YuvImage based on the content in bitmap. If paddings > 0, the
    225     // strides of YuvImage are calculated by adding paddings to bitmap.getWidth().
    226     private YuvImage generateYuvImage(int format, Bitmap bitmap, int paddings) {
    227         int width = bitmap.getWidth();
    228         int height = bitmap.getHeight();
    229 
    230         int stride = width + paddings;
    231 
    232         YuvImage image = null;
    233         int[] argb = new int [stride * height];
    234         bitmap.getPixels(argb, 0, stride, 0, 0, width, height);
    235         byte[] yuv = convertArgbsToYuvs(argb, stride, height, format);
    236 
    237         int[] strides = null;
    238         if (format == ImageFormat.NV21) {
    239             strides = new int[] {stride, stride};
    240         } else if (format == ImageFormat.YUY2) {
    241            strides = new int[] {stride * 2};
    242         }
    243         image = new YuvImage(yuv, format, width, height, strides);
    244 
    245         return image;
    246     }
    247 
    248     // Compress rect1 in testBitmap and rect2 in image.
    249     // Then, compare the two resutls to check their MSE.
    250     private void compressRects(Bitmap testBitmap, YuvImage image,
    251             Rect rect1, Rect rect2) {
    252         Bitmap expected = null;
    253         Bitmap actual = null;
    254         boolean sameRect = rect1.equals(rect2) ? true : false;
    255 
    256 		Rect actualRect = new Rect(rect2);
    257         actual = compressDecompress(image, actualRect);
    258 
    259         Rect expectedRect = sameRect ? actualRect : rect1;
    260         expected = Bitmap.createBitmap(testBitmap, expectedRect.left, expectedRect.top,
    261                 expectedRect.width(), expectedRect.height());
    262         compareBitmaps(expected, actual, MSE_MARGIN, sameRect);
    263     }
    264 
    265     // Compress rect in image.
    266     // If side lengths of rect are odd, the rect might be modified by image,
    267     // We use the modified one to get pixels from testBitmap.
    268     private void compressOddRect(Bitmap testBitmap, YuvImage image,
    269             Rect rect) {
    270         Bitmap expected = null;
    271         Bitmap actual = null;
    272         actual = compressDecompress(image, rect);
    273 
    274         Rect newRect = rect;
    275         expected = Bitmap.createBitmap(testBitmap, newRect.left, newRect.top,
    276               newRect.width(), newRect.height());
    277 
    278         compareBitmaps(expected, actual, MSE_MARGIN, true);
    279     }
    280 
    281     // Compress rect in image to a jpeg and then decode the jpeg to a bitmap.
    282     private Bitmap compressDecompress(YuvImage image, Rect rect) {
    283         Bitmap result = null;
    284         ByteArrayOutputStream stream = new ByteArrayOutputStream();
    285         try {
    286             boolean rt = image.compressToJpeg(rect, 90, stream);
    287             assertTrue("fail in compression", rt);
    288             byte[] jpegData = stream.toByteArray();
    289             result = BitmapFactory.decodeByteArray(jpegData, 0,
    290                     jpegData.length);
    291         } catch(Exception e){
    292             Log.e(TAG, "unexpected exception", e);
    293             fail("unexpected exception");
    294         }
    295         return result;
    296     }
    297 
    298     private byte[] convertArgbsToYuvs(int[] argb, int width, int height,
    299             int format) {
    300         byte[] yuv = new byte[width * height *
    301                 ImageFormat.getBitsPerPixel(format)];
    302         if (format == ImageFormat.NV21) {
    303             int vuStart = width * height;
    304             byte[] yuvColor = new byte[3];
    305             for (int row = 0; row < height; ++row) {
    306                 for (int col = 0; col < width; ++col) {
    307                     int idx = row * width + col;
    308                     argb2yuv(argb[idx], yuvColor);
    309                     yuv[idx] = yuvColor[0];
    310                     if ((row & 1) == 0 && (col & 1) == 0) {
    311                         int offset = row / 2 * width + col / 2 * 2;
    312                         yuv[vuStart + offset] = yuvColor[2];
    313                         yuv[vuStart + offset + 1] = yuvColor[1];
    314                     }
    315                 }
    316             }
    317         } else if (format == ImageFormat.YUY2) {
    318             byte[] yuvColor0 = new byte[3];
    319             byte[] yuvColor1 = new byte[3];
    320             for (int row = 0; row < height; ++row) {
    321                 for (int col = 0; col < width; col += 2) {
    322                     int idx = row * width + col;
    323                     argb2yuv(argb[idx], yuvColor0);
    324                     argb2yuv(argb[idx + 1], yuvColor1);
    325                     int offset = idx / 2 * 4;
    326                     yuv[offset] = yuvColor0[0];
    327                     yuv[offset + 1] = yuvColor0[1];
    328                     yuv[offset + 2] = yuvColor1[0];
    329                     yuv[offset + 3] = yuvColor0[2];
    330                 }
    331             }
    332         }
    333 
    334         return yuv;
    335     }
    336 
    337     // Compare expected to actual to see if their diff is less then mseMargin.
    338     // lessThanMargin is to indicate whether we expect the diff to be
    339     // "less than" or "no less than".
    340     private void compareBitmaps(Bitmap expected, Bitmap actual,
    341             int mseMargin, boolean lessThanMargin) {
    342         assertEquals("mismatching widths", expected.getWidth(),
    343                 actual.getWidth());
    344         assertEquals("mismatching heights", expected.getHeight(),
    345                 actual.getHeight());
    346 
    347         double mse = 0;
    348         int width = expected.getWidth();
    349         int height = expected.getHeight();
    350         int[] expColors = new int [width * height];
    351         expected.getPixels(expColors, 0, width, 0, 0, width, height);
    352 
    353         int[] actualColors = new int [width * height];
    354         actual.getPixels(actualColors, 0, width, 0, 0, width, height);
    355 
    356         for (int row = 0; row < height; ++row) {
    357             for (int col = 0; col < width; ++col) {
    358                 int idx = row * width + col;
    359                 mse += distance(expColors[idx], actualColors[idx]);
    360             }
    361         }
    362         mse /= width * height;
    363 
    364         Log.i(TAG, "MSE: " + mse);
    365         if (lessThanMargin) {
    366             assertTrue("MSE too large for normal case: " + mse,
    367                     mse <= mseMargin);
    368         } else {
    369             assertFalse("MSE too small for abnormal case: " + mse,
    370                     mse <= mseMargin);
    371         }
    372     }
    373 
    374     private double distance(int exp, int actual) {
    375         int r = Color.red(actual) - Color.red(exp);
    376         int g = Color.green(actual) - Color.green(exp);
    377         int b = Color.blue(actual) - Color.blue(exp);
    378         return r * r + g * g + b * b;
    379     }
    380 
    381     private void argb2yuv(int argb, byte[] yuv) {
    382         int r = Color.red(argb);
    383         int g = Color.green(argb);
    384         int b = Color.blue(argb);
    385         yuv[0] = (byte) ((CYR * r + CYG * g + CYB * b) >> CSHIFT);
    386         yuv[1] = (byte) (((CUR * r + CUG * g + CUB * b) >> CSHIFT) + 128);
    387         yuv[2] = (byte) (((CVR * r + CVG * g + CVB * b) >> CSHIFT) + 128);
    388     }
    389 
    390     private void verifyParameters() {
    391         int format = ImageFormat.NV21;
    392         int[] argb = new int[WIDTH * HEIGHT];
    393         mTestBitmaps[0].getPixels(argb, 0, WIDTH, 0, 0, WIDTH, HEIGHT);
    394         byte[] yuv = convertArgbsToYuvs(argb, WIDTH, HEIGHT, format);
    395 
    396         YuvImage image = new YuvImage(yuv, format, WIDTH, HEIGHT, null);
    397         ByteArrayOutputStream stream = new ByteArrayOutputStream();
    398 
    399         // abnormal case: quality > 100
    400         try{
    401             Rect rect = new Rect(0, 0, WIDTH, HEIGHT);
    402             image.compressToJpeg(rect, 101, stream);
    403             fail("not catching illegal compression quality");
    404         } catch(IllegalArgumentException e){
    405             // expected
    406         }
    407 
    408         // abnormal case: quality < 0
    409         try{
    410             Rect rect = new Rect(0, 0, WIDTH, HEIGHT);
    411             image.compressToJpeg(rect, -1, stream);
    412             fail("not catching illegal compression quality");
    413         } catch(IllegalArgumentException e){
    414             // expected
    415         }
    416 
    417         // abnormal case: stream is null
    418         try {
    419             Rect rect = new Rect(0, 0, WIDTH, HEIGHT);
    420             image.compressToJpeg(rect, 80, null);
    421             fail("not catching null stream");
    422         } catch(IllegalArgumentException e){
    423             // expected
    424         }
    425 
    426         // abnormal case: rectangle not within the whole image
    427         try {
    428             Rect rect = new Rect(10, 0, WIDTH, HEIGHT + 5);
    429             image.compressToJpeg(rect, 80, stream);
    430             fail("not catching illegal rectangular region");
    431         } catch(IllegalArgumentException e){
    432             // expected
    433         }
    434     }
    435 }
    436