diff --git a/src/video/SDL_yuv.c b/src/video/SDL_yuv.c index 81ace699a1fbb..6c7ab1cd7e014 100644 --- a/src/video/SDL_yuv.c +++ b/src/video/SDL_yuv.c @@ -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)) { @@ -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, @@ -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; @@ -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]; \ @@ -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) { @@ -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; } diff --git a/src/video/yuv2rgb/yuv_rgb_common.h b/src/video/yuv2rgb/yuv_rgb_common.h index 774292b0cd0fd..a4ef8eaeb7bb4 100644 --- a/src/video/yuv2rgb/yuv_rgb_common.h +++ b/src/video/yuv2rgb/yuv_rgb_common.h @@ -7,6 +7,7 @@ typedef enum { YCBCR_601_FULL, YCBCR_601_LIMITED, + YCBCR_709_FULL, YCBCR_709_LIMITED, YCBCR_2020_NCL_FULL, } YCbCrType; diff --git a/src/video/yuv2rgb/yuv_rgb_internal.h b/src/video/yuv2rgb/yuv_rgb_internal.h index 23ae705669bad..d5939ed651cba 100644 --- a/src/video/yuv2rgb/yuv_rgb_internal.h +++ b/src/video/yuv2rgb/yuv_rgb_internal.h @@ -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 diff --git a/test/testyuv.c b/test/testyuv.c index 2406ce784445c..8048742477745 100644 --- a/test/testyuv.c +++ b/test/testyuv.c @@ -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, @@ -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"); @@ -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); @@ -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 @@ -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 */ @@ -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; @@ -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, }; @@ -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);