History

avutil/csp: add EOTF function definitions

The existing av_csp_trc_func_from_id() mostly implements the OETF, except for PQ. As such, we are currently missing a precise definition of an ITU-R EOTF. Introduce the new functions av_csp_itu_eotf() and av_csp_itu_eotf_inv(), to fill this void. Note that this is not possible in all cases, e.g. AVCOL_TRC_LOG which has no corresponding EOTF definition in any ITU-R standard. Note that we cannot implement the proper HLG and SMPTE 428 OOTFs without access to all three color channels, because they are not independent per channel. As a result, we need to define them on double[3] instead of double (*func)(double).

Niklas Haas committed 1 year ago

Commit 06f08441 parent bf0a6c44 files · +242 −1Browse files

Unified · Split

4 files
  1. doc/APIchanges +3 −0
  2. libavutil/csp.c +205 −0
  3. libavutil/csp.h +33 −0
  4. libavutil/version.h +1 −1
doc/APIchanges+3−0
@@ -2,6 +2,9 @@ The last version increases of all libraries were on 2024-03-07 · 1 unmodified line
2 2
3API changes, most recent first:3API changes, most recent first:
4 4
52024-12-05 - xxxxxxxxxx - lavu 59.49.100 - csp.h
6 Add av_csp_itu_eotf() and av_csp_itu_eotf_inv().
7
52024-12-05 - xxxxxxxxxx - lavu 59.48.100 - csp.h82024-12-05 - xxxxxxxxxx - lavu 59.48.100 - csp.h
6 Add av_csp_trc_func_inv_from_id().9 Add av_csp_trc_func_inv_from_id().
7 10
libavutil/csp.c+205−0
@@ -429,3 +429,208 @@ av_csp_trc_function av_csp_trc_func_inv_from_id(enum AVColorTransferCharacterist · 428 unmodified lines
429 return NULL;429 return NULL;
430 return trc_inv_funcs[trc];430 return trc_inv_funcs[trc];
431}431}
432
433static void eotf_linear(const double Lw, const double Lb, double E[3])
434{
435 for (int i = 0; i < 3; i++)
436 E[i] = (Lw - Lb) * E[i] + Lb;
437}
438
439static void eotf_linear_inv(const double Lw, const double Lb, double L[3])
440{
441 for (int i = 0; i < 3; i++)
442 L[i] = (L[i] - Lb) / (Lw - Lb);
443}
444
445#define WRAP_SDR_OETF(name) \
446static void oetf_##name(double L[3]) \
447{ \
448 for (int i = 0; i < 3; i++) \
449 L[i] = trc_##name(L[i]); \
450} \
451 \
452static void oetf_##name##_inv(double E[3]) \
453{ \
454 for (int i = 0; i < 3; i++) \
455 E[i] = trc_##name##_inv(E[i]); \
456}
457
458WRAP_SDR_OETF(gamma22)
459WRAP_SDR_OETF(gamma28)
460WRAP_SDR_OETF(iec61966_2_1)
461
462#define WRAP_SDR_EOTF(name) \
463static void eotf_##name(double Lw, double Lb, double E[3]) \
464{ \
465 oetf_##name##_inv(E); \
466 eotf_linear(Lw, Lb, E); \
467} \
468 \
469static void eotf_##name##_inv(double Lw, double Lb, double L[3]) \
470{ \
471 eotf_linear_inv(Lw, Lb, L); \
472 oetf_##name(L); \
473}
474
475WRAP_SDR_EOTF(gamma22)
476WRAP_SDR_EOTF(gamma28)
477WRAP_SDR_EOTF(iec61966_2_1)
478
479static void eotf_bt1886(const double Lw, const double Lb, double E[3])
480{
481 const double Lw_inv = pow(Lw, 1.0 / 2.4);
482 const double Lb_inv = pow(Lb, 1.0 / 2.4);
483 const double a = pow(Lw_inv - Lb_inv, 2.4);
484 const double b = Lb_inv / (Lw_inv - Lb_inv);
485
486 for (int i = 0; i < 3; i++)
487 E[i] = (-b > E[i]) ? 0.0 : a * pow(E[i] + b, 2.4);
488}
489
490static void eotf_bt1886_inv(const double Lw, const double Lb, double L[3])
491{
492 const double Lw_inv = pow(Lw, 1.0 / 2.4);
493 const double Lb_inv = pow(Lb, 1.0 / 2.4);
494 const double a = pow(Lw_inv - Lb_inv, 2.4);
495 const double b = Lb_inv / (Lw_inv - Lb_inv);
496
497 for (int i = 0; i < 3; i++)
498 L[i] = (0.0 > L[i]) ? 0.0 : pow(L[i] / a, 1.0 / 2.4) - b;
499}
500
501static void eotf_smpte_st2084(const double Lw, const double Lb, double E[3])
502{
503 for (int i = 0; i < 3; i++)
504 E[i] = trc_smpte_st2084_inv(E[i]);
505}
506
507static void eotf_smpte_st2084_inv(const double Lw, const double Lb, double L[3])
508{
509 for (int i = 0; i < 3; i++)
510 L[i] = trc_smpte_st2084(L[i]);
511}
512
513/* This implementation assumes an SMPTE RP 431-2 reference projector (DCI) */
514#define DCI_L 48.00
515#define DCI_P 52.37
516#define DCI_X (42.94 / DCI_L)
517#define DCI_Z (45.82 / DCI_L)
518
519static void eotf_smpte_st428_1(const double Lw_Y, const double Lb_Y, double E[3])
520{
521 const double Lw[3] = { DCI_X * Lw_Y, Lw_Y, DCI_Z * Lw_Y };
522 const double Lb[3] = { DCI_X * Lb_Y, Lb_Y, DCI_Z * Lb_Y };
523
524 for (int i = 0; i < 3; i++) {
525 E[i] = (0.0 > E[i]) ? 0.0 : pow(E[i], 2.6) * DCI_P / DCI_L;
526 E[i] = E[i] * (Lw[i] - Lb[i]) + Lb[i];
527 }
528}
529
530static void eotf_smpte_st428_1_inv(const double Lw_Y, const double Lb_Y, double L[3])
531{
532 const double Lw[3] = { DCI_X * Lw_Y, Lw_Y, DCI_Z * Lw_Y };
533 const double Lb[3] = { DCI_X * Lb_Y, Lb_Y, DCI_Z * Lb_Y };
534
535 for (int i = 0; i < 3; i++) {
536 L[i] = (L[i] - Lb[i]) / (Lw[i] - Lb[i]);
537 L[i] = (0.0 > L[i]) ? 0.0 : pow(L[i] * DCI_L / DCI_P, 1.0 / 2.6);
538 }
539}
540
541static void eotf_arib_std_b67(const double Lw, const double Lb, double E[3])
542{
543 const double gamma = fmax(1.2 + 0.42 * log10(Lw / 1000.0), 1.0);
544
545 /**
546 * Note: This equation is technically only accurate if the contrast ratio
547 * Lw:Lb is greater than 12:1; otherwise we would need to use a different,
548 * significantly more complicated solution. Ignore this as a highly
549 * degenerate case, since any real world reference display will have a
550 * static contrast ratio multiple orders of magnitude higher.
551 */
552 const double beta = sqrt(3 * pow(Lb / Lw, 1.0 / gamma));
553 double luma;
554
555 for (int i = 0; i < 3; i++)
556 E[i] = trc_arib_std_b67_inv((1 - beta) * E[i] + beta);
557
558 luma = 0.2627 * E[0] + 0.6780 * E[1] + 0.0593 * E[2];
559 luma = pow(fmax(luma, 0.0), gamma - 1.0);
560 for (int i = 0; i < 3; i++)
561 E[i] *= Lw * luma;
562}
563
564static void eotf_arib_std_b67_inv(const double Lw, const double Lb, double L[3])
565{
566 const double gamma = fmax(1.2 + 0.42 * log10(Lw / 1000.0), 1.0);
567 const double beta = sqrt(3 * pow(Lb / Lw, 1 / gamma));
568 double luma = 0.2627 * L[0] + 0.6780 * L[1] + 0.0593 * L[2];
569
570 if (luma > 0.0) {
571 luma = pow(luma / Lw, (1 - gamma) / gamma);
572 for (int i = 0; i < 3; i++)
573 L[i] *= luma / Lw;
574 } else {
575 L[0] = L[1] = L[2] = 0.0;
576 }
577
578 for (int i = 0; i < 3; i++)
579 L[i] = (trc_arib_std_b67(L[i]) - beta) / (1 - beta);
580}
581
582static const av_csp_eotf_function eotf_funcs[AVCOL_TRC_NB] = {
583 [AVCOL_TRC_BT709] = eotf_bt1886,
584 [AVCOL_TRC_GAMMA22] = eotf_gamma22,
585 [AVCOL_TRC_GAMMA28] = eotf_gamma28,
586 [AVCOL_TRC_SMPTE170M] = eotf_bt1886,
587 [AVCOL_TRC_SMPTE240M] = eotf_bt1886,
588 [AVCOL_TRC_LINEAR] = eotf_linear,
589 /* There is no EOTF associated with these logarithmic encodings, since they
590 * are defined purely for transmission of scene referred data. */
591 [AVCOL_TRC_LOG] = NULL,
592 [AVCOL_TRC_LOG_SQRT] = NULL,
593 /* BT.1886 is already defined for values below 0.0, as far as physically
594 * meaningful, so we can directly use it for extended range encodings */
595 [AVCOL_TRC_IEC61966_2_4] = eotf_bt1886,
596 [AVCOL_TRC_BT1361_ECG] = eotf_bt1886,
597 [AVCOL_TRC_IEC61966_2_1] = eotf_iec61966_2_1,
598 [AVCOL_TRC_BT2020_10] = eotf_bt1886,
599 [AVCOL_TRC_BT2020_12] = eotf_bt1886,
600 [AVCOL_TRC_SMPTE2084] = eotf_smpte_st2084,
601 [AVCOL_TRC_SMPTE428] = eotf_smpte_st428_1,
602 [AVCOL_TRC_ARIB_STD_B67] = eotf_arib_std_b67,
603};
604
605av_csp_eotf_function av_csp_itu_eotf(enum AVColorTransferCharacteristic trc)
606{
607 if (trc < 0 || trc >= AVCOL_TRC_NB)
608 return NULL;
609 return eotf_funcs[trc];
610}
611
612static const av_csp_eotf_function eotf_inv_funcs[AVCOL_TRC_NB] = {
613 [AVCOL_TRC_BT709] = eotf_bt1886_inv,
614 [AVCOL_TRC_GAMMA22] = eotf_gamma22_inv,
615 [AVCOL_TRC_GAMMA28] = eotf_gamma28_inv,
616 [AVCOL_TRC_SMPTE170M] = eotf_bt1886_inv,
617 [AVCOL_TRC_SMPTE240M] = eotf_bt1886_inv,
618 [AVCOL_TRC_LINEAR] = eotf_linear_inv,
619 [AVCOL_TRC_LOG] = NULL,
620 [AVCOL_TRC_LOG_SQRT] = NULL,
621 [AVCOL_TRC_IEC61966_2_4] = eotf_bt1886_inv,
622 [AVCOL_TRC_BT1361_ECG] = eotf_bt1886_inv,
623 [AVCOL_TRC_IEC61966_2_1] = eotf_iec61966_2_1_inv,
624 [AVCOL_TRC_BT2020_10] = eotf_bt1886_inv,
625 [AVCOL_TRC_BT2020_12] = eotf_bt1886_inv,
626 [AVCOL_TRC_SMPTE2084] = eotf_smpte_st2084_inv,
627 [AVCOL_TRC_SMPTE428] = eotf_smpte_st428_1_inv,
628 [AVCOL_TRC_ARIB_STD_B67] = eotf_arib_std_b67_inv,
629};
630
631av_csp_eotf_function av_csp_itu_eotf_inv(enum AVColorTransferCharacteristic trc)
632{
633 if (trc < 0 || trc >= AVCOL_TRC_NB)
634 return NULL;
635 return eotf_inv_funcs[trc];
636}
libavutil/csp.h+33−0
@@ -152,6 +152,39 @@ av_csp_trc_function av_csp_trc_func_from_id(enum AVColorTransferCharacteristic t · 151 unmodified lines
152 */152 */
153av_csp_trc_function av_csp_trc_func_inv_from_id(enum AVColorTransferCharacteristic trc);153av_csp_trc_function av_csp_trc_func_inv_from_id(enum AVColorTransferCharacteristic trc);
154 154
155/**
156 * Function pointer representing an ITU EOTF transfer for a given reference
157 * display configuration.
158 *
159 * @param Lw The white point luminance of the display, in nits (cd/m^2).
160 * @param Lb The black point luminance of the display, in nits (cd/m^2).
161 */
162typedef void (*av_csp_eotf_function)(double Lw, double Lb, double c[3]);
163
164/**
165 * Returns the ITU EOTF corresponding to a given TRC. This converts from the
166 * signal level [0,1] to the raw output display luminance in nits (cd/m^2).
167 * This is done per channel in RGB space, except for AVCOL_TRC_SMPTE428, which
168 * assumes CIE XYZ in- and output.
169 *
170 * @return A pointer to the function implementing the given TRC, or NULL if no
171 * such function is defined.
172 *
173 * @note In general, the resulting function is defined (wherever possible) for
174 * out-of-range values, even though these values do not have a physical
175 * meaning on the given display. Users should clamp inputs (or outputs)
176 * if this behavior is not desired.
177 *
178 * This is also the case for functions like PQ, which are defined over an
179 * absolute signal range independent of the target display capabilities.
180 */
181av_csp_eotf_function av_csp_itu_eotf(enum AVColorTransferCharacteristic trc);
182
183/**
184 * Returns the mathematical inverse of the corresponding EOTF.
185 */
186av_csp_eotf_function av_csp_itu_eotf_inv(enum AVColorTransferCharacteristic trc);
187
155/**188/**
156 * @}189 * @}
157 */190 */
libavutil/version.h+1−1
@@ -79,7 +79,7 @@ · 78 unmodified lines
79 */79 */
80 80
81#define LIBAVUTIL_VERSION_MAJOR 5981#define LIBAVUTIL_VERSION_MAJOR 59
82#define LIBAVUTIL_VERSION_MINOR 4882#define LIBAVUTIL_VERSION_MINOR 49
83#define LIBAVUTIL_VERSION_MICRO 10083#define LIBAVUTIL_VERSION_MICRO 100
84 84
85#define LIBAVUTIL_VERSION_INT AV_VERSION_INT(LIBAVUTIL_VERSION_MAJOR, \85#define LIBAVUTIL_VERSION_INT AV_VERSION_INT(LIBAVUTIL_VERSION_MAJOR, \

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t