Atlas - testyuv_cvt.c
Home / ext / SDL / test Lines: 1 | Size: 16907 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)][FILE BEGIN]1/* 2 Copyright (C) 1997-2025 Sam Lantinga <[email protected]> 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 13#include <SDL3/SDL.h> 14 15#include "testyuv_cvt.h" 16 17#define YUV_SD_THRESHOLD 576 18 19static YUV_CONVERSION_MODE YUV_ConversionMode = YUV_CONVERSION_BT601; 20 21void SetYUVConversionMode(YUV_CONVERSION_MODE mode) 22{ 23 YUV_ConversionMode = mode; 24} 25 26YUV_CONVERSION_MODE GetYUVConversionMode(void) 27{ 28 return YUV_ConversionMode; 29} 30 31YUV_CONVERSION_MODE GetYUVConversionModeForResolution(int width, int height) 32{ 33 YUV_CONVERSION_MODE mode = GetYUVConversionMode(); 34 if (mode == YUV_CONVERSION_AUTOMATIC) { 35 if (height <= YUV_SD_THRESHOLD) { 36 mode = YUV_CONVERSION_BT601; 37 } else { 38 mode = YUV_CONVERSION_BT709; 39 } 40 } 41 return mode; 42} 43 44SDL_Colorspace GetColorspaceForYUVConversionMode(YUV_CONVERSION_MODE mode) 45{ 46 SDL_Colorspace colorspace; 47 48 switch (mode) { 49 case YUV_CONVERSION_JPEG: 50 colorspace = SDL_DEFINE_COLORSPACE(SDL_COLOR_TYPE_YCBCR, 51 SDL_COLOR_RANGE_FULL, 52 SDL_COLOR_PRIMARIES_BT709, 53 SDL_TRANSFER_CHARACTERISTICS_BT601, 54 SDL_MATRIX_COEFFICIENTS_BT601, 55 SDL_CHROMA_LOCATION_CENTER); 56 break; 57 case YUV_CONVERSION_BT601: 58 colorspace = SDL_DEFINE_COLORSPACE(SDL_COLOR_TYPE_YCBCR, 59 SDL_COLOR_RANGE_LIMITED, 60 SDL_COLOR_PRIMARIES_BT709, 61 SDL_TRANSFER_CHARACTERISTICS_BT601, 62 SDL_MATRIX_COEFFICIENTS_BT601, 63 SDL_CHROMA_LOCATION_CENTER); 64 break; 65 case YUV_CONVERSION_BT709: 66 colorspace = SDL_DEFINE_COLORSPACE(SDL_COLOR_TYPE_YCBCR, 67 SDL_COLOR_RANGE_LIMITED, 68 SDL_COLOR_PRIMARIES_BT709, 69 SDL_TRANSFER_CHARACTERISTICS_BT709, 70 SDL_MATRIX_COEFFICIENTS_BT709, 71 SDL_CHROMA_LOCATION_CENTER); 72 break; 73 case YUV_CONVERSION_BT2020: 74 colorspace = SDL_COLORSPACE_BT2020_FULL; 75 break; 76 default: 77 colorspace = SDL_COLORSPACE_UNKNOWN; 78 break; 79 } 80 return colorspace; 81} 82 83static float clip3(float x, float y, float z) 84{ 85 return (z < x) ? x : ((z > y) ? y : z); 86} 87 88static float sRGBtoNits(float v) 89{ 90 /* Normalize from 0..255 */ 91 v /= 255.0f; 92 93 /* Convert from sRGB */ 94 v = v <= 0.04045f ? (v / 12.92f) : SDL_powf(((v + 0.055f) / 1.055f), 2.4f); 95 96 /* Convert to nits, using a default SDR whitepoint of 203 */ 97 v *= 203.0f; 98 99 return v; 100} 101 102static float PQfromNits(float v) 103{ 104 const float c1 = 0.8359375f; 105 const float c2 = 18.8515625f; 106 const float c3 = 18.6875f; 107 const float m1 = 0.1593017578125f; 108 const float m2 = 78.84375f; 109 110 float y = SDL_clamp(v / 10000.0f, 0.0f, 1.0f); 111 float num = c1 + c2 * SDL_powf(y, m1); 112 float den = 1.0f + c3 * SDL_powf(y, m1); 113 return SDL_powf(num / den, m2); 114} 115 116void ConvertRec709toRec2020(float *fR, float *fG, float *fB) 117{ 118 static const float mat709to2020[] = { 119 0.627404f, 0.329283f, 0.043313f, 120 0.069097f, 0.919541f, 0.011362f, 121 0.016391f, 0.088013f, 0.895595f, 122 }; 123 const float *matrix = mat709to2020; 124 float v[3]; 125 126 v[0] = *fR; 127 v[1] = *fG; 128 v[2] = *fB; 129 130 *fR = matrix[0 * 3 + 0] * v[0] + matrix[0 * 3 + 1] * v[1] + matrix[0 * 3 + 2] * v[2]; 131 *fG = matrix[1 * 3 + 0] * v[0] + matrix[1 * 3 + 1] * v[1] + matrix[1 * 3 + 2] * v[2]; 132 *fB = matrix[2 * 3 + 0] * v[0] + matrix[2 * 3 + 1] * v[1] + matrix[2 * 3 + 2] * v[2]; 133} 134 135static void RGBtoYUV(const Uint8 *rgb, int rgb_bits, int *yuv, int yuv_bits, YUV_CONVERSION_MODE mode, int monochrome, int luminance) 136{ 137 /** 138 * This formula is from Microsoft's documentation: 139 * https://msdn.microsoft.com/en-us/library/windows/desktop/dd206750(v=vs.85).aspx 140 * L = Kr * R + Kb * B + (1 - Kr - Kb) * G 141 * Y = floor(2^(M-8) * (219*(L-Z)/S + 16) + 0.5); 142 * U = clip3(0, (2^M)-1, floor(2^(M-8) * (112*(B-L) / ((1-Kb)*S) + 128) + 0.5)); 143 * V = clip3(0, (2^M)-1, floor(2^(M-8) * (112*(R-L) / ((1-Kr)*S) + 128) + 0.5)); 144 */ 145 bool studio_RGB = false; 146 bool full_range_YUV = false; 147 float N, M, S, Z, R, G, B, L, Kr, Kb, Y, U, V; 148 149 N = (float)rgb_bits; 150 M = (float)yuv_bits; 151 switch (mode) { 152 case YUV_CONVERSION_JPEG: 153 case YUV_CONVERSION_BT601: 154 /* BT.601 */ 155 Kr = 0.299f; 156 Kb = 0.114f; 157 break; 158 case YUV_CONVERSION_BT709: 159 /* BT.709 */ 160 Kr = 0.2126f; 161 Kb = 0.0722f; 162 break; 163 case YUV_CONVERSION_BT2020: 164 /* BT.2020 */ 165 Kr = 0.2627f; 166 Kb = 0.0593f; 167 break; 168 default: 169 /* Invalid */ 170 Kr = 1.0f; 171 Kb = 1.0f; 172 break; 173 } 174 175 R = rgb[0]; 176 G = rgb[1]; 177 B = rgb[2]; 178 179 if (mode == YUV_CONVERSION_JPEG || mode == YUV_CONVERSION_BT2020) { 180 full_range_YUV = true; 181 } 182 183 if (mode == YUV_CONVERSION_BT2020) { 184 /* Input is sRGB, need to convert to BT.2020 PQ YUV */ 185 R = sRGBtoNits(R); 186 G = sRGBtoNits(G); 187 B = sRGBtoNits(B); 188 ConvertRec709toRec2020(&R, &G, &B); 189 R = PQfromNits(R); 190 G = PQfromNits(G); 191 B = PQfromNits(B); 192 S = 1.0f; 193 Z = 0.0f; 194 } else if (studio_RGB) { 195 S = 219.0f * SDL_powf(2.0f, N - 8); 196 Z = 16.0f * SDL_powf(2.0f, N - 8); 197 } else { 198 S = 255.0f; 199 Z = 0.0f; 200 } 201 L = Kr * R + Kb * B + (1 - Kr - Kb) * G; 202 if (monochrome) { 203 R = L; 204 B = L; 205 } 206 if (full_range_YUV) { 207 Y = SDL_floorf((SDL_powf(2.0f, M) - 1) * ((L - Z) / S) + 0.5f); 208 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)); 209 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)); 210 } else { 211 Y = SDL_floorf(SDL_powf(2.0f, (M - 8)) * (219.0f * (L - Z) / S + 16) + 0.5f); 212 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)); 213 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)); 214 } 215 216 yuv[0] = (int)Y; 217 yuv[1] = (int)U; 218 yuv[2] = (int)V; 219 220 if (luminance != 100) { 221 yuv[0] = (int)clip3(0, SDL_powf(2.0f, M) - 1, SDL_roundf(yuv[0] * (luminance / 100.0f))); 222 } 223} 224 225static void ConvertRGBtoPlanar2x2(Uint32 format, Uint8 *src, int pitch, Uint8 *out, int w, int h, YUV_CONVERSION_MODE mode, int monochrome, int luminance) 226{ 227 int x, y; 228 int yuv[4][3]; 229 Uint8 *Y1, *Y2, *U, *V; 230 Uint8 *rgb1, *rgb2; 231 int rgb_row_advance = (pitch - w * 3) + pitch; 232 int UV_advance; 233 234 rgb1 = src; 235 rgb2 = src + pitch; 236 237 Y1 = out; 238 Y2 = Y1 + w; 239 switch (format) { 240 case SDL_PIXELFORMAT_YV12: 241 V = (Y1 + h * w); 242 U = V + ((h + 1) / 2) * ((w + 1) / 2); 243 UV_advance = 1; 244 break; 245 case SDL_PIXELFORMAT_IYUV: 246 U = (Y1 + h * w); 247 V = U + ((h + 1) / 2) * ((w + 1) / 2); 248 UV_advance = 1; 249 break; 250 case SDL_PIXELFORMAT_NV12: 251 U = (Y1 + h * w); 252 V = U + 1; 253 UV_advance = 2; 254 break; 255 case SDL_PIXELFORMAT_NV21: 256 V = (Y1 + h * w); 257 U = V + 1; 258 UV_advance = 2; 259 break; 260 default: 261 SDL_assert(!"Unsupported planar YUV format"); 262 return; 263 } 264 265 for (y = 0; y < (h - 1); y += 2) { 266 for (x = 0; x < (w - 1); x += 2) { 267 RGBtoYUV(rgb1, 8, yuv[0], 8, mode, monochrome, luminance); 268 rgb1 += 3; 269 *Y1++ = (Uint8)yuv[0][0]; 270 271 RGBtoYUV(rgb1, 8, yuv[1], 8, mode, monochrome, luminance); 272 rgb1 += 3; 273 *Y1++ = (Uint8)yuv[1][0]; 274 275 RGBtoYUV(rgb2, 8, yuv[2], 8, mode, monochrome, luminance); 276 rgb2 += 3; 277 *Y2++ = (Uint8)yuv[2][0]; 278 279 RGBtoYUV(rgb2, 8, yuv[3], 8, mode, monochrome, luminance); 280 rgb2 += 3; 281 *Y2++ = (Uint8)yuv[3][0]; 282 283 *U = (Uint8)SDL_floorf((yuv[0][1] + yuv[1][1] + yuv[2][1] + yuv[3][1]) / 4.0f + 0.5f); 284 U += UV_advance; 285 286 *V = (Uint8)SDL_floorf((yuv[0][2] + yuv[1][2] + yuv[2][2] + yuv[3][2]) / 4.0f + 0.5f); 287 V += UV_advance; 288 } 289 /* Last column */ 290 if (x == (w - 1)) { 291 RGBtoYUV(rgb1, 8, yuv[0], 8, mode, monochrome, luminance); 292 rgb1 += 3; 293 *Y1++ = (Uint8)yuv[0][0]; 294 295 RGBtoYUV(rgb2, 8, yuv[2], 8, mode, monochrome, luminance); 296 rgb2 += 3; 297 *Y2++ = (Uint8)yuv[2][0]; 298 299 *U = (Uint8)SDL_floorf((yuv[0][1] + yuv[2][1]) / 2.0f + 0.5f); 300 U += UV_advance; 301 302 *V = (Uint8)SDL_floorf((yuv[0][2] + yuv[2][2]) / 2.0f + 0.5f); 303 V += UV_advance; 304 } 305 Y1 += w; 306 Y2 += w; 307 rgb1 += rgb_row_advance; 308 rgb2 += rgb_row_advance; 309 } 310 /* Last row */ 311 if (y == (h - 1)) { 312 for (x = 0; x < (w - 1); x += 2) { 313 RGBtoYUV(rgb1, 8, yuv[0], 8, mode, monochrome, luminance); 314 rgb1 += 3; 315 *Y1++ = (Uint8)yuv[0][0]; 316 317 RGBtoYUV(rgb1, 8, yuv[1], 8, mode, monochrome, luminance); 318 rgb1 += 3; 319 *Y1++ = (Uint8)yuv[1][0]; 320 321 *U = (Uint8)SDL_floorf((yuv[0][1] + yuv[1][1]) / 2.0f + 0.5f); 322 U += UV_advance; 323 324 *V = (Uint8)SDL_floorf((yuv[0][2] + yuv[1][2]) / 2.0f + 0.5f); 325 V += UV_advance; 326 } 327 /* Last column */ 328 if (x == (w - 1)) { 329 RGBtoYUV(rgb1, 8, yuv[0], 8, mode, monochrome, luminance); 330 *Y1++ = (Uint8)yuv[0][0]; 331 332 *U = (Uint8)yuv[0][1]; 333 U += UV_advance; 334 335 *V = (Uint8)yuv[0][2]; 336 V += UV_advance; 337 } 338 } 339} 340 341static Uint16 Pack10to16(int v) 342{ 343 return (Uint16)(v << 6); 344} 345 346static void ConvertRGBtoPlanar2x2_P010(Uint32 format, Uint8 *src, int pitch, Uint8 *out, int w, int h, YUV_CONVERSION_MODE mode, int monochrome, int luminance) 347{ 348 int x, y; 349 int yuv[4][3]; 350 Uint16 *Y1, *Y2, *U, *V; 351 Uint8 *rgb1, *rgb2; 352 int rgb_row_advance = (pitch - w * 3) + pitch; 353 int UV_advance; 354 355 rgb1 = src; 356 rgb2 = src + pitch; 357 358 Y1 = (Uint16 *)out; 359 Y2 = Y1 + w; 360 switch (format) { 361 case SDL_PIXELFORMAT_P010: 362 U = (Y1 + h * w); 363 V = U + 1; 364 UV_advance = 2; 365 break; 366 default: 367 SDL_assert(!"Unsupported planar YUV format"); 368 return; 369 } 370 371 for (y = 0; y < (h - 1); y += 2) { 372 for (x = 0; x < (w - 1); x += 2) { 373 RGBtoYUV(rgb1, 8, yuv[0], 10, mode, monochrome, luminance); 374 rgb1 += 3; 375 *Y1++ = Pack10to16(yuv[0][0]); 376 377 RGBtoYUV(rgb1, 8, yuv[1], 10, mode, monochrome, luminance); 378 rgb1 += 3; 379 *Y1++ = Pack10to16(yuv[1][0]); 380 381 RGBtoYUV(rgb2, 8, yuv[2], 10, mode, monochrome, luminance); 382 rgb2 += 3; 383 *Y2++ = Pack10to16(yuv[2][0]); 384 385 RGBtoYUV(rgb2, 8, yuv[3], 10, mode, monochrome, luminance); 386 rgb2 += 3; 387 *Y2++ = Pack10to16(yuv[3][0]); 388 389 *U = Pack10to16((int)SDL_floorf((yuv[0][1] + yuv[1][1] + yuv[2][1] + yuv[3][1]) / 4.0f + 0.5f)); 390 U += UV_advance; 391 392 *V = Pack10to16((int)SDL_floorf((yuv[0][2] + yuv[1][2] + yuv[2][2] + yuv[3][2]) / 4.0f + 0.5f)); 393 V += UV_advance; 394 } 395 /* Last column */ 396 if (x == (w - 1)) { 397 RGBtoYUV(rgb1, 8, yuv[0], 10, mode, monochrome, luminance); 398 rgb1 += 3; 399 *Y1++ = Pack10to16(yuv[0][0]); 400 401 RGBtoYUV(rgb2, 8, yuv[2], 10, mode, monochrome, luminance); 402 rgb2 += 3; 403 *Y2++ = Pack10to16(yuv[2][0]); 404 405 *U = Pack10to16((int)SDL_floorf((yuv[0][1] + yuv[2][1]) / 2.0f + 0.5f)); 406 U += UV_advance; 407 408 *V = Pack10to16((int)SDL_floorf((yuv[0][2] + yuv[2][2]) / 2.0f + 0.5f)); 409 V += UV_advance; 410 } 411 Y1 += w; 412 Y2 += w; 413 rgb1 += rgb_row_advance; 414 rgb2 += rgb_row_advance; 415 } 416 /* Last row */ 417 if (y == (h - 1)) { 418 for (x = 0; x < (w - 1); x += 2) { 419 RGBtoYUV(rgb1, 8, yuv[0], 10, mode, monochrome, luminance); 420 rgb1 += 3; 421 *Y1++ = Pack10to16(yuv[0][0]); 422 423 RGBtoYUV(rgb1, 8, yuv[1], 10, mode, monochrome, luminance); 424 rgb1 += 3; 425 *Y1++ = Pack10to16(yuv[1][0]); 426 427 *U = Pack10to16((int)SDL_floorf((yuv[0][1] + yuv[1][1]) / 2.0f + 0.5f)); 428 U += UV_advance; 429 430 *V = Pack10to16((int)SDL_floorf((yuv[0][2] + yuv[1][2]) / 2.0f + 0.5f)); 431 V += UV_advance; 432 } 433 /* Last column */ 434 if (x == (w - 1)) { 435 RGBtoYUV(rgb1, 8, yuv[0], 10, mode, monochrome, luminance); 436 *Y1++ = Pack10to16(yuv[0][0]); 437 438 *U = Pack10to16(yuv[0][1]); 439 U += UV_advance; 440 441 *V = Pack10to16(yuv[0][2]); 442 V += UV_advance; 443 } 444 } 445} 446 447static void ConvertRGBtoPacked4(Uint32 format, Uint8 *src, int pitch, Uint8 *out, int w, int h, YUV_CONVERSION_MODE mode, int monochrome, int luminance) 448{ 449 int x, y; 450 int yuv[2][3]; 451 Uint8 *Y1, *Y2, *U, *V; 452 Uint8 *rgb; 453 int rgb_row_advance = (pitch - w * 3); 454 455 rgb = src; 456 457 switch (format) { 458 case SDL_PIXELFORMAT_YUY2: 459 Y1 = out; 460 U = out + 1; 461 Y2 = out + 2; 462 V = out + 3; 463 break; 464 case SDL_PIXELFORMAT_UYVY: 465 U = out; 466 Y1 = out + 1; 467 V = out + 2; 468 Y2 = out + 3; 469 break; 470 case SDL_PIXELFORMAT_YVYU: 471 Y1 = out; 472 V = out + 1; 473 Y2 = out + 2; 474 U = out + 3; 475 break; 476 default: 477 SDL_assert(!"Unsupported packed YUV format"); 478 return; 479 } 480 481 for (y = 0; y < h; ++y) { 482 for (x = 0; x < (w - 1); x += 2) { 483 RGBtoYUV(rgb, 8, yuv[0], 8, mode, monochrome, luminance); 484 rgb += 3; 485 *Y1 = (Uint8)yuv[0][0]; 486 Y1 += 4; 487 488 RGBtoYUV(rgb, 8, yuv[1], 8, mode, monochrome, luminance); 489 rgb += 3; 490 *Y2 = (Uint8)yuv[1][0]; 491 Y2 += 4; 492 493 *U = (Uint8)SDL_floorf((yuv[0][1] + yuv[1][1]) / 2.0f + 0.5f); 494 U += 4; 495 496 *V = (Uint8)SDL_floorf((yuv[0][2] + yuv[1][2]) / 2.0f + 0.5f); 497 V += 4; 498 } 499 /* Last column */ 500 if (x == (w - 1)) { 501 RGBtoYUV(rgb, 8, yuv[0], 8, mode, monochrome, luminance); 502 rgb += 3; 503 *Y2 = *Y1 = (Uint8)yuv[0][0]; 504 Y1 += 4; 505 Y2 += 4; 506 507 *U = (Uint8)yuv[0][1]; 508 U += 4; 509 510 *V = (Uint8)yuv[0][2]; 511 V += 4; 512 } 513 rgb += rgb_row_advance; 514 } 515} 516 517bool ConvertRGBtoYUV(Uint32 format, Uint8 *src, int pitch, Uint8 *out, int w, int h, YUV_CONVERSION_MODE mode, int monochrome, int luminance) 518{ 519 switch (format) { 520 case SDL_PIXELFORMAT_P010: 521 ConvertRGBtoPlanar2x2_P010(format, src, pitch, out, w, h, mode, monochrome, luminance); 522 return true; 523 case SDL_PIXELFORMAT_YV12: 524 case SDL_PIXELFORMAT_IYUV: 525 case SDL_PIXELFORMAT_NV12: 526 case SDL_PIXELFORMAT_NV21: 527 ConvertRGBtoPlanar2x2(format, src, pitch, out, w, h, mode, monochrome, luminance); 528 return true; 529 case SDL_PIXELFORMAT_YUY2: 530 case SDL_PIXELFORMAT_UYVY: 531 case SDL_PIXELFORMAT_YVYU: 532 ConvertRGBtoPacked4(format, src, pitch, out, w, h, mode, monochrome, luminance); 533 return true; 534 default: 535 return false; 536 } 537} 538 539int CalculateYUVPitch(Uint32 format, int width) 540{ 541 switch (format) { 542 case SDL_PIXELFORMAT_P010: 543 return width * 2; 544 case SDL_PIXELFORMAT_YV12: 545 case SDL_PIXELFORMAT_IYUV: 546 case SDL_PIXELFORMAT_NV12: 547 case SDL_PIXELFORMAT_NV21: 548 return width; 549 case SDL_PIXELFORMAT_YUY2: 550 case SDL_PIXELFORMAT_UYVY: 551 case SDL_PIXELFORMAT_YVYU: 552 return 4 * ((width + 1) / 2); 553 default: 554 return 0; 555 } 556} 557[FILE END](C) 2025 0x4248 (C) 2025 4248 Media and 4248 Systems, All part of 0x4248 See LICENCE files for more information. Not all files are by 0x4248 always check Licencing.