Skip to content

Commit

Permalink
Added support for full range BT.709 YUV conversion
Browse files Browse the repository at this point in the history
Also added simple colorspace testing to testyuv.
  • Loading branch information
slouken committed Oct 14, 2024
1 parent 817fa9a commit befd822
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 18 deletions.
31 changes: 20 additions & 11 deletions src/video/SDL_yuv.c
Original file line number Diff line number Diff line change
Expand Up @@ -171,8 +171,10 @@ static bool GetYUVConversionType(SDL_Colorspace colorspace, YCbCrType *yuv_type)
if (SDL_ISCOLORSPACE_MATRIX_BT709(colorspace)) {
if (SDL_ISCOLORSPACE_LIMITED_RANGE(colorspace)) {
*yuv_type = YCBCR_709_LIMITED;
return true;
} else {
*yuv_type = YCBCR_709_FULL;
}
return true;
}

if (SDL_ISCOLORSPACE_MATRIX_BT2020_NCL(colorspace)) {
Expand Down Expand Up @@ -691,6 +693,13 @@ static struct RGB2YUVFactors RGB2YUVFactorTables[] = {
{ -0.1482f, -0.2910f, 0.4392f },
{ 0.4392f, -0.3678f, -0.0714f },
},
// ITU-R BT.709-6 full range
{
0,
{ 0.2126f, 0.7152f, 0.0722f },
{ -0.1141f, -0.3839f, 0.498f },
{ 0.498f, -0.4524f, -0.0457f },
},
// ITU-R BT.709-6
{
16,
Expand All @@ -707,7 +716,7 @@ static struct RGB2YUVFactors RGB2YUVFactorTables[] = {
},
};

static bool SDL_ConvertPixels_ARGB8888_to_YUV(int width, int height, const void *src, int src_pitch, SDL_PixelFormat dst_format, void *dst, int dst_pitch, YCbCrType yuv_type)
static bool SDL_ConvertPixels_XRGB8888_to_YUV(int width, int height, const void *src, int src_pitch, SDL_PixelFormat 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;
Expand All @@ -718,9 +727,9 @@ static bool SDL_ConvertPixels_ARGB8888_to_YUV(int width, int height, const void

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)
#define MAKE_U(r, g, b) (Uint8)((int)(cvt->u[0] * (r) + cvt->u[1] * (g) + cvt->u[2] * (b) + 0.5f) + 128)
#define MAKE_V(r, g, b) (Uint8)((int)(cvt->v[0] * (r) + cvt->v[1] * (g) + cvt->v[2] * (b) + 0.5f) + 128)
#define MAKE_Y(r, g, b) (Uint8)SDL_clamp(((int)(cvt->y[0] * (r) + cvt->y[1] * (g) + cvt->y[2] * (b) + 0.5f) + cvt->y_offset), 0, 255)
#define MAKE_U(r, g, b) (Uint8)SDL_clamp(((int)(cvt->u[0] * (r) + cvt->u[1] * (g) + cvt->u[2] * (b) + 0.5f) + 128), 0, 255)
#define MAKE_V(r, g, b) (Uint8)SDL_clamp(((int)(cvt->v[0] * (r) + cvt->v[1] * (g) + cvt->v[2] * (b) + 0.5f) + 128), 0, 255)

#define READ_2x2_PIXELS \
const Uint32 p1 = ((const Uint32 *)curr_row)[2 * i]; \
Expand Down Expand Up @@ -1149,9 +1158,9 @@ bool SDL_ConvertPixels_RGB_to_YUV(int width, int height,
#endif

// ARGB8888 to FOURCC
if (src_format == SDL_PIXELFORMAT_ARGB8888 &&
if ((src_format == SDL_PIXELFORMAT_ARGB8888 || src_format == SDL_PIXELFORMAT_XRGB8888) &&
SDL_COLORSPACEPRIMARIES(src_colorspace) == SDL_COLORSPACEPRIMARIES(dst_colorspace)) {
return SDL_ConvertPixels_ARGB8888_to_YUV(width, height, src, src_pitch, dst_format, dst, dst_pitch, yuv_type);
return SDL_ConvertPixels_XRGB8888_to_YUV(width, height, src, src_pitch, dst_format, dst, dst_pitch, yuv_type);
}

if (dst_format == SDL_PIXELFORMAT_P010) {
Expand Down Expand Up @@ -1194,15 +1203,15 @@ bool SDL_ConvertPixels_RGB_to_YUV(int width, int height,
return false;
}

// convert src/src_format to tmp/ARGB8888
result = SDL_ConvertPixelsAndColorspace(width, height, src_format, src_colorspace, src_properties, src, src_pitch, SDL_PIXELFORMAT_ARGB8888, dst_colorspace, dst_properties, tmp, tmp_pitch);
// convert src/src_format to tmp/XRGB8888
result = SDL_ConvertPixelsAndColorspace(width, height, src_format, src_colorspace, src_properties, src, src_pitch, SDL_PIXELFORMAT_XRGB8888, SDL_COLORSPACE_SRGB, 0, tmp, tmp_pitch);
if (!result) {
SDL_free(tmp);
return false;
}

// convert tmp/ARGB8888 to dst/FOURCC
result = SDL_ConvertPixels_ARGB8888_to_YUV(width, height, tmp, tmp_pitch, dst_format, dst, dst_pitch, yuv_type);
// convert tmp/XRGB8888 to dst/FOURCC
result = SDL_ConvertPixels_XRGB8888_to_YUV(width, height, tmp, tmp_pitch, dst_format, dst, dst_pitch, yuv_type);
SDL_free(tmp);
return result;
}
Expand Down
1 change: 1 addition & 0 deletions src/video/yuv2rgb/yuv_rgb_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ typedef enum
{
YCBCR_601_FULL,
YCBCR_601_LIMITED,
YCBCR_709_FULL,
YCBCR_709_LIMITED,
YCBCR_2020_NCL_FULL,
} YCbCrType;
Expand Down
8 changes: 6 additions & 2 deletions src/video/yuv2rgb/yuv_rgb_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,22 +40,26 @@ typedef struct
// 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[4] = {
static const YUV2RGBParam YUV2RGB[] = {
// 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 full range
{/*.y_shift=*/ 0, /*.y_factor=*/ V(1.0), /*.v_r_factor=*/ V(1.581), /*.u_g_factor=*/ -V(0.1881), /*.v_g_factor=*/ -V(0.47), /*.u_b_factor=*/ V(1.8629)},
// 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)},
// 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[4] = {
static const RGB2YUVParam RGB2YUV[] = {
// 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 full range
{/*.y_shift=*/ 0, /*.matrix=*/ {{V(0.2126), V(0.7152), V(0.0722)}, {-V(0.1141), -V(0.3839), V(0.498)}, {V(0.498), -V(0.4524), -V(0.0457)}}},
// 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)}}},
// ITU-R BT.2020 10-bit full range
Expand Down
144 changes: 139 additions & 5 deletions test/testyuv.c
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ static bool verify_yuv_data(Uint32 format, SDL_Colorspace colorspace, const Uint
return result;
}

static int run_automated_tests(int pattern_size, int extra_pitch)
static bool run_automated_tests(int pattern_size, int extra_pitch)
{
const Uint32 formats[] = {
SDL_PIXELFORMAT_YV12,
Expand All @@ -125,7 +125,7 @@ static int run_automated_tests(int pattern_size, int extra_pitch)
SDL_Colorspace colorspace;
const int tight_tolerance = 20;
const int loose_tolerance = 333;
int result = -1;
bool result = false;

if (!pattern || !yuv1 || !yuv2) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't allocate test surfaces");
Expand Down Expand Up @@ -230,7 +230,7 @@ static int run_automated_tests(int pattern_size, int extra_pitch)
goto done;
}

result = 0;
result = true;

done:
SDL_free(yuv1);
Expand All @@ -239,6 +239,129 @@ static int run_automated_tests(int pattern_size, int extra_pitch)
return result;
}

static bool run_colorspace_test()
{
bool result = false;
SDL_Window *window;
SDL_Renderer *renderer;
struct {
const char *name;
SDL_Colorspace colorspace;
} colorspaces[] = {
#define COLORSPACE(X) { #X, X }
COLORSPACE(SDL_COLORSPACE_JPEG),
#if 0 /* We don't support converting color primaries here */
COLORSPACE(SDL_COLORSPACE_BT601_LIMITED),
COLORSPACE(SDL_COLORSPACE_BT601_FULL),
#endif
COLORSPACE(SDL_COLORSPACE_BT709_LIMITED),
COLORSPACE(SDL_COLORSPACE_BT709_FULL)
#undef COLORSPACE
};
SDL_Surface *rgb = NULL;
SDL_Surface *yuv = NULL;
SDL_Texture *texture = NULL;
int allowed_error = 2;
int i;

if (!SDL_CreateWindowAndRenderer("testyuv", 320, 240, 0, &window, &renderer)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create window and renderer: %s\n", SDL_GetError());
goto done;
}

rgb = SDL_CreateSurface(32, 32, SDL_PIXELFORMAT_XRGB8888);
if (!rgb) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create RGB surface: %s\n", SDL_GetError());
goto done;
}
SDL_FillSurfaceRect(rgb, NULL, SDL_MapSurfaceRGB(rgb, 255, 0, 0));

for (i = 0; i < SDL_arraysize(colorspaces); ++i) {
bool next = false;
Uint8 r, g, b, a;

SDL_Log("Checking colorspace %s\n", colorspaces[i].name);

yuv = SDL_ConvertSurfaceAndColorspace(rgb, SDL_PIXELFORMAT_NV12, NULL, colorspaces[i].colorspace, 0);
if (!yuv) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create YUV surface: %s\n", SDL_GetError());
goto done;
}

if (!SDL_ReadSurfacePixel(yuv, 0, 0, &r, &g, &b, &a)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't read YUV surface: %s\n", SDL_GetError());
goto done;
}

if (SDL_abs((int)r - 255) > allowed_error ||
SDL_abs((int)g - 0) > allowed_error ||
SDL_abs((int)b - 0) > allowed_error) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed color conversion, expected 255,0,0, got %d,%d,%d\n", r, g, b);
}

texture = SDL_CreateTextureFromSurface(renderer, yuv);
if (!texture) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create YUV texture: %s\n", SDL_GetError());
goto done;
}

SDL_DestroySurface(yuv);
yuv = NULL;

SDL_RenderTexture(renderer, texture, NULL, NULL);
yuv = SDL_RenderReadPixels(renderer, NULL);

if (!SDL_ReadSurfacePixel(yuv, 0, 0, &r, &g, &b, &a)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't read YUV surface: %s\n", SDL_GetError());
goto done;
}

if (SDL_abs((int)r - 255) > allowed_error ||
SDL_abs((int)g - 0) > allowed_error ||
SDL_abs((int)b - 0) > allowed_error) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed renderer color conversion, expected 255,0,0, got %d,%d,%d\n", r, g, b);
}

while (!next) {
SDL_Event event;
while (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_EVENT_KEY_DOWN:
if (event.key.key == SDLK_ESCAPE) {
result = true;
goto done;
}
if (event.key.key == SDLK_SPACE) {
next = true;
}
break;
case SDL_EVENT_QUIT:
result = true;
goto done;
}
}

SDL_RenderTexture(renderer, texture, NULL, NULL);
SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
SDL_RenderDebugText(renderer, 4, 4, colorspaces[i].name);
SDL_RenderPresent(renderer);
SDL_Delay(10);
}

SDL_DestroyTexture(texture);
texture = NULL;
}

result = true;

done:
SDL_DestroySurface(rgb);
SDL_DestroySurface(yuv);
SDL_DestroyTexture(texture);
SDL_Quit();
return result;
}

int main(int argc, char **argv)
{
struct
Expand Down Expand Up @@ -297,6 +420,7 @@ int main(int argc, char **argv)
Uint64 then, now;
int i, iterations = 100;
bool should_run_automated_tests = false;
bool should_run_colorspace_test = false;
SDLTest_CommonState *state;

/* Initialize test framework */
Expand Down Expand Up @@ -377,6 +501,9 @@ int main(int argc, char **argv)
} else if (SDL_strcmp(argv[i], "--automated") == 0) {
should_run_automated_tests = true;
consumed = 1;
} else if (SDL_strcmp(argv[i], "--colorspace-test") == 0) {
should_run_colorspace_test = true;
consumed = 1;
} else if (!filename) {
filename = argv[i];
consumed = 1;
Expand All @@ -388,7 +515,7 @@ int main(int argc, char **argv)
"[--yv12|--iyuv|--yuy2|--uyvy|--yvyu|--nv12|--nv21]",
"[--rgb555|--rgb565|--rgb24|--argb|--abgr|--rgba|--bgra]",
"[--monochrome] [--luminance N%]",
"[--automated]",
"[--automated] [--colorspace-test]",
"[sample.bmp]",
NULL,
};
Expand All @@ -407,13 +534,20 @@ int main(int argc, char **argv)
automated_test_params[i].pattern_size,
automated_test_params[i].extra_pitch,
automated_test_params[i].enable_intrinsics ? "enabled" : "disabled");
if (run_automated_tests(automated_test_params[i].pattern_size, automated_test_params[i].extra_pitch) < 0) {
if (!run_automated_tests(automated_test_params[i].pattern_size, automated_test_params[i].extra_pitch)) {
return 2;
}
}
return 0;
}

if (should_run_colorspace_test) {
if (!run_colorspace_test()) {
return 2;
}
return 0;
}

filename = GetResourceFilename(filename, "testyuv.bmp");
bmp = SDL_LoadBMP(filename);
original = SDL_ConvertSurface(bmp, SDL_PIXELFORMAT_RGB24);
Expand Down

0 comments on commit befd822

Please sign in to comment.