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 testYuvImage()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) testYuvImageNegativeWidth()132 public void testYuvImageNegativeWidth(){ 133 new YuvImage(new byte[100 * 100 * 2], FORMATS[0], -1, 100, null); 134 } 135 136 @Test(expected=IllegalArgumentException.class) testYuvImageNegativeHeight()137 public void testYuvImageNegativeHeight(){ 138 new YuvImage(new byte[100 * 100 * 2], FORMATS[0], 100, -1, null); 139 } 140 141 @Test(expected=IllegalArgumentException.class) testYuvImageNullArray()142 public void testYuvImageNullArray(){ 143 new YuvImage(null, FORMATS[0], 100, 100, null); 144 } 145 146 @Test testCompressYuvToJpeg()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 testGetHeight()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 testGetYuvData()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 testGetYuvFormat()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 generateTestBitmaps(int width, int height)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(). generateYuvImage(int format, Bitmap bitmap, int paddings)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. compressRects(Bitmap testBitmap, YuvImage image, Rect rect1, Rect rect2)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. compressOddRect(Bitmap testBitmap, YuvImage image, Rect rect)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. compressDecompress(YuvImage image, Rect rect)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 convertArgbsToYuvs(int[] argb, int width, int height, int format)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". compareBitmaps(Bitmap expected, Bitmap actual, int mseMargin, boolean lessThanMargin)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 distance(int exp, int actual)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 argb2yuv(int argb, byte[] yuv)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 verifyParameters()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