diff options
Diffstat (limited to 'contrib/SDL-3.2.8/test/testyuv.c')
| -rw-r--r-- | contrib/SDL-3.2.8/test/testyuv.c | 690 |
1 files changed, 690 insertions, 0 deletions
diff --git a/contrib/SDL-3.2.8/test/testyuv.c b/contrib/SDL-3.2.8/test/testyuv.c new file mode 100644 index 0000000..8dda4c5 --- /dev/null +++ b/contrib/SDL-3.2.8/test/testyuv.c | |||
| @@ -0,0 +1,690 @@ | |||
| 1 | /* | ||
| 2 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 3 | |||
| 4 | This software is provided 'as-is', without any express or implied | ||
| 5 | warranty. In no event will the authors be held liable for any damages | ||
| 6 | arising from the use of this software. | ||
| 7 | |||
| 8 | Permission is granted to anyone to use this software for any purpose, | ||
| 9 | including commercial applications, and to alter it and redistribute it | ||
| 10 | freely. | ||
| 11 | */ | ||
| 12 | #include <SDL3/SDL.h> | ||
| 13 | #include <SDL3/SDL_main.h> | ||
| 14 | #include <SDL3/SDL_test.h> | ||
| 15 | #include "testyuv_cvt.h" | ||
| 16 | #include "testutils.h" | ||
| 17 | |||
| 18 | /* 422 (YUY2, etc) and P010 formats are the largest */ | ||
| 19 | #define MAX_YUV_SURFACE_SIZE(W, H, P) ((H + 1) * ((W + 1) + P) * 4) | ||
| 20 | |||
| 21 | /* Return true if the YUV format is packed pixels */ | ||
| 22 | static bool is_packed_yuv_format(Uint32 format) | ||
| 23 | { | ||
| 24 | return format == SDL_PIXELFORMAT_YUY2 || format == SDL_PIXELFORMAT_UYVY || format == SDL_PIXELFORMAT_YVYU; | ||
| 25 | } | ||
| 26 | |||
| 27 | /* Create a surface with a good pattern for verifying YUV conversion */ | ||
| 28 | static SDL_Surface *generate_test_pattern(int pattern_size) | ||
| 29 | { | ||
| 30 | SDL_Surface *pattern = SDL_CreateSurface(pattern_size, pattern_size, SDL_PIXELFORMAT_RGB24); | ||
| 31 | |||
| 32 | if (pattern) { | ||
| 33 | int i, x, y; | ||
| 34 | Uint8 *p, c; | ||
| 35 | const int thickness = 2; /* Important so 2x2 blocks of color are the same, to avoid Cr/Cb interpolation over pixels */ | ||
| 36 | |||
| 37 | /* R, G, B in alternating horizontal bands */ | ||
| 38 | for (y = 0; y < pattern->h - (thickness - 1); y += thickness) { | ||
| 39 | for (i = 0; i < thickness; ++i) { | ||
| 40 | p = (Uint8 *)pattern->pixels + (y + i) * pattern->pitch + ((y / thickness) % 3); | ||
| 41 | for (x = 0; x < pattern->w; ++x) { | ||
| 42 | *p = 0xFF; | ||
| 43 | p += 3; | ||
| 44 | } | ||
| 45 | } | ||
| 46 | } | ||
| 47 | |||
| 48 | /* Black and white in alternating vertical bands */ | ||
| 49 | c = 0xFF; | ||
| 50 | for (x = 1 * thickness; x < pattern->w; x += 2 * thickness) { | ||
| 51 | for (i = 0; i < thickness; ++i) { | ||
| 52 | p = (Uint8 *)pattern->pixels + (x + i) * 3; | ||
| 53 | for (y = 0; y < pattern->h; ++y) { | ||
| 54 | SDL_memset(p, c, 3); | ||
| 55 | p += pattern->pitch; | ||
| 56 | } | ||
| 57 | } | ||
| 58 | if (c) { | ||
| 59 | c = 0x00; | ||
| 60 | } else { | ||
| 61 | c = 0xFF; | ||
| 62 | } | ||
| 63 | } | ||
| 64 | } | ||
| 65 | return pattern; | ||
| 66 | } | ||
| 67 | |||
| 68 | static bool verify_yuv_data(Uint32 format, SDL_Colorspace colorspace, const Uint8 *yuv, int yuv_pitch, SDL_Surface *surface, int tolerance) | ||
| 69 | { | ||
| 70 | const int size = (surface->h * surface->pitch); | ||
| 71 | Uint8 *rgb; | ||
| 72 | bool result = false; | ||
| 73 | |||
| 74 | rgb = (Uint8 *)SDL_malloc(size); | ||
| 75 | if (!rgb) { | ||
| 76 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Out of memory"); | ||
| 77 | return false; | ||
| 78 | } | ||
| 79 | |||
| 80 | if (SDL_ConvertPixelsAndColorspace(surface->w, surface->h, format, colorspace, 0, yuv, yuv_pitch, surface->format, SDL_COLORSPACE_SRGB, 0, rgb, surface->pitch)) { | ||
| 81 | int x, y; | ||
| 82 | result = true; | ||
| 83 | for (y = 0; y < surface->h; ++y) { | ||
| 84 | const Uint8 *actual = rgb + y * surface->pitch; | ||
| 85 | const Uint8 *expected = (const Uint8 *)surface->pixels + y * surface->pitch; | ||
| 86 | for (x = 0; x < surface->w; ++x) { | ||
| 87 | int deltaR = (int)actual[0] - expected[0]; | ||
| 88 | int deltaG = (int)actual[1] - expected[1]; | ||
| 89 | int deltaB = (int)actual[2] - expected[2]; | ||
| 90 | int distance = (deltaR * deltaR + deltaG * deltaG + deltaB * deltaB); | ||
| 91 | if (distance > tolerance) { | ||
| 92 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Pixel at %d,%d was 0x%.2x,0x%.2x,0x%.2x, expected 0x%.2x,0x%.2x,0x%.2x, distance = %d", x, y, actual[0], actual[1], actual[2], expected[0], expected[1], expected[2], distance); | ||
| 93 | result = false; | ||
| 94 | } | ||
| 95 | actual += 3; | ||
| 96 | expected += 3; | ||
| 97 | } | ||
| 98 | } | ||
| 99 | } else { | ||
| 100 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't convert %s to %s: %s", SDL_GetPixelFormatName(format), SDL_GetPixelFormatName(surface->format), SDL_GetError()); | ||
| 101 | } | ||
| 102 | SDL_free(rgb); | ||
| 103 | |||
| 104 | return result; | ||
| 105 | } | ||
| 106 | |||
| 107 | static bool run_automated_tests(int pattern_size, int extra_pitch) | ||
| 108 | { | ||
| 109 | const Uint32 formats[] = { | ||
| 110 | SDL_PIXELFORMAT_YV12, | ||
| 111 | SDL_PIXELFORMAT_IYUV, | ||
| 112 | SDL_PIXELFORMAT_NV12, | ||
| 113 | SDL_PIXELFORMAT_NV21, | ||
| 114 | SDL_PIXELFORMAT_YUY2, | ||
| 115 | SDL_PIXELFORMAT_UYVY, | ||
| 116 | SDL_PIXELFORMAT_YVYU | ||
| 117 | }; | ||
| 118 | int i, j; | ||
| 119 | SDL_Surface *pattern = generate_test_pattern(pattern_size); | ||
| 120 | const int yuv_len = MAX_YUV_SURFACE_SIZE(pattern->w, pattern->h, extra_pitch); | ||
| 121 | Uint8 *yuv1 = (Uint8 *)SDL_malloc(yuv_len); | ||
| 122 | Uint8 *yuv2 = (Uint8 *)SDL_malloc(yuv_len); | ||
| 123 | int yuv1_pitch, yuv2_pitch; | ||
| 124 | YUV_CONVERSION_MODE mode; | ||
| 125 | SDL_Colorspace colorspace; | ||
| 126 | const int tight_tolerance = 20; | ||
| 127 | const int loose_tolerance = 333; | ||
| 128 | bool result = false; | ||
| 129 | |||
| 130 | if (!pattern || !yuv1 || !yuv2) { | ||
| 131 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't allocate test surfaces"); | ||
| 132 | goto done; | ||
| 133 | } | ||
| 134 | |||
| 135 | mode = GetYUVConversionModeForResolution(pattern->w, pattern->h); | ||
| 136 | colorspace = GetColorspaceForYUVConversionMode(mode); | ||
| 137 | |||
| 138 | /* Verify conversion from YUV formats */ | ||
| 139 | for (i = 0; i < SDL_arraysize(formats); ++i) { | ||
| 140 | if (!ConvertRGBtoYUV(formats[i], pattern->pixels, pattern->pitch, yuv1, pattern->w, pattern->h, mode, 0, 100)) { | ||
| 141 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "ConvertRGBtoYUV() doesn't support converting to %s", SDL_GetPixelFormatName(formats[i])); | ||
| 142 | goto done; | ||
| 143 | } | ||
| 144 | yuv1_pitch = CalculateYUVPitch(formats[i], pattern->w); | ||
| 145 | if (!verify_yuv_data(formats[i], colorspace, yuv1, yuv1_pitch, pattern, tight_tolerance)) { | ||
| 146 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed conversion from %s to RGB", SDL_GetPixelFormatName(formats[i])); | ||
| 147 | goto done; | ||
| 148 | } | ||
| 149 | } | ||
| 150 | |||
| 151 | /* Verify conversion to YUV formats */ | ||
| 152 | for (i = 0; i < SDL_arraysize(formats); ++i) { | ||
| 153 | yuv1_pitch = CalculateYUVPitch(formats[i], pattern->w) + extra_pitch; | ||
| 154 | if (!SDL_ConvertPixelsAndColorspace(pattern->w, pattern->h, pattern->format, SDL_COLORSPACE_SRGB, 0, pattern->pixels, pattern->pitch, formats[i], colorspace, 0, yuv1, yuv1_pitch)) { | ||
| 155 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't convert %s to %s: %s", SDL_GetPixelFormatName(pattern->format), SDL_GetPixelFormatName(formats[i]), SDL_GetError()); | ||
| 156 | goto done; | ||
| 157 | } | ||
| 158 | if (!verify_yuv_data(formats[i], colorspace, yuv1, yuv1_pitch, pattern, tight_tolerance)) { | ||
| 159 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed conversion from RGB to %s", SDL_GetPixelFormatName(formats[i])); | ||
| 160 | goto done; | ||
| 161 | } | ||
| 162 | } | ||
| 163 | |||
| 164 | /* Verify conversion between YUV formats */ | ||
| 165 | for (i = 0; i < SDL_arraysize(formats); ++i) { | ||
| 166 | for (j = 0; j < SDL_arraysize(formats); ++j) { | ||
| 167 | yuv1_pitch = CalculateYUVPitch(formats[i], pattern->w) + extra_pitch; | ||
| 168 | yuv2_pitch = CalculateYUVPitch(formats[j], pattern->w) + extra_pitch; | ||
| 169 | if (!SDL_ConvertPixelsAndColorspace(pattern->w, pattern->h, pattern->format, SDL_COLORSPACE_SRGB, 0, pattern->pixels, pattern->pitch, formats[i], colorspace, 0, yuv1, yuv1_pitch)) { | ||
| 170 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't convert %s to %s: %s", SDL_GetPixelFormatName(pattern->format), SDL_GetPixelFormatName(formats[i]), SDL_GetError()); | ||
| 171 | goto done; | ||
| 172 | } | ||
| 173 | if (!SDL_ConvertPixelsAndColorspace(pattern->w, pattern->h, formats[i], colorspace, 0, yuv1, yuv1_pitch, formats[j], colorspace, 0, yuv2, yuv2_pitch)) { | ||
| 174 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't convert %s to %s: %s", SDL_GetPixelFormatName(formats[i]), SDL_GetPixelFormatName(formats[j]), SDL_GetError()); | ||
| 175 | goto done; | ||
| 176 | } | ||
| 177 | if (!verify_yuv_data(formats[j], colorspace, yuv2, yuv2_pitch, pattern, tight_tolerance)) { | ||
| 178 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed conversion from %s to %s", SDL_GetPixelFormatName(formats[i]), SDL_GetPixelFormatName(formats[j])); | ||
| 179 | goto done; | ||
| 180 | } | ||
| 181 | } | ||
| 182 | } | ||
| 183 | |||
| 184 | /* Verify conversion between YUV formats in-place */ | ||
| 185 | for (i = 0; i < SDL_arraysize(formats); ++i) { | ||
| 186 | for (j = 0; j < SDL_arraysize(formats); ++j) { | ||
| 187 | if (is_packed_yuv_format(formats[i]) != is_packed_yuv_format(formats[j])) { | ||
| 188 | /* Can't change plane vs packed pixel layout in-place */ | ||
| 189 | continue; | ||
| 190 | } | ||
| 191 | |||
| 192 | yuv1_pitch = CalculateYUVPitch(formats[i], pattern->w) + extra_pitch; | ||
| 193 | yuv2_pitch = CalculateYUVPitch(formats[j], pattern->w) + extra_pitch; | ||
| 194 | if (!SDL_ConvertPixelsAndColorspace(pattern->w, pattern->h, pattern->format, SDL_COLORSPACE_SRGB, 0, pattern->pixels, pattern->pitch, formats[i], colorspace, 0, yuv1, yuv1_pitch)) { | ||
| 195 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't convert %s to %s: %s", SDL_GetPixelFormatName(pattern->format), SDL_GetPixelFormatName(formats[i]), SDL_GetError()); | ||
| 196 | goto done; | ||
| 197 | } | ||
| 198 | if (!SDL_ConvertPixelsAndColorspace(pattern->w, pattern->h, formats[i], colorspace, 0, yuv1, yuv1_pitch, formats[j], colorspace, 0, yuv1, yuv2_pitch)) { | ||
| 199 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't convert %s to %s: %s", SDL_GetPixelFormatName(formats[i]), SDL_GetPixelFormatName(formats[j]), SDL_GetError()); | ||
| 200 | goto done; | ||
| 201 | } | ||
| 202 | if (!verify_yuv_data(formats[j], colorspace, yuv1, yuv2_pitch, pattern, tight_tolerance)) { | ||
| 203 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed conversion from %s to %s", SDL_GetPixelFormatName(formats[i]), SDL_GetPixelFormatName(formats[j])); | ||
| 204 | goto done; | ||
| 205 | } | ||
| 206 | } | ||
| 207 | } | ||
| 208 | |||
| 209 | /* Verify round trip through BT.2020 */ | ||
| 210 | colorspace = SDL_COLORSPACE_BT2020_FULL; | ||
| 211 | if (!ConvertRGBtoYUV(SDL_PIXELFORMAT_P010, pattern->pixels, pattern->pitch, yuv1, pattern->w, pattern->h, YUV_CONVERSION_BT2020, 0, 100)) { | ||
| 212 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "ConvertRGBtoYUV() doesn't support converting to %s", SDL_GetPixelFormatName(SDL_PIXELFORMAT_P010)); | ||
| 213 | goto done; | ||
| 214 | } | ||
| 215 | yuv1_pitch = CalculateYUVPitch(SDL_PIXELFORMAT_P010, pattern->w); | ||
| 216 | if (!verify_yuv_data(SDL_PIXELFORMAT_P010, colorspace, yuv1, yuv1_pitch, pattern, tight_tolerance)) { | ||
| 217 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed conversion from %s to RGB", SDL_GetPixelFormatName(SDL_PIXELFORMAT_P010)); | ||
| 218 | goto done; | ||
| 219 | } | ||
| 220 | |||
| 221 | /* The pitch needs to be Uint16 aligned for P010 pixels */ | ||
| 222 | yuv1_pitch = CalculateYUVPitch(SDL_PIXELFORMAT_P010, pattern->w) + ((extra_pitch + 1) & ~1); | ||
| 223 | if (!SDL_ConvertPixelsAndColorspace(pattern->w, pattern->h, pattern->format, SDL_COLORSPACE_SRGB, 0, pattern->pixels, pattern->pitch, SDL_PIXELFORMAT_P010, colorspace, 0, yuv1, yuv1_pitch)) { | ||
| 224 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't convert %s to %s: %s", SDL_GetPixelFormatName(pattern->format), SDL_GetPixelFormatName(SDL_PIXELFORMAT_P010), SDL_GetError()); | ||
| 225 | goto done; | ||
| 226 | } | ||
| 227 | /* Going through XRGB2101010 format during P010 conversion is slightly lossy, so use looser tolerance here */ | ||
| 228 | if (!verify_yuv_data(SDL_PIXELFORMAT_P010, colorspace, yuv1, yuv1_pitch, pattern, loose_tolerance)) { | ||
| 229 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed conversion from RGB to %s", SDL_GetPixelFormatName(SDL_PIXELFORMAT_P010)); | ||
| 230 | goto done; | ||
| 231 | } | ||
| 232 | |||
| 233 | result = true; | ||
| 234 | |||
| 235 | done: | ||
| 236 | SDL_free(yuv1); | ||
| 237 | SDL_free(yuv2); | ||
| 238 | SDL_DestroySurface(pattern); | ||
| 239 | return result; | ||
| 240 | } | ||
| 241 | |||
| 242 | static bool run_colorspace_test(void) | ||
| 243 | { | ||
| 244 | bool result = false; | ||
| 245 | SDL_Window *window; | ||
| 246 | SDL_Renderer *renderer; | ||
| 247 | struct { | ||
| 248 | const char *name; | ||
| 249 | SDL_Colorspace colorspace; | ||
| 250 | } colorspaces[] = { | ||
| 251 | #define COLORSPACE(X) { #X, X } | ||
| 252 | COLORSPACE(SDL_COLORSPACE_JPEG), | ||
| 253 | #if 0 /* We don't support converting color primaries here */ | ||
| 254 | COLORSPACE(SDL_COLORSPACE_BT601_LIMITED), | ||
| 255 | COLORSPACE(SDL_COLORSPACE_BT601_FULL), | ||
| 256 | #endif | ||
| 257 | COLORSPACE(SDL_COLORSPACE_BT709_LIMITED), | ||
| 258 | COLORSPACE(SDL_COLORSPACE_BT709_FULL) | ||
| 259 | #undef COLORSPACE | ||
| 260 | }; | ||
| 261 | SDL_Surface *rgb = NULL; | ||
| 262 | SDL_Surface *yuv = NULL; | ||
| 263 | SDL_Texture *texture = NULL; | ||
| 264 | int allowed_error = 2; | ||
| 265 | int i; | ||
| 266 | |||
| 267 | if (!SDL_CreateWindowAndRenderer("testyuv", 320, 240, 0, &window, &renderer)) { | ||
| 268 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create window and renderer: %s", SDL_GetError()); | ||
| 269 | goto done; | ||
| 270 | } | ||
| 271 | |||
| 272 | rgb = SDL_CreateSurface(32, 32, SDL_PIXELFORMAT_XRGB8888); | ||
| 273 | if (!rgb) { | ||
| 274 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create RGB surface: %s", SDL_GetError()); | ||
| 275 | goto done; | ||
| 276 | } | ||
| 277 | SDL_FillSurfaceRect(rgb, NULL, SDL_MapSurfaceRGB(rgb, 255, 0, 0)); | ||
| 278 | |||
| 279 | for (i = 0; i < SDL_arraysize(colorspaces); ++i) { | ||
| 280 | bool next = false; | ||
| 281 | Uint8 r, g, b, a; | ||
| 282 | |||
| 283 | SDL_Log("Checking colorspace %s", colorspaces[i].name); | ||
| 284 | |||
| 285 | yuv = SDL_ConvertSurfaceAndColorspace(rgb, SDL_PIXELFORMAT_NV12, NULL, colorspaces[i].colorspace, 0); | ||
| 286 | if (!yuv) { | ||
| 287 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create YUV surface: %s", SDL_GetError()); | ||
| 288 | goto done; | ||
| 289 | } | ||
| 290 | |||
| 291 | if (!SDL_ReadSurfacePixel(yuv, 0, 0, &r, &g, &b, &a)) { | ||
| 292 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't read YUV surface: %s", SDL_GetError()); | ||
| 293 | goto done; | ||
| 294 | } | ||
| 295 | |||
| 296 | if (SDL_abs((int)r - 255) > allowed_error || | ||
| 297 | SDL_abs((int)g - 0) > allowed_error || | ||
| 298 | SDL_abs((int)b - 0) > allowed_error) { | ||
| 299 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed color conversion, expected 255,0,0, got %d,%d,%d", r, g, b); | ||
| 300 | } | ||
| 301 | |||
| 302 | texture = SDL_CreateTextureFromSurface(renderer, yuv); | ||
| 303 | if (!texture) { | ||
| 304 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create YUV texture: %s", SDL_GetError()); | ||
| 305 | goto done; | ||
| 306 | } | ||
| 307 | |||
| 308 | SDL_DestroySurface(yuv); | ||
| 309 | yuv = NULL; | ||
| 310 | |||
| 311 | SDL_RenderTexture(renderer, texture, NULL, NULL); | ||
| 312 | yuv = SDL_RenderReadPixels(renderer, NULL); | ||
| 313 | |||
| 314 | if (!SDL_ReadSurfacePixel(yuv, 0, 0, &r, &g, &b, &a)) { | ||
| 315 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't read YUV surface: %s", SDL_GetError()); | ||
| 316 | goto done; | ||
| 317 | } | ||
| 318 | |||
| 319 | if (SDL_abs((int)r - 255) > allowed_error || | ||
| 320 | SDL_abs((int)g - 0) > allowed_error || | ||
| 321 | SDL_abs((int)b - 0) > allowed_error) { | ||
| 322 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed renderer color conversion, expected 255,0,0, got %d,%d,%d", r, g, b); | ||
| 323 | } | ||
| 324 | |||
| 325 | while (!next) { | ||
| 326 | SDL_Event event; | ||
| 327 | while (SDL_PollEvent(&event)) { | ||
| 328 | switch (event.type) { | ||
| 329 | case SDL_EVENT_KEY_DOWN: | ||
| 330 | if (event.key.key == SDLK_ESCAPE) { | ||
| 331 | result = true; | ||
| 332 | goto done; | ||
| 333 | } | ||
| 334 | if (event.key.key == SDLK_SPACE) { | ||
| 335 | next = true; | ||
| 336 | } | ||
| 337 | break; | ||
| 338 | case SDL_EVENT_QUIT: | ||
| 339 | result = true; | ||
| 340 | goto done; | ||
| 341 | } | ||
| 342 | } | ||
| 343 | |||
| 344 | SDL_RenderTexture(renderer, texture, NULL, NULL); | ||
| 345 | SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); | ||
| 346 | SDL_RenderDebugText(renderer, 4, 4, colorspaces[i].name); | ||
| 347 | SDL_RenderPresent(renderer); | ||
| 348 | SDL_Delay(10); | ||
| 349 | } | ||
| 350 | |||
| 351 | SDL_DestroyTexture(texture); | ||
| 352 | texture = NULL; | ||
| 353 | } | ||
| 354 | |||
| 355 | result = true; | ||
| 356 | |||
| 357 | done: | ||
| 358 | SDL_DestroySurface(rgb); | ||
| 359 | SDL_DestroySurface(yuv); | ||
| 360 | SDL_DestroyTexture(texture); | ||
| 361 | SDL_Quit(); | ||
| 362 | return result; | ||
| 363 | } | ||
| 364 | |||
| 365 | int main(int argc, char **argv) | ||
| 366 | { | ||
| 367 | struct | ||
| 368 | { | ||
| 369 | bool enable_intrinsics; | ||
| 370 | int pattern_size; | ||
| 371 | int extra_pitch; | ||
| 372 | } automated_test_params[] = { | ||
| 373 | /* Test: single pixel */ | ||
| 374 | { false, 1, 0 }, | ||
| 375 | /* Test: even width and height */ | ||
| 376 | { false, 2, 0 }, | ||
| 377 | { false, 4, 0 }, | ||
| 378 | /* Test: odd width and height */ | ||
| 379 | { false, 1, 0 }, | ||
| 380 | { false, 3, 0 }, | ||
| 381 | /* Test: even width and height, extra pitch */ | ||
| 382 | { false, 2, 3 }, | ||
| 383 | { false, 4, 3 }, | ||
| 384 | /* Test: odd width and height, extra pitch */ | ||
| 385 | { false, 1, 3 }, | ||
| 386 | { false, 3, 3 }, | ||
| 387 | /* Test: even width and height with intrinsics */ | ||
| 388 | { true, 32, 0 }, | ||
| 389 | /* Test: odd width and height with intrinsics */ | ||
| 390 | { true, 33, 0 }, | ||
| 391 | { true, 37, 0 }, | ||
| 392 | /* Test: even width and height with intrinsics, extra pitch */ | ||
| 393 | { true, 32, 3 }, | ||
| 394 | /* Test: odd width and height with intrinsics, extra pitch */ | ||
| 395 | { true, 33, 3 }, | ||
| 396 | { true, 37, 3 }, | ||
| 397 | }; | ||
| 398 | char *filename = NULL; | ||
| 399 | SDL_Surface *original; | ||
| 400 | SDL_Surface *converted; | ||
| 401 | SDL_Surface *bmp; | ||
| 402 | SDL_Window *window; | ||
| 403 | SDL_Renderer *renderer; | ||
| 404 | SDL_Texture *output[3]; | ||
| 405 | const char *titles[3] = { "ORIGINAL", "SOFTWARE", "HARDWARE" }; | ||
| 406 | char title[128]; | ||
| 407 | YUV_CONVERSION_MODE yuv_mode; | ||
| 408 | const char *yuv_mode_name; | ||
| 409 | Uint32 yuv_format = SDL_PIXELFORMAT_YV12; | ||
| 410 | const char *yuv_format_name; | ||
| 411 | SDL_Colorspace yuv_colorspace; | ||
| 412 | Uint32 rgb_format = SDL_PIXELFORMAT_RGBX8888; | ||
| 413 | SDL_Colorspace rgb_colorspace = SDL_COLORSPACE_SRGB; | ||
| 414 | SDL_PropertiesID props; | ||
| 415 | bool monochrome = false; | ||
| 416 | int luminance = 100; | ||
| 417 | int current = 0; | ||
| 418 | int pitch; | ||
| 419 | Uint8 *raw_yuv; | ||
| 420 | Uint64 then, now; | ||
| 421 | int i, iterations = 100; | ||
| 422 | bool should_run_automated_tests = false; | ||
| 423 | bool should_run_colorspace_test = false; | ||
| 424 | SDLTest_CommonState *state; | ||
| 425 | |||
| 426 | /* Initialize test framework */ | ||
| 427 | state = SDLTest_CommonCreateState(argv, 0); | ||
| 428 | if (!state) { | ||
| 429 | return 1; | ||
| 430 | } | ||
| 431 | |||
| 432 | /* Parse commandline */ | ||
| 433 | for (i = 1; i < argc;) { | ||
| 434 | int consumed; | ||
| 435 | |||
| 436 | consumed = SDLTest_CommonArg(state, i); | ||
| 437 | if (!consumed) { | ||
| 438 | if (SDL_strcmp(argv[i], "--jpeg") == 0) { | ||
| 439 | SetYUVConversionMode(YUV_CONVERSION_JPEG); | ||
| 440 | consumed = 1; | ||
| 441 | } else if (SDL_strcmp(argv[i], "--bt601") == 0) { | ||
| 442 | SetYUVConversionMode(YUV_CONVERSION_BT601); | ||
| 443 | consumed = 1; | ||
| 444 | } else if (SDL_strcmp(argv[i], "--bt709") == 0) { | ||
| 445 | SetYUVConversionMode(YUV_CONVERSION_BT709); | ||
| 446 | consumed = 1; | ||
| 447 | } else if (SDL_strcmp(argv[i], "--bt2020") == 0) { | ||
| 448 | SetYUVConversionMode(YUV_CONVERSION_BT2020); | ||
| 449 | consumed = 1; | ||
| 450 | } else if (SDL_strcmp(argv[i], "--auto") == 0) { | ||
| 451 | SetYUVConversionMode(YUV_CONVERSION_AUTOMATIC); | ||
| 452 | consumed = 1; | ||
| 453 | } else if (SDL_strcmp(argv[i], "--yv12") == 0) { | ||
| 454 | yuv_format = SDL_PIXELFORMAT_YV12; | ||
| 455 | consumed = 1; | ||
| 456 | } else if (SDL_strcmp(argv[i], "--iyuv") == 0) { | ||
| 457 | yuv_format = SDL_PIXELFORMAT_IYUV; | ||
| 458 | consumed = 1; | ||
| 459 | } else if (SDL_strcmp(argv[i], "--yuy2") == 0) { | ||
| 460 | yuv_format = SDL_PIXELFORMAT_YUY2; | ||
| 461 | consumed = 1; | ||
| 462 | } else if (SDL_strcmp(argv[i], "--uyvy") == 0) { | ||
| 463 | yuv_format = SDL_PIXELFORMAT_UYVY; | ||
| 464 | consumed = 1; | ||
| 465 | } else if (SDL_strcmp(argv[i], "--yvyu") == 0) { | ||
| 466 | yuv_format = SDL_PIXELFORMAT_YVYU; | ||
| 467 | consumed = 1; | ||
| 468 | } else if (SDL_strcmp(argv[i], "--nv12") == 0) { | ||
| 469 | yuv_format = SDL_PIXELFORMAT_NV12; | ||
| 470 | consumed = 1; | ||
| 471 | } else if (SDL_strcmp(argv[i], "--nv21") == 0) { | ||
| 472 | yuv_format = SDL_PIXELFORMAT_NV21; | ||
| 473 | consumed = 1; | ||
| 474 | } else if (SDL_strcmp(argv[i], "--rgb555") == 0) { | ||
| 475 | rgb_format = SDL_PIXELFORMAT_XRGB1555; | ||
| 476 | consumed = 1; | ||
| 477 | } else if (SDL_strcmp(argv[i], "--rgb565") == 0) { | ||
| 478 | rgb_format = SDL_PIXELFORMAT_RGB565; | ||
| 479 | consumed = 1; | ||
| 480 | } else if (SDL_strcmp(argv[i], "--rgb24") == 0) { | ||
| 481 | rgb_format = SDL_PIXELFORMAT_RGB24; | ||
| 482 | consumed = 1; | ||
| 483 | } else if (SDL_strcmp(argv[i], "--argb") == 0) { | ||
| 484 | rgb_format = SDL_PIXELFORMAT_ARGB8888; | ||
| 485 | consumed = 1; | ||
| 486 | } else if (SDL_strcmp(argv[i], "--abgr") == 0) { | ||
| 487 | rgb_format = SDL_PIXELFORMAT_ABGR8888; | ||
| 488 | consumed = 1; | ||
| 489 | } else if (SDL_strcmp(argv[i], "--rgba") == 0) { | ||
| 490 | rgb_format = SDL_PIXELFORMAT_RGBA8888; | ||
| 491 | consumed = 1; | ||
| 492 | } else if (SDL_strcmp(argv[i], "--bgra") == 0) { | ||
| 493 | rgb_format = SDL_PIXELFORMAT_BGRA8888; | ||
| 494 | consumed = 1; | ||
| 495 | } else if (SDL_strcmp(argv[i], "--monochrome") == 0) { | ||
| 496 | monochrome = true; | ||
| 497 | consumed = 1; | ||
| 498 | } else if (SDL_strcmp(argv[i], "--luminance") == 0 && argv[i+1]) { | ||
| 499 | luminance = SDL_atoi(argv[i+1]); | ||
| 500 | consumed = 2; | ||
| 501 | } else if (SDL_strcmp(argv[i], "--automated") == 0) { | ||
| 502 | should_run_automated_tests = true; | ||
| 503 | consumed = 1; | ||
| 504 | } else if (SDL_strcmp(argv[i], "--colorspace-test") == 0) { | ||
| 505 | should_run_colorspace_test = true; | ||
| 506 | consumed = 1; | ||
| 507 | } else if (!filename) { | ||
| 508 | filename = argv[i]; | ||
| 509 | consumed = 1; | ||
| 510 | } | ||
| 511 | } | ||
| 512 | if (consumed <= 0) { | ||
| 513 | static const char *options[] = { | ||
| 514 | "[--jpeg|--bt601|--bt709|--bt2020|--auto]", | ||
| 515 | "[--yv12|--iyuv|--yuy2|--uyvy|--yvyu|--nv12|--nv21]", | ||
| 516 | "[--rgb555|--rgb565|--rgb24|--argb|--abgr|--rgba|--bgra]", | ||
| 517 | "[--monochrome] [--luminance N%]", | ||
| 518 | "[--automated] [--colorspace-test]", | ||
| 519 | "[sample.bmp]", | ||
| 520 | NULL, | ||
| 521 | }; | ||
| 522 | SDLTest_CommonLogUsage(state, argv[0], options); | ||
| 523 | SDL_Quit(); | ||
| 524 | SDLTest_CommonDestroyState(state); | ||
| 525 | return 1; | ||
| 526 | } | ||
| 527 | i += consumed; | ||
| 528 | } | ||
| 529 | |||
| 530 | /* Run automated tests */ | ||
| 531 | if (should_run_automated_tests) { | ||
| 532 | for (i = 0; i < (int)SDL_arraysize(automated_test_params); ++i) { | ||
| 533 | SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Running automated test, pattern size %d, extra pitch %d, intrinsics %s", | ||
| 534 | automated_test_params[i].pattern_size, | ||
| 535 | automated_test_params[i].extra_pitch, | ||
| 536 | automated_test_params[i].enable_intrinsics ? "enabled" : "disabled"); | ||
| 537 | if (!run_automated_tests(automated_test_params[i].pattern_size, automated_test_params[i].extra_pitch)) { | ||
| 538 | return 2; | ||
| 539 | } | ||
| 540 | } | ||
| 541 | return 0; | ||
| 542 | } | ||
| 543 | |||
| 544 | if (should_run_colorspace_test) { | ||
| 545 | if (!run_colorspace_test()) { | ||
| 546 | return 2; | ||
| 547 | } | ||
| 548 | return 0; | ||
| 549 | } | ||
| 550 | |||
| 551 | filename = GetResourceFilename(filename, "testyuv.bmp"); | ||
| 552 | bmp = SDL_LoadBMP(filename); | ||
| 553 | original = SDL_ConvertSurface(bmp, SDL_PIXELFORMAT_RGB24); | ||
| 554 | SDL_DestroySurface(bmp); | ||
| 555 | if (!original) { | ||
| 556 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't load %s: %s", filename, SDL_GetError()); | ||
| 557 | return 3; | ||
| 558 | } | ||
| 559 | |||
| 560 | yuv_mode = GetYUVConversionModeForResolution(original->w, original->h); | ||
| 561 | switch (yuv_mode) { | ||
| 562 | case YUV_CONVERSION_JPEG: | ||
| 563 | yuv_mode_name = "JPEG"; | ||
| 564 | break; | ||
| 565 | case YUV_CONVERSION_BT601: | ||
| 566 | yuv_mode_name = "BT.601"; | ||
| 567 | break; | ||
| 568 | case YUV_CONVERSION_BT709: | ||
| 569 | yuv_mode_name = "BT.709"; | ||
| 570 | break; | ||
| 571 | case YUV_CONVERSION_BT2020: | ||
| 572 | yuv_mode_name = "BT.2020"; | ||
| 573 | yuv_format = SDL_PIXELFORMAT_P010; | ||
| 574 | rgb_format = SDL_PIXELFORMAT_XBGR2101010; | ||
| 575 | rgb_colorspace = SDL_COLORSPACE_HDR10; | ||
| 576 | break; | ||
| 577 | default: | ||
| 578 | yuv_mode_name = "UNKNOWN"; | ||
| 579 | break; | ||
| 580 | } | ||
| 581 | yuv_colorspace = GetColorspaceForYUVConversionMode(yuv_mode); | ||
| 582 | |||
| 583 | raw_yuv = SDL_calloc(1, MAX_YUV_SURFACE_SIZE(original->w, original->h, 0)); | ||
| 584 | ConvertRGBtoYUV(yuv_format, original->pixels, original->pitch, raw_yuv, original->w, original->h, yuv_mode, monochrome, luminance); | ||
| 585 | pitch = CalculateYUVPitch(yuv_format, original->w); | ||
| 586 | |||
| 587 | converted = SDL_CreateSurface(original->w, original->h, rgb_format); | ||
| 588 | if (!converted) { | ||
| 589 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create converted surface: %s", SDL_GetError()); | ||
| 590 | return 3; | ||
| 591 | } | ||
| 592 | |||
| 593 | then = SDL_GetTicks(); | ||
| 594 | for (i = 0; i < iterations; ++i) { | ||
| 595 | SDL_ConvertPixelsAndColorspace(original->w, original->h, yuv_format, yuv_colorspace, 0, raw_yuv, pitch, rgb_format, rgb_colorspace, 0, converted->pixels, converted->pitch); | ||
| 596 | } | ||
| 597 | now = SDL_GetTicks(); | ||
| 598 | SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "%d iterations in %" SDL_PRIu64 " ms, %.2fms each", iterations, (now - then), (float)(now - then) / iterations); | ||
| 599 | |||
| 600 | window = SDL_CreateWindow("YUV test", original->w, original->h, 0); | ||
| 601 | if (!window) { | ||
| 602 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create window: %s", SDL_GetError()); | ||
| 603 | return 4; | ||
| 604 | } | ||
| 605 | |||
| 606 | renderer = SDL_CreateRenderer(window, NULL); | ||
| 607 | if (!renderer) { | ||
| 608 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create renderer: %s", SDL_GetError()); | ||
| 609 | return 4; | ||
| 610 | } | ||
| 611 | |||
| 612 | output[0] = SDL_CreateTextureFromSurface(renderer, original); | ||
| 613 | output[1] = SDL_CreateTextureFromSurface(renderer, converted); | ||
| 614 | props = SDL_CreateProperties(); | ||
| 615 | SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_COLORSPACE_NUMBER, yuv_colorspace); | ||
| 616 | SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_FORMAT_NUMBER, yuv_format); | ||
| 617 | SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_ACCESS_NUMBER, SDL_TEXTUREACCESS_STREAMING); | ||
| 618 | SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_WIDTH_NUMBER, original->w); | ||
| 619 | SDL_SetNumberProperty(props, SDL_PROP_TEXTURE_CREATE_HEIGHT_NUMBER, original->h); | ||
| 620 | output[2] = SDL_CreateTextureWithProperties(renderer, props); | ||
| 621 | SDL_DestroyProperties(props); | ||
| 622 | if (!output[0] || !output[1] || !output[2]) { | ||
| 623 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't set create texture: %s", SDL_GetError()); | ||
| 624 | return 5; | ||
| 625 | } | ||
| 626 | SDL_UpdateTexture(output[2], NULL, raw_yuv, pitch); | ||
| 627 | |||
| 628 | yuv_format_name = SDL_GetPixelFormatName(yuv_format); | ||
| 629 | if (SDL_strncmp(yuv_format_name, "SDL_PIXELFORMAT_", 16) == 0) { | ||
| 630 | yuv_format_name += 16; | ||
| 631 | } | ||
| 632 | |||
| 633 | { | ||
| 634 | int done = 0; | ||
| 635 | while (!done) { | ||
| 636 | SDL_Event event; | ||
| 637 | while (SDL_PollEvent(&event) > 0) { | ||
| 638 | if (event.type == SDL_EVENT_QUIT) { | ||
| 639 | done = 1; | ||
| 640 | } | ||
| 641 | if (event.type == SDL_EVENT_KEY_DOWN) { | ||
| 642 | if (event.key.key == SDLK_ESCAPE) { | ||
| 643 | done = 1; | ||
| 644 | } else if (event.key.key == SDLK_LEFT) { | ||
| 645 | --current; | ||
| 646 | } else if (event.key.key == SDLK_RIGHT) { | ||
| 647 | ++current; | ||
| 648 | } | ||
| 649 | } | ||
| 650 | if (event.type == SDL_EVENT_MOUSE_BUTTON_DOWN) { | ||
| 651 | if (event.button.x < (original->w / 2)) { | ||
| 652 | --current; | ||
| 653 | } else { | ||
| 654 | ++current; | ||
| 655 | } | ||
| 656 | } | ||
| 657 | } | ||
| 658 | |||
| 659 | /* Handle wrapping */ | ||
| 660 | if (current < 0) { | ||
| 661 | current += SDL_arraysize(output); | ||
| 662 | } | ||
| 663 | if (current >= SDL_arraysize(output)) { | ||
| 664 | current -= SDL_arraysize(output); | ||
| 665 | } | ||
| 666 | |||
| 667 | SDL_RenderClear(renderer); | ||
| 668 | SDL_RenderTexture(renderer, output[current], NULL, NULL); | ||
| 669 | SDL_SetRenderDrawColor(renderer, 0xFF, 0xFF, 0xFF, 0xFF); | ||
| 670 | if (current == 0) { | ||
| 671 | SDLTest_DrawString(renderer, 4, 4, titles[current]); | ||
| 672 | } else { | ||
| 673 | (void)SDL_snprintf(title, sizeof(title), "%s %s %s", titles[current], yuv_format_name, yuv_mode_name); | ||
| 674 | SDLTest_DrawString(renderer, 4, 4, title); | ||
| 675 | } | ||
| 676 | SDL_RenderPresent(renderer); | ||
| 677 | SDL_Delay(10); | ||
| 678 | } | ||
| 679 | } | ||
| 680 | SDL_free(raw_yuv); | ||
| 681 | SDL_free(filename); | ||
| 682 | SDL_DestroySurface(original); | ||
| 683 | SDL_DestroySurface(converted); | ||
| 684 | SDLTest_CleanupTextDrawing(); | ||
| 685 | SDL_DestroyRenderer(renderer); | ||
| 686 | SDL_DestroyWindow(window); | ||
| 687 | SDL_Quit(); | ||
| 688 | SDLTest_CommonDestroyState(state); | ||
| 689 | return 0; | ||
| 690 | } | ||
