1 /* 2 * Copyright 2018 The WebRTC project authors. All Rights Reserved. 3 * 4 * Use of this source code is governed by a BSD-style license 5 * that can be found in the LICENSE file in the root of the source 6 * tree. An additional intellectual property rights grant can be found 7 * in the file PATENTS. All contributing project authors may 8 * be found in the AUTHORS file in the root of the source tree. 9 */ 10 11 package org.webrtc; 12 13 import static org.hamcrest.Matchers.greaterThanOrEqualTo; 14 import static org.hamcrest.Matchers.lessThanOrEqualTo; 15 import static org.junit.Assert.assertEquals; 16 import static org.junit.Assert.assertThat; 17 18 import android.graphics.Matrix; 19 import android.opengl.GLES20; 20 import android.os.Handler; 21 import android.os.HandlerThread; 22 import android.support.test.filters.SmallTest; 23 import java.nio.ByteBuffer; 24 import java.util.ArrayList; 25 import java.util.Collections; 26 import java.util.List; 27 import org.chromium.base.test.params.BaseJUnit4RunnerDelegate; 28 import org.chromium.base.test.params.ParameterAnnotations.ClassParameter; 29 import org.chromium.base.test.params.ParameterAnnotations.UseRunnerDelegate; 30 import org.chromium.base.test.params.ParameterSet; 31 import org.chromium.base.test.params.ParameterizedRunner; 32 import org.junit.BeforeClass; 33 import org.junit.Test; 34 import org.junit.runner.RunWith; 35 36 /** 37 * Test VideoFrame buffers of different kind of formats: I420, RGB, OES, NV12, NV21, and verify 38 * toI420() and cropAndScale() behavior. Creating RGB/OES textures involves VideoFrameDrawer and 39 * GlRectDrawer and we are testing the full chain I420 -> OES/RGB texture -> I420, with and without 40 * cropping in the middle. Reading textures back to I420 also exercises the YuvConverter code. 41 */ 42 @RunWith(ParameterizedRunner.class) 43 @UseRunnerDelegate(BaseJUnit4RunnerDelegate.class) 44 public class VideoFrameBufferTest { 45 /** 46 * These tests are parameterized on this enum which represents the different VideoFrame.Buffers. 47 */ 48 private static enum BufferType { I420, RGB_TEXTURE, OES_TEXTURE, NV21, NV12 } 49 50 @ClassParameter private static List<ParameterSet> CLASS_PARAMS = new ArrayList<>(); 51 52 static { 53 for (BufferType bufferType : BufferType.values()) { 54 // The parameterization framework can not send Enums, so we pass it as a string instead. CLASS_PARAMS.add(new ParameterSet().value(bufferType.name()).name(bufferType.name()))55 CLASS_PARAMS.add(new ParameterSet().value(bufferType.name()).name(bufferType.name())); 56 } 57 } 58 59 @BeforeClass setUp()60 public static void setUp() { 61 // Needed for JniCommon.nativeAllocateByteBuffer() to work, which is used from JavaI420Buffer. 62 System.loadLibrary(TestConstants.NATIVE_LIBRARY); 63 } 64 65 private final BufferType bufferType; 66 VideoFrameBufferTest(String bufferTypeName)67 public VideoFrameBufferTest(String bufferTypeName) { 68 // Parse the string back to an enum. 69 bufferType = BufferType.valueOf(bufferTypeName); 70 } 71 72 /** 73 * Create a VideoFrame.Buffer of the given type with the same pixel content as the given I420 74 * buffer. 75 */ createBufferWithType( BufferType bufferType, VideoFrame.I420Buffer i420Buffer)76 private static VideoFrame.Buffer createBufferWithType( 77 BufferType bufferType, VideoFrame.I420Buffer i420Buffer) { 78 switch (bufferType) { 79 case I420: 80 return i420Buffer.toI420(); 81 case RGB_TEXTURE: 82 return createRgbTextureBuffer(/* eglContext= */ null, i420Buffer); 83 case OES_TEXTURE: 84 return createOesTextureBuffer(/* eglContext= */ null, i420Buffer); 85 case NV21: 86 return createNV21Buffer(i420Buffer); 87 case NV12: 88 return createNV12Buffer(i420Buffer); 89 default: 90 throw new IllegalArgumentException("Unknown buffer type: " + bufferType); 91 } 92 } 93 createBufferToTest(VideoFrame.I420Buffer i420Buffer)94 private VideoFrame.Buffer createBufferToTest(VideoFrame.I420Buffer i420Buffer) { 95 return createBufferWithType(this.bufferType, i420Buffer); 96 } 97 98 /** 99 * Creates a 16x16 I420 buffer that varies smoothly and spans all RGB values. 100 */ createTestI420Buffer()101 public static VideoFrame.I420Buffer createTestI420Buffer() { 102 final int width = 16; 103 final int height = 16; 104 final int[] yData = new int[] {156, 162, 167, 172, 177, 182, 187, 193, 199, 203, 209, 214, 219, 105 224, 229, 235, 147, 152, 157, 162, 168, 173, 178, 183, 188, 193, 199, 205, 210, 215, 220, 106 225, 138, 143, 148, 153, 158, 163, 168, 174, 180, 184, 190, 195, 200, 205, 211, 216, 128, 107 133, 138, 144, 149, 154, 159, 165, 170, 175, 181, 186, 191, 196, 201, 206, 119, 124, 129, 108 134, 140, 145, 150, 156, 161, 166, 171, 176, 181, 187, 192, 197, 109, 114, 119, 126, 130, 109 136, 141, 146, 151, 156, 162, 167, 172, 177, 182, 187, 101, 105, 111, 116, 121, 126, 132, 110 137, 142, 147, 152, 157, 162, 168, 173, 178, 90, 96, 101, 107, 112, 117, 122, 127, 132, 138, 111 143, 148, 153, 158, 163, 168, 82, 87, 92, 97, 102, 107, 113, 118, 123, 128, 133, 138, 144, 112 149, 154, 159, 72, 77, 83, 88, 93, 98, 103, 108, 113, 119, 124, 129, 134, 139, 144, 150, 63, 113 68, 73, 78, 83, 89, 94, 99, 104, 109, 114, 119, 125, 130, 135, 140, 53, 58, 64, 69, 74, 79, 114 84, 89, 95, 100, 105, 110, 115, 120, 126, 131, 44, 49, 54, 59, 64, 70, 75, 80, 85, 90, 95, 115 101, 106, 111, 116, 121, 34, 40, 45, 50, 55, 60, 65, 71, 76, 81, 86, 91, 96, 101, 107, 113, 116 25, 30, 35, 40, 46, 51, 56, 61, 66, 71, 77, 82, 87, 92, 98, 103, 16, 21, 26, 31, 36, 41, 46, 117 52, 57, 62, 67, 72, 77, 83, 89, 94}; 118 final int[] uData = new int[] {110, 113, 116, 118, 120, 123, 125, 128, 113, 116, 118, 120, 123, 119 125, 128, 130, 116, 118, 120, 123, 125, 128, 130, 132, 118, 120, 123, 125, 128, 130, 132, 120 135, 120, 123, 125, 128, 130, 132, 135, 138, 123, 125, 128, 130, 132, 135, 138, 139, 125, 121 128, 130, 132, 135, 138, 139, 142, 128, 130, 132, 135, 138, 139, 142, 145}; 122 final int[] vData = new int[] {31, 45, 59, 73, 87, 100, 114, 127, 45, 59, 73, 87, 100, 114, 128, 123 141, 59, 73, 87, 100, 114, 127, 141, 155, 73, 87, 100, 114, 127, 141, 155, 168, 87, 100, 124 114, 128, 141, 155, 168, 182, 100, 114, 128, 141, 155, 168, 182, 197, 114, 127, 141, 155, 125 168, 182, 196, 210, 127, 141, 155, 168, 182, 196, 210, 224}; 126 return JavaI420Buffer.wrap(width, height, toByteBuffer(yData), 127 /* strideY= */ width, toByteBuffer(uData), /* strideU= */ width / 2, toByteBuffer(vData), 128 /* strideV= */ width / 2, 129 /* releaseCallback= */ null); 130 } 131 132 /** 133 * Create an RGB texture buffer available in |eglContext| with the same pixel content as the given 134 * I420 buffer. 135 */ createRgbTextureBuffer( EglBase.Context eglContext, VideoFrame.I420Buffer i420Buffer)136 public static VideoFrame.TextureBuffer createRgbTextureBuffer( 137 EglBase.Context eglContext, VideoFrame.I420Buffer i420Buffer) { 138 final int width = i420Buffer.getWidth(); 139 final int height = i420Buffer.getHeight(); 140 141 final HandlerThread renderThread = new HandlerThread("RGB texture thread"); 142 renderThread.start(); 143 final Handler renderThreadHandler = new Handler(renderThread.getLooper()); 144 return ThreadUtils.invokeAtFrontUninterruptibly(renderThreadHandler, () -> { 145 // Create EGL base with a pixel buffer as display output. 146 final EglBase eglBase = EglBase.create(eglContext, EglBase.CONFIG_PIXEL_BUFFER); 147 eglBase.createDummyPbufferSurface(); 148 eglBase.makeCurrent(); 149 150 final GlTextureFrameBuffer textureFrameBuffer = new GlTextureFrameBuffer(GLES20.GL_RGBA); 151 textureFrameBuffer.setSize(width, height); 152 153 GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, textureFrameBuffer.getFrameBufferId()); 154 drawI420Buffer(i420Buffer); 155 GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); 156 157 final YuvConverter yuvConverter = new YuvConverter(); 158 return new TextureBufferImpl(width, height, VideoFrame.TextureBuffer.Type.RGB, 159 textureFrameBuffer.getTextureId(), 160 /* transformMatrix= */ new Matrix(), renderThreadHandler, yuvConverter, 161 /* releaseCallback= */ () -> renderThreadHandler.post(() -> { 162 textureFrameBuffer.release(); 163 yuvConverter.release(); 164 eglBase.release(); 165 renderThread.quit(); 166 })); 167 }); 168 } 169 170 /** 171 * Create an OES texture buffer available in |eglContext| with the same pixel content as the given 172 * I420 buffer. 173 */ 174 public static VideoFrame.TextureBuffer createOesTextureBuffer( 175 EglBase.Context eglContext, VideoFrame.I420Buffer i420Buffer) { 176 final int width = i420Buffer.getWidth(); 177 final int height = i420Buffer.getHeight(); 178 179 // Create resources for generating OES textures. 180 final SurfaceTextureHelper surfaceTextureHelper = 181 SurfaceTextureHelper.create("SurfaceTextureHelper test", eglContext); 182 surfaceTextureHelper.setTextureSize(width, height); 183 184 final HandlerThread renderThread = new HandlerThread("OES texture thread"); 185 renderThread.start(); 186 final Handler renderThreadHandler = new Handler(renderThread.getLooper()); 187 final VideoFrame.TextureBuffer oesBuffer = 188 ThreadUtils.invokeAtFrontUninterruptibly(renderThreadHandler, () -> { 189 // Create EGL base with the SurfaceTexture as display output. 190 final EglBase eglBase = EglBase.create(eglContext, EglBase.CONFIG_PLAIN); 191 eglBase.createSurface(surfaceTextureHelper.getSurfaceTexture()); 192 eglBase.makeCurrent(); 193 assertEquals(width, eglBase.surfaceWidth()); 194 assertEquals(height, eglBase.surfaceHeight()); 195 196 final SurfaceTextureHelperTest.MockTextureListener listener = 197 new SurfaceTextureHelperTest.MockTextureListener(); 198 surfaceTextureHelper.startListening(listener); 199 200 // Draw the frame and block until an OES texture is delivered. 201 drawI420Buffer(i420Buffer); 202 eglBase.swapBuffers(); 203 final VideoFrame.TextureBuffer textureBuffer = listener.waitForTextureBuffer(); 204 surfaceTextureHelper.stopListening(); 205 surfaceTextureHelper.dispose(); 206 207 return textureBuffer; 208 }); 209 renderThread.quit(); 210 211 return oesBuffer; 212 } 213 214 /** Create an NV21Buffer with the same pixel content as the given I420 buffer. */ 215 public static NV21Buffer createNV21Buffer(VideoFrame.I420Buffer i420Buffer) { 216 final int width = i420Buffer.getWidth(); 217 final int height = i420Buffer.getHeight(); 218 final int chromaStride = width; 219 final int chromaWidth = (width + 1) / 2; 220 final int chromaHeight = (height + 1) / 2; 221 final int ySize = width * height; 222 223 final ByteBuffer nv21Buffer = ByteBuffer.allocateDirect(ySize + chromaStride * chromaHeight); 224 // We don't care what the array offset is since we only want an array that is direct. 225 @SuppressWarnings("ByteBufferBackingArray") final byte[] nv21Data = nv21Buffer.array(); 226 227 for (int y = 0; y < height; ++y) { 228 for (int x = 0; x < width; ++x) { 229 final byte yValue = i420Buffer.getDataY().get(y * i420Buffer.getStrideY() + x); 230 nv21Data[y * width + x] = yValue; 231 } 232 } 233 for (int y = 0; y < chromaHeight; ++y) { 234 for (int x = 0; x < chromaWidth; ++x) { 235 final byte uValue = i420Buffer.getDataU().get(y * i420Buffer.getStrideU() + x); 236 final byte vValue = i420Buffer.getDataV().get(y * i420Buffer.getStrideV() + x); 237 nv21Data[ySize + y * chromaStride + 2 * x + 0] = vValue; 238 nv21Data[ySize + y * chromaStride + 2 * x + 1] = uValue; 239 } 240 } 241 return new NV21Buffer(nv21Data, width, height, /* releaseCallback= */ null); 242 } 243 244 /** Create an NV12Buffer with the same pixel content as the given I420 buffer. */ 245 public static NV12Buffer createNV12Buffer(VideoFrame.I420Buffer i420Buffer) { 246 final int width = i420Buffer.getWidth(); 247 final int height = i420Buffer.getHeight(); 248 final int chromaStride = width; 249 final int chromaWidth = (width + 1) / 2; 250 final int chromaHeight = (height + 1) / 2; 251 final int ySize = width * height; 252 253 final ByteBuffer nv12Buffer = ByteBuffer.allocateDirect(ySize + chromaStride * chromaHeight); 254 255 for (int y = 0; y < height; ++y) { 256 for (int x = 0; x < width; ++x) { 257 final byte yValue = i420Buffer.getDataY().get(y * i420Buffer.getStrideY() + x); 258 nv12Buffer.put(y * width + x, yValue); 259 } 260 } 261 for (int y = 0; y < chromaHeight; ++y) { 262 for (int x = 0; x < chromaWidth; ++x) { 263 final byte uValue = i420Buffer.getDataU().get(y * i420Buffer.getStrideU() + x); 264 final byte vValue = i420Buffer.getDataV().get(y * i420Buffer.getStrideV() + x); 265 nv12Buffer.put(ySize + y * chromaStride + 2 * x + 0, uValue); 266 nv12Buffer.put(ySize + y * chromaStride + 2 * x + 1, vValue); 267 } 268 } 269 return new NV12Buffer(width, height, /* stride= */ width, /* sliceHeight= */ height, nv12Buffer, 270 /* releaseCallback */ null); 271 } 272 273 /** Print the ByteBuffer plane to the StringBuilder. */ 274 private static void printPlane( 275 StringBuilder stringBuilder, int width, int height, ByteBuffer plane, int stride) { 276 for (int y = 0; y < height; ++y) { 277 for (int x = 0; x < width; ++x) { 278 final int value = plane.get(y * stride + x) & 0xFF; 279 if (x != 0) { 280 stringBuilder.append(", "); 281 } 282 stringBuilder.append(value); 283 } 284 stringBuilder.append("\n"); 285 } 286 } 287 288 /** Convert the pixel content of an I420 buffer to a string representation. */ 289 private static String i420BufferToString(VideoFrame.I420Buffer buffer) { 290 final StringBuilder stringBuilder = new StringBuilder(); 291 stringBuilder.append( 292 "I420 buffer with size: " + buffer.getWidth() + "x" + buffer.getHeight() + ".\n"); 293 stringBuilder.append("Y-plane:\n"); 294 printPlane(stringBuilder, buffer.getWidth(), buffer.getHeight(), buffer.getDataY(), 295 buffer.getStrideY()); 296 final int chromaWidth = (buffer.getWidth() + 1) / 2; 297 final int chromaHeight = (buffer.getHeight() + 1) / 2; 298 stringBuilder.append("U-plane:\n"); 299 printPlane(stringBuilder, chromaWidth, chromaHeight, buffer.getDataU(), buffer.getStrideU()); 300 stringBuilder.append("V-plane:\n"); 301 printPlane(stringBuilder, chromaWidth, chromaHeight, buffer.getDataV(), buffer.getStrideV()); 302 return stringBuilder.toString(); 303 } 304 305 /** 306 * Assert that the given I420 buffers are almost identical, allowing for some difference due to 307 * numerical errors. It has limits for both overall PSNR and maximum individual pixel difference. 308 */ 309 public static void assertAlmostEqualI420Buffers( 310 VideoFrame.I420Buffer bufferA, VideoFrame.I420Buffer bufferB) { 311 final int diff = maxDiff(bufferA, bufferB); 312 assertThat("Pixel difference too high: " + diff + "." 313 + "\nBuffer A: " + i420BufferToString(bufferA) 314 + "Buffer B: " + i420BufferToString(bufferB), 315 diff, lessThanOrEqualTo(4)); 316 final double psnr = calculatePsnr(bufferA, bufferB); 317 assertThat("PSNR too low: " + psnr + "." 318 + "\nBuffer A: " + i420BufferToString(bufferA) 319 + "Buffer B: " + i420BufferToString(bufferB), 320 psnr, greaterThanOrEqualTo(50.0)); 321 } 322 323 /** Returns a flattened list of pixel differences for two ByteBuffer planes. */ 324 private static List<Integer> getPixelDiffs( 325 int width, int height, ByteBuffer planeA, int strideA, ByteBuffer planeB, int strideB) { 326 List<Integer> res = new ArrayList<>(); 327 for (int y = 0; y < height; ++y) { 328 for (int x = 0; x < width; ++x) { 329 final int valueA = planeA.get(y * strideA + x) & 0xFF; 330 final int valueB = planeB.get(y * strideB + x) & 0xFF; 331 res.add(Math.abs(valueA - valueB)); 332 } 333 } 334 return res; 335 } 336 337 /** Returns a flattened list of pixel differences for two I420 buffers. */ 338 private static List<Integer> getPixelDiffs( 339 VideoFrame.I420Buffer bufferA, VideoFrame.I420Buffer bufferB) { 340 assertEquals(bufferA.getWidth(), bufferB.getWidth()); 341 assertEquals(bufferA.getHeight(), bufferB.getHeight()); 342 final int width = bufferA.getWidth(); 343 final int height = bufferA.getHeight(); 344 final int chromaWidth = (width + 1) / 2; 345 final int chromaHeight = (height + 1) / 2; 346 final List<Integer> diffs = getPixelDiffs(width, height, bufferA.getDataY(), 347 bufferA.getStrideY(), bufferB.getDataY(), bufferB.getStrideY()); 348 diffs.addAll(getPixelDiffs(chromaWidth, chromaHeight, bufferA.getDataU(), bufferA.getStrideU(), 349 bufferB.getDataU(), bufferB.getStrideU())); 350 diffs.addAll(getPixelDiffs(chromaWidth, chromaHeight, bufferA.getDataV(), bufferA.getStrideV(), 351 bufferB.getDataV(), bufferB.getStrideV())); 352 return diffs; 353 } 354 355 /** Returns the maximum pixel difference from any of the Y/U/V planes in the given buffers. */ 356 private static int maxDiff(VideoFrame.I420Buffer bufferA, VideoFrame.I420Buffer bufferB) { 357 return Collections.max(getPixelDiffs(bufferA, bufferB)); 358 } 359 360 /** 361 * Returns the PSNR given a sum of squared error and the number of measurements that were added. 362 */ 363 private static double sseToPsnr(long sse, int count) { 364 if (sse == 0) { 365 return Double.POSITIVE_INFINITY; 366 } 367 final double meanSquaredError = (double) sse / (double) count; 368 final double maxPixelValue = 255.0; 369 return 10.0 * Math.log10(maxPixelValue * maxPixelValue / meanSquaredError); 370 } 371 372 /** Returns the PSNR of the given I420 buffers. */ 373 private static double calculatePsnr( 374 VideoFrame.I420Buffer bufferA, VideoFrame.I420Buffer bufferB) { 375 final List<Integer> pixelDiffs = getPixelDiffs(bufferA, bufferB); 376 long sse = 0; 377 for (int pixelDiff : pixelDiffs) { 378 sse += pixelDiff * pixelDiff; 379 } 380 return sseToPsnr(sse, pixelDiffs.size()); 381 } 382 383 /** 384 * Convert an int array to a byte array and make sure the values are within the range [0, 255]. 385 */ 386 private static byte[] toByteArray(int[] array) { 387 final byte[] res = new byte[array.length]; 388 for (int i = 0; i < array.length; ++i) { 389 final int value = array[i]; 390 assertThat(value, greaterThanOrEqualTo(0)); 391 assertThat(value, lessThanOrEqualTo(255)); 392 res[i] = (byte) value; 393 } 394 return res; 395 } 396 397 /** Convert a byte array to a direct ByteBuffer. */ 398 private static ByteBuffer toByteBuffer(int[] array) { 399 final ByteBuffer buffer = ByteBuffer.allocateDirect(array.length); 400 buffer.put(toByteArray(array)); 401 buffer.rewind(); 402 return buffer; 403 } 404 405 /** 406 * Draw an I420 buffer on the currently bound frame buffer, allocating and releasing any 407 * resources necessary. 408 */ 409 private static void drawI420Buffer(VideoFrame.I420Buffer i420Buffer) { 410 final GlRectDrawer drawer = new GlRectDrawer(); 411 final VideoFrameDrawer videoFrameDrawer = new VideoFrameDrawer(); 412 videoFrameDrawer.drawFrame( 413 new VideoFrame(i420Buffer, /* rotation= */ 0, /* timestampNs= */ 0), drawer); 414 videoFrameDrawer.release(); 415 drawer.release(); 416 } 417 418 /** 419 * Helper function that tests cropAndScale() with the given cropping and scaling parameters, and 420 * compares the pixel content against a reference I420 buffer. 421 */ 422 private void testCropAndScale( 423 int cropX, int cropY, int cropWidth, int cropHeight, int scaleWidth, int scaleHeight) { 424 final VideoFrame.I420Buffer referenceI420Buffer = createTestI420Buffer(); 425 final VideoFrame.Buffer bufferToTest = createBufferToTest(referenceI420Buffer); 426 427 final VideoFrame.Buffer croppedReferenceBuffer = referenceI420Buffer.cropAndScale( 428 cropX, cropY, cropWidth, cropHeight, scaleWidth, scaleHeight); 429 referenceI420Buffer.release(); 430 final VideoFrame.I420Buffer croppedReferenceI420Buffer = croppedReferenceBuffer.toI420(); 431 croppedReferenceBuffer.release(); 432 433 final VideoFrame.Buffer croppedBufferToTest = 434 bufferToTest.cropAndScale(cropX, cropY, cropWidth, cropHeight, scaleWidth, scaleHeight); 435 bufferToTest.release(); 436 437 final VideoFrame.I420Buffer croppedOutputI420Buffer = croppedBufferToTest.toI420(); 438 croppedBufferToTest.release(); 439 440 assertAlmostEqualI420Buffers(croppedReferenceI420Buffer, croppedOutputI420Buffer); 441 croppedReferenceI420Buffer.release(); 442 croppedOutputI420Buffer.release(); 443 } 444 445 @Test 446 @SmallTest 447 /** Test calling toI420() and comparing pixel content against I420 reference. */ 448 public void testToI420() { 449 final VideoFrame.I420Buffer referenceI420Buffer = createTestI420Buffer(); 450 final VideoFrame.Buffer bufferToTest = createBufferToTest(referenceI420Buffer); 451 452 final VideoFrame.I420Buffer outputI420Buffer = bufferToTest.toI420(); 453 bufferToTest.release(); 454 455 assertAlmostEqualI420Buffers(referenceI420Buffer, outputI420Buffer); 456 referenceI420Buffer.release(); 457 outputI420Buffer.release(); 458 } 459 460 @Test 461 @SmallTest 462 /** Pure 2x scaling with no cropping. */ 463 public void testScale2x() { 464 testCropAndScale(0 /* cropX= */, 0 /* cropY= */, /* cropWidth= */ 16, /* cropHeight= */ 16, 465 /* scaleWidth= */ 8, /* scaleHeight= */ 8); 466 } 467 468 @Test 469 @SmallTest 470 /** Test cropping only X direction, with no scaling. */ 471 public void testCropX() { 472 testCropAndScale(8 /* cropX= */, 0 /* cropY= */, /* cropWidth= */ 8, /* cropHeight= */ 16, 473 /* scaleWidth= */ 8, /* scaleHeight= */ 16); 474 } 475 476 @Test 477 @SmallTest 478 /** Test cropping only Y direction, with no scaling. */ 479 public void testCropY() { 480 testCropAndScale(0 /* cropX= */, 8 /* cropY= */, /* cropWidth= */ 16, /* cropHeight= */ 8, 481 /* scaleWidth= */ 16, /* scaleHeight= */ 8); 482 } 483 484 @Test 485 @SmallTest 486 /** Test center crop, with no scaling. */ 487 public void testCenterCrop() { 488 testCropAndScale(4 /* cropX= */, 4 /* cropY= */, /* cropWidth= */ 8, /* cropHeight= */ 8, 489 /* scaleWidth= */ 8, /* scaleHeight= */ 8); 490 } 491 492 @Test 493 @SmallTest 494 /** Test non-center crop for right bottom corner, with no scaling. */ 495 public void testRightBottomCornerCrop() { 496 testCropAndScale(8 /* cropX= */, 8 /* cropY= */, /* cropWidth= */ 8, /* cropHeight= */ 8, 497 /* scaleWidth= */ 8, /* scaleHeight= */ 8); 498 } 499 500 @Test 501 @SmallTest 502 /** Test combined cropping and scaling. */ 503 public void testCropAndScale() { 504 testCropAndScale(4 /* cropX= */, 4 /* cropY= */, /* cropWidth= */ 12, /* cropHeight= */ 12, 505 /* scaleWidth= */ 8, /* scaleHeight= */ 8); 506 } 507 } 508