From f2cd361e255d52be073c46b519055c70c4724fcf Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Fri, 1 Mar 2024 15:35:25 -0800 Subject: [PATCH] testyuv: added validation of P010 YUV format Also added conversion between RGB and P010, using XRGB2101010 as a bridging format in PQ space --- include/SDL3/SDL_pixels.h | 5 +- src/video/SDL_yuv.c | 286 ++++++++++++++++++++++++--- src/video/yuv2rgb/yuv_rgb_common.h | 3 +- src/video/yuv2rgb/yuv_rgb_internal.h | 14 +- src/video/yuv2rgb/yuv_rgb_std.c | 21 ++ src/video/yuv2rgb/yuv_rgb_std.h | 6 + src/video/yuv2rgb/yuv_rgb_std_func.h | 73 ++++--- test/testyuv.c | 75 +++++-- test/testyuv_cvt.c | 269 +++++++++++++++++++++---- test/testyuv_cvt.h | 1 + 10 files changed, 637 insertions(+), 116 deletions(-) diff --git a/include/SDL3/SDL_pixels.h b/include/SDL3/SDL_pixels.h index 0d0325a31..ac8f1e07a 100644 --- a/include/SDL3/SDL_pixels.h +++ b/include/SDL3/SDL_pixels.h @@ -560,8 +560,9 @@ typedef enum #define SDL_ISCOLORSPACE_YUV_BT601(X) (SDL_COLORSPACEMATRIX(X) == SDL_MATRIX_COEFFICIENTS_BT601 || SDL_COLORSPACEMATRIX(X) == SDL_MATRIX_COEFFICIENTS_BT470BG) #define SDL_ISCOLORSPACE_YUV_BT709(X) (SDL_COLORSPACEMATRIX(X) == SDL_MATRIX_COEFFICIENTS_BT709) -#define SDL_ISCOLORSPACE_LIMITED_RANGE(X) (SDL_COLORSPACERANGE(X) == SDL_COLOR_RANGE_LIMITED) -#define SDL_ISCOLORSPACE_FULL_RANGE(X) (SDL_COLORSPACERANGE(X) == SDL_COLOR_RANGE_LIMITED) +#define SDL_ISCOLORSPACE_YUV_BT2020(X) (SDL_COLORSPACEMATRIX(X) == SDL_MATRIX_COEFFICIENTS_BT2020_NCL || SDL_COLORSPACEMATRIX(X) == SDL_MATRIX_COEFFICIENTS_BT2020_CL) +#define SDL_ISCOLORSPACE_LIMITED_RANGE(X) (SDL_COLORSPACERANGE(X) != SDL_COLOR_RANGE_FULL) +#define SDL_ISCOLORSPACE_FULL_RANGE(X) (SDL_COLORSPACERANGE(X) == SDL_COLOR_RANGE_FULL) typedef enum { diff --git a/src/video/SDL_yuv.c b/src/video/SDL_yuv.c index 4a8e00ef3..b9dbf7726 100644 --- a/src/video/SDL_yuv.c +++ b/src/video/SDL_yuv.c @@ -167,22 +167,29 @@ static int GetYUVConversionType(SDL_Colorspace colorspace, YCbCrType *yuv_type) } else { *yuv_type = YCBCR_JPEG; } - } else if (SDL_ISCOLORSPACE_YUV_BT709(colorspace)) { + return 0; + } + + if (SDL_ISCOLORSPACE_YUV_BT709(colorspace)) { if (SDL_ISCOLORSPACE_LIMITED_RANGE(colorspace)) { *yuv_type = YCBCR_709; - } else { - /* BT709 full range isn't supported yet */ - return SDL_SetError("Unsupported YUV colorspace"); + return 0; } - } else { - return SDL_SetError("Unsupported YUV colorspace"); } - return 0; + + if (SDL_ISCOLORSPACE_YUV_BT2020(colorspace)) { + if (SDL_ISCOLORSPACE_FULL_RANGE(colorspace)) { + *yuv_type = YCBCR_2020; + return 0; + } + } + + return SDL_SetError("Unsupported YUV colorspace"); } static SDL_bool IsPlanar2x2Format(Uint32 format) { - return format == SDL_PIXELFORMAT_YV12 || format == SDL_PIXELFORMAT_IYUV || format == SDL_PIXELFORMAT_NV12 || format == SDL_PIXELFORMAT_NV21; + return format == SDL_PIXELFORMAT_YV12 || format == SDL_PIXELFORMAT_IYUV || format == SDL_PIXELFORMAT_NV12 || format == SDL_PIXELFORMAT_NV21 || format == SDL_PIXELFORMAT_P010; } static SDL_bool IsPacked4Format(Uint32 format) @@ -195,6 +202,7 @@ static int GetYUVPlanes(int width, int height, Uint32 format, const void *yuv, i { const Uint8 *planes[3] = { NULL, NULL, NULL }; int pitches[3] = { 0, 0, 0 }; + int uv_width; switch (format) { case SDL_PIXELFORMAT_YV12: @@ -219,6 +227,13 @@ static int GetYUVPlanes(int width, int height, Uint32 format, const void *yuv, i planes[0] = (const Uint8 *)yuv; planes[1] = planes[0] + pitches[0] * height; break; + case SDL_PIXELFORMAT_P010: + pitches[0] = yuv_pitch; + uv_width = ((width + 1) / 2) * 2; + pitches[1] = SDL_max(pitches[0], uv_width * sizeof(Uint16)); + planes[0] = (const Uint8 *)yuv; + planes[1] = planes[0] + pitches[0] * height; + break; default: return SDL_SetError("GetYUVPlanes(): Unsupported YUV format: %s", SDL_GetPixelFormatName(format)); } @@ -273,6 +288,13 @@ static int GetYUVPlanes(int width, int height, Uint32 format, const void *yuv, i *u = *v + 1; *uv_stride = pitches[1]; break; + case SDL_PIXELFORMAT_P010: + *y = planes[0]; + *y_stride = pitches[0]; + *u = planes[1]; + *v = *u + sizeof(Uint16); + *uv_stride = pitches[1]; + break; default: /* Should have caught this above */ return SDL_SetError("GetYUVPlanes[2]: Unsupported YUV format: %s", SDL_GetPixelFormatName(format)); @@ -551,6 +573,14 @@ static SDL_bool yuv_rgb_std( break; } } + + if (src_format == SDL_PIXELFORMAT_P010) { + switch (dst_format) { + case SDL_PIXELFORMAT_XBGR2101010: + yuvp010_xbgr2101010_std(width, height, (const uint16_t *)y, (const uint16_t *)u, (const uint16_t *)v, y_stride, uv_stride, rgb, rgb_stride, yuv_type); + return SDL_TRUE; + } + } return SDL_FALSE; } @@ -586,6 +616,29 @@ int SDL_ConvertPixels_YUV_to_RGB(int width, int height, } /* No fast path for the RGB format, instead convert using an intermediate buffer */ + if (src_format == SDL_PIXELFORMAT_P010 && dst_format != SDL_PIXELFORMAT_XBGR2101010) { + int ret; + void *tmp; + int tmp_pitch = (width * sizeof(Uint32)); + + tmp = SDL_malloc((size_t)tmp_pitch * height); + if (!tmp) { + return -1; + } + + /* convert src/src_format to tmp/XBGR2101010 */ + ret = SDL_ConvertPixels_YUV_to_RGB(width, height, src_format, src_colorspace, src_properties, src, src_pitch, SDL_PIXELFORMAT_XBGR2101010, src_colorspace, src_properties, tmp, tmp_pitch); + if (ret < 0) { + SDL_free(tmp); + return ret; + } + + /* convert tmp/XBGR2101010 to dst/RGB */ + ret = SDL_ConvertPixelsAndColorspace(width, height, SDL_PIXELFORMAT_XBGR2101010, src_colorspace, src_properties, tmp, tmp_pitch, dst_format, dst_colorspace, dst_properties, dst, dst_pitch); + SDL_free(tmp); + return ret; + } + if (dst_format != SDL_PIXELFORMAT_ARGB8888) { int ret; void *tmp; @@ -620,6 +673,37 @@ struct RGB2YUVFactors float v[3]; /* Rfactor, Gfactor, Bfactor */ }; +static struct RGB2YUVFactors RGB2YUVFactorTables[] = { + /* ITU-T T.871 (JPEG) */ + { + 0, + { 0.2990f, 0.5870f, 0.1140f }, + { -0.1687f, -0.3313f, 0.5000f }, + { 0.5000f, -0.4187f, -0.0813f }, + }, + /* ITU-R BT.601-7 */ + { + 16, + { 0.2568f, 0.5041f, 0.0979f }, + { -0.1482f, -0.2910f, 0.4392f }, + { 0.4392f, -0.3678f, -0.0714f }, + }, + /* ITU-R BT.709-6 */ + { + 16, + { 0.1826f, 0.6142f, 0.0620f }, + { -0.1006f, -0.3386f, 0.4392f }, + { 0.4392f, -0.3989f, -0.0403f }, + }, + /* ITU-R BT.2020 10-bit full range */ + { + 0, + { 0.2627f, 0.6780f, 0.0593f }, + { -0.1395f, -0.3600f, 0.4995f }, + { 0.4995f, -0.4593f, -0.0402f }, + }, +}; + static int SDL_ConvertPixels_ARGB8888_to_YUV(int width, int height, const void *src, int src_pitch, Uint32 dst_format, void *dst, int dst_pitch, YCbCrType yuv_type) { const int src_pitch_x_2 = src_pitch * 2; @@ -629,29 +713,6 @@ static int SDL_ConvertPixels_ARGB8888_to_YUV(int width, int height, const void * const int width_remainder = (width & 0x1); int i, j; - static struct RGB2YUVFactors RGB2YUVFactorTables[] = { - /* ITU-T T.871 (JPEG) */ - { - 0, - { 0.2990f, 0.5870f, 0.1140f }, - { -0.1687f, -0.3313f, 0.5000f }, - { 0.5000f, -0.4187f, -0.0813f }, - }, - /* ITU-R BT.601-7 */ - { - 16, - { 0.2568f, 0.5041f, 0.0979f }, - { -0.1482f, -0.2910f, 0.4392f }, - { 0.4392f, -0.3678f, -0.0714f }, - }, - /* ITU-R BT.709-6 */ - { - 16, - { 0.1826f, 0.6142f, 0.0620f }, - { -0.1006f, -0.3386f, 0.4392f }, - { 0.4392f, -0.3989f, -0.0403f }, - }, - }; const struct RGB2YUVFactors *cvt = &RGB2YUVFactorTables[yuv_type]; #define MAKE_Y(r, g, b) (Uint8)((int)(cvt->y[0] * (r) + cvt->y[1] * (g) + cvt->y[2] * (b) + 0.5f) + cvt->y_offset) @@ -934,6 +995,128 @@ static int SDL_ConvertPixels_ARGB8888_to_YUV(int width, int height, const void * return 0; } +static int SDL_ConvertPixels_XBGR2101010_to_P010(int width, int height, const void *src, int src_pitch, Uint32 dst_format, void *dst, int dst_pitch, YCbCrType yuv_type) +{ + const int src_pitch_x_2 = src_pitch * 2; + const int height_half = height / 2; + const int height_remainder = (height & 0x1); + const int width_half = width / 2; + const int width_remainder = (width & 0x1); + int i, j; + + const struct RGB2YUVFactors *cvt = &RGB2YUVFactorTables[yuv_type]; + +#define MAKE_Y(r, g, b) (Uint16)(((int)(cvt->y[0] * (r) + cvt->y[1] * (g) + cvt->y[2] * (b) + 0.5f) + cvt->y_offset) << 6) +#define MAKE_U(r, g, b) (Uint16)(((int)(cvt->u[0] * (r) + cvt->u[1] * (g) + cvt->u[2] * (b) + 0.5f) + 512) << 6) +#define MAKE_V(r, g, b) (Uint16)(((int)(cvt->v[0] * (r) + cvt->v[1] * (g) + cvt->v[2] * (b) + 0.5f) + 512) << 6) + +#define READ_2x2_PIXELS \ + const Uint32 p1 = ((const Uint32 *)curr_row)[2 * i]; \ + const Uint32 p2 = ((const Uint32 *)curr_row)[2 * i + 1]; \ + const Uint32 p3 = ((const Uint32 *)next_row)[2 * i]; \ + const Uint32 p4 = ((const Uint32 *)next_row)[2 * i + 1]; \ + const Uint32 r = ((p1 & 0x000003ff) + (p2 & 0x000003ff) + (p3 & 0x000003ff) + (p4 & 0x000003ff)) >> 2; \ + const Uint32 g = ((p1 & 0x000ffc00) + (p2 & 0x000ffc00) + (p3 & 0x000ffc00) + (p4 & 0x000ffc00)) >> 12; \ + const Uint32 b = ((p1 & 0x3ff00000) + (p2 & 0x3ff00000) + (p3 & 0x3ff00000) + (p4 & 0x3ff00000)) >> 22; + +#define READ_2x1_PIXELS \ + const Uint32 p1 = ((const Uint32 *)curr_row)[2 * i]; \ + const Uint32 p2 = ((const Uint32 *)next_row)[2 * i]; \ + const Uint32 r = ((p1 & 0x000003ff) + (p2 & 0x000003ff)) >> 1; \ + const Uint32 g = ((p1 & 0x000ffc00) + (p2 & 0x000ffc00)) >> 11; \ + const Uint32 b = ((p1 & 0x3ff00000) + (p2 & 0x3ff00000)) >> 21; + +#define READ_1x2_PIXELS \ + const Uint32 p1 = ((const Uint32 *)curr_row)[2 * i]; \ + const Uint32 p2 = ((const Uint32 *)curr_row)[2 * i + 1]; \ + const Uint32 r = ((p1 & 0x000003ff) + (p2 & 0x000003ff)) >> 1; \ + const Uint32 g = ((p1 & 0x000ffc00) + (p2 & 0x000ffc00)) >> 11; \ + const Uint32 b = ((p1 & 0x3ff00000) + (p2 & 0x3ff00000)) >> 21; + +#define READ_1x1_PIXEL \ + const Uint32 p = ((const Uint32 *)curr_row)[2 * i]; \ + const Uint32 r = (p & 0x000003ff); \ + const Uint32 g = (p & 0x000ffc00) >> 10; \ + const Uint32 b = (p & 0x3ff00000) >> 20; + + const Uint8 *curr_row, *next_row; + + Uint16 *plane_y; + Uint16 *plane_u; + Uint16 *plane_v; + Uint16 *plane_interleaved_uv; + Uint32 y_stride, uv_stride, y_skip, uv_skip; + + if (GetYUVPlanes(width, height, dst_format, dst, dst_pitch, + (const Uint8 **)&plane_y, (const Uint8 **)&plane_u, (const Uint8 **)&plane_v, + &y_stride, &uv_stride) != 0) { + return -1; + } + + y_stride /= sizeof(Uint16); + uv_stride /= sizeof(Uint16); + + plane_interleaved_uv = (plane_y + height * y_stride); + y_skip = (y_stride - width); + + curr_row = (const Uint8 *)src; + + /* Write Y plane */ + for (j = 0; j < height; j++) { + for (i = 0; i < width; i++) { + const Uint32 p1 = ((const Uint32 *)curr_row)[i]; + const Uint32 r = (p1 >> 0) & 0x03ff; + const Uint32 g = (p1 >> 10) & 0x03ff; + const Uint32 b = (p1 >> 20) & 0x03ff; + *plane_y++ = MAKE_Y(r, g, b); + } + plane_y += y_skip; + curr_row += src_pitch; + } + + curr_row = (const Uint8 *)src; + next_row = (const Uint8 *)src; + next_row += src_pitch; + + uv_skip = (uv_stride - ((width + 1) / 2) * 2); + for (j = 0; j < height_half; j++) { + for (i = 0; i < width_half; i++) { + READ_2x2_PIXELS; + *plane_interleaved_uv++ = MAKE_U(r, g, b); + *plane_interleaved_uv++ = MAKE_V(r, g, b); + } + if (width_remainder) { + READ_2x1_PIXELS; + *plane_interleaved_uv++ = MAKE_U(r, g, b); + *plane_interleaved_uv++ = MAKE_V(r, g, b); + } + plane_interleaved_uv += uv_skip; + curr_row += src_pitch_x_2; + next_row += src_pitch_x_2; + } + if (height_remainder) { + for (i = 0; i < width_half; i++) { + READ_1x2_PIXELS; + *plane_interleaved_uv++ = MAKE_U(r, g, b); + *plane_interleaved_uv++ = MAKE_V(r, g, b); + } + if (width_remainder) { + READ_1x1_PIXEL; + *plane_interleaved_uv++ = MAKE_U(r, g, b); + *plane_interleaved_uv++ = MAKE_V(r, g, b); + } + } + +#undef MAKE_Y +#undef MAKE_U +#undef MAKE_V +#undef READ_2x2_PIXELS +#undef READ_2x1_PIXELS +#undef READ_1x2_PIXELS +#undef READ_1x1_PIXEL + return 0; +} + int SDL_ConvertPixels_RGB_to_YUV(int width, int height, Uint32 src_format, SDL_Colorspace src_colorspace, SDL_PropertiesID src_properties, const void *src, int src_pitch, Uint32 dst_format, SDL_Colorspace dst_colorspace, SDL_PropertiesID dst_properties, void *dst, int dst_pitch) @@ -967,6 +1150,34 @@ int SDL_ConvertPixels_RGB_to_YUV(int width, int height, return SDL_ConvertPixels_ARGB8888_to_YUV(width, height, src, src_pitch, dst_format, dst, dst_pitch, yuv_type); } + if (dst_format == SDL_PIXELFORMAT_P010) { + if (src_format == SDL_PIXELFORMAT_XBGR2101010) { + return SDL_ConvertPixels_XBGR2101010_to_P010(width, height, src, src_pitch, dst_format, dst, dst_pitch, yuv_type); + } + + /* We currently only support converting from XBGR2101010 to P010 */ + int ret; + void *tmp; + int tmp_pitch = (width * sizeof(Uint32)); + + tmp = SDL_malloc((size_t)tmp_pitch * height); + if (!tmp) { + return -1; + } + + /* convert src/src_format to tmp/XBGR2101010 */ + ret = SDL_ConvertPixelsAndColorspace(width, height, src_format, src_colorspace, src_properties, src, src_pitch, SDL_PIXELFORMAT_XBGR2101010, dst_colorspace, dst_properties, tmp, tmp_pitch); + if (ret == -1) { + SDL_free(tmp); + return ret; + } + + /* convert tmp/XBGR2101010 to dst/P010 */ + ret = SDL_ConvertPixels_XBGR2101010_to_P010(width, height, tmp, tmp_pitch, dst_format, dst, dst_pitch, yuv_type); + SDL_free(tmp); + return ret; + } + /* not ARGB8888 to FOURCC : need an intermediate conversion */ { int ret; @@ -979,7 +1190,7 @@ int SDL_ConvertPixels_RGB_to_YUV(int width, int height, } /* convert src/src_format to tmp/ARGB8888 */ - ret = SDL_ConvertPixels(width, height, src_format, src, src_pitch, SDL_PIXELFORMAT_ARGB8888, tmp, tmp_pitch); + ret = SDL_ConvertPixelsAndColorspace(width, height, src_format, src_colorspace, src_properties, src, src_pitch, SDL_PIXELFORMAT_ARGB8888, dst_colorspace, dst_properties, tmp, tmp_pitch); if (ret == -1) { SDL_free(tmp); return ret; @@ -1027,6 +1238,17 @@ static int SDL_ConvertPixels_YUV_to_YUV_Copy(int width, int height, Uint32 forma src = (const Uint8 *)src + src_pitch; dst = (Uint8 *)dst + dst_pitch; } + } else if (format == SDL_PIXELFORMAT_P010) { + /* U/V plane is half the height of the Y plane, rounded up */ + height = (height + 1) / 2; + width = ((width + 1) / 2) * 2; + src_pitch = ((src_pitch + 1) / 2) * 2; + dst_pitch = ((dst_pitch + 1) / 2) * 2; + for (i = height; i--;) { + SDL_memcpy(dst, src, width * sizeof(Uint16)); + src = (const Uint8 *)src + src_pitch; + dst = (Uint8 *)dst + dst_pitch; + } } return 0; } diff --git a/src/video/yuv2rgb/yuv_rgb_common.h b/src/video/yuv2rgb/yuv_rgb_common.h index ae787ed5f..1ab607d01 100644 --- a/src/video/yuv2rgb/yuv_rgb_common.h +++ b/src/video/yuv2rgb/yuv_rgb_common.h @@ -7,7 +7,8 @@ typedef enum { YCBCR_JPEG, YCBCR_601, - YCBCR_709 + YCBCR_709, + YCBCR_2020 } YCbCrType; #endif /* YUV_RGB_COMMON_H_ */ diff --git a/src/video/yuv2rgb/yuv_rgb_internal.h b/src/video/yuv2rgb/yuv_rgb_internal.h index 31405a6d0..23ae70566 100644 --- a/src/video/yuv2rgb/yuv_rgb_internal.h +++ b/src/video/yuv2rgb/yuv_rgb_internal.h @@ -37,24 +37,29 @@ typedef struct // for ITU-T T.871, values can be found in section 7 // for ITU-R BT.601-7 values are derived from equations in sections 2.5.1-2.5.3, assuming RGB is encoded using full range ([0-1]<->[0-255]) // for ITU-R BT.709-6 values are derived from equations in sections 3.2-3.4, assuming RGB is encoded using full range ([0-1]<->[0-255]) +// for ITU-R BT.2020 values are assuming RGB is encoded using full 10-bit range ([0-1]<->[0-1023]) // all values are rounded to the fourth decimal -static const YUV2RGBParam YUV2RGB[3] = { +static const YUV2RGBParam YUV2RGB[4] = { // ITU-T T.871 (JPEG) {/*.y_shift=*/ 0, /*.y_factor=*/ V(1.0), /*.v_r_factor=*/ V(1.402), /*.u_g_factor=*/ -V(0.3441), /*.v_g_factor=*/ -V(0.7141), /*.u_b_factor=*/ V(1.772)}, // ITU-R BT.601-7 {/*.y_shift=*/ 16, /*.y_factor=*/ V(1.1644), /*.v_r_factor=*/ V(1.596), /*.u_g_factor=*/ -V(0.3918), /*.v_g_factor=*/ -V(0.813), /*.u_b_factor=*/ V(2.0172)}, // ITU-R BT.709-6 - {/*.y_shift=*/ 16, /*.y_factor=*/ V(1.1644), /*.v_r_factor=*/ V(1.7927), /*.u_g_factor=*/ -V(0.2132), /*.v_g_factor=*/ -V(0.5329), /*.u_b_factor=*/ V(2.1124)} + {/*.y_shift=*/ 16, /*.y_factor=*/ V(1.1644), /*.v_r_factor=*/ V(1.7927), /*.u_g_factor=*/ -V(0.2132), /*.v_g_factor=*/ -V(0.5329), /*.u_b_factor=*/ V(2.1124)}, + // ITU-R BT.2020 10-bit full range + {/*.y_shift=*/ 0, /*.y_factor=*/ V(1.0), /*.v_r_factor=*/ V(1.4760), /*.u_g_factor=*/ -V(0.1647), /*.v_g_factor=*/ -V(0.5719), /*.u_b_factor=*/ V(1.8832) } }; -static const RGB2YUVParam RGB2YUV[3] = { +static const RGB2YUVParam RGB2YUV[4] = { // ITU-T T.871 (JPEG) {/*.y_shift=*/ 0, /*.matrix=*/ {{V(0.299), V(0.587), V(0.114)}, {-V(0.1687), -V(0.3313), V(0.5)}, {V(0.5), -V(0.4187), -V(0.0813)}}}, // ITU-R BT.601-7 {/*.y_shift=*/ 16, /*.matrix=*/ {{V(0.2568), V(0.5041), V(0.0979)}, {-V(0.1482), -V(0.291), V(0.4392)}, {V(0.4392), -V(0.3678), -V(0.0714)}}}, // ITU-R BT.709-6 - {/*.y_shift=*/ 16, /*.matrix=*/ {{V(0.1826), V(0.6142), V(0.062)}, {-V(0.1006), -V(0.3386), V(0.4392)}, {V(0.4392), -V(0.3989), -V(0.0403)}}} + {/*.y_shift=*/ 16, /*.matrix=*/ {{V(0.1826), V(0.6142), V(0.062)}, {-V(0.1006), -V(0.3386), V(0.4392)}, {V(0.4392), -V(0.3989), -V(0.0403)}}}, + // ITU-R BT.2020 10-bit full range + {/*.y_shift=*/ 0, /*.matrix=*/ {{V(0.2627), V(0.6780), V(0.0593)}, {-V(0.1395), -V(0.3600), V(0.4995)}, {V(0.4995), -V(0.4593), -V(0.0402)}}}, }; #ifdef _MSC_VER @@ -73,3 +78,4 @@ static const RGB2YUVParam RGB2YUV[3] = { #define RGB_FORMAT_BGRA 4 #define RGB_FORMAT_ARGB 5 #define RGB_FORMAT_ABGR 6 +#define RGB_FORMAT_XBGR2101010 7 diff --git a/src/video/yuv2rgb/yuv_rgb_std.c b/src/video/yuv2rgb/yuv_rgb_std.c index 4251b7f20..2641fdd1e 100644 --- a/src/video/yuv2rgb/yuv_rgb_std.c +++ b/src/video/yuv2rgb/yuv_rgb_std.c @@ -28,6 +28,19 @@ static uint8_t clampU8(int32_t v) return lut[((v+128*PRECISION_FACTOR)>>PRECISION)&511]; } +static uint16_t clamp10(int32_t v) +{ + v >>= PRECISION; + if (v < 0) { + return 0; + } else if (v > 1023) { + return 1023; + } else { + return (uint16_t)v; + } +} + +#define YUV_BITS 8 #define STD_FUNCTION_NAME yuv420_rgb565_std #define YUV_FORMAT YUV_FORMAT_420 @@ -119,6 +132,14 @@ static uint8_t clampU8(int32_t v) #define RGB_FORMAT RGB_FORMAT_ABGR #include "yuv_rgb_std_func.h" +#undef YUV_BITS +#define YUV_BITS 10 + +#define STD_FUNCTION_NAME yuvp010_xbgr2101010_std +#define YUV_FORMAT YUV_FORMAT_NV12 +#define RGB_FORMAT RGB_FORMAT_XBGR2101010 +#include "yuv_rgb_std_func.h" + void rgb24_yuv420_std( uint32_t width, uint32_t height, const uint8_t *RGB, uint32_t RGB_stride, diff --git a/src/video/yuv2rgb/yuv_rgb_std.h b/src/video/yuv2rgb/yuv_rgb_std.h index 5791b7aa2..c9f856ba9 100644 --- a/src/video/yuv2rgb/yuv_rgb_std.h +++ b/src/video/yuv2rgb/yuv_rgb_std.h @@ -129,6 +129,12 @@ void yuvnv12_abgr_std( uint8_t *rgb, uint32_t rgb_stride, YCbCrType yuv_type); +void yuvp010_xbgr2101010_std( + uint32_t width, uint32_t height, + const uint16_t *y, const uint16_t *u, const uint16_t *v, uint32_t y_stride, uint32_t uv_stride, + uint8_t *rgb, uint32_t rgb_stride, + YCbCrType yuv_type); + // rgb to yuv, standard c implementation void rgb24_yuv420_std( uint32_t width, uint32_t height, diff --git a/src/video/yuv2rgb/yuv_rgb_std_func.h b/src/video/yuv2rgb/yuv_rgb_std_func.h index f359abae8..8091ea9ab 100644 --- a/src/video/yuv2rgb/yuv_rgb_std_func.h +++ b/src/video/yuv2rgb/yuv_rgb_std_func.h @@ -64,6 +64,16 @@ (((Uint32)clampU8(y_tmp+r_tmp)) << 0); \ rgb_ptr += 4; \ +#elif RGB_FORMAT == RGB_FORMAT_XBGR2101010 + +#define PACK_PIXEL(rgb_ptr) \ + *(Uint32 *)rgb_ptr = \ + 0xC0000000 | \ + (((Uint32)clamp10(y_tmp+b_tmp)) << 20) | \ + (((Uint32)clamp10(y_tmp+g_tmp)) << 10) | \ + (((Uint32)clamp10(y_tmp+r_tmp)) << 0); \ + rgb_ptr += 4; \ + #else #error PACK_PIXEL unimplemented #endif @@ -74,9 +84,25 @@ #pragma warning(disable : 6239) #endif +#undef YUV_TYPE +#if YUV_BITS > 8 +#define YUV_TYPE uint16_t +#else +#define YUV_TYPE uint8_t +#endif +#undef UV_OFFSET +#define UV_OFFSET (1 << ((YUV_BITS)-1)) + +#undef GET +#if YUV_BITS == 10 +#define GET(X) ((X) >> 6) +#else +#define GET(X) (X) +#endif + void STD_FUNCTION_NAME( uint32_t width, uint32_t height, - const uint8_t *Y, const uint8_t *U, const uint8_t *V, uint32_t Y_stride, uint32_t UV_stride, + const YUV_TYPE *Y, const YUV_TYPE *U, const YUV_TYPE *V, uint32_t Y_stride, uint32_t UV_stride, uint8_t *RGB, uint32_t RGB_stride, YCbCrType yuv_type) { @@ -98,29 +124,32 @@ void STD_FUNCTION_NAME( #define uv_y_sample_interval 2 #endif + Y_stride /= sizeof(YUV_TYPE); + UV_stride /= sizeof(YUV_TYPE); + uint32_t x, y; for(y=0; y<(height-(uv_y_sample_interval-1)); y+=uv_y_sample_interval) { - const uint8_t *y_ptr1=Y+y*Y_stride, + const YUV_TYPE *y_ptr1=Y+y*Y_stride, *u_ptr=U+(y/uv_y_sample_interval)*UV_stride, *v_ptr=V+(y/uv_y_sample_interval)*UV_stride; #if uv_y_sample_interval > 1 - const uint8_t *y_ptr2=Y+(y+1)*Y_stride; + const YUV_TYPE *y_ptr2=Y+(y+1)*Y_stride; #endif uint8_t *rgb_ptr1=RGB+y*RGB_stride; #if uv_y_sample_interval > 1 - uint8_t *rgb_ptr2=RGB+(y+1)*RGB_stride; + uint8_t *rgb_ptr2=RGB+(y+1)*RGB_stride; #endif for(x=0; x<(width-(uv_x_sample_interval-1)); x+=uv_x_sample_interval) { // Compute U and V contributions, common to the four pixels - int32_t u_tmp = ((*u_ptr)-128); - int32_t v_tmp = ((*v_ptr)-128); + int32_t u_tmp = (GET(*u_ptr)-UV_OFFSET); + int32_t v_tmp = (GET(*v_ptr)-UV_OFFSET); int32_t r_tmp = (v_tmp*param->v_r_factor); int32_t g_tmp = (u_tmp*param->u_g_factor + v_tmp*param->v_g_factor); @@ -128,17 +157,17 @@ void STD_FUNCTION_NAME( // Compute the Y contribution for each pixel - int32_t y_tmp = ((y_ptr1[0]-param->y_shift)*param->y_factor); + int32_t y_tmp = (GET(y_ptr1[0]-param->y_shift)*param->y_factor); PACK_PIXEL(rgb_ptr1); - y_tmp = ((y_ptr1[y_pixel_stride]-param->y_shift)*param->y_factor); + y_tmp = (GET(y_ptr1[y_pixel_stride]-param->y_shift)*param->y_factor); PACK_PIXEL(rgb_ptr1); #if uv_y_sample_interval > 1 - y_tmp = ((y_ptr2[0]-param->y_shift)*param->y_factor); + y_tmp = (GET(y_ptr2[0]-param->y_shift)*param->y_factor); PACK_PIXEL(rgb_ptr2); - y_tmp = ((y_ptr2[y_pixel_stride]-param->y_shift)*param->y_factor); + y_tmp = (GET(y_ptr2[y_pixel_stride]-param->y_shift)*param->y_factor); PACK_PIXEL(rgb_ptr2); #endif @@ -155,8 +184,8 @@ void STD_FUNCTION_NAME( { // Compute U and V contributions, common to the four pixels - int32_t u_tmp = ((*u_ptr)-128); - int32_t v_tmp = ((*v_ptr)-128); + int32_t u_tmp = (GET(*u_ptr)-UV_OFFSET); + int32_t v_tmp = (GET(*v_ptr)-UV_OFFSET); int32_t r_tmp = (v_tmp*param->v_r_factor); int32_t g_tmp = (u_tmp*param->u_g_factor + v_tmp*param->v_g_factor); @@ -164,11 +193,11 @@ void STD_FUNCTION_NAME( // Compute the Y contribution for each pixel - int32_t y_tmp = ((y_ptr1[0]-param->y_shift)*param->y_factor); + int32_t y_tmp = (GET(y_ptr1[0]-param->y_shift)*param->y_factor); PACK_PIXEL(rgb_ptr1); #if uv_y_sample_interval > 1 - y_tmp = ((y_ptr2[0]-param->y_shift)*param->y_factor); + y_tmp = (GET(y_ptr2[0]-param->y_shift)*param->y_factor); PACK_PIXEL(rgb_ptr2); #endif } @@ -177,7 +206,7 @@ void STD_FUNCTION_NAME( /* Catch the last line, if needed */ if (uv_y_sample_interval == 2 && y == (height-1)) { - const uint8_t *y_ptr1=Y+y*Y_stride, + const YUV_TYPE *y_ptr1=Y+y*Y_stride, *u_ptr=U+(y/uv_y_sample_interval)*UV_stride, *v_ptr=V+(y/uv_y_sample_interval)*UV_stride; @@ -187,8 +216,8 @@ void STD_FUNCTION_NAME( { // Compute U and V contributions, common to the four pixels - int32_t u_tmp = ((*u_ptr)-128); - int32_t v_tmp = ((*v_ptr)-128); + int32_t u_tmp = (GET(*u_ptr)-UV_OFFSET); + int32_t v_tmp = (GET(*v_ptr)-UV_OFFSET); int32_t r_tmp = (v_tmp*param->v_r_factor); int32_t g_tmp = (u_tmp*param->u_g_factor + v_tmp*param->v_g_factor); @@ -196,10 +225,10 @@ void STD_FUNCTION_NAME( // Compute the Y contribution for each pixel - int32_t y_tmp = ((y_ptr1[0]-param->y_shift)*param->y_factor); + int32_t y_tmp = (GET(y_ptr1[0]-param->y_shift)*param->y_factor); PACK_PIXEL(rgb_ptr1); - y_tmp = ((y_ptr1[y_pixel_stride]-param->y_shift)*param->y_factor); + y_tmp = (GET(y_ptr1[y_pixel_stride]-param->y_shift)*param->y_factor); PACK_PIXEL(rgb_ptr1); y_ptr1+=2*y_pixel_stride; @@ -212,8 +241,8 @@ void STD_FUNCTION_NAME( { // Compute U and V contributions, common to the four pixels - int32_t u_tmp = ((*u_ptr)-128); - int32_t v_tmp = ((*v_ptr)-128); + int32_t u_tmp = (GET(*u_ptr)-UV_OFFSET); + int32_t v_tmp = (GET(*v_ptr)-UV_OFFSET); int32_t r_tmp = (v_tmp*param->v_r_factor); int32_t g_tmp = (u_tmp*param->u_g_factor + v_tmp*param->v_g_factor); @@ -221,7 +250,7 @@ void STD_FUNCTION_NAME( // Compute the Y contribution for each pixel - int32_t y_tmp = ((y_ptr1[0]-param->y_shift)*param->y_factor); + int32_t y_tmp = (GET(y_ptr1[0]-param->y_shift)*param->y_factor); PACK_PIXEL(rgb_ptr1); } } diff --git a/test/testyuv.c b/test/testyuv.c index 2974164d6..e452466b1 100644 --- a/test/testyuv.c +++ b/test/testyuv.c @@ -15,8 +15,8 @@ #include "testyuv_cvt.h" #include "testutils.h" -/* 422 (YUY2, etc) formats are the largest */ -#define MAX_YUV_SURFACE_SIZE(W, H, P) (H * 4 * (W + P + 1) / 2) +/* 422 (YUY2, etc) and P010 formats are the largest */ +#define MAX_YUV_SURFACE_SIZE(W, H, P) ((H + 1) * ((W + 1) + P) * 4) /* Return true if the YUV format is packed pixels */ static SDL_bool is_packed_yuv_format(Uint32 format) @@ -65,9 +65,8 @@ static SDL_Surface *generate_test_pattern(int pattern_size) return pattern; } -static SDL_bool verify_yuv_data(Uint32 format, SDL_Colorspace colorspace, const Uint8 *yuv, int yuv_pitch, SDL_Surface *surface) +static SDL_bool verify_yuv_data(Uint32 format, SDL_Colorspace colorspace, SDL_PropertiesID props, const Uint8 *yuv, int yuv_pitch, SDL_Surface *surface, int tolerance) { - const int tolerance = 20; const int size = (surface->h * surface->pitch); Uint8 *rgb; SDL_bool result = SDL_FALSE; @@ -78,7 +77,7 @@ static SDL_bool verify_yuv_data(Uint32 format, SDL_Colorspace colorspace, const return SDL_FALSE; } - if (SDL_ConvertPixelsAndColorspace(surface->w, surface->h, format, colorspace, 0, yuv, yuv_pitch, surface->format->format, SDL_COLORSPACE_SRGB, 0, rgb, surface->pitch) == 0) { + if (SDL_ConvertPixelsAndColorspace(surface->w, surface->h, format, colorspace, props, yuv, yuv_pitch, surface->format->format, SDL_COLORSPACE_SRGB, 0, rgb, surface->pitch) == 0) { int x, y; result = SDL_TRUE; for (y = 0; y < surface->h; ++y) { @@ -124,6 +123,9 @@ static int run_automated_tests(int pattern_size, int extra_pitch) int yuv1_pitch, yuv2_pitch; YUV_CONVERSION_MODE mode; SDL_Colorspace colorspace; + SDL_PropertiesID props; + const int tight_tolerance = 20; + const int loose_tolerance = 333; int result = -1; if (!pattern || !yuv1 || !yuv2) { @@ -134,6 +136,10 @@ static int run_automated_tests(int pattern_size, int extra_pitch) mode = GetYUVConversionModeForResolution(pattern->w, pattern->h); colorspace = GetColorspaceForYUVConversionMode(mode); + /* All tests are being done with SDR content */ + props = SDL_CreateProperties(); + SDL_SetFloatProperty(props, SDL_PROP_SURFACE_HDR_HEADROOM_FLOAT, 1.0f); + /* Verify conversion from YUV formats */ for (i = 0; i < SDL_arraysize(formats); ++i) { if (!ConvertRGBtoYUV(formats[i], pattern->pixels, pattern->pitch, yuv1, pattern->w, pattern->h, mode, 0, 100)) { @@ -141,7 +147,7 @@ static int run_automated_tests(int pattern_size, int extra_pitch) goto done; } yuv1_pitch = CalculateYUVPitch(formats[i], pattern->w); - if (!verify_yuv_data(formats[i], colorspace, yuv1, yuv1_pitch, pattern)) { + if (!verify_yuv_data(formats[i], colorspace, props, yuv1, yuv1_pitch, pattern, tight_tolerance)) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed conversion from %s to RGB\n", SDL_GetPixelFormatName(formats[i])); goto done; } @@ -154,7 +160,7 @@ static int run_automated_tests(int pattern_size, int extra_pitch) SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't convert %s to %s: %s\n", SDL_GetPixelFormatName(pattern->format->format), SDL_GetPixelFormatName(formats[i]), SDL_GetError()); goto done; } - if (!verify_yuv_data(formats[i], colorspace, yuv1, yuv1_pitch, pattern)) { + if (!verify_yuv_data(formats[i], colorspace, props, yuv1, yuv1_pitch, pattern, tight_tolerance)) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed conversion from RGB to %s\n", SDL_GetPixelFormatName(formats[i])); goto done; } @@ -173,7 +179,7 @@ static int run_automated_tests(int pattern_size, int extra_pitch) SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't convert %s to %s: %s\n", SDL_GetPixelFormatName(formats[i]), SDL_GetPixelFormatName(formats[j]), SDL_GetError()); goto done; } - if (!verify_yuv_data(formats[j], colorspace, yuv2, yuv2_pitch, pattern)) { + if (!verify_yuv_data(formats[j], colorspace, props, yuv2, yuv2_pitch, pattern, tight_tolerance)) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed conversion from %s to %s\n", SDL_GetPixelFormatName(formats[i]), SDL_GetPixelFormatName(formats[j])); goto done; } @@ -198,13 +204,37 @@ static int run_automated_tests(int pattern_size, int extra_pitch) SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't convert %s to %s: %s\n", SDL_GetPixelFormatName(formats[i]), SDL_GetPixelFormatName(formats[j]), SDL_GetError()); goto done; } - if (!verify_yuv_data(formats[j], colorspace, yuv1, yuv2_pitch, pattern)) { + if (!verify_yuv_data(formats[j], colorspace, props, yuv1, yuv2_pitch, pattern, tight_tolerance)) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed conversion from %s to %s\n", SDL_GetPixelFormatName(formats[i]), SDL_GetPixelFormatName(formats[j])); goto done; } } } + /* Verify round trip through BT.2020 */ + colorspace = SDL_COLORSPACE_BT2020_FULL; + if (!ConvertRGBtoYUV(SDL_PIXELFORMAT_P010, pattern->pixels, pattern->pitch, yuv1, pattern->w, pattern->h, YUV_CONVERSION_BT2020, 0, 100)) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "ConvertRGBtoYUV() doesn't support converting to %s\n", SDL_GetPixelFormatName(SDL_PIXELFORMAT_P010)); + goto done; + } + yuv1_pitch = CalculateYUVPitch(SDL_PIXELFORMAT_P010, pattern->w); + if (!verify_yuv_data(SDL_PIXELFORMAT_P010, colorspace, props, yuv1, yuv1_pitch, pattern, tight_tolerance)) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed conversion from %s to RGB\n", SDL_GetPixelFormatName(SDL_PIXELFORMAT_P010)); + goto done; + } + + /* The pitch needs to be Uint16 aligned for P010 pixels */ + yuv1_pitch = CalculateYUVPitch(SDL_PIXELFORMAT_P010, pattern->w) + ((extra_pitch + 1) & ~1); + if (SDL_ConvertPixelsAndColorspace(pattern->w, pattern->h, pattern->format->format, SDL_COLORSPACE_SRGB, 0, pattern->pixels, pattern->pitch, SDL_PIXELFORMAT_P010, colorspace, props, yuv1, yuv1_pitch) < 0) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't convert %s to %s: %s\n", SDL_GetPixelFormatName(pattern->format->format), SDL_GetPixelFormatName(SDL_PIXELFORMAT_P010), SDL_GetError()); + goto done; + } + /* Going through XRGB2101010 format during P010 conversion is slightly lossy, so use looser tolerance here */ + if (!verify_yuv_data(SDL_PIXELFORMAT_P010, colorspace, props, yuv1, yuv1_pitch, pattern, loose_tolerance)) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed conversion from RGB to %s\n", SDL_GetPixelFormatName(SDL_PIXELFORMAT_P010)); + goto done; + } + result = 0; done: @@ -222,6 +252,8 @@ int main(int argc, char **argv) int pattern_size; int extra_pitch; } automated_test_params[] = { + /* Test: single pixel */ + { SDL_FALSE, 1, 0 }, /* Test: even width and height */ { SDL_FALSE, 2, 0 }, { SDL_FALSE, 4, 0 }, @@ -258,9 +290,10 @@ int main(int argc, char **argv) const char *yuv_mode_name; Uint32 yuv_format = SDL_PIXELFORMAT_YV12; const char *yuv_format_name; + SDL_Colorspace yuv_colorspace; Uint32 rgb_format = SDL_PIXELFORMAT_RGBX8888; + SDL_Colorspace rgb_colorspace = SDL_COLORSPACE_SRGB; SDL_PropertiesID props; - SDL_Colorspace colorspace; SDL_bool monochrome = SDL_FALSE; int luminance = 100; int current = 0; @@ -295,6 +328,9 @@ int main(int argc, char **argv) } else if (SDL_strcmp(argv[i], "--bt709") == 0) { SetYUVConversionMode(YUV_CONVERSION_BT709); consumed = 1; + } else if (SDL_strcmp(argv[i], "--bt2020") == 0) { + SetYUVConversionMode(YUV_CONVERSION_BT2020); + consumed = 1; } else if (SDL_strcmp(argv[i], "--auto") == 0) { SetYUVConversionMode(YUV_CONVERSION_AUTOMATIC); consumed = 1; @@ -356,7 +392,7 @@ int main(int argc, char **argv) } if (consumed <= 0) { static const char *options[] = { - "[--jpeg|--bt601|--bt709|--auto]", + "[--jpeg|--bt601|--bt709|--bt2020|--auto]", "[--yv12|--iyuv|--yuy2|--uyvy|--yvyu|--nv12|--nv21]", "[--rgb555|--rgb565|--rgb24|--argb|--abgr|--rgba|--bgra]", "[--monochrome] [--luminance N%]", @@ -405,11 +441,17 @@ int main(int argc, char **argv) case YUV_CONVERSION_BT709: yuv_mode_name = "BT.709"; break; + case YUV_CONVERSION_BT2020: + yuv_mode_name = "BT.2020"; + yuv_format = SDL_PIXELFORMAT_P010; + rgb_format = SDL_PIXELFORMAT_XBGR2101010; + rgb_colorspace = SDL_COLORSPACE_HDR10; + break; default: yuv_mode_name = "UNKNOWN"; break; } - colorspace = GetColorspaceForYUVConversionMode(yuv_mode); + yuv_colorspace = GetColorspaceForYUVConversionMode(yuv_mode); raw_yuv = SDL_calloc(1, MAX_YUV_SURFACE_SIZE(original->w, original->h, 0)); ConvertRGBtoYUV(yuv_format, original->pixels, original->pitch, raw_yuv, original->w, original->h, yuv_mode, monochrome, luminance); @@ -421,9 +463,13 @@ int main(int argc, char **argv) return 3; } + /* All tests are being done with SDR content */ + props = SDL_GetSurfaceProperties(converted); + SDL_SetFloatProperty(props, SDL_PROP_SURFACE_HDR_HEADROOM_FLOAT, 1.0f); + then = SDL_GetTicks(); for (i = 0; i < iterations; ++i) { - SDL_ConvertPixelsAndColorspace(original->w, original->h, yuv_format, colorspace, 0, raw_yuv, pitch, rgb_format, SDL_COLORSPACE_SRGB, 0, converted->pixels, converted->pitch); + SDL_ConvertPixelsAndColorspace(original->w, original->h, yuv_format, yuv_colorspace, props, raw_yuv, pitch, rgb_format, rgb_colorspace, props, converted->pixels, converted->pitch); } now = SDL_GetTicks(); SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "%d iterations in %" SDL_PRIu64 " ms, %.2fms each\n", iterations, (now - then), (float)(now - then) / iterations); @@ -443,7 +489,8 @@ int main(int argc, char **argv) output[0] = SDL_CreateTextureFromSurface(renderer, original); output[1] = SDL_CreateTextureFromSurface(renderer, converted); props = SDL_CreateProperties(); - SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_COLORSPACE_NUMBER, colorspace); + SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_COLORSPACE_NUMBER, yuv_colorspace); + SDL_SetFloatProperty(props, SDL_PROP_TEXTURE_CREATE_HDR_HEADROOM_FLOAT, 1.0f); SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_FORMAT_NUMBER, yuv_format); SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_ACCESS_NUMBER, SDL_TEXTUREACCESS_STREAMING); SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_WIDTH_NUMBER, original->w); diff --git a/test/testyuv_cvt.c b/test/testyuv_cvt.c index c165b3218..08e8c6ff7 100644 --- a/test/testyuv_cvt.c +++ b/test/testyuv_cvt.c @@ -70,6 +70,9 @@ SDL_Colorspace GetColorspaceForYUVConversionMode(YUV_CONVERSION_MODE mode) SDL_MATRIX_COEFFICIENTS_BT709, SDL_CHROMA_LOCATION_CENTER); break; + case YUV_CONVERSION_BT2020: + colorspace = SDL_COLORSPACE_BT2020_FULL; + break; default: colorspace = SDL_COLORSPACE_UNKNOWN; break; @@ -82,7 +85,54 @@ static float clip3(float x, float y, float z) return (z < x) ? x : ((z > y) ? y : z); } -static void RGBtoYUV(const Uint8 *rgb, int *yuv, YUV_CONVERSION_MODE mode, int monochrome, int luminance) +static float sRGBtoNits(float v) +{ + /* Normalize from 0..255 */ + v /= 255.0f; + + /* Convert from sRGB */ + v = v <= 0.04045f ? (v / 12.92f) : SDL_powf(((v + 0.055f) / 1.055f), 2.4f); + + /* Convert to nits, using a default SDR whitepoint of 203 */ + v *= 203.0f; + + return v; +} + +static float PQfromNits(float v) +{ + const float c1 = 0.8359375f; + const float c2 = 18.8515625f; + const float c3 = 18.6875f; + const float m1 = 0.1593017578125f; + const float m2 = 78.84375f; + + float y = SDL_clamp(v / 10000.0f, 0.0f, 1.0f); + float num = c1 + c2 * SDL_powf(y, m1); + float den = 1.0f + c3 * SDL_powf(y, m1); + return SDL_powf(num / den, m2); +} + +void ConvertRec709toRec2020(float *fR, float *fG, float *fB) +{ + static const float mat709to2020[] = { + 0.627404f, 0.329283f, 0.043313f, + 0.069097f, 0.919541f, 0.011362f, + 0.016391f, 0.088013f, 0.895595f, + }; + const float *matrix = mat709to2020; + float v[3]; + + v[0] = *fR; + v[1] = *fG; + v[2] = *fB; + + *fR = matrix[0 * 3 + 0] * v[0] + matrix[0 * 3 + 1] * v[1] + matrix[0 * 3 + 2] * v[2]; + *fG = matrix[1 * 3 + 0] * v[0] + matrix[1 * 3 + 1] * v[1] + matrix[1 * 3 + 2] * v[2]; + *fB = matrix[2 * 3 + 0] * v[0] + matrix[2 * 3 + 1] * v[1] + matrix[2 * 3 + 2] * v[2]; +} + +static void RGBtoYUV(const Uint8 *rgb, int rgb_bits, int *yuv, int yuv_bits, YUV_CONVERSION_MODE mode, int monochrome, int luminance) { /** * This formula is from Microsoft's documentation: @@ -93,56 +143,82 @@ static void RGBtoYUV(const Uint8 *rgb, int *yuv, YUV_CONVERSION_MODE mode, int m * V = clip3(0, (2^M)-1, floor(2^(M-8) * (112*(R-L) / ((1-Kr)*S) + 128) + 0.5)); */ SDL_bool studio_RGB = SDL_FALSE; - SDL_bool studio_YUV = SDL_FALSE; + SDL_bool full_range_YUV = SDL_FALSE; float N, M, S, Z, R, G, B, L, Kr, Kb, Y, U, V; - N = 8.0f; /* 8 bit RGB */ - M = 8.0f; /* 8 bit YUV */ - if (mode == YUV_CONVERSION_BT709) { - /* BT.709 */ - Kr = 0.2126f; - Kb = 0.0722f; - } else { + N = (float)rgb_bits; + M = (float)yuv_bits; + switch (mode) { + case YUV_CONVERSION_JPEG: + case YUV_CONVERSION_BT601: /* BT.601 */ Kr = 0.299f; Kb = 0.114f; + break; + case YUV_CONVERSION_BT709: + /* BT.709 */ + Kr = 0.2126f; + Kb = 0.0722f; + break; + case YUV_CONVERSION_BT2020: + /* BT.2020 */ + Kr = 0.2627f; + Kb = 0.0593f; + break; + default: + /* Invalid */ + Kr = 1.0f; + Kb = 1.0f; + break; } - if (mode == YUV_CONVERSION_JPEG) { - studio_YUV = SDL_FALSE; - } else { - studio_YUV = SDL_TRUE; + R = rgb[0]; + G = rgb[1]; + B = rgb[2]; + + if (mode == YUV_CONVERSION_JPEG || mode == YUV_CONVERSION_BT2020) { + full_range_YUV = SDL_TRUE; } - if (studio_RGB || !studio_YUV) { + if (mode == YUV_CONVERSION_BT2020) { + /* Input is sRGB, need to convert to BT.2020 PQ YUV */ + R = sRGBtoNits(R); + G = sRGBtoNits(G); + B = sRGBtoNits(B); + ConvertRec709toRec2020(&R, &G, &B); + R = PQfromNits(R); + G = PQfromNits(G); + B = PQfromNits(B); + S = 1.0f; + Z = 0.0f; + } else if (studio_RGB) { S = 219.0f * SDL_powf(2.0f, N - 8); Z = 16.0f * SDL_powf(2.0f, N - 8); } else { S = 255.0f; Z = 0.0f; } - R = rgb[0]; - G = rgb[1]; - B = rgb[2]; L = Kr * R + Kb * B + (1 - Kr - Kb) * G; - Y = SDL_floorf(SDL_powf(2.0f, (M - 8)) * (219.0f * (L - Z) / S + 16) + 0.5f); - U = clip3(0, SDL_powf(2.0f, M) - 1, SDL_floorf(SDL_powf(2.0f, (M - 8)) * (112.0f * (B - L) / ((1.0f - Kb) * S) + 128) + 0.5f)); - V = clip3(0, SDL_powf(2.0f, M) - 1, SDL_floorf(SDL_powf(2.0f, (M - 8)) * (112.0f * (R - L) / ((1.0f - Kr) * S) + 128) + 0.5f)); + if (monochrome) { + R = L; + B = L; + } + if (full_range_YUV) { + Y = SDL_floorf((SDL_powf(2.0f, M) - 1) * ((L - Z) / S) + 0.5f); + U = clip3(0, SDL_powf(2.0f, M) - 1, SDL_floorf((SDL_powf(2.0f, M) / 2 - 1) * ((B - L) / ((1.0f - Kb) * S)) + SDL_powf(2.0f, M) / 2 + 0.5f)); + V = clip3(0, SDL_powf(2.0f, M) - 1, SDL_floorf((SDL_powf(2.0f, M) / 2 - 1) * ((R - L) / ((1.0f - Kr) * S)) + SDL_powf(2.0f, M) / 2 + 0.5f)); + } else { + Y = SDL_floorf(SDL_powf(2.0f, (M - 8)) * (219.0f * (L - Z) / S + 16) + 0.5f); + U = clip3(0, SDL_powf(2.0f, M) - 1, SDL_floorf(SDL_powf(2.0f, (M - 8)) * (112.0f * (B - L) / ((1.0f - Kb) * S) + 128) + 0.5f)); + V = clip3(0, SDL_powf(2.0f, M) - 1, SDL_floorf(SDL_powf(2.0f, (M - 8)) * (112.0f * (R - L) / ((1.0f - Kr) * S) + 128) + 0.5f)); + } yuv[0] = (int)Y; yuv[1] = (int)U; yuv[2] = (int)V; - if (monochrome) { - yuv[1] = 128; - yuv[2] = 128; - } - if (luminance != 100) { - yuv[0] = (int)SDL_roundf(yuv[0] * (luminance / 100.0f)); - if (yuv[0] > 255) { - yuv[0] = 255; - } + yuv[0] = (int)clip3(0, SDL_powf(2.0f, M) - 1, SDL_roundf(yuv[0] * (luminance / 100.0f))); } } @@ -188,19 +264,19 @@ static void ConvertRGBtoPlanar2x2(Uint32 format, Uint8 *src, int pitch, Uint8 *o for (y = 0; y < (h - 1); y += 2) { for (x = 0; x < (w - 1); x += 2) { - RGBtoYUV(rgb1, yuv[0], mode, monochrome, luminance); + RGBtoYUV(rgb1, 8, yuv[0], 8, mode, monochrome, luminance); rgb1 += 3; *Y1++ = (Uint8)yuv[0][0]; - RGBtoYUV(rgb1, yuv[1], mode, monochrome, luminance); + RGBtoYUV(rgb1, 8, yuv[1], 8, mode, monochrome, luminance); rgb1 += 3; *Y1++ = (Uint8)yuv[1][0]; - RGBtoYUV(rgb2, yuv[2], mode, monochrome, luminance); + RGBtoYUV(rgb2, 8, yuv[2], 8, mode, monochrome, luminance); rgb2 += 3; *Y2++ = (Uint8)yuv[2][0]; - RGBtoYUV(rgb2, yuv[3], mode, monochrome, luminance); + RGBtoYUV(rgb2, 8, yuv[3], 8, mode, monochrome, luminance); rgb2 += 3; *Y2++ = (Uint8)yuv[3][0]; @@ -212,11 +288,11 @@ static void ConvertRGBtoPlanar2x2(Uint32 format, Uint8 *src, int pitch, Uint8 *o } /* Last column */ if (x == (w - 1)) { - RGBtoYUV(rgb1, yuv[0], mode, monochrome, luminance); + RGBtoYUV(rgb1, 8, yuv[0], 8, mode, monochrome, luminance); rgb1 += 3; *Y1++ = (Uint8)yuv[0][0]; - RGBtoYUV(rgb2, yuv[2], mode, monochrome, luminance); + RGBtoYUV(rgb2, 8, yuv[2], 8, mode, monochrome, luminance); rgb2 += 3; *Y2++ = (Uint8)yuv[2][0]; @@ -234,11 +310,11 @@ static void ConvertRGBtoPlanar2x2(Uint32 format, Uint8 *src, int pitch, Uint8 *o /* Last row */ if (y == (h - 1)) { for (x = 0; x < (w - 1); x += 2) { - RGBtoYUV(rgb1, yuv[0], mode, monochrome, luminance); + RGBtoYUV(rgb1, 8, yuv[0], 8, mode, monochrome, luminance); rgb1 += 3; *Y1++ = (Uint8)yuv[0][0]; - RGBtoYUV(rgb1, yuv[1], mode, monochrome, luminance); + RGBtoYUV(rgb1, 8, yuv[1], 8, mode, monochrome, luminance); rgb1 += 3; *Y1++ = (Uint8)yuv[1][0]; @@ -250,7 +326,7 @@ static void ConvertRGBtoPlanar2x2(Uint32 format, Uint8 *src, int pitch, Uint8 *o } /* Last column */ if (x == (w - 1)) { - RGBtoYUV(rgb1, yuv[0], mode, monochrome, luminance); + RGBtoYUV(rgb1, 8, yuv[0], 8, mode, monochrome, luminance); *Y1++ = (Uint8)yuv[0][0]; *U = (Uint8)yuv[0][1]; @@ -262,6 +338,112 @@ static void ConvertRGBtoPlanar2x2(Uint32 format, Uint8 *src, int pitch, Uint8 *o } } +static Uint16 Pack10to16(int v) +{ + return (Uint16)(v << 6); +} + +static void ConvertRGBtoPlanar2x2_P010(Uint32 format, Uint8 *src, int pitch, Uint8 *out, int w, int h, YUV_CONVERSION_MODE mode, int monochrome, int luminance) +{ + int x, y; + int yuv[4][3]; + Uint16 *Y1, *Y2, *U, *V; + Uint8 *rgb1, *rgb2; + int rgb_row_advance = (pitch - w * 3) + pitch; + int UV_advance; + + rgb1 = src; + rgb2 = src + pitch; + + Y1 = (Uint16 *)out; + Y2 = Y1 + w; + switch (format) { + case SDL_PIXELFORMAT_P010: + U = (Y1 + h * w); + V = U + 1; + UV_advance = 2; + break; + default: + SDL_assert(!"Unsupported planar YUV format"); + return; + } + + for (y = 0; y < (h - 1); y += 2) { + for (x = 0; x < (w - 1); x += 2) { + RGBtoYUV(rgb1, 8, yuv[0], 10, mode, monochrome, luminance); + rgb1 += 3; + *Y1++ = Pack10to16(yuv[0][0]); + + RGBtoYUV(rgb1, 8, yuv[1], 10, mode, monochrome, luminance); + rgb1 += 3; + *Y1++ = Pack10to16(yuv[1][0]); + + RGBtoYUV(rgb2, 8, yuv[2], 10, mode, monochrome, luminance); + rgb2 += 3; + *Y2++ = Pack10to16(yuv[2][0]); + + RGBtoYUV(rgb2, 8, yuv[3], 10, mode, monochrome, luminance); + rgb2 += 3; + *Y2++ = Pack10to16(yuv[3][0]); + + *U = Pack10to16((int)SDL_floorf((yuv[0][1] + yuv[1][1] + yuv[2][1] + yuv[3][1]) / 4.0f + 0.5f)); + U += UV_advance; + + *V = Pack10to16((int)SDL_floorf((yuv[0][2] + yuv[1][2] + yuv[2][2] + yuv[3][2]) / 4.0f + 0.5f)); + V += UV_advance; + } + /* Last column */ + if (x == (w - 1)) { + RGBtoYUV(rgb1, 8, yuv[0], 10, mode, monochrome, luminance); + rgb1 += 3; + *Y1++ = Pack10to16(yuv[0][0]); + + RGBtoYUV(rgb2, 8, yuv[2], 10, mode, monochrome, luminance); + rgb2 += 3; + *Y2++ = Pack10to16(yuv[2][0]); + + *U = Pack10to16((int)SDL_floorf((yuv[0][1] + yuv[2][1]) / 2.0f + 0.5f)); + U += UV_advance; + + *V = Pack10to16((int)SDL_floorf((yuv[0][2] + yuv[2][2]) / 2.0f + 0.5f)); + V += UV_advance; + } + Y1 += w; + Y2 += w; + rgb1 += rgb_row_advance; + rgb2 += rgb_row_advance; + } + /* Last row */ + if (y == (h - 1)) { + for (x = 0; x < (w - 1); x += 2) { + RGBtoYUV(rgb1, 8, yuv[0], 10, mode, monochrome, luminance); + rgb1 += 3; + *Y1++ = Pack10to16(yuv[0][0]); + + RGBtoYUV(rgb1, 8, yuv[1], 10, mode, monochrome, luminance); + rgb1 += 3; + *Y1++ = Pack10to16(yuv[1][0]); + + *U = Pack10to16((int)SDL_floorf((yuv[0][1] + yuv[1][1]) / 2.0f + 0.5f)); + U += UV_advance; + + *V = Pack10to16((int)SDL_floorf((yuv[0][2] + yuv[1][2]) / 2.0f + 0.5f)); + V += UV_advance; + } + /* Last column */ + if (x == (w - 1)) { + RGBtoYUV(rgb1, 8, yuv[0], 10, mode, monochrome, luminance); + *Y1++ = Pack10to16(yuv[0][0]); + + *U = Pack10to16(yuv[0][1]); + U += UV_advance; + + *V = Pack10to16(yuv[0][2]); + V += UV_advance; + } + } +} + static void ConvertRGBtoPacked4(Uint32 format, Uint8 *src, int pitch, Uint8 *out, int w, int h, YUV_CONVERSION_MODE mode, int monochrome, int luminance) { int x, y; @@ -298,12 +480,12 @@ static void ConvertRGBtoPacked4(Uint32 format, Uint8 *src, int pitch, Uint8 *out for (y = 0; y < h; ++y) { for (x = 0; x < (w - 1); x += 2) { - RGBtoYUV(rgb, yuv[0], mode, monochrome, luminance); + RGBtoYUV(rgb, 8, yuv[0], 8, mode, monochrome, luminance); rgb += 3; *Y1 = (Uint8)yuv[0][0]; Y1 += 4; - RGBtoYUV(rgb, yuv[1], mode, monochrome, luminance); + RGBtoYUV(rgb, 8, yuv[1], 8, mode, monochrome, luminance); rgb += 3; *Y2 = (Uint8)yuv[1][0]; Y2 += 4; @@ -316,7 +498,7 @@ static void ConvertRGBtoPacked4(Uint32 format, Uint8 *src, int pitch, Uint8 *out } /* Last column */ if (x == (w - 1)) { - RGBtoYUV(rgb, yuv[0], mode, monochrome, luminance); + RGBtoYUV(rgb, 8, yuv[0], 8, mode, monochrome, luminance); rgb += 3; *Y2 = *Y1 = (Uint8)yuv[0][0]; Y1 += 4; @@ -335,6 +517,9 @@ static void ConvertRGBtoPacked4(Uint32 format, Uint8 *src, int pitch, Uint8 *out SDL_bool ConvertRGBtoYUV(Uint32 format, Uint8 *src, int pitch, Uint8 *out, int w, int h, YUV_CONVERSION_MODE mode, int monochrome, int luminance) { switch (format) { + case SDL_PIXELFORMAT_P010: + ConvertRGBtoPlanar2x2_P010(format, src, pitch, out, w, h, mode, monochrome, luminance); + return SDL_TRUE; case SDL_PIXELFORMAT_YV12: case SDL_PIXELFORMAT_IYUV: case SDL_PIXELFORMAT_NV12: @@ -354,6 +539,8 @@ SDL_bool ConvertRGBtoYUV(Uint32 format, Uint8 *src, int pitch, Uint8 *out, int w int CalculateYUVPitch(Uint32 format, int width) { switch (format) { + case SDL_PIXELFORMAT_P010: + return width * 2; case SDL_PIXELFORMAT_YV12: case SDL_PIXELFORMAT_IYUV: case SDL_PIXELFORMAT_NV12: diff --git a/test/testyuv_cvt.h b/test/testyuv_cvt.h index c8962142b..36917b720 100644 --- a/test/testyuv_cvt.h +++ b/test/testyuv_cvt.h @@ -17,6 +17,7 @@ typedef enum YUV_CONVERSION_JPEG, /**< Full range JPEG */ YUV_CONVERSION_BT601, /**< BT.601 (the default) */ YUV_CONVERSION_BT709, /**< BT.709 */ + YUV_CONVERSION_BT2020, /**< BT.2020 */ YUV_CONVERSION_AUTOMATIC /**< BT.601 for SD content, BT.709 for HD content */ } YUV_CONVERSION_MODE;