From a8aa921c4614f9d6a0e8f3459648ca1ae75cdbe6 Mon Sep 17 00:00:00 2001 From: Robert Bares Date: Sun, 15 Apr 2018 22:47:32 +0100 Subject: [PATCH] Allow negative height when ConvertToI420/ARGB is called with NV12/NV21 ConvertToI420 and ConvertToARGB support the use of a negative height parameter to flip the image vertically. When converting from NV12 or NV21 this parameter was misinterpreted, resulting in invalid output. This CL introduces the use of abs_src_height to correctly calculate the location of the source UV plane. The sign of crop_height is not used, to reduce confusion ConvertToI420 and ConvertToARGB no longer accept negative crop height. Unit tests for Android420ToI420 are updated to fix miscalculation of src_stride_uv, fix incorrect pixel strides, and to test inversion. New unit tests are included to test inversion for ConvertToARGB, ConvertToI420, Android420ToARGB, and Android420ToABGR. For consistency the test NV12Crop is renamed ConvertToI420_NV12_Crop. Bug: libyuv:446 Test: out/Release/libyuv_unittest --gtest_filter=*.ConvertTo*:*.Android420To* Change-Id: Idc98e62671cb30272cfa7e24fafbc8b73712f7c6 Reviewed-on: https://chromium-review.googlesource.com/994074 Commit-Queue: Frank Barchard Reviewed-by: Frank Barchard --- AUTHORS | 2 + source/convert_to_argb.cc | 18 +- source/convert_to_i420.cc | 19 +- unit_test/convert_test.cc | 420 +++++++++++++++++++++++++++----------- 4 files changed, 321 insertions(+), 138 deletions(-) diff --git a/AUTHORS b/AUTHORS index 9686ac13e..dcf8394b6 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,4 +1,6 @@ # Names should be added to this file like so: # Name or Organization +Robert Bares + Google Inc. diff --git a/source/convert_to_argb.cc b/source/convert_to_argb.cc index 677e5d56f..a884dcc41 100644 --- a/source/convert_to_argb.cc +++ b/source/convert_to_argb.cc @@ -46,7 +46,7 @@ int ConvertToARGB(const uint8_t* sample, const uint8_t* src; const uint8_t* src_uv; int abs_src_height = (src_height < 0) ? -src_height : src_height; - int inv_crop_height = (crop_height < 0) ? -crop_height : crop_height; + int inv_crop_height; int r = 0; // One pass rotation is available for some formats. For the rest, convert @@ -59,18 +59,16 @@ int ConvertToARGB(const uint8_t* sample, uint8_t* dest_argb = dst_argb; int dest_dst_stride_argb = dst_stride_argb; uint8_t* rotate_buffer = NULL; - int abs_crop_height = (crop_height < 0) ? -crop_height : crop_height; if (dst_argb == NULL || sample == NULL || src_width <= 0 || crop_width <= 0 || - src_height == 0 || crop_height == 0) { + src_height == 0 || crop_height <= 0) { return -1; } - if (src_height < 0) { - inv_crop_height = -inv_crop_height; - } + + inv_crop_height = (src_height < 0) ? -crop_height : crop_height; if (need_buf) { - int argb_size = crop_width * 4 * abs_crop_height; + int argb_size = crop_width * 4 * crop_height; rotate_buffer = (uint8_t*)malloc(argb_size); /* NOLINT */ if (!rotate_buffer) { return 1; // Out of memory runtime error. @@ -147,13 +145,13 @@ int ConvertToARGB(const uint8_t* sample, // Biplanar formats case FOURCC_NV12: src = sample + (src_width * crop_y + crop_x); - src_uv = sample + aligned_src_width * (src_height + crop_y / 2) + crop_x; + src_uv = sample + aligned_src_width * (abs_src_height + crop_y / 2) + crop_x; r = NV12ToARGB(src, src_width, src_uv, aligned_src_width, dst_argb, dst_stride_argb, crop_width, inv_crop_height); break; case FOURCC_NV21: src = sample + (src_width * crop_y + crop_x); - src_uv = sample + aligned_src_width * (src_height + crop_y / 2) + crop_x; + src_uv = sample + aligned_src_width * (abs_src_height + crop_y / 2) + crop_x; // Call NV12 but with u and v parameters swapped. r = NV21ToARGB(src, src_width, src_uv, aligned_src_width, dst_argb, dst_stride_argb, crop_width, inv_crop_height); @@ -252,7 +250,7 @@ int ConvertToARGB(const uint8_t* sample, if (need_buf) { if (!r) { r = ARGBRotate(dst_argb, dst_stride_argb, dest_argb, dest_dst_stride_argb, - crop_width, abs_crop_height, rotation); + crop_width, crop_height, rotation); } free(rotate_buffer); } else if (rotation) { diff --git a/source/convert_to_i420.cc b/source/convert_to_i420.cc index 1bed9d644..094dc6345 100644 --- a/source/convert_to_i420.cc +++ b/source/convert_to_i420.cc @@ -46,8 +46,6 @@ int ConvertToI420(const uint8_t* sample, const uint8_t* src; const uint8_t* src_uv; const int abs_src_height = (src_height < 0) ? -src_height : src_height; - // TODO(nisse): Why allow crop_height < 0? - const int abs_crop_height = (crop_height < 0) ? -crop_height : crop_height; int r = 0; LIBYUV_BOOL need_buf = (rotation && format != FOURCC_I420 && format != FOURCC_NV12 && @@ -60,22 +58,23 @@ int ConvertToI420(const uint8_t* sample, int tmp_u_stride = dst_stride_u; int tmp_v_stride = dst_stride_v; uint8_t* rotate_buffer = NULL; - const int inv_crop_height = - (src_height < 0) ? -abs_crop_height : abs_crop_height; + int inv_crop_height; if (!dst_y || !dst_u || !dst_v || !sample || src_width <= 0 || - crop_width <= 0 || src_height == 0 || crop_height == 0) { + crop_width <= 0 || src_height == 0 || crop_height <= 0) { return -1; } + inv_crop_height = (src_height < 0) ? -crop_height : crop_height; + // One pass rotation is available for some formats. For the rest, convert // to I420 (with optional vertical flipping) into a temporary I420 buffer, // and then rotate the I420 to the final destination buffer. // For in-place conversion, if destination dst_y is same as source sample, // also enable temporary buffer. if (need_buf) { - int y_size = crop_width * abs_crop_height; - int uv_size = ((crop_width + 1) / 2) * ((abs_crop_height + 1) / 2); + int y_size = crop_width * crop_height; + int uv_size = ((crop_width + 1) / 2) * ((crop_height + 1) / 2); rotate_buffer = (uint8_t*)malloc(y_size + uv_size * 2); /* NOLINT */ if (!rotate_buffer) { return 1; // Out of memory runtime error. @@ -163,7 +162,7 @@ int ConvertToI420(const uint8_t* sample, // Biplanar formats case FOURCC_NV12: src = sample + (src_width * crop_y + crop_x); - src_uv = sample + (src_width * src_height) + + src_uv = sample + (src_width * abs_src_height) + ((crop_y / 2) * aligned_src_width) + ((crop_x / 2) * 2); r = NV12ToI420Rotate(src, src_width, src_uv, aligned_src_width, dst_y, dst_stride_y, dst_u, dst_stride_u, dst_v, @@ -171,7 +170,7 @@ int ConvertToI420(const uint8_t* sample, break; case FOURCC_NV21: src = sample + (src_width * crop_y + crop_x); - src_uv = sample + (src_width * src_height) + + src_uv = sample + (src_width * abs_src_height) + ((crop_y / 2) * aligned_src_width) + ((crop_x / 2) * 2); // Call NV12 but with dst_u and dst_v parameters swapped. r = NV12ToI420Rotate(src, src_width, src_uv, aligned_src_width, dst_y, @@ -261,7 +260,7 @@ int ConvertToI420(const uint8_t* sample, if (!r) { r = I420Rotate(dst_y, dst_stride_y, dst_u, dst_stride_u, dst_v, dst_stride_v, tmp_y, tmp_y_stride, tmp_u, tmp_u_stride, - tmp_v, tmp_v_stride, crop_width, abs_crop_height, + tmp_v, tmp_v_stride, crop_width, crop_height, rotation); } free(rotate_buffer); diff --git a/unit_test/convert_test.cc b/unit_test/convert_test.cc index e11b101fc..cd1ab348e 100644 --- a/unit_test/convert_test.cc +++ b/unit_test/convert_test.cc @@ -156,31 +156,34 @@ TESTPLANARTOP(H010, uint16_t, 2, 2, 2, H010, uint16_t, 2, 2, 2) TESTPLANARTOP(H010, uint16_t, 2, 2, 2, H420, uint8_t, 1, 2, 2) TESTPLANARTOP(H420, uint8_t, 1, 2, 2, H010, uint16_t, 2, 2, 2) -// Test Android 420 to I420 +// Test Android 420 to I420. #define TESTAPLANARTOPI(SRC_FMT_PLANAR, PIXEL_STRIDE, SRC_SUBSAMP_X, \ SRC_SUBSAMP_Y, FMT_PLANAR, SUBSAMP_X, SUBSAMP_Y, \ - W1280, N, NEG, OFF, PN, OFF_U, OFF_V) \ + W1280, N, NEG, OFF, PN, OFF_U, OFF_V, BASELINE) \ TEST_F(LibYUVConvertTest, SRC_FMT_PLANAR##To##FMT_PLANAR##_##PN##N) { \ const int kWidth = ((W1280) > 0) ? (W1280) : 1; \ const int kHeight = benchmark_height_; \ - const int kSizeUV = \ - SUBSAMPLE(kWidth, SRC_SUBSAMP_X) * SUBSAMPLE(kHeight, SRC_SUBSAMP_Y); \ - align_buffer_page_end(src_y, kWidth* kHeight + OFF); \ + const int kStrideUVSrc = SUBSAMPLE(kWidth, SRC_SUBSAMP_X); \ + const int kStrideUVDst = SUBSAMPLE(kWidth, SUBSAMP_X); \ + const int kSizeUVSrc = \ + kStrideUVSrc * SUBSAMPLE(kHeight, SRC_SUBSAMP_Y); \ + const int kSizeUVDst = \ + kStrideUVDst * SUBSAMPLE(kHeight, SUBSAMP_Y); \ + align_buffer_page_end(src_y, kWidth * kHeight + OFF); \ align_buffer_page_end(src_uv, \ - kSizeUV*((PIXEL_STRIDE == 3) ? 3 : 2) + OFF); \ - align_buffer_page_end(dst_y_c, kWidth* kHeight); \ - align_buffer_page_end(dst_u_c, SUBSAMPLE(kWidth, SUBSAMP_X) * \ - SUBSAMPLE(kHeight, SUBSAMP_Y)); \ - align_buffer_page_end(dst_v_c, SUBSAMPLE(kWidth, SUBSAMP_X) * \ - SUBSAMPLE(kHeight, SUBSAMP_Y)); \ - align_buffer_page_end(dst_y_opt, kWidth* kHeight); \ - align_buffer_page_end(dst_u_opt, SUBSAMPLE(kWidth, SUBSAMP_X) * \ - SUBSAMPLE(kHeight, SUBSAMP_Y)); \ - align_buffer_page_end(dst_v_opt, SUBSAMPLE(kWidth, SUBSAMP_X) * \ - SUBSAMPLE(kHeight, SUBSAMP_Y)); \ + kSizeUVSrc*((PIXEL_STRIDE == 3) ? 3 : 2) + OFF); \ + align_buffer_page_end(dst_y_c, kWidth * kHeight); \ + align_buffer_page_end(dst_u_c, kSizeUVDst); \ + align_buffer_page_end(dst_v_c, kSizeUVDst); \ + align_buffer_page_end(dst_y_baseline, kWidth * kHeight); \ + align_buffer_page_end(dst_u_baseline, kSizeUVDst); \ + align_buffer_page_end(dst_v_baseline, kSizeUVDst); \ + align_buffer_page_end(dst_y_opt, kWidth * kHeight); \ + align_buffer_page_end(dst_u_opt, kSizeUVDst); \ + align_buffer_page_end(dst_v_opt, kSizeUVDst); \ uint8_t* src_u = src_uv + OFF_U; \ - uint8_t* src_v = src_uv + (PIXEL_STRIDE == 1 ? kSizeUV : OFF_V); \ - int src_stride_uv = SUBSAMPLE(kWidth, SUBSAMP_X) * PIXEL_STRIDE; \ + uint8_t* src_v = src_uv + (PIXEL_STRIDE == 1 ? kSizeUVSrc : OFF_V); \ + int src_stride_uv = kStrideUVSrc * PIXEL_STRIDE; \ for (int i = 0; i < kHeight; ++i) \ for (int j = 0; j < kWidth; ++j) \ src_y[i * kWidth + j + OFF] = (fastrand() & 0xff); \ @@ -192,31 +195,68 @@ TESTPLANARTOP(H420, uint8_t, 1, 2, 2, H010, uint16_t, 2, 2, 2) (fastrand() & 0xff); \ } \ } \ - memset(dst_y_c, 1, kWidth* kHeight); \ - memset(dst_u_c, 2, \ - SUBSAMPLE(kWidth, SUBSAMP_X) * SUBSAMPLE(kHeight, SUBSAMP_Y)); \ - memset(dst_v_c, 3, \ - SUBSAMPLE(kWidth, SUBSAMP_X) * SUBSAMPLE(kHeight, SUBSAMP_Y)); \ - memset(dst_y_opt, 101, kWidth* kHeight); \ - memset(dst_u_opt, 102, \ - SUBSAMPLE(kWidth, SUBSAMP_X) * SUBSAMPLE(kHeight, SUBSAMP_Y)); \ - memset(dst_v_opt, 103, \ - SUBSAMPLE(kWidth, SUBSAMP_X) * SUBSAMPLE(kHeight, SUBSAMP_Y)); \ + memset(dst_y_c, 1, kWidth * kHeight); \ + memset(dst_u_c, 2, kSizeUVDst); \ + memset(dst_v_c, 3, kSizeUVDst); \ + memset(dst_y_baseline, 51, kWidth * kHeight); \ + memset(dst_u_baseline, 52, kSizeUVDst); \ + memset(dst_v_baseline, 53, kSizeUVDst); \ + memset(dst_y_opt, 101, kWidth * kHeight); \ + memset(dst_u_opt, 102, kSizeUVDst); \ + memset(dst_v_opt, 103, kSizeUVDst); \ MaskCpuFlags(disable_cpu_flags_); \ SRC_FMT_PLANAR##To##FMT_PLANAR( \ - src_y + OFF, kWidth, src_u + OFF, SUBSAMPLE(kWidth, SRC_SUBSAMP_X), \ - src_v + OFF, SUBSAMPLE(kWidth, SRC_SUBSAMP_X), PIXEL_STRIDE, dst_y_c, \ - kWidth, dst_u_c, SUBSAMPLE(kWidth, SUBSAMP_X), dst_v_c, \ - SUBSAMPLE(kWidth, SUBSAMP_X), kWidth, NEG kHeight); \ + src_y + OFF, kWidth, src_u + OFF, src_stride_uv, \ + src_v + OFF, src_stride_uv, PIXEL_STRIDE, dst_y_c, \ + kWidth, dst_u_c, kStrideUVDst, dst_v_c, \ + kStrideUVDst, kWidth, NEG kHeight); \ + BASELINE(src_y + OFF, kWidth, src_uv + OFF, src_stride_uv, \ + dst_y_baseline, kWidth, dst_u_baseline, \ + kStrideUVDst, dst_v_baseline, \ + kStrideUVDst, kWidth, NEG kHeight); \ MaskCpuFlags(benchmark_cpu_info_); \ for (int i = 0; i < benchmark_iterations_; ++i) { \ SRC_FMT_PLANAR##To##FMT_PLANAR( \ - src_y + OFF, kWidth, src_u + OFF, SUBSAMPLE(kWidth, SRC_SUBSAMP_X), \ - src_v + OFF, SUBSAMPLE(kWidth, SRC_SUBSAMP_X), PIXEL_STRIDE, \ - dst_y_opt, kWidth, dst_u_opt, SUBSAMPLE(kWidth, SUBSAMP_X), \ - dst_v_opt, SUBSAMPLE(kWidth, SUBSAMP_X), kWidth, NEG kHeight); \ + src_y + OFF, kWidth, src_u + OFF, src_stride_uv, \ + src_v + OFF, src_stride_uv, PIXEL_STRIDE, \ + dst_y_opt, kWidth, dst_u_opt, kStrideUVDst, \ + dst_v_opt, kStrideUVDst, kWidth, NEG kHeight); \ } \ int max_diff = 0; \ + for (int i = 0; i < kHeight; ++i) { \ + for (int j = 0; j < kWidth; ++j) { \ + int abs_diff = abs(static_cast(dst_y_c[i * kWidth + j]) - \ + static_cast(dst_y_baseline[i * kWidth + j])); \ + if (abs_diff > max_diff) { \ + max_diff = abs_diff; \ + } \ + } \ + } \ + EXPECT_EQ(0, max_diff); \ + for (int i = 0; i < SUBSAMPLE(kHeight, SUBSAMP_Y); ++i) { \ + for (int j = 0; j < SUBSAMPLE(kWidth, SUBSAMP_X); ++j) { \ + int abs_diff = abs( \ + static_cast(dst_u_c[i * SUBSAMPLE(kWidth, SUBSAMP_X) + j]) - \ + static_cast( \ + dst_u_baseline[i * SUBSAMPLE(kWidth, SUBSAMP_X) + j])); \ + if (abs_diff > max_diff) { \ + max_diff = abs_diff; \ + } \ + } \ + } \ + EXPECT_EQ(0, max_diff); \ + for (int i = 0; i < SUBSAMPLE(kHeight, SUBSAMP_Y); ++i) { \ + for (int j = 0; j < SUBSAMPLE(kWidth, SUBSAMP_X); ++j) { \ + int abs_diff = abs( \ + static_cast(dst_v_c[i * SUBSAMPLE(kWidth, SUBSAMP_X) + j]) - \ + static_cast( \ + dst_v_baseline[i * SUBSAMPLE(kWidth, SUBSAMP_X) + j])); \ + if (abs_diff > max_diff) { \ + max_diff = abs_diff; \ + } \ + } \ + } \ + EXPECT_EQ(0, max_diff); \ for (int i = 0; i < kHeight; ++i) { \ for (int j = 0; j < kWidth; ++j) { \ int abs_diff = abs(static_cast(dst_y_c[i * kWidth + j]) - \ @@ -254,6 +294,9 @@ TESTPLANARTOP(H420, uint8_t, 1, 2, 2, H010, uint16_t, 2, 2, 2) free_aligned_buffer_page_end(dst_y_c); \ free_aligned_buffer_page_end(dst_u_c); \ free_aligned_buffer_page_end(dst_v_c); \ + free_aligned_buffer_page_end(dst_y_baseline); \ + free_aligned_buffer_page_end(dst_u_baseline); \ + free_aligned_buffer_page_end(dst_v_baseline); \ free_aligned_buffer_page_end(dst_y_opt); \ free_aligned_buffer_page_end(dst_u_opt); \ free_aligned_buffer_page_end(dst_v_opt); \ @@ -263,23 +306,33 @@ TESTPLANARTOP(H420, uint8_t, 1, 2, 2, H010, uint16_t, 2, 2, 2) #define TESTAPLANARTOP(SRC_FMT_PLANAR, PN, PIXEL_STRIDE, OFF_U, OFF_V, \ SRC_SUBSAMP_X, SRC_SUBSAMP_Y, FMT_PLANAR, SUBSAMP_X, \ - SUBSAMP_Y) \ + SUBSAMP_Y, BASELINE) \ TESTAPLANARTOPI(SRC_FMT_PLANAR, PIXEL_STRIDE, SRC_SUBSAMP_X, SRC_SUBSAMP_Y, \ FMT_PLANAR, SUBSAMP_X, SUBSAMP_Y, benchmark_width_ - 4, \ - _Any, +, 0, PN, OFF_U, OFF_V) \ + _Any, +, 0, PN, OFF_U, OFF_V, BASELINE) \ TESTAPLANARTOPI(SRC_FMT_PLANAR, PIXEL_STRIDE, SRC_SUBSAMP_X, SRC_SUBSAMP_Y, \ FMT_PLANAR, SUBSAMP_X, SUBSAMP_Y, benchmark_width_, \ - _Unaligned, +, 1, PN, OFF_U, OFF_V) \ + _Unaligned, +, 1, PN, OFF_U, OFF_V, BASELINE) \ TESTAPLANARTOPI(SRC_FMT_PLANAR, PIXEL_STRIDE, SRC_SUBSAMP_X, SRC_SUBSAMP_Y, \ FMT_PLANAR, SUBSAMP_X, SUBSAMP_Y, benchmark_width_, _Invert, \ - -, 0, PN, OFF_U, OFF_V) \ + -, 0, PN, OFF_U, OFF_V, BASELINE) \ TESTAPLANARTOPI(SRC_FMT_PLANAR, PIXEL_STRIDE, SRC_SUBSAMP_X, SRC_SUBSAMP_Y, \ FMT_PLANAR, SUBSAMP_X, SUBSAMP_Y, benchmark_width_, _Opt, +, \ - 0, PN, OFF_U, OFF_V) + 0, PN, OFF_U, OFF_V, BASELINE) -TESTAPLANARTOP(Android420, I420, 1, 0, 0, 2, 2, I420, 2, 2) -TESTAPLANARTOP(Android420, NV12, 2, 0, 1, 2, 2, I420, 2, 2) -TESTAPLANARTOP(Android420, NV21, 2, 1, 0, 2, 2, I420, 2, 2) +// I420ToI420 matching the NV12ToI420 signature, requires kSizeUVSrc in scope. +#define I420TOI420_I(src_y, src_stride_y, src_uv, src_stride_uv, \ + dst_y, dst_stride_y, dst_u, dst_stride_u, \ + dst_v, dst_stride_v, width, height) \ + I420ToI420(src_y, src_stride_y, src_uv, src_stride_uv, \ + src_uv + kSizeUVSrc, src_stride_uv, dst_y, dst_stride_y, \ + dst_u, dst_stride_u, dst_v, dst_stride_v, width, height) + +TESTAPLANARTOP(Android420, I420, 1, 0, 0, 2, 2, I420, 2, 2, I420TOI420_I) +TESTAPLANARTOP(Android420, NV12, 2, 0, 1, 2, 2, I420, 2, 2, NV12ToI420) +TESTAPLANARTOP(Android420, NV21, 2, 1, 0, 2, 2, I420, 2, 2, NV21ToI420) + +#undef I420TOI420_I #define TESTPLANARTOBPI(SRC_FMT_PLANAR, SRC_SUBSAMP_X, SRC_SUBSAMP_Y, \ FMT_PLANAR, SUBSAMP_X, SUBSAMP_Y, W1280, N, NEG, OFF) \ @@ -1424,85 +1477,216 @@ TEST_F(LibYUVConvertTest, MJPGToARGB) { #endif // HAVE_JPEG -TEST_F(LibYUVConvertTest, NV12Crop) { - const int SUBSAMP_X = 2; - const int SUBSAMP_Y = 2; - const int kWidth = benchmark_width_; - const int kHeight = benchmark_height_; - const int crop_y = - ((benchmark_height_ - (benchmark_height_ * 360 / 480)) / 2 + 1) & ~1; - const int kDestWidth = benchmark_width_; - const int kDestHeight = benchmark_height_ - crop_y * 2; - const int kStrideUV = SUBSAMPLE(kWidth, SUBSAMP_X); - const int sample_size = - kWidth * kHeight + kStrideUV * SUBSAMPLE(kHeight, SUBSAMP_Y) * 2; - align_buffer_page_end(src_y, sample_size); - uint8_t* src_uv = src_y + kWidth * kHeight; - - align_buffer_page_end(dst_y, kDestWidth * kDestHeight); - align_buffer_page_end(dst_u, SUBSAMPLE(kDestWidth, SUBSAMP_X) * - SUBSAMPLE(kDestHeight, SUBSAMP_Y)); - align_buffer_page_end(dst_v, SUBSAMPLE(kDestWidth, SUBSAMP_X) * - SUBSAMPLE(kDestHeight, SUBSAMP_Y)); - - align_buffer_page_end(dst_y_2, kDestWidth * kDestHeight); - align_buffer_page_end(dst_u_2, SUBSAMPLE(kDestWidth, SUBSAMP_X) * - SUBSAMPLE(kDestHeight, SUBSAMP_Y)); - align_buffer_page_end(dst_v_2, SUBSAMPLE(kDestWidth, SUBSAMP_X) * - SUBSAMPLE(kDestHeight, SUBSAMP_Y)); - - for (int i = 0; i < kHeight * kWidth; ++i) { - src_y[i] = (fastrand() & 0xff); +#define TESTTOI420(FMT, NEG, N) \ + TEST_F(LibYUVConvertTest, ConvertToI420_##FMT##N) { \ + const int SUBSAMP_X = 2; \ + const int SUBSAMP_Y = 2; \ + const int kWidth = benchmark_width_; \ + const int kHeight = benchmark_height_; \ + const int crop_y = \ + ((benchmark_height_ - (benchmark_height_ * 360 / 480)) / 2 + 1) & ~1; \ + const int kDestWidth = benchmark_width_; \ + const int kDestHeight = benchmark_height_ - crop_y * 2; \ + const int kStrideUV = SUBSAMPLE(kWidth, SUBSAMP_X); \ + const int sample_size = \ + kWidth * kHeight + kStrideUV * SUBSAMPLE(kHeight, SUBSAMP_Y) * 2; \ + align_buffer_page_end(src_y, sample_size); \ + uint8_t* src_uv = src_y + kWidth * kHeight; \ + \ + align_buffer_page_end(dst_y, kDestWidth * kDestHeight); \ + align_buffer_page_end(dst_u, SUBSAMPLE(kDestWidth, SUBSAMP_X) * \ + SUBSAMPLE(kDestHeight, SUBSAMP_Y)); \ + align_buffer_page_end(dst_v, SUBSAMPLE(kDestWidth, SUBSAMP_X) * \ + SUBSAMPLE(kDestHeight, SUBSAMP_Y)); \ + \ + align_buffer_page_end(dst_y_2, kDestWidth * kDestHeight); \ + align_buffer_page_end(dst_u_2, SUBSAMPLE(kDestWidth, SUBSAMP_X) * \ + SUBSAMPLE(kDestHeight, SUBSAMP_Y)); \ + align_buffer_page_end(dst_v_2, SUBSAMPLE(kDestWidth, SUBSAMP_X) * \ + SUBSAMPLE(kDestHeight, SUBSAMP_Y)); \ + \ + for (int i = 0; i < kHeight * kWidth; ++i) { \ + src_y[i] = (fastrand() & 0xff); \ + } \ + for (int i = 0; i < (SUBSAMPLE(kHeight, SUBSAMP_Y) * kStrideUV) * 2; ++i) { \ + src_uv[i] = (fastrand() & 0xff); \ + } \ + memset(dst_y, 1, kDestWidth * kDestHeight); \ + memset(dst_u, 2, \ + SUBSAMPLE(kDestWidth, SUBSAMP_X) * SUBSAMPLE(kDestHeight, SUBSAMP_Y)); \ + memset(dst_v, 3, \ + SUBSAMPLE(kDestWidth, SUBSAMP_X) * SUBSAMPLE(kDestHeight, SUBSAMP_Y)); \ + memset(dst_y_2, 1, kDestWidth * kDestHeight); \ + memset(dst_u_2, 2, \ + SUBSAMPLE(kDestWidth, SUBSAMP_X) * SUBSAMPLE(kDestHeight, SUBSAMP_Y)); \ + memset(dst_v_2, 3, \ + SUBSAMPLE(kDestWidth, SUBSAMP_X) * SUBSAMPLE(kDestHeight, SUBSAMP_Y)); \ + \ + ConvertToI420(src_y, sample_size, dst_y_2, kDestWidth, dst_u_2, \ + SUBSAMPLE(kDestWidth, SUBSAMP_X), dst_v_2, \ + SUBSAMPLE(kDestWidth, SUBSAMP_X), 0, crop_y, kWidth, NEG kHeight, \ + kDestWidth, kDestHeight, libyuv::kRotate0, libyuv::FOURCC_##FMT); \ + \ + FMT##ToI420(src_y + crop_y * kWidth, kWidth, \ + src_uv + (crop_y / 2) * kStrideUV * 2, kStrideUV * 2, dst_y, \ + kDestWidth, dst_u, SUBSAMPLE(kDestWidth, SUBSAMP_X), dst_v, \ + SUBSAMPLE(kDestWidth, SUBSAMP_X), kDestWidth, NEG kDestHeight); \ + \ + for (int i = 0; i < kDestHeight; ++i) { \ + for (int j = 0; j < kDestWidth; ++j) { \ + EXPECT_EQ(dst_y[i * kWidth + j], dst_y_2[i * kWidth + j]); \ + } \ + } \ + for (int i = 0; i < SUBSAMPLE(kDestHeight, SUBSAMP_Y); ++i) { \ + for (int j = 0; j < SUBSAMPLE(kDestWidth, SUBSAMP_X); ++j) { \ + EXPECT_EQ(dst_u[i * SUBSAMPLE(kDestWidth, SUBSAMP_X) + j], \ + dst_u_2[i * SUBSAMPLE(kDestWidth, SUBSAMP_X) + j]); \ + } \ + } \ + for (int i = 0; i < SUBSAMPLE(kDestHeight, SUBSAMP_Y); ++i) { \ + for (int j = 0; j < SUBSAMPLE(kDestWidth, SUBSAMP_X); ++j) { \ + EXPECT_EQ(dst_v[i * SUBSAMPLE(kDestWidth, SUBSAMP_X) + j], \ + dst_v_2[i * SUBSAMPLE(kDestWidth, SUBSAMP_X) + j]); \ + } \ + } \ + free_aligned_buffer_page_end(dst_y); \ + free_aligned_buffer_page_end(dst_u); \ + free_aligned_buffer_page_end(dst_v); \ + free_aligned_buffer_page_end(dst_y_2); \ + free_aligned_buffer_page_end(dst_u_2); \ + free_aligned_buffer_page_end(dst_v_2); \ + free_aligned_buffer_page_end(src_y); \ } - for (int i = 0; i < (SUBSAMPLE(kHeight, SUBSAMP_Y) * kStrideUV) * 2; ++i) { - src_uv[i] = (fastrand() & 0xff); - } - memset(dst_y, 1, kDestWidth * kDestHeight); - memset(dst_u, 2, - SUBSAMPLE(kDestWidth, SUBSAMP_X) * SUBSAMPLE(kDestHeight, SUBSAMP_Y)); - memset(dst_v, 3, - SUBSAMPLE(kDestWidth, SUBSAMP_X) * SUBSAMPLE(kDestHeight, SUBSAMP_Y)); - memset(dst_y_2, 1, kDestWidth * kDestHeight); - memset(dst_u_2, 2, - SUBSAMPLE(kDestWidth, SUBSAMP_X) * SUBSAMPLE(kDestHeight, SUBSAMP_Y)); - memset(dst_v_2, 3, - SUBSAMPLE(kDestWidth, SUBSAMP_X) * SUBSAMPLE(kDestHeight, SUBSAMP_Y)); - ConvertToI420(src_y, sample_size, dst_y_2, kDestWidth, dst_u_2, - SUBSAMPLE(kDestWidth, SUBSAMP_X), dst_v_2, - SUBSAMPLE(kDestWidth, SUBSAMP_X), 0, crop_y, kWidth, kHeight, - kDestWidth, kDestHeight, libyuv::kRotate0, libyuv::FOURCC_NV12); +TESTTOI420(NV12, +, _Crop) +TESTTOI420(NV12, -, _Crop_Invert) +TESTTOI420(NV21, +, _Crop) +TESTTOI420(NV21, -, _Crop_Invert) - NV12ToI420(src_y + crop_y * kWidth, kWidth, - src_uv + (crop_y / 2) * kStrideUV * 2, kStrideUV * 2, dst_y, - kDestWidth, dst_u, SUBSAMPLE(kDestWidth, SUBSAMP_X), dst_v, - SUBSAMPLE(kDestWidth, SUBSAMP_X), kDestWidth, kDestHeight); +#define TESTTOARGB(FMT, NEG, N) \ + TEST_F(LibYUVConvertTest, ConvertToARGB_##FMT##N) { \ + const int SUBSAMP_X = 2; \ + const int SUBSAMP_Y = 2; \ + const int kWidth = benchmark_width_; \ + const int kHeight = benchmark_height_; \ + const int crop_y = \ + ((benchmark_height_ - (benchmark_height_ * 360 / 480)) / 2 + 1) & ~1; \ + const int kDestWidth = benchmark_width_; \ + const int kDestHeight = benchmark_height_ - crop_y * 2; \ + const int kStrideUV = SUBSAMPLE(kWidth, SUBSAMP_X); \ + const int kSizeUV = kStrideUV * SUBSAMPLE(kHeight, SUBSAMP_Y); \ + const int sample_size = kWidth * kHeight + kSizeUV * 2; \ + align_buffer_page_end(src_y, sample_size); \ + uint8_t* src_uv = src_y + kWidth * kHeight; \ + \ + align_buffer_page_end(dst_argb, kWidth * 4 * kHeight); \ + align_buffer_page_end(dst_argb_2, kWidth * 4 * kHeight); \ + \ + for (int i = 0; i < kHeight * kWidth; ++i) { \ + src_y[i] = (fastrand() & 0xff); \ + } \ + for (int i = 0; i < kSizeUV * 2; ++i) { \ + src_uv[i] = (fastrand() & 0xff); \ + } \ + memset(dst_argb, 1, kDestWidth * 4 * kDestHeight); \ + memset(dst_argb_2, 1, kDestWidth * 4 * kDestHeight); \ + \ + ConvertToARGB(src_y, sample_size, dst_argb_2, kDestWidth * 4, 0, crop_y, \ + kWidth, NEG kHeight, kDestWidth, kDestHeight, \ + libyuv::kRotate0, libyuv::FOURCC_##FMT); \ + \ + FMT##ToARGB(src_y + crop_y * kWidth, kWidth, \ + src_uv + (crop_y / 2) * kStrideUV * 2, kStrideUV * 2, \ + dst_argb, kDestWidth * 4, kDestWidth, NEG kDestHeight); \ + \ + for (int i = 0; i < kDestHeight; ++i) { \ + for (int j = 0; j < kDestWidth * 4; ++j) { \ + EXPECT_EQ(dst_argb[i * kWidth + j], dst_argb_2[i * kWidth + j]); \ + } \ + } \ + free_aligned_buffer_page_end(dst_argb); \ + free_aligned_buffer_page_end(dst_argb_2); \ + free_aligned_buffer_page_end(src_y); \ + } - for (int i = 0; i < kDestHeight; ++i) { - for (int j = 0; j < kDestWidth; ++j) { - EXPECT_EQ(dst_y[i * kWidth + j], dst_y_2[i * kWidth + j]); - } +TESTTOARGB(NV12, +, _Crop) +TESTTOARGB(NV12, -, _Crop_Invert) +TESTTOARGB(NV21, +, _Crop) +TESTTOARGB(NV21, -, _Crop_Invert) + +#define TESTANDROID420TOBI(FMT_A, PIXEL_STRIDE, OFF_U, OFF_V, FMT_B, BASELINE, \ + NEG, N) \ + TEST_F(LibYUVConvertTest, Android420To##FMT_B##_##FMT_A##N) { \ + const int SUBSAMP_X = 2; \ + const int SUBSAMP_Y = 2; \ + const int kWidth = benchmark_width_; \ + const int kHeight = benchmark_height_; \ + const int kDestWidth = benchmark_width_; \ + const int kDestHeight = benchmark_height_; \ + const int kStrideUV = SUBSAMPLE(kWidth, SUBSAMP_X); \ + const int kSizeUV = kStrideUV * SUBSAMPLE(kHeight, SUBSAMP_Y); \ + const int sample_size = kWidth * kHeight + kSizeUV * 2; \ + align_buffer_page_end(src_y, sample_size); \ + uint8_t* src_uv = src_y + kWidth * kHeight; \ + uint8_t* src_u = src_uv + OFF_U; \ + uint8_t* src_v = src_uv + (PIXEL_STRIDE == 1 ? kSizeUV : OFF_V); \ + int src_stride_uv = kStrideUV * PIXEL_STRIDE; \ + align_buffer_page_end(dst_argb, kWidth * 4 * kHeight); \ + align_buffer_page_end(dst_argb_2, kWidth * 4 * kHeight); \ + \ + for (int i = 0; i < kHeight * kWidth; ++i) { \ + src_y[i] = (fastrand() & 0xff); \ + } \ + for (int i = 0; i < kSizeUV * 2; ++i) { \ + src_uv[i] = (fastrand() & 0xff); \ + } \ + memset(dst_argb, 1, kDestWidth * 4 * kDestHeight); \ + memset(dst_argb_2, 1, kDestWidth * 4 * kDestHeight); \ + \ + Android420To##FMT_B(src_y, kWidth, src_u, src_stride_uv, src_v, \ + src_stride_uv, PIXEL_STRIDE, dst_argb_2, \ + kDestWidth * 4, kWidth, NEG kHeight); \ + \ + BASELINE(src_y, kWidth, src_uv, src_stride_uv, dst_argb, \ + kDestWidth * 4, kDestWidth, NEG kDestHeight); \ + \ + for (int i = 0; i < kDestHeight; ++i) { \ + for (int j = 0; j < kDestWidth * 4; ++j) { \ + EXPECT_EQ(dst_argb[i * kWidth + j], dst_argb_2[i * kWidth + j]); \ + } \ + } \ + free_aligned_buffer_page_end(dst_argb); \ + free_aligned_buffer_page_end(dst_argb_2); \ + free_aligned_buffer_page_end(src_y); \ } - for (int i = 0; i < SUBSAMPLE(kDestHeight, SUBSAMP_Y); ++i) { - for (int j = 0; j < SUBSAMPLE(kDestWidth, SUBSAMP_X); ++j) { - EXPECT_EQ(dst_u[i * SUBSAMPLE(kDestWidth, SUBSAMP_X) + j], - dst_u_2[i * SUBSAMPLE(kDestWidth, SUBSAMP_X) + j]); - } - } - for (int i = 0; i < SUBSAMPLE(kDestHeight, SUBSAMP_Y); ++i) { - for (int j = 0; j < SUBSAMPLE(kDestWidth, SUBSAMP_X); ++j) { - EXPECT_EQ(dst_v[i * SUBSAMPLE(kDestWidth, SUBSAMP_X) + j], - dst_v_2[i * SUBSAMPLE(kDestWidth, SUBSAMP_X) + j]); - } - } - free_aligned_buffer_page_end(dst_y); - free_aligned_buffer_page_end(dst_u); - free_aligned_buffer_page_end(dst_v); - free_aligned_buffer_page_end(dst_y_2); - free_aligned_buffer_page_end(dst_u_2); - free_aligned_buffer_page_end(dst_v_2); - free_aligned_buffer_page_end(src_y); -} + +#define TESTANDROID420TOB(FMT_A, PIXEL_STRIDE, OFF_U, OFF_V, FMT_B, BASELINE) \ + TESTANDROID420TOBI(FMT_A, PIXEL_STRIDE, OFF_U, OFF_V, FMT_B, BASELINE, \ + +, ) \ + TESTANDROID420TOBI(FMT_A, PIXEL_STRIDE, OFF_U, OFF_V, FMT_B, BASELINE, \ + -, _Invert) + +// I420ToARGB/ABGR matching the NV12ToI420 signature, requires kSizeUV in scope. +#define I420TOARGB_I(src_y, src_stride_y, src_uv, src_stride_uv, \ + dst_argb, dst_stride_argb, width, height) \ + I420ToARGB(src_y, src_stride_y, src_uv, src_stride_uv, \ + src_uv + kSizeUV, src_stride_uv, dst_argb, \ + dst_stride_argb, width, height) +#define I420TOABGR_I(src_y, src_stride_y, src_uv, src_stride_uv, \ + dst_argb, dst_stride_argb, width, height) \ + I420ToABGR(src_y, src_stride_y, src_uv, src_stride_uv, \ + src_uv + kSizeUV, src_stride_uv, dst_argb, \ + dst_stride_argb, width, height) + +TESTANDROID420TOB(NV12, 2, 0, 1, ARGB, NV12ToARGB) +TESTANDROID420TOB(NV21, 2, 1, 0, ARGB, NV21ToARGB) +TESTANDROID420TOB(I420, 1, 0, 0, ARGB, I420TOARGB_I) +TESTANDROID420TOB(NV12, 2, 0, 1, ABGR, NV12ToABGR) +TESTANDROID420TOB(NV21, 2, 1, 0, ABGR, NV21ToABGR) +TESTANDROID420TOB(I420, 1, 0, 0, ABGR, I420TOABGR_I) + +#undef I420TOABGR_I +#undef I420TOARGB_I TEST_F(LibYUVConvertTest, TestYToARGB) { uint8_t y[32];