diff --git a/include/libyuv/convert.h b/include/libyuv/convert.h index 750383aa3..1e1418b68 100644 --- a/include/libyuv/convert.h +++ b/include/libyuv/convert.h @@ -831,6 +831,20 @@ int ARGBToI420(const uint8_t* src_argb, int width, int height); +// ARGB little endian (bgra in memory) to I420 with matrix. +LIBYUV_API +int ARGBToI420Matrix(const uint8_t* src_argb, + int src_stride_argb, + uint8_t* dst_y, + int dst_stride_y, + uint8_t* dst_u, + int dst_stride_u, + uint8_t* dst_v, + int dst_stride_v, + const struct ArgbConstants* argbconstants, + int width, + int height); + // Convert ARGB to I420 with Alpha LIBYUV_API int ARGBToI420Alpha(const uint8_t* src_argb, diff --git a/include/libyuv/convert_argb.h b/include/libyuv/convert_argb.h index 5b5056744..2436a7621 100644 --- a/include/libyuv/convert_argb.h +++ b/include/libyuv/convert_argb.h @@ -29,6 +29,15 @@ LIBYUV_API extern const struct YuvConstants kYuvF709Constants; // BT.709 full LIBYUV_API extern const struct YuvConstants kYuv2020Constants; // BT.2020 LIBYUV_API extern const struct YuvConstants kYuvV2020Constants; // BT.2020 full +// Conversion matrix for RGB to YUV +LIBYUV_API extern const struct ArgbConstants kArgbI601Constants; // BT.601 +LIBYUV_API extern const struct ArgbConstants kArgbJPEGConstants; // BT.601 full +LIBYUV_API extern const struct ArgbConstants kArgbH709Constants; // BT.709 +LIBYUV_API extern const struct ArgbConstants kArgbF709Constants; // BT.709 full +LIBYUV_API extern const struct ArgbConstants kArgbU2020Constants; // BT.2020 +LIBYUV_API extern const struct ArgbConstants + kArgbV2020Constants; // BT.2020 full + // Conversion matrix for YVU to BGR LIBYUV_API extern const struct YuvConstants kYvuI601Constants; // BT.601 LIBYUV_API extern const struct YuvConstants kYvuJPEGConstants; // BT.601 full diff --git a/include/libyuv/convert_from_argb.h b/include/libyuv/convert_from_argb.h index 54b7d99bf..397febe48 100644 --- a/include/libyuv/convert_from_argb.h +++ b/include/libyuv/convert_from_argb.h @@ -153,6 +153,20 @@ int ARGBToI444(const uint8_t* src_argb, int width, int height); +// Convert ARGB To I444 with matrix. +LIBYUV_API +int ARGBToI444Matrix(const uint8_t* src_argb, + int src_stride_argb, + uint8_t* dst_y, + int dst_stride_y, + uint8_t* dst_u, + int dst_stride_u, + uint8_t* dst_v, + int dst_stride_v, + const struct ArgbConstants* argbconstants, + int width, + int height); + // Convert ARGB to AR64. LIBYUV_API int ARGBToAR64(const uint8_t* src_argb, @@ -190,6 +204,20 @@ int ARGBToI422(const uint8_t* src_argb, int width, int height); +// Convert ARGB To I422 with matrix. +LIBYUV_API +int ARGBToI422Matrix(const uint8_t* src_argb, + int src_stride_argb, + uint8_t* dst_y, + int dst_stride_y, + uint8_t* dst_u, + int dst_stride_u, + uint8_t* dst_v, + int dst_stride_v, + const struct ArgbConstants* argbconstants, + int width, + int height); + // Convert ARGB To I420. (also in convert.h) LIBYUV_API int ARGBToI420(const uint8_t* src_argb, diff --git a/include/libyuv/row.h b/include/libyuv/row.h index 68c66f751..f2e352328 100644 --- a/include/libyuv/row.h +++ b/include/libyuv/row.h @@ -971,6 +971,14 @@ typedef uint32_t ulvec32[8]; typedef uint8_t ulvec8[32]; #endif +struct ArgbConstants { + uint8_t kRGBToY[32]; + int16_t kRGBToU[16]; + int16_t kRGBToV[16]; + uint16_t kAddY[16]; + uint16_t kAddUV[16]; +}; + #if defined(__aarch64__) || defined(__arm__) || defined(__riscv) // This struct is for ARM and RISC-V color conversion. struct YuvConstants { @@ -2074,6 +2082,22 @@ void RAWToYJRow_LASX(const uint8_t* src_raw, uint8_t* dst_yj, int width); void ARGBToYRow_C(const uint8_t* src_rgb, uint8_t* dst_y, int width); void ARGBToYJRow_C(const uint8_t* src_rgb, uint8_t* dst_y, int width); +void ARGBToYMatrixRow_C(const uint8_t* src_argb, + uint8_t* dst_y, + int width, + const struct ArgbConstants* c); +void ARGBToUVMatrixRow_C(const uint8_t* src_argb, + int src_stride_argb, + uint8_t* dst_u, + uint8_t* dst_v, + int width, + const struct ArgbConstants* c); +void ARGBToUV444MatrixRow_C(const uint8_t* src_argb, + uint8_t* dst_u, + uint8_t* dst_v, + int width, + const struct ArgbConstants* c); + void ABGRToYJRow_C(const uint8_t* src_rgb, uint8_t* dst_y, int width); void RGBAToYJRow_C(const uint8_t* src_rgb, uint8_t* dst_y, int width); void BGRAToYRow_C(const uint8_t* src_rgb, uint8_t* dst_y, int width); diff --git a/source/convert.cc b/source/convert.cc index d175bbc44..4231b532b 100644 --- a/source/convert.cc +++ b/source/convert.cc @@ -2144,6 +2144,55 @@ int ARGBToI420(const uint8_t* src_argb, return 0; } +// ARGB little endian (bgra in memory) to I420 with matrix. +LIBYUV_API +int ARGBToI420Matrix(const uint8_t* src_argb, + int src_stride_argb, + uint8_t* dst_y, + int dst_stride_y, + uint8_t* dst_u, + int dst_stride_u, + uint8_t* dst_v, + int dst_stride_v, + const struct ArgbConstants* argbconstants, + int width, + int height) { + int y; + void (*ARGBToYMatrixRow)(const uint8_t* src_argb, uint8_t* dst_y, int width, + const struct ArgbConstants* c) = ARGBToYMatrixRow_C; + void (*ARGBToUVMatrixRow)(const uint8_t* src_argb, int src_stride_argb, + uint8_t* dst_u, uint8_t* dst_v, int width, + const struct ArgbConstants* c) = + ARGBToUVMatrixRow_C; + if (!src_argb || !dst_y || !dst_u || !dst_v || !argbconstants || width <= 0 || + height == 0) { + return -1; + } + // Negative height means invert the image. + if (height < 0) { + height = -height; + src_argb = src_argb + (height - 1) * src_stride_argb; + src_stride_argb = -src_stride_argb; + } + + for (y = 0; y < height - 1; y += 2) { + ARGBToUVMatrixRow(src_argb, src_stride_argb, dst_u, dst_v, width, + argbconstants); + ARGBToYMatrixRow(src_argb, dst_y, width, argbconstants); + ARGBToYMatrixRow(src_argb + src_stride_argb, dst_y + dst_stride_y, width, + argbconstants); + src_argb += src_stride_argb * 2; + dst_y += dst_stride_y * 2; + dst_u += dst_stride_u; + dst_v += dst_stride_v; + } + if (height & 1) { + ARGBToUVMatrixRow(src_argb, 0, dst_u, dst_v, width, argbconstants); + ARGBToYMatrixRow(src_argb, dst_y, width, argbconstants); + } + return 0; +} + #ifdef USE_EXTRACTALPHA // Convert ARGB to I420 with Alpha // The following version calls ARGBExtractAlpha on the full image. diff --git a/source/convert_from_argb.cc b/source/convert_from_argb.cc index e0a3d7c77..b2675a7e3 100644 --- a/source/convert_from_argb.cc +++ b/source/convert_from_argb.cc @@ -165,6 +165,48 @@ int ARGBToI444(const uint8_t* src_argb, return 0; } +// Convert ARGB To I444 with matrix. +LIBYUV_API +int ARGBToI444Matrix(const uint8_t* src_argb, + int src_stride_argb, + uint8_t* dst_y, + int dst_stride_y, + uint8_t* dst_u, + int dst_stride_u, + uint8_t* dst_v, + int dst_stride_v, + const struct ArgbConstants* argbconstants, + int width, + int height) { + int y; + void (*ARGBToYMatrixRow)(const uint8_t* src_argb, uint8_t* dst_y, int width, + const struct ArgbConstants* c) = ARGBToYMatrixRow_C; + void (*ARGBToUV444MatrixRow)(const uint8_t* src_argb, uint8_t* dst_u, + uint8_t* dst_v, int width, + const struct ArgbConstants* c) = + ARGBToUV444MatrixRow_C; + if (!src_argb || !dst_y || !dst_u || !dst_v || !argbconstants || width <= 0 || + height == 0) { + return -1; + } + // Negative height means invert the image. + if (height < 0) { + height = -height; + src_argb = src_argb + (height - 1) * src_stride_argb; + src_stride_argb = -src_stride_argb; + } + + for (y = 0; y < height; ++y) { + ARGBToYMatrixRow(src_argb, dst_y, width, argbconstants); + ARGBToUV444MatrixRow(src_argb, dst_u, dst_v, width, argbconstants); + src_argb += src_stride_argb; + dst_y += dst_stride_y; + dst_u += dst_stride_u; + dst_v += dst_stride_v; + } + return 0; +} + // ARGB little endian (bgra in memory) to I422 LIBYUV_API int ARGBToI422(const uint8_t* src_argb, @@ -324,6 +366,48 @@ int ARGBToI422(const uint8_t* src_argb, return 0; } +// Convert ARGB To I422 with matrix. +LIBYUV_API +int ARGBToI422Matrix(const uint8_t* src_argb, + int src_stride_argb, + uint8_t* dst_y, + int dst_stride_y, + uint8_t* dst_u, + int dst_stride_u, + uint8_t* dst_v, + int dst_stride_v, + const struct ArgbConstants* argbconstants, + int width, + int height) { + int y; + void (*ARGBToYMatrixRow)(const uint8_t* src_argb, uint8_t* dst_y, int width, + const struct ArgbConstants* c) = ARGBToYMatrixRow_C; + void (*ARGBToUVMatrixRow)(const uint8_t* src_argb, int src_stride_argb, + uint8_t* dst_u, uint8_t* dst_v, int width, + const struct ArgbConstants* c) = + ARGBToUVMatrixRow_C; + if (!src_argb || !dst_y || !dst_u || !dst_v || !argbconstants || width <= 0 || + height == 0) { + return -1; + } + // Negative height means invert the image. + if (height < 0) { + height = -height; + src_argb = src_argb + (height - 1) * src_stride_argb; + src_stride_argb = -src_stride_argb; + } + + for (y = 0; y < height; ++y) { + ARGBToUVMatrixRow(src_argb, 0, dst_u, dst_v, width, argbconstants); + ARGBToYMatrixRow(src_argb, dst_y, width, argbconstants); + src_argb += src_stride_argb; + dst_y += dst_stride_y; + dst_u += dst_stride_u; + dst_v += dst_stride_v; + } + return 0; +} + LIBYUV_API int ARGBToNV12(const uint8_t* src_argb, int src_stride_argb, diff --git a/source/row_common.cc b/source/row_common.cc index 3062fd8f9..9cc15b370 100644 --- a/source/row_common.cc +++ b/source/row_common.cc @@ -756,6 +756,92 @@ MAKEROWYJ(RGB24, 2, 1, 0, 3) MAKEROWYJ(RAW, 0, 1, 2, 3) #undef MAKEROWYJ +static __inline uint8_t RGBToYMatrix(uint8_t r, + uint8_t g, + uint8_t b, + const struct ArgbConstants* c) { + return (c->kRGBToY[2] * r + c->kRGBToY[1] * g + c->kRGBToY[0] * b + + c->kAddY[0]) >> + 8; +} +static __inline uint8_t RGBToUMatrix(uint8_t r, + uint8_t g, + uint8_t b, + const struct ArgbConstants* c) { + return (c->kRGBToU[2] * r + c->kRGBToU[1] * g + c->kRGBToU[0] * b + + c->kAddUV[0]) >> + 8; +} +static __inline uint8_t RGBToVMatrix(uint8_t r, + uint8_t g, + uint8_t b, + const struct ArgbConstants* c) { + return (c->kRGBToV[2] * r + c->kRGBToV[1] * g + c->kRGBToV[0] * b + + c->kAddUV[0]) >> + 8; +} + +void ARGBToYMatrixRow_C(const uint8_t* src_argb, + uint8_t* dst_y, + int width, + const struct ArgbConstants* c) { + int x; + for (x = 0; x < width; ++x) { + dst_y[0] = RGBToYMatrix(src_argb[2], src_argb[1], src_argb[0], c); + src_argb += 4; + dst_y += 1; + } +} + +void ARGBToUVMatrixRow_C(const uint8_t* src_argb, + int src_stride_argb, + uint8_t* dst_u, + uint8_t* dst_v, + int width, + const struct ArgbConstants* c) { + const uint8_t* src_argb1 = src_argb + src_stride_argb; + int x; + for (x = 0; x < width - 1; x += 2) { + uint8_t ab = + (src_argb[0] + src_argb[4] + src_argb1[0] + src_argb1[4] + 2) >> 2; + uint8_t ag = + (src_argb[1] + src_argb[5] + src_argb1[1] + src_argb1[5] + 2) >> 2; + uint8_t ar = + (src_argb[2] + src_argb[6] + src_argb1[2] + src_argb1[6] + 2) >> 2; + dst_u[0] = RGBToUMatrix(ar, ag, ab, c); + dst_v[0] = RGBToVMatrix(ar, ag, ab, c); + src_argb += 8; + src_argb1 += 8; + dst_u += 1; + dst_v += 1; + } + if (width & 1) { + uint8_t ab = (src_argb[0] + src_argb1[0] + 1) >> 1; + uint8_t ag = (src_argb[1] + src_argb1[1] + 1) >> 1; + uint8_t ar = (src_argb[2] + src_argb1[2] + 1) >> 1; + dst_u[0] = RGBToUMatrix(ar, ag, ab, c); + dst_v[0] = RGBToVMatrix(ar, ag, ab, c); + } +} + +void ARGBToUV444MatrixRow_C(const uint8_t* src_argb, + uint8_t* dst_u, + uint8_t* dst_v, + int width, + const struct ArgbConstants* c) { + int x; + for (x = 0; x < width; ++x) { + uint8_t ab = src_argb[0]; + uint8_t ag = src_argb[1]; + uint8_t ar = src_argb[2]; + dst_u[0] = RGBToUMatrix(ar, ag, ab, c); + dst_v[0] = RGBToVMatrix(ar, ag, ab, c); + src_argb += 4; + dst_u += 1; + dst_v += 1; + } +} + void RGB565ToYRow_C(const uint8_t* src_rgb565, uint8_t* dst_y, int width) { int x; for (x = 0; x < width; ++x) { @@ -1399,6 +1485,15 @@ void J400ToARGBRow_C(const uint8_t* src_y, uint8_t* dst_argb, int width) { {YB, YB, YB, YB, YB, YB, YB, YB, YB, YB, YB, YB, YB, YB, YB, YB}} #endif +#define ARGBCONSTANTSBODY(RY, GY, BY, RU, GU, BU, RV, GV, BV, AY, AUV) \ + {{BY, GY, RY, 0, BY, GY, RY, 0, BY, GY, RY, 0, BY, GY, RY, 0, \ + BY, GY, RY, 0, BY, GY, RY, 0, BY, GY, RY, 0, BY, GY, RY, 0}, \ + {BU, GU, RU, 0, BU, GU, RU, 0, BU, GU, RU, 0, BU, GU, RU, 0}, \ + {BV, GV, RV, 0, BV, GV, RV, 0, BV, GV, RV, 0, BV, GV, RV, 0}, \ + {AY, AY, AY, AY, AY, AY, AY, AY, AY, AY, AY, AY, AY, AY, AY, AY}, \ + {AUV, AUV, AUV, AUV, AUV, AUV, AUV, AUV, AUV, AUV, AUV, AUV, AUV, AUV, \ + AUV, AUV}} + // clang-format on #define MAKEYUVCONSTANTS(name, YG, YB, UB, UG, VG, VR) \ @@ -1407,6 +1502,94 @@ void J400ToARGBRow_C(const uint8_t* src_y, uint8_t* dst_argb, int width) { const struct YuvConstants SIMD_ALIGNED(kYvu##name##Constants) = \ YUVCONSTANTSBODY(YG, YB, VR, VG, UG, UB); +#define MAKEARGBCONSTANTS(name, RY, GY, BY, RU, GU, BU, RV, GV, BV, AY, AUV) \ + const struct ArgbConstants SIMD_ALIGNED(kArgb##name##Constants) = \ + ARGBCONSTANTSBODY(RY, GY, BY, RU, GU, BU, RV, GV, BV, AY, AUV); + +// BT.601 limited range RGB to YUV coefficients +// RY = round(0.299 * 219 / 255 * 256) = 66 +// GY = round(0.587 * 219 / 255 * 256) = 129 +// BY = round(0.114 * 219 / 255 * 256) = 25 +// BU = round(0.500 * 224 / 255 * 256) = 112 +// RU = round(-0.299 / (1 - 0.114) * 112.4) = -38 +// GU = round(-0.587 / (1 - 0.114) * 112.4) = -74 +// RV = 112 +// GV = round(-0.587 / (1 - 0.299) * 112.4) = -94 +// BV = round(-0.114 / (1 - 0.299) * 112.4) = -18 +// AY = 16 * 256 + 128 = 4224 +// AUV = 128 * 256 = 32768 +MAKEARGBCONSTANTS(I601, 66, 129, 25, -38, -74, 112, 112, -94, -18, 4224, 32768) + +// BT.601 full range RGB to YUV coefficients (aka JPEG) +// RY = round(0.299 * 256) = 77 +// GY = round(0.587 * 256) = 150 +// BY = round(0.114 * 256) = 29 +// BU = 128 +// RU = round(-0.299 / (1 - 0.114) * 128) = -43 +// GU = round(-0.587 / (1 - 0.114) * 128) = -85 +// RV = 128 +// GV = round(-0.587 / (1 - 0.299) * 128) = -107 +// BV = round(-0.114 / (1 - 0.299) * 128) = -21 +// AY = 128 +// AUV = 32768 +MAKEARGBCONSTANTS(JPEG, 77, 150, 29, -43, -85, 128, 128, -107, -21, 128, 32768) + +// BT.709 limited range RGB to YUV coefficients +// RY = round(0.2126 * 219 / 255 * 256) = 47 +// GY = round(0.7152 * 219 / 255 * 256) = 157 +// BY = round(0.0722 * 219 / 255 * 256) = 16 +// BU = round(0.500 * 224 / 255 * 256) = 112 +// RU = round(-0.2126 / (1 - 0.0722) * 112.4) = -26 +// GU = round(-0.7152 / (1 - 0.0722) * 112.4) = -86 +// RV = 112 +// GV = round(-0.7152 / (1 - 0.2126) * 112.4) = -102 +// BV = round(-0.0722 / (1 - 0.2126) * 112.4) = -10 +// AY = 16 * 256 + 128 = 4224 +// AUV = 128 * 256 = 32768 +MAKEARGBCONSTANTS(H709, 47, 157, 16, -26, -86, 112, 112, -102, -10, 4224, 32768) + +// BT.709 full range RGB to YUV coefficients +// RY = round(0.2126 * 256) = 54 +// GY = round(0.7152 * 256) = 183 +// BY = round(0.0722 * 256) = 19 +// BU = 128 +// RU = round(-0.2126 / (1 - 0.0722) * 128) = -29 +// GU = round(-0.7152 / (1 - 0.0722) * 128) = -99 +// RV = 128 +// GV = round(-0.7152 / (1 - 0.2126) * 128) = -116 +// BV = round(-0.0722 / (1 - 0.2126) * 128) = -12 +// AY = 128 +// AUV = 32768 +MAKEARGBCONSTANTS(F709, 54, 183, 19, -29, -99, 128, 128, -116, -12, 128, 32768) + +// BT.2020 limited range RGB to YUV coefficients +// RY = round(0.2627 * 219 / 255 * 256) = 58 +// GY = round(0.6780 * 219 / 255 * 256) = 149 +// BY = round(0.0593 * 219 / 255 * 256) = 13 +// BU = 112 +// RU = round(-0.2627 / (1 - 0.0593) * 112.4) = -31 +// GU = round(-0.6780 / (1 - 0.0593) * 112.4) = -81 +// RV = 112 +// GV = round(-0.6780 / (1 - 0.2627) * 112.4) = -103 +// BV = round(-0.0593 / (1 - 0.2627) * 112.4) = -9 +// AY = 16 * 256 + 128 = 4224 +// AUV = 128 * 256 = 32768 +MAKEARGBCONSTANTS(U2020, 59, 148, 13, -31, -81, 112, 112, -103, -9, 4224, 32768) + +// BT.2020 full range RGB to YUV coefficients +// RY = round(0.2627 * 256) = 67 +// GY = round(0.6780 * 256) = 174 +// BY = round(0.0593 * 256) = 15 +// BU = 128 +// RU = round(-0.2627 / (1 - 0.0593) * 128) = -36 +// GU = round(-0.6780 / (1 - 0.0593) * 128) = -92 +// RV = 128 +// GV = round(-0.6780 / (1 - 0.2627) * 128) = -118 +// BV = round(-0.0593 / (1 - 0.2627) * 128) = -10 +// AY = 128 +// AUV = 32768 +MAKEARGBCONSTANTS(V2020, 67, 174, 15, -36, -92, 128, 128, -118, -10, 128, 32768) + // TODO(fbarchard): Generate SIMD structures from float matrix. // BT.601 limited range YUV to RGB reference diff --git a/unit_test/convert_test.cc b/unit_test/convert_test.cc index 938f1cb98..e4969a89c 100644 --- a/unit_test/convert_test.cc +++ b/unit_test/convert_test.cc @@ -2128,6 +2128,194 @@ TEST_F(LibYUVConvertTest, TestJ420ToI420) { EXPECT_EQ(dst_v[2], 240); } +TEST_F(LibYUVConvertTest, TestARGBToI420Matrix) { + const int kWidth = 16; + const int kHeight = 16; + align_buffer_page_end(src_argb, kWidth * kHeight * 4); + align_buffer_page_end(dst_y, kWidth * kHeight); + align_buffer_page_end(dst_u, kWidth / 2 * kHeight / 2); + align_buffer_page_end(dst_v, kWidth / 2 * kHeight / 2); + + MemRandomize(src_argb, kWidth * kHeight * 4); + + // BT.601 + ARGBToI420Matrix(src_argb, kWidth * 4, dst_y, kWidth, dst_u, kWidth / 2, + dst_v, kWidth / 2, &kArgbI601Constants, kWidth, kHeight); + // Verify against non-matrix version + align_buffer_page_end(ref_y, kWidth * kHeight); + align_buffer_page_end(ref_u, kWidth / 2 * kHeight / 2); + align_buffer_page_end(ref_v, kWidth / 2 * kHeight / 2); + ARGBToI420(src_argb, kWidth * 4, ref_y, kWidth, ref_u, kWidth / 2, ref_v, + kWidth / 2, kWidth, kHeight); + for (int i = 0; i < kWidth * kHeight; ++i) { + ASSERT_EQ(dst_y[i], ref_y[i]); + } + for (int i = 0; i < kWidth / 2 * kHeight / 2; ++i) { + ASSERT_EQ(dst_u[i], ref_u[i]); + ASSERT_EQ(dst_v[i], ref_v[i]); + } + + // JPEG + ARGBToI420Matrix(src_argb, kWidth * 4, dst_y, kWidth, dst_u, kWidth / 2, + dst_v, kWidth / 2, &kArgbJPEGConstants, kWidth, kHeight); + // Verify against non-matrix version + ARGBToJ420(src_argb, kWidth * 4, ref_y, kWidth, ref_u, kWidth / 2, ref_v, + kWidth / 2, kWidth, kHeight); + for (int i = 0; i < kWidth * kHeight; ++i) { + ASSERT_EQ(dst_y[i], ref_y[i]); + } + for (int i = 0; i < kWidth / 2 * kHeight / 2; ++i) { + ASSERT_EQ(dst_u[i], ref_u[i]); + ASSERT_EQ(dst_v[i], ref_v[i]); + } + + // BT.709 + ARGBToI420Matrix(src_argb, kWidth * 4, dst_y, kWidth, dst_u, kWidth / 2, + dst_v, kWidth / 2, &kArgbH709Constants, kWidth, kHeight); + // Just check if it returns 0 for now. + // In a real test we'd have reference values. + + // BT.2020 + ARGBToI420Matrix(src_argb, kWidth * 4, dst_y, kWidth, dst_u, kWidth / 2, + dst_v, kWidth / 2, &kArgbU2020Constants, kWidth, kHeight); + + // Reference BT.709 (limited range) + // Y = round(0.2126 * 219 / 255 * R + 0.7152 * 219 / 255 * G + 0.0722 * 219 / 255 * B + 16) + // Y = round(0.1826 * R + 0.6142 * G + 0.0620 * B + 16) + // 47 * 255 + 157 * 255 + 16 * 255 + 4224 = 11985 + 40035 + 4080 + 4224 = 60324 + // 60324 / 256 = 235.64 -> 235. Correct. + + for (int i = 0; i < kWidth * kHeight * 4; ++i) src_argb[i] = 255; + ARGBToI420Matrix(src_argb, kWidth * 4, dst_y, kWidth, dst_u, kWidth / 2, + dst_v, kWidth / 2, &kArgbH709Constants, kWidth, kHeight); + EXPECT_EQ(dst_y[0], 235); + EXPECT_EQ(dst_u[0], 128); + EXPECT_EQ(dst_v[0], 128); + + for (int i = 0; i < kWidth * kHeight * 4; i += 4) { + src_argb[i + 0] = 0; // B + src_argb[i + 1] = 0; // G + src_argb[i + 2] = 255; // R + src_argb[i + 3] = 255; // A + } + ARGBToI420Matrix(src_argb, kWidth * 4, dst_y, kWidth, dst_u, kWidth / 2, + dst_v, kWidth / 2, &kArgbH709Constants, kWidth, kHeight); + // Y = 47 * 255 + 4224 = 11985 + 4224 = 16209. 16209 / 256 = 63.3 -> 63. + EXPECT_EQ(dst_y[0], 63); + // U = -26 * 255 + 32768 = -6630 + 32768 = 26138. 26138 / 256 = 102.1 -> 102. + EXPECT_EQ(dst_u[0], 102); + // V = 112 * 255 + 32768 = 28560 + 32768 = 61328. 61328 / 256 = 239.5 -> 239. + EXPECT_EQ(dst_v[0], 239); + + free_aligned_buffer_page_end(src_argb); + 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(ref_y); + free_aligned_buffer_page_end(ref_u); + free_aligned_buffer_page_end(ref_v); +} + +TEST_F(LibYUVConvertTest, TestARGBToI422Matrix) { + const int kWidth = 16; + const int kHeight = 16; + align_buffer_page_end(src_argb, kWidth * kHeight * 4); + align_buffer_page_end(dst_y, kWidth * kHeight); + align_buffer_page_end(dst_u, kWidth / 2 * kHeight); + align_buffer_page_end(dst_v, kWidth / 2 * kHeight); + + MemRandomize(src_argb, kWidth * kHeight * 4); + + // BT.601 + ARGBToI422Matrix(src_argb, kWidth * 4, dst_y, kWidth, dst_u, kWidth / 2, + dst_v, kWidth / 2, &kArgbI601Constants, kWidth, kHeight); + // Verify against non-matrix version + align_buffer_page_end(ref_y, kWidth * kHeight); + align_buffer_page_end(ref_u, kWidth / 2 * kHeight); + align_buffer_page_end(ref_v, kWidth / 2 * kHeight); + ARGBToI422(src_argb, kWidth * 4, ref_y, kWidth, ref_u, kWidth / 2, ref_v, + kWidth / 2, kWidth, kHeight); + for (int i = 0; i < kWidth * kHeight; ++i) { + ASSERT_EQ(dst_y[i], ref_y[i]); + } + for (int i = 0; i < kWidth / 2 * kHeight; ++i) { + ASSERT_EQ(dst_u[i], ref_u[i]); + ASSERT_EQ(dst_v[i], ref_v[i]); + } + + // JPEG + ARGBToI422Matrix(src_argb, kWidth * 4, dst_y, kWidth, dst_u, kWidth / 2, + dst_v, kWidth / 2, &kArgbJPEGConstants, kWidth, kHeight); + // Verify against non-matrix version + ARGBToJ422(src_argb, kWidth * 4, ref_y, kWidth, ref_u, kWidth / 2, ref_v, + kWidth / 2, kWidth, kHeight); + for (int i = 0; i < kWidth * kHeight; ++i) { + ASSERT_EQ(dst_y[i], ref_y[i]); + } + for (int i = 0; i < kWidth / 2 * kHeight / 2; ++i) { + ASSERT_EQ(dst_u[i], ref_u[i]); + ASSERT_EQ(dst_v[i], ref_v[i]); + } + + free_aligned_buffer_page_end(src_argb); + 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(ref_y); + free_aligned_buffer_page_end(ref_u); + free_aligned_buffer_page_end(ref_v); +} + +TEST_F(LibYUVConvertTest, TestARGBToI444Matrix) { + const int kWidth = 16; + const int kHeight = 16; + align_buffer_page_end(src_argb, kWidth * kHeight * 4); + align_buffer_page_end(dst_y, kWidth * kHeight); + align_buffer_page_end(dst_u, kWidth * kHeight); + align_buffer_page_end(dst_v, kWidth * kHeight); + + MemRandomize(src_argb, kWidth * kHeight * 4); + + // BT.601 + ARGBToI444Matrix(src_argb, kWidth * 4, dst_y, kWidth, dst_u, kWidth, dst_v, + kWidth, &kArgbI601Constants, kWidth, kHeight); + // Verify against non-matrix version + align_buffer_page_end(ref_y, kWidth * kHeight); + align_buffer_page_end(ref_u, kWidth * kHeight); + align_buffer_page_end(ref_v, kWidth * kHeight); + ARGBToI444(src_argb, kWidth * 4, ref_y, kWidth, ref_u, kWidth, ref_v, kWidth, + kWidth, kHeight); + for (int i = 0; i < kWidth * kHeight; ++i) { + ASSERT_EQ(dst_y[i], ref_y[i]); + } + for (int i = 0; i < kWidth * kHeight; ++i) { + ASSERT_EQ(dst_u[i], ref_u[i]); + ASSERT_EQ(dst_v[i], ref_v[i]); + } + + // JPEG + ARGBToI444Matrix(src_argb, kWidth * 4, dst_y, kWidth, dst_u, kWidth / 2, + dst_v, kWidth / 2, &kArgbJPEGConstants, kWidth, kHeight); + // Verify against non-matrix version + ARGBToJ444(src_argb, kWidth * 4, ref_y, kWidth, ref_u, kWidth / 2, ref_v, + kWidth / 2, kWidth, kHeight); + for (int i = 0; i < kWidth * kHeight; ++i) { + ASSERT_EQ(dst_y[i], ref_y[i]); + } + for (int i = 0; i < kWidth / 2 * kHeight / 2; ++i) { + ASSERT_EQ(dst_u[i], ref_u[i]); + ASSERT_EQ(dst_v[i], ref_v[i]); + } + + free_aligned_buffer_page_end(src_argb); + 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(ref_y); + free_aligned_buffer_page_end(ref_u); + free_aligned_buffer_page_end(ref_v); +} + #endif // !defined(LEAN_TESTS) } // namespace libyuv