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