LCOV - code coverage report
Current view: top level - src - colorspace.c (source / functions) Hit Total Coverage
Test: Code coverage Lines: 623 683 91.2 %
Date: 2025-03-29 09:04:10 Functions: 54 56 96.4 %
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 435 570 76.3 %

           Branch data     Line data    Source code
       1                 :            : /*
       2                 :            :  * This file is part of libplacebo.
       3                 :            :  *
       4                 :            :  * libplacebo is free software; you can redistribute it and/or
       5                 :            :  * modify it under the terms of the GNU Lesser General Public
       6                 :            :  * License as published by the Free Software Foundation; either
       7                 :            :  * version 2.1 of the License, or (at your option) any later version.
       8                 :            :  *
       9                 :            :  * libplacebo is distributed in the hope that it will be useful,
      10                 :            :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      11                 :            :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12                 :            :  * GNU Lesser General Public License for more details.
      13                 :            :  *
      14                 :            :  * You should have received a copy of the GNU Lesser General Public
      15                 :            :  * License along with libplacebo. If not, see <http://www.gnu.org/licenses/>.
      16                 :            :  */
      17                 :            : 
      18                 :            : #include <math.h>
      19                 :            : 
      20                 :            : #include "common.h"
      21                 :            : #include "colorspace.h"
      22                 :            : #include "hash.h"
      23                 :            : 
      24                 :            : #include <libplacebo/colorspace.h>
      25                 :            : #include <libplacebo/tone_mapping.h>
      26                 :            : 
      27                 :      10466 : bool pl_color_system_is_ycbcr_like(enum pl_color_system sys)
      28                 :            : {
      29      [ +  -  + ]:      10466 :     switch (sys) {
      30                 :            :     case PL_COLOR_SYSTEM_UNKNOWN:
      31                 :            :     case PL_COLOR_SYSTEM_RGB:
      32                 :            :     case PL_COLOR_SYSTEM_XYZ:
      33                 :            :         return false;
      34                 :       6208 :     case PL_COLOR_SYSTEM_BT_601:
      35                 :            :     case PL_COLOR_SYSTEM_BT_709:
      36                 :            :     case PL_COLOR_SYSTEM_SMPTE_240M:
      37                 :            :     case PL_COLOR_SYSTEM_BT_2020_NC:
      38                 :            :     case PL_COLOR_SYSTEM_BT_2020_C:
      39                 :            :     case PL_COLOR_SYSTEM_BT_2100_PQ:
      40                 :            :     case PL_COLOR_SYSTEM_BT_2100_HLG:
      41                 :            :     case PL_COLOR_SYSTEM_DOLBYVISION:
      42                 :            :     case PL_COLOR_SYSTEM_YCGCO:
      43                 :       6208 :         return true;
      44                 :            :     case PL_COLOR_SYSTEM_COUNT: break;
      45                 :            :     };
      46                 :            : 
      47                 :          0 :     pl_unreachable();
      48                 :            : }
      49                 :            : 
      50                 :         12 : bool pl_color_system_is_linear(enum pl_color_system sys)
      51                 :            : {
      52      [ +  -  + ]:         12 :     switch (sys) {
      53                 :            :     case PL_COLOR_SYSTEM_UNKNOWN:
      54                 :            :     case PL_COLOR_SYSTEM_RGB:
      55                 :            :     case PL_COLOR_SYSTEM_BT_601:
      56                 :            :     case PL_COLOR_SYSTEM_BT_709:
      57                 :            :     case PL_COLOR_SYSTEM_SMPTE_240M:
      58                 :            :     case PL_COLOR_SYSTEM_BT_2020_NC:
      59                 :            :     case PL_COLOR_SYSTEM_YCGCO:
      60                 :            :         return true;
      61                 :          5 :     case PL_COLOR_SYSTEM_BT_2020_C:
      62                 :            :     case PL_COLOR_SYSTEM_BT_2100_PQ:
      63                 :            :     case PL_COLOR_SYSTEM_BT_2100_HLG:
      64                 :            :     case PL_COLOR_SYSTEM_DOLBYVISION:
      65                 :            :     case PL_COLOR_SYSTEM_XYZ:
      66                 :          5 :         return false;
      67                 :            :     case PL_COLOR_SYSTEM_COUNT: break;
      68                 :            :     };
      69                 :            : 
      70                 :          0 :     pl_unreachable();
      71                 :            : }
      72                 :            : 
      73                 :            : const char *const pl_color_system_names[PL_COLOR_SYSTEM_COUNT] = {
      74                 :            :     [PL_COLOR_SYSTEM_UNKNOWN]       = "Auto (unknown)",
      75                 :            :     [PL_COLOR_SYSTEM_BT_601]        = "ITU-R Rec. BT.601 (SD)",
      76                 :            :     [PL_COLOR_SYSTEM_BT_709]        = "ITU-R Rec. BT.709 (HD)",
      77                 :            :     [PL_COLOR_SYSTEM_SMPTE_240M]    = "SMPTE-240M",
      78                 :            :     [PL_COLOR_SYSTEM_BT_2020_NC]    = "ITU-R Rec. BT.2020 (non-constant luminance)",
      79                 :            :     [PL_COLOR_SYSTEM_BT_2020_C]     = "ITU-R Rec. BT.2020 (constant luminance)",
      80                 :            :     [PL_COLOR_SYSTEM_BT_2100_PQ]    = "ITU-R Rec. BT.2100 ICtCp PQ variant",
      81                 :            :     [PL_COLOR_SYSTEM_BT_2100_HLG]   = "ITU-R Rec. BT.2100 ICtCp HLG variant",
      82                 :            :     [PL_COLOR_SYSTEM_DOLBYVISION]   = "Dolby Vision (invalid for output)",
      83                 :            :     [PL_COLOR_SYSTEM_YCGCO]         = "YCgCo (derived from RGB)",
      84                 :            :     [PL_COLOR_SYSTEM_RGB]           = "Red, Green and Blue",
      85                 :            :     [PL_COLOR_SYSTEM_XYZ]           = "Digital Cinema Distribution Master (XYZ)",
      86                 :            : };
      87                 :            : 
      88                 :         25 : const char *pl_color_system_name(enum pl_color_system sys)
      89                 :            : {
      90         [ -  + ]:         25 :     pl_assert(sys >= 0 && sys < PL_COLOR_SYSTEM_COUNT);
      91                 :         25 :     return pl_color_system_names[sys];
      92                 :            : }
      93                 :            : 
      94                 :          5 : enum pl_color_system pl_color_system_guess_ycbcr(int width, int height)
      95                 :            : {
      96         [ +  + ]:          5 :     if (width >= 1280 || height > 576) {
      97                 :            :         // Typical HD content
      98                 :            :         return PL_COLOR_SYSTEM_BT_709;
      99                 :            :     } else {
     100                 :            :         // Typical SD content
     101                 :          3 :         return PL_COLOR_SYSTEM_BT_601;
     102                 :            :     }
     103                 :            : }
     104                 :            : 
     105                 :       1033 : bool pl_bit_encoding_equal(const struct pl_bit_encoding *b1,
     106                 :            :                            const struct pl_bit_encoding *b2)
     107                 :            : {
     108                 :       2066 :     return b1->sample_depth == b2->sample_depth &&
     109   [ +  -  +  - ]:       1033 :            b1->color_depth  == b2->color_depth &&
     110         [ -  + ]:       1033 :            b1->bit_shift    == b2->bit_shift;
     111                 :            : }
     112                 :            : 
     113                 :            : const struct pl_color_repr pl_color_repr_unknown = {0};
     114                 :            : 
     115                 :            : const struct pl_color_repr pl_color_repr_rgb = {
     116                 :            :     .sys    = PL_COLOR_SYSTEM_RGB,
     117                 :            :     .levels = PL_COLOR_LEVELS_FULL,
     118                 :            : };
     119                 :            : 
     120                 :            : const struct pl_color_repr pl_color_repr_sdtv = {
     121                 :            :     .sys    = PL_COLOR_SYSTEM_BT_601,
     122                 :            :     .levels = PL_COLOR_LEVELS_LIMITED,
     123                 :            : };
     124                 :            : 
     125                 :            : const struct pl_color_repr pl_color_repr_hdtv = {
     126                 :            :     .sys    = PL_COLOR_SYSTEM_BT_709,
     127                 :            :     .levels = PL_COLOR_LEVELS_LIMITED,
     128                 :            : };
     129                 :            : 
     130                 :            : const struct pl_color_repr pl_color_repr_uhdtv = {
     131                 :            :     .sys    = PL_COLOR_SYSTEM_BT_2020_NC,
     132                 :            :     .levels = PL_COLOR_LEVELS_LIMITED,
     133                 :            : };
     134                 :            : 
     135                 :            : const struct pl_color_repr pl_color_repr_jpeg = {
     136                 :            :     .sys    = PL_COLOR_SYSTEM_BT_601,
     137                 :            :     .levels = PL_COLOR_LEVELS_FULL,
     138                 :            : };
     139                 :            : 
     140                 :        805 : bool pl_color_repr_equal(const struct pl_color_repr *c1,
     141                 :            :                          const struct pl_color_repr *c2)
     142                 :            : {
     143                 :        805 :     return c1->sys    == c2->sys &&
     144                 :        804 :            c1->levels == c2->levels &&
     145         [ -  + ]:        804 :            c1->alpha  == c2->alpha &&
     146   [ +  +  -  +  :       2413 :            c1->dovi   == c2->dovi &&
                   -  + ]
     147                 :        804 :            pl_bit_encoding_equal(&c1->bits, &c2->bits);
     148                 :            : }
     149                 :            : 
     150                 :            : static struct pl_bit_encoding pl_bit_encoding_merge(const struct pl_bit_encoding *orig,
     151                 :            :                                                     const struct pl_bit_encoding *new)
     152                 :            : {
     153                 :            :     return (struct pl_bit_encoding) {
     154         [ +  - ]:          1 :         .sample_depth = PL_DEF(orig->sample_depth, new->sample_depth),
     155         [ +  - ]:          1 :         .color_depth  = PL_DEF(orig->color_depth,  new->color_depth),
     156         [ +  - ]:          1 :         .bit_shift    = PL_DEF(orig->bit_shift,    new->bit_shift),
     157                 :            :     };
     158                 :            : }
     159                 :            : 
     160                 :          1 : void pl_color_repr_merge(struct pl_color_repr *orig, const struct pl_color_repr *new)
     161                 :            : {
     162                 :          1 :     *orig = (struct pl_color_repr) {
     163         [ +  - ]:          1 :         .sys    = PL_DEF(orig->sys,    new->sys),
     164         [ +  - ]:          1 :         .levels = PL_DEF(orig->levels, new->levels),
     165         [ +  - ]:          1 :         .alpha  = PL_DEF(orig->alpha,  new->alpha),
     166         [ +  - ]:          1 :         .dovi   = PL_DEF(orig->dovi,   new->dovi),
     167                 :            :         .bits   = pl_bit_encoding_merge(&orig->bits, &new->bits),
     168                 :            :     };
     169                 :          1 : }
     170                 :            : 
     171                 :       4799 : enum pl_color_levels pl_color_levels_guess(const struct pl_color_repr *repr)
     172                 :            : {
     173         [ +  + ]:       4799 :     if (repr->sys == PL_COLOR_SYSTEM_DOLBYVISION)
     174                 :            :         return PL_COLOR_LEVELS_FULL;
     175                 :            : 
     176         [ +  + ]:       4793 :     if (repr->levels)
     177                 :            :         return repr->levels;
     178                 :            : 
     179                 :        117 :     return pl_color_system_is_ycbcr_like(repr->sys)
     180                 :            :                 ? PL_COLOR_LEVELS_LIMITED
     181         [ +  + ]:        117 :                 : PL_COLOR_LEVELS_FULL;
     182                 :            : }
     183                 :            : 
     184                 :       3014 : float pl_color_repr_normalize(struct pl_color_repr *repr)
     185                 :            : {
     186                 :            :     float scale = 1.0;
     187                 :            :     struct pl_bit_encoding *bits = &repr->bits;
     188                 :            : 
     189         [ +  + ]:       3014 :     if (bits->bit_shift) {
     190                 :          1 :         scale /= (1LL << bits->bit_shift);
     191                 :          1 :         bits->bit_shift = 0;
     192                 :            :     }
     193                 :            : 
     194                 :            :     // If one of these is set but not the other, use the set one
     195         [ +  + ]:       3014 :     int tex_bits = PL_DEF(bits->sample_depth, 8);
     196         [ +  + ]:       3014 :     int col_bits = PL_DEF(bits->color_depth, tex_bits);
     197                 :            :     tex_bits = PL_DEF(tex_bits, col_bits);
     198                 :            : 
     199         [ +  + ]:       3014 :     if (pl_color_levels_guess(repr) == PL_COLOR_LEVELS_LIMITED) {
     200                 :            :         // Limit range is always shifted directly
     201                 :        112 :         scale *= (float) (1LL << tex_bits) / (1LL << col_bits);
     202                 :            :     } else {
     203                 :            :         // Full range always uses the full range available
     204                 :       2902 :         scale *= ((1LL << tex_bits) - 1.) / ((1LL << col_bits) - 1.);
     205                 :            :     }
     206                 :            : 
     207                 :       3014 :     bits->color_depth = bits->sample_depth;
     208                 :       3014 :     return scale;
     209                 :            : }
     210                 :            : 
     211                 :         17 : bool pl_color_primaries_is_wide_gamut(enum pl_color_primaries prim)
     212                 :            : {
     213      [ +  -  + ]:         17 :     switch (prim) {
     214                 :            :     case PL_COLOR_PRIM_UNKNOWN:
     215                 :            :     case PL_COLOR_PRIM_BT_601_525:
     216                 :            :     case PL_COLOR_PRIM_BT_601_625:
     217                 :            :     case PL_COLOR_PRIM_BT_709:
     218                 :            :     case PL_COLOR_PRIM_BT_470M:
     219                 :            :     case PL_COLOR_PRIM_EBU_3213:
     220                 :            :         return false;
     221                 :          9 :     case PL_COLOR_PRIM_BT_2020:
     222                 :            :     case PL_COLOR_PRIM_APPLE:
     223                 :            :     case PL_COLOR_PRIM_ADOBE:
     224                 :            :     case PL_COLOR_PRIM_PRO_PHOTO:
     225                 :            :     case PL_COLOR_PRIM_CIE_1931:
     226                 :            :     case PL_COLOR_PRIM_DCI_P3:
     227                 :            :     case PL_COLOR_PRIM_DISPLAY_P3:
     228                 :            :     case PL_COLOR_PRIM_V_GAMUT:
     229                 :            :     case PL_COLOR_PRIM_S_GAMUT:
     230                 :            :     case PL_COLOR_PRIM_FILM_C:
     231                 :            :     case PL_COLOR_PRIM_ACES_AP0:
     232                 :            :     case PL_COLOR_PRIM_ACES_AP1:
     233                 :          9 :         return true;
     234                 :            :     case PL_COLOR_PRIM_COUNT: break;
     235                 :            :     }
     236                 :            : 
     237                 :          0 :     pl_unreachable();
     238                 :            : }
     239                 :            : 
     240                 :            : const char *const pl_color_primaries_names[PL_COLOR_PRIM_COUNT] = {
     241                 :            :     [PL_COLOR_PRIM_UNKNOWN]     = "Auto (unknown)",
     242                 :            :     [PL_COLOR_PRIM_BT_601_525]  = "ITU-R Rec. BT.601 (525-line = NTSC, SMPTE-C)",
     243                 :            :     [PL_COLOR_PRIM_BT_601_625]  = "ITU-R Rec. BT.601 (625-line = PAL, SECAM)",
     244                 :            :     [PL_COLOR_PRIM_BT_709]      = "ITU-R Rec. BT.709 (HD), also sRGB",
     245                 :            :     [PL_COLOR_PRIM_BT_470M]     = "ITU-R Rec. BT.470 M",
     246                 :            :     [PL_COLOR_PRIM_EBU_3213]    = "EBU Tech. 3213-E / JEDEC P22 phosphors",
     247                 :            :     [PL_COLOR_PRIM_BT_2020]     = "ITU-R Rec. BT.2020 (Ultra HD)",
     248                 :            :     [PL_COLOR_PRIM_APPLE]       = "Apple RGB",
     249                 :            :     [PL_COLOR_PRIM_ADOBE]       = "Adobe RGB (1998)",
     250                 :            :     [PL_COLOR_PRIM_PRO_PHOTO]   = "ProPhoto RGB (ROMM)",
     251                 :            :     [PL_COLOR_PRIM_CIE_1931]    = "CIE 1931 RGB primaries",
     252                 :            :     [PL_COLOR_PRIM_DCI_P3]      = "DCI-P3 (Digital Cinema)",
     253                 :            :     [PL_COLOR_PRIM_DISPLAY_P3]  = "DCI-P3 (Digital Cinema) with D65 white point",
     254                 :            :     [PL_COLOR_PRIM_V_GAMUT]     = "Panasonic V-Gamut (VARICAM)",
     255                 :            :     [PL_COLOR_PRIM_S_GAMUT]     = "Sony S-Gamut",
     256                 :            :     [PL_COLOR_PRIM_FILM_C]      = "Traditional film primaries with Illuminant C",
     257                 :            :     [PL_COLOR_PRIM_ACES_AP0]    = "ACES Primaries #0",
     258                 :            :     [PL_COLOR_PRIM_ACES_AP1]    = "ACES Primaries #1",
     259                 :            : };
     260                 :            : 
     261                 :         11 : const char *pl_color_primaries_name(enum pl_color_primaries prim)
     262                 :            : {
     263         [ -  + ]:         11 :     pl_assert(prim >= 0 && prim < PL_COLOR_PRIM_COUNT);
     264                 :         11 :     return pl_color_primaries_names[prim];
     265                 :            : }
     266                 :            : 
     267                 :          7 : enum pl_color_primaries pl_color_primaries_guess(int width, int height)
     268                 :            : {
     269                 :            :     // HD content
     270         [ +  + ]:          7 :     if (width >= 1280 || height > 576)
     271                 :            :         return PL_COLOR_PRIM_BT_709;
     272                 :            : 
     273      [ +  +  + ]:          4 :     switch (height) {
     274                 :            :     case 576: // Typical PAL content, including anamorphic/squared
     275                 :            :         return PL_COLOR_PRIM_BT_601_625;
     276                 :            : 
     277                 :          1 :     case 480: // Typical NTSC content, including squared
     278                 :            :     case 486: // NTSC Pro or anamorphic NTSC
     279                 :          1 :         return PL_COLOR_PRIM_BT_601_525;
     280                 :            : 
     281                 :            :     default: // No good metric, just pick BT.709 to minimize damage
     282                 :            :         return PL_COLOR_PRIM_BT_709;
     283                 :            :     }
     284                 :            : }
     285                 :            : 
     286                 :            : const char *const pl_color_transfer_names[PL_COLOR_TRC_COUNT] = {
     287                 :            :     [PL_COLOR_TRC_UNKNOWN]      = "Auto (unknown SDR)",
     288                 :            :     [PL_COLOR_TRC_BT_1886]      = "ITU-R Rec. BT.1886 (CRT emulation + OOTF)",
     289                 :            :     [PL_COLOR_TRC_SRGB]         = "IEC 61966-2-4 sRGB (CRT emulation)",
     290                 :            :     [PL_COLOR_TRC_LINEAR]       = "Linear light content",
     291                 :            :     [PL_COLOR_TRC_GAMMA18]      = "Pure power gamma 1.8",
     292                 :            :     [PL_COLOR_TRC_GAMMA20]      = "Pure power gamma 2.0",
     293                 :            :     [PL_COLOR_TRC_GAMMA22]      = "Pure power gamma 2.2",
     294                 :            :     [PL_COLOR_TRC_GAMMA24]      = "Pure power gamma 2.4",
     295                 :            :     [PL_COLOR_TRC_GAMMA26]      = "Pure power gamma 2.6",
     296                 :            :     [PL_COLOR_TRC_GAMMA28]      = "Pure power gamma 2.8",
     297                 :            :     [PL_COLOR_TRC_PRO_PHOTO]    = "ProPhoto RGB (ROMM)",
     298                 :            :     [PL_COLOR_TRC_ST428]        = "Digital Cinema Distribution Master (XYZ)",
     299                 :            :     [PL_COLOR_TRC_PQ]           = "ITU-R BT.2100 PQ (perceptual quantizer), aka SMPTE ST2048",
     300                 :            :     [PL_COLOR_TRC_HLG]          = "ITU-R BT.2100 HLG (hybrid log-gamma), aka ARIB STD-B67",
     301                 :            :     [PL_COLOR_TRC_V_LOG]        = "Panasonic V-Log (VARICAM)",
     302                 :            :     [PL_COLOR_TRC_S_LOG1]       = "Sony S-Log1",
     303                 :            :     [PL_COLOR_TRC_S_LOG2]       = "Sony S-Log2",
     304                 :            : };
     305                 :            : 
     306                 :         61 : const char *pl_color_transfer_name(enum pl_color_transfer trc)
     307                 :            : {
     308         [ -  + ]:         61 :     pl_assert(trc >= 0 && trc < PL_COLOR_TRC_COUNT);
     309                 :         61 :     return pl_color_transfer_names[trc];
     310                 :            : }
     311                 :            : 
     312                 :            : // HLG 75% value (scene-referred)
     313                 :            : #define HLG_75 3.17955
     314                 :            : 
     315         [ +  - ]:      96900 : float pl_color_transfer_nominal_peak(enum pl_color_transfer trc)
     316                 :            : {
     317                 :            :     switch (trc) {
     318                 :            :     case PL_COLOR_TRC_UNKNOWN:
     319                 :            :     case PL_COLOR_TRC_BT_1886:
     320                 :            :     case PL_COLOR_TRC_SRGB:
     321                 :            :     case PL_COLOR_TRC_LINEAR:
     322                 :            :     case PL_COLOR_TRC_GAMMA18:
     323                 :            :     case PL_COLOR_TRC_GAMMA20:
     324                 :            :     case PL_COLOR_TRC_GAMMA22:
     325                 :            :     case PL_COLOR_TRC_GAMMA24:
     326                 :            :     case PL_COLOR_TRC_GAMMA26:
     327                 :            :     case PL_COLOR_TRC_GAMMA28:
     328                 :            :     case PL_COLOR_TRC_PRO_PHOTO:
     329                 :            :     case PL_COLOR_TRC_ST428:
     330                 :            :         return 1.0;
     331                 :            :     case PL_COLOR_TRC_PQ:       return 10000.0 / PL_COLOR_SDR_WHITE;
     332                 :            :     case PL_COLOR_TRC_HLG:      return 12.0 / HLG_75;
     333                 :            :     case PL_COLOR_TRC_V_LOG:    return 46.0855;
     334                 :            :     case PL_COLOR_TRC_S_LOG1:   return 6.52;
     335                 :            :     case PL_COLOR_TRC_S_LOG2:   return 9.212;
     336                 :            :     case PL_COLOR_TRC_COUNT: break;
     337                 :            :     }
     338                 :            : 
     339                 :          0 :     pl_unreachable();
     340                 :            : }
     341                 :            : 
     342                 :            : const struct pl_hdr_metadata pl_hdr_metadata_empty = {0};
     343                 :            : const struct pl_hdr_metadata pl_hdr_metadata_hdr10 ={
     344                 :            :     .prim = {
     345                 :            :         .red   = {0.708,    0.292},
     346                 :            :         .green = {0.170,    0.797},
     347                 :            :         .blue  = {0.131,    0.046},
     348                 :            :         .white = {0.31271,  0.32902},
     349                 :            :     },
     350                 :            :     .min_luma = 0,
     351                 :            :     .max_luma = 10000,
     352                 :            :     .max_cll  = 10000,
     353                 :            :     .max_fall = 0, // unknown
     354                 :            : };
     355                 :            : 
     356                 :     333488 : float pl_hdr_rescale(enum pl_hdr_scaling from, enum pl_hdr_scaling to, float x)
     357                 :            : {
     358         [ +  + ]:     333488 :     if (from == to)
     359                 :            :         return x;
     360         [ +  + ]:     257570 :     if (!x) // micro-optimization for common value
     361                 :            :         return x;
     362                 :            : 
     363                 :     257452 :     x = fmaxf(x, 0.0f);
     364                 :            : 
     365                 :            :     // Convert input to PL_SCALE_RELATIVE
     366   [ +  +  +  -  :     257452 :     switch (from) {
                      - ]
     367                 :      54316 :     case PL_HDR_PQ:
     368                 :      54316 :         x = powf(x, 1.0f / PQ_M2);
     369                 :      54316 :         x = fmaxf(x - PQ_C1, 0.0f) / (PQ_C2 - PQ_C3 * x);
     370                 :      54316 :         x = powf(x, 1.0f / PQ_M1);
     371                 :      54316 :         x *= 10000.0f;
     372                 :            :         // fall through
     373                 :     222969 :     case PL_HDR_NITS:
     374                 :     222969 :         x /= PL_COLOR_SDR_WHITE;
     375                 :            :         // fall through
     376                 :     257452 :     case PL_HDR_NORM:
     377                 :     257452 :         goto output;
     378                 :          0 :     case PL_HDR_SQRT:
     379                 :          0 :         x *= x;
     380                 :          0 :         goto output;
     381                 :            :     case PL_HDR_SCALING_COUNT:
     382                 :            :         break;
     383                 :            :     }
     384                 :            : 
     385                 :          0 :     pl_unreachable();
     386                 :            : 
     387                 :     257452 : output:
     388                 :            :     // Convert PL_SCALE_RELATIVE to output
     389   [ -  +  +  -  :     257452 :     switch (to) {
                      + ]
     390                 :            :     case PL_HDR_NORM:
     391                 :            :         return x;
     392                 :          0 :     case PL_HDR_SQRT:
     393                 :          0 :         return sqrtf(x);
     394                 :      40368 :     case PL_HDR_NITS:
     395                 :      40368 :         return x * PL_COLOR_SDR_WHITE;
     396                 :       3261 :     case PL_HDR_PQ:
     397                 :       3261 :         x *= PL_COLOR_SDR_WHITE / 10000.0f;
     398                 :       3261 :         x = powf(x, PQ_M1);
     399                 :       3261 :         x = (PQ_C1 + PQ_C2 * x) / (1.0f + PQ_C3 * x);
     400                 :       3261 :         x = powf(x, PQ_M2);
     401                 :       3261 :         return x;
     402                 :            :     case PL_HDR_SCALING_COUNT:
     403                 :            :         break;
     404                 :            :     }
     405                 :            : 
     406                 :          0 :     pl_unreachable();
     407                 :            : }
     408                 :            : 
     409                 :       2538 : static inline bool pl_hdr_bezier_equal(const struct pl_hdr_bezier *a,
     410                 :            :                                        const struct pl_hdr_bezier *b)
     411                 :            : {
     412                 :       5076 :     return a->target_luma == b->target_luma &&
     413         [ +  - ]:       2538 :            a->knee_x      == b->knee_x &&
     414         [ +  - ]:       2538 :            a->knee_y      == b->knee_y &&
     415   [ +  -  +  - ]:       5076 :            a->num_anchors == b->num_anchors &&
     416         [ -  + ]:       2538 :            !memcmp(a->anchors, b->anchors, sizeof(a->anchors[0]) * a->num_anchors);
     417                 :            : }
     418                 :            : 
     419                 :       2542 : bool pl_hdr_metadata_equal(const struct pl_hdr_metadata *a,
     420                 :            :                            const struct pl_hdr_metadata *b)
     421                 :            : {
     422                 :       2542 :     return pl_raw_primaries_equal(&a->prim, &b->prim) &&
     423         [ -  + ]:       2538 :            a->min_luma == b->min_luma &&
     424         [ -  + ]:       2538 :            a->max_luma == b->max_luma &&
     425         [ -  + ]:       2538 :            a->max_cll  == b->max_cll  &&
     426         [ -  + ]:       2538 :            a->max_fall == b->max_fall &&
     427         [ -  + ]:       2538 :            a->scene_max[0] == b->scene_max[0] &&
     428         [ -  + ]:       2538 :            a->scene_max[1] == b->scene_max[1] &&
     429         [ -  + ]:       2538 :            a->scene_max[2] == b->scene_max[2] &&
     430         [ -  + ]:       2538 :            a->scene_avg == b->scene_avg &&
     431         [ -  + ]:       2538 :            pl_hdr_bezier_equal(&a->ootf, &b->ootf) &&
     432   [ +  +  -  + ]:       5080 :            a->max_pq_y == b->max_pq_y &&
     433         [ -  + ]:       2538 :            a->avg_pq_y == b->avg_pq_y;
     434                 :            : }
     435                 :            : 
     436                 :         57 : void pl_hdr_metadata_merge(struct pl_hdr_metadata *orig,
     437                 :            :                            const struct pl_hdr_metadata *update)
     438                 :            : {
     439                 :         57 :     pl_raw_primaries_merge(&orig->prim, &update->prim);
     440         [ +  - ]:         57 :     if (!orig->min_luma)
     441                 :         57 :         orig->min_luma = update->min_luma;
     442         [ +  - ]:         57 :     if (!orig->max_luma)
     443                 :         57 :         orig->max_luma = update->max_luma;
     444         [ +  - ]:         57 :     if (!orig->max_cll)
     445                 :         57 :         orig->max_cll = update->max_cll;
     446         [ +  - ]:         57 :     if (!orig->max_fall)
     447                 :         57 :         orig->max_fall = update->max_fall;
     448         [ +  - ]:         57 :     if (!orig->scene_max[1])
     449                 :         57 :         memcpy(orig->scene_max, update->scene_max, sizeof(orig->scene_max));
     450         [ +  - ]:         57 :     if (!orig->scene_avg)
     451                 :         57 :         orig->scene_avg = update->scene_avg;
     452         [ +  - ]:         57 :     if (!orig->ootf.target_luma)
     453                 :         57 :         orig->ootf = update->ootf;
     454         [ +  - ]:         57 :     if (!orig->max_pq_y)
     455                 :         57 :         orig->max_pq_y = update->max_pq_y;
     456         [ +  - ]:         57 :     if (!orig->avg_pq_y)
     457                 :         57 :         orig->avg_pq_y = update->avg_pq_y;
     458                 :         57 : }
     459                 :            : 
     460                 :        155 : bool pl_hdr_metadata_contains(const struct pl_hdr_metadata *data,
     461                 :            :                               enum pl_hdr_metadata_type type)
     462                 :            : {
     463                 :        155 :     bool has_hdr10 = data->max_luma;
     464   [ +  +  -  + ]:        155 :     bool has_hdr10plus = data->scene_avg && (data->scene_max[0] ||
     465         [ #  # ]:          0 :                                              data->scene_max[1] ||
     466         [ #  # ]:          0 :                                              data->scene_max[2]);
     467   [ +  +  -  + ]:        155 :     bool has_cie_y = data->max_pq_y && data->avg_pq_y;
     468                 :            : 
     469   [ +  +  +  +  :        155 :     switch (type) {
                   -  + ]
     470                 :            :     case PL_HDR_METADATA_NONE:          return true;
     471                 :          5 :     case PL_HDR_METADATA_ANY:           return has_hdr10 || has_hdr10plus || has_cie_y;
     472                 :          5 :     case PL_HDR_METADATA_HDR10:         return has_hdr10;
     473                 :         70 :     case PL_HDR_METADATA_HDR10PLUS:     return has_hdr10plus;
     474                 :         70 :     case PL_HDR_METADATA_CIE_Y:         return has_cie_y;
     475                 :            :     case PL_HDR_METADATA_TYPE_COUNT:    break;
     476                 :            :     }
     477                 :            : 
     478                 :          0 :     pl_unreachable();
     479                 :            : }
     480                 :            : 
     481                 :            : const struct pl_color_space pl_color_space_unknown = {0};
     482                 :            : 
     483                 :            : const struct pl_color_space pl_color_space_srgb = {
     484                 :            :     .primaries = PL_COLOR_PRIM_BT_709,
     485                 :            :     .transfer  = PL_COLOR_TRC_SRGB,
     486                 :            : };
     487                 :            : 
     488                 :            : const struct pl_color_space pl_color_space_bt709 = {
     489                 :            :     .primaries = PL_COLOR_PRIM_BT_709,
     490                 :            :     .transfer  = PL_COLOR_TRC_BT_1886,
     491                 :            : };
     492                 :            : 
     493                 :            : const struct pl_color_space pl_color_space_hdr10 = {
     494                 :            :     .primaries = PL_COLOR_PRIM_BT_2020,
     495                 :            :     .transfer  = PL_COLOR_TRC_PQ,
     496                 :            : };
     497                 :            : 
     498                 :            : const struct pl_color_space pl_color_space_bt2020_hlg = {
     499                 :            :     .primaries = PL_COLOR_PRIM_BT_2020,
     500                 :            :     .transfer  = PL_COLOR_TRC_HLG,
     501                 :            : };
     502                 :            : 
     503                 :            : const struct pl_color_space pl_color_space_monitor = {
     504                 :            :     .primaries = PL_COLOR_PRIM_BT_709, // sRGB primaries
     505                 :            :     .transfer  = PL_COLOR_TRC_UNKNOWN, // unknown SDR response
     506                 :            : };
     507                 :            : 
     508                 :       8864 : bool pl_color_space_is_hdr(const struct pl_color_space *csp)
     509                 :            : {
     510   [ +  +  +  + ]:      17646 :     return csp->hdr.max_luma > PL_COLOR_SDR_WHITE ||
     511                 :       8782 :            pl_color_transfer_is_hdr(csp->transfer);
     512                 :            : }
     513                 :            : 
     514                 :      47160 : bool pl_color_space_is_black_scaled(const struct pl_color_space *csp)
     515                 :            : {
     516      [ +  -  + ]:      47160 :     switch (csp->transfer) {
     517                 :            :     case PL_COLOR_TRC_UNKNOWN:
     518                 :            :     case PL_COLOR_TRC_SRGB:
     519                 :            :     case PL_COLOR_TRC_LINEAR:
     520                 :            :     case PL_COLOR_TRC_GAMMA18:
     521                 :            :     case PL_COLOR_TRC_GAMMA20:
     522                 :            :     case PL_COLOR_TRC_GAMMA22:
     523                 :            :     case PL_COLOR_TRC_GAMMA24:
     524                 :            :     case PL_COLOR_TRC_GAMMA26:
     525                 :            :     case PL_COLOR_TRC_GAMMA28:
     526                 :            :     case PL_COLOR_TRC_PRO_PHOTO:
     527                 :            :     case PL_COLOR_TRC_ST428:
     528                 :            :     case PL_COLOR_TRC_HLG:
     529                 :            :         return true;
     530                 :            : 
     531                 :      12367 :     case PL_COLOR_TRC_BT_1886:
     532                 :            :     case PL_COLOR_TRC_PQ:
     533                 :            :     case PL_COLOR_TRC_V_LOG:
     534                 :            :     case PL_COLOR_TRC_S_LOG1:
     535                 :            :     case PL_COLOR_TRC_S_LOG2:
     536                 :      12367 :         return false;
     537                 :            : 
     538                 :            :     case PL_COLOR_TRC_COUNT: break;
     539                 :            :     }
     540                 :            : 
     541                 :          0 :     pl_unreachable();
     542                 :            : }
     543                 :            : 
     544                 :            : #define MAP3(...)                                                               \
     545                 :            :     do {                                                                        \
     546                 :            :         float X;                                                                \
     547                 :            :         for (int _i = 0; _i < 3; _i++) {                                        \
     548                 :            :             X = color[_i];                                                      \
     549                 :            :             color[_i] = __VA_ARGS__;                                            \
     550                 :            :         }                                                                       \
     551                 :            :     } while (0)
     552                 :            : 
     553                 :      46320 : void pl_color_linearize(const struct pl_color_space *csp, float color[3])
     554                 :            : {
     555         [ +  + ]:      46320 :     if (csp->transfer == PL_COLOR_TRC_LINEAR)
     556                 :       5689 :         return;
     557                 :            : 
     558                 :            :     float csp_min, csp_max;
     559                 :      43526 :     pl_color_space_nominal_luma_ex(pl_nominal_luma_params(
     560                 :            :         .color      = csp,
     561                 :            :         .metadata   = PL_HDR_METADATA_HDR10,
     562                 :            :         .scaling    = PL_HDR_NORM,
     563                 :            :         .out_min    = &csp_min,
     564                 :            :         .out_max    = &csp_max,
     565                 :            :     ));
     566                 :            : 
     567         [ +  + ]:     174104 :     MAP3(fmaxf(X, 0));
     568                 :            : 
     569   [ +  +  +  +  :      43526 :     switch (csp->transfer) {
          +  +  +  +  +  
          +  +  +  +  +  
                   +  - ]
     570                 :            :     case PL_COLOR_TRC_SRGB:
     571   [ +  +  +  + ]:      11580 :         MAP3(X > 0.04045f ? powf((X + 0.055f) / 1.055f, 2.4f)
     572                 :            :                           : X / 12.92f);
     573                 :       2895 :         goto scale_out;
     574                 :       2895 :     case PL_COLOR_TRC_BT_1886: {
     575                 :       2895 :         const float lb = powf(csp_min, 1/2.4f);
     576                 :       2895 :         const float lw = powf(csp_max, 1/2.4f);
     577                 :       2895 :         const float a = powf(lw - lb, 2.4f);
     578                 :       2895 :         const float b = lb / (lw - lb);
     579         [ +  + ]:      11580 :         MAP3(a * powf(X + b, 2.4f));
     580                 :            :         return;
     581                 :            :     }
     582         [ +  + ]:      11580 :     case PL_COLOR_TRC_GAMMA18: MAP3(powf(X, 1.8f)); goto scale_out;
     583         [ +  + ]:      11580 :     case PL_COLOR_TRC_GAMMA20: MAP3(powf(X, 2.0f)); goto scale_out;
     584                 :            :     case PL_COLOR_TRC_UNKNOWN:
     585         [ +  + ]:      11984 :     case PL_COLOR_TRC_GAMMA22: MAP3(powf(X, 2.2f)); goto scale_out;
     586         [ +  + ]:      11580 :     case PL_COLOR_TRC_GAMMA24: MAP3(powf(X, 2.4f)); goto scale_out;
     587         [ +  + ]:      11580 :     case PL_COLOR_TRC_GAMMA26: MAP3(powf(X, 2.6f)); goto scale_out;
     588         [ +  + ]:      11580 :     case PL_COLOR_TRC_GAMMA28: MAP3(powf(X, 2.8f)); goto scale_out;
     589                 :            :     case PL_COLOR_TRC_PRO_PHOTO:
     590   [ +  +  +  + ]:      11580 :         MAP3(X > 0.03125f ? powf(X, 1.8f) : X / 16);
     591                 :       2895 :         goto scale_out;
     592                 :            :     case PL_COLOR_TRC_ST428:
     593         [ +  + ]:      11580 :         MAP3(52.37f/48 * powf(X, 2.6f));
     594                 :       2895 :         goto scale_out;
     595                 :            :     case PL_COLOR_TRC_PQ:
     596         [ +  + ]:      11580 :         MAP3(powf(X, 1 / PQ_M2));
     597         [ +  + ]:      11580 :         MAP3(fmaxf(X - PQ_C1, 0) / (PQ_C2 - PQ_C3 * X));
     598         [ +  + ]:      11580 :         MAP3(10000 / PL_COLOR_SDR_WHITE * powf(X, 1 / PQ_M1));
     599                 :       2895 :         goto scale_out;
     600                 :       2895 :     case PL_COLOR_TRC_HLG: {
     601                 :       2895 :         const float y = fmaxf(1.2f + 0.42f * log10f(csp_max / HLG_REF), 1);
     602                 :       2895 :         const float b = sqrtf(3 * powf(csp_min / csp_max, 1 / y));
     603                 :            :         const pl_matrix3x3 rgb2xyz =
     604                 :       2895 :             pl_get_rgb2xyz_matrix(pl_raw_primaries_get(csp->primaries));
     605                 :            :         const float *coef = rgb2xyz.m[1];
     606                 :            :         // OETF^-1
     607         [ +  + ]:      11580 :         MAP3((1 - b) * X + b);
     608   [ +  +  +  + ]:      11580 :         MAP3(X > 0.5f ? expf((X - HLG_C) / HLG_A) + HLG_B
     609                 :            :                       : 4 * X * X);
     610                 :            :         // OOTF
     611                 :       2895 :         float luma = coef[0] * color[0] + coef[1] * color[1] + coef[2] * color[2];
     612                 :       2895 :         luma = powf(fmaxf(luma / 12, 0), y - 1);
     613         [ +  + ]:      11580 :         MAP3(luma * X / 12);
     614                 :       2895 :         goto scale_out;
     615                 :            :     }
     616                 :            :     case PL_COLOR_TRC_V_LOG:
     617   [ +  +  +  + ]:      11580 :         MAP3(X >= 0.181f ? powf(10, (X - VLOG_D) / VLOG_C) - VLOG_B
     618                 :            :                          : (X - 0.125f) / 5.6f);
     619                 :       2895 :         goto scale_out;
     620                 :            :     case PL_COLOR_TRC_S_LOG1:
     621         [ +  + ]:      11580 :         MAP3(powf(10, (X - SLOG_C) / SLOG_A) - SLOG_B);
     622                 :       2895 :         goto scale_out;
     623                 :            :     case PL_COLOR_TRC_S_LOG2:
     624   [ +  +  +  + ]:      11580 :         MAP3(X >= SLOG_Q ? (powf(10, (X - SLOG_C) / SLOG_A) - SLOG_B) / SLOG_K2
     625                 :            :                          : (X - SLOG_Q) / SLOG_P);
     626                 :       2895 :         goto scale_out;
     627                 :            :     case PL_COLOR_TRC_LINEAR:
     628                 :            :     case PL_COLOR_TRC_COUNT:
     629                 :            :         break;
     630                 :            :     }
     631                 :            : 
     632                 :          0 :     pl_unreachable();
     633                 :            : 
     634                 :      40631 : scale_out:
     635   [ +  +  +  + ]:      40631 :     if (pl_color_space_is_black_scaled(csp) && csp->transfer != PL_COLOR_TRC_HLG)
     636         [ +  + ]:     104624 :         MAP3((csp_max - csp_min) * X + csp_min);
     637                 :            : }
     638                 :            : 
     639                 :       1313 : void pl_color_delinearize(const struct pl_color_space *csp, float color[3])
     640                 :            : {
     641         [ +  - ]:       1313 :     if (csp->transfer == PL_COLOR_TRC_LINEAR)
     642                 :            :         return;
     643                 :            : 
     644                 :            :     float csp_min, csp_max;
     645                 :       1313 :     pl_color_space_nominal_luma_ex(pl_nominal_luma_params(
     646                 :            :         .color      = csp,
     647                 :            :         .metadata   = PL_HDR_METADATA_HDR10,
     648                 :            :         .scaling    = PL_HDR_NORM,
     649                 :            :         .out_min    = &csp_min,
     650                 :            :         .out_max    = &csp_max,
     651                 :            :     ));
     652                 :            : 
     653   [ +  +  +  + ]:       1313 :     if (pl_color_space_is_black_scaled(csp) && csp->transfer != PL_COLOR_TRC_HLG)
     654         [ +  + ]:       4040 :         MAP3((X - csp_min) / (csp_max - csp_min));
     655                 :            : 
     656         [ +  + ]:       5252 :     MAP3(fmaxf(X, 0));
     657                 :            : 
     658   [ +  +  +  +  :       1313 :     switch (csp->transfer) {
          +  +  +  +  +  
          +  +  +  -  -  
                   -  - ]
     659                 :            :     case PL_COLOR_TRC_SRGB:
     660   [ +  +  +  + ]:        404 :         MAP3(X >= 0.0031308f ? 1.055f * powf(X, 1/2.4f) - 0.055f
     661                 :            :                              : 12.92f * X);
     662                 :            :         return;
     663                 :        101 :     case PL_COLOR_TRC_BT_1886: {
     664                 :        101 :         const float lb = powf(csp_min, 1/2.4f);
     665                 :        101 :         const float lw = powf(csp_max, 1/2.4f);
     666                 :        101 :         const float a = powf(lw - lb, 2.4f);
     667                 :        101 :         const float b = lb / (lw - lb);
     668         [ +  + ]:        404 :         MAP3(powf(X / a, 1/2.4f) - b);
     669                 :            :             return;
     670                 :            :     }
     671         [ +  + ]:        404 :     case PL_COLOR_TRC_GAMMA18: MAP3(powf(X, 1/1.8f)); return;
     672         [ +  + ]:        404 :     case PL_COLOR_TRC_GAMMA20: MAP3(powf(X, 1/2.0f)); return;
     673                 :            :     case PL_COLOR_TRC_UNKNOWN:
     674         [ +  + ]:        808 :     case PL_COLOR_TRC_GAMMA22: MAP3(powf(X, 1/2.2f)); return;
     675         [ +  + ]:        404 :     case PL_COLOR_TRC_GAMMA24: MAP3(powf(X, 1/2.4f)); return;
     676         [ +  + ]:        404 :     case PL_COLOR_TRC_GAMMA26: MAP3(powf(X, 1/2.6f)); return;
     677         [ +  + ]:        404 :     case PL_COLOR_TRC_GAMMA28: MAP3(powf(X, 1/2.8f)); return;
     678                 :            :     case PL_COLOR_TRC_ST428:
     679         [ +  + ]:        404 :         MAP3(powf(X * 48/52.37f, 1/2.6f));
     680                 :            :         return;
     681                 :            :     case PL_COLOR_TRC_PRO_PHOTO:
     682   [ +  +  +  + ]:        404 :         MAP3(X >= 0.001953f ? powf(X, 1/1.8f) : 16 * X);
     683                 :            :         return;
     684                 :            :     case PL_COLOR_TRC_PQ:
     685         [ +  + ]:        404 :         MAP3(powf(X * PL_COLOR_SDR_WHITE / 10000, PQ_M1));
     686         [ +  + ]:        404 :         MAP3(powf((PQ_C1 + PQ_C2 * X) / (1 + PQ_C3 * X), PQ_M2));
     687                 :            :         return;
     688                 :        101 :     case PL_COLOR_TRC_HLG: {
     689                 :        101 :         const float y = fmaxf(1.2f + 0.42f * log10f(csp_max / HLG_REF), 1);
     690                 :        101 :         const float b = sqrtf(3 * powf(csp_min / csp_max, 1 / y));
     691                 :            :         const pl_matrix3x3 rgb2xyz =
     692                 :        101 :             pl_get_rgb2xyz_matrix(pl_raw_primaries_get(csp->primaries));
     693                 :            :         const float *coef = rgb2xyz.m[1];
     694                 :            :         // OOTF^-1
     695                 :        101 :         float luma = coef[0] * color[0] + coef[1] * color[1] + coef[2] * color[2];
     696                 :        101 :         luma = fmaxf(1e-6f, powf(luma / csp_max, (1 - y) / y));
     697         [ +  + ]:        404 :         MAP3(12 / csp_max * luma * X);
     698                 :            :         // OETF
     699   [ +  +  +  + ]:        404 :         MAP3(X > 1 ? HLG_A * logf(X - HLG_B) + HLG_C : 0.5f * sqrtf(X));
     700         [ +  + ]:        404 :         MAP3((X - b) / (1 - b));
     701                 :            :         return;
     702                 :            :     }
     703                 :            :     case PL_COLOR_TRC_V_LOG:
     704   [ #  #  #  # ]:          0 :         MAP3(X >= 0.01f ? VLOG_C * log10f(X + VLOG_B) + VLOG_D
     705                 :            :                         : 5.6f * X + 0.125f);
     706                 :            :         return;
     707                 :            :     case PL_COLOR_TRC_S_LOG1:
     708         [ #  # ]:          0 :         MAP3(SLOG_A * log10f(X + SLOG_B) + SLOG_C);
     709                 :            :         return;
     710                 :            :     case PL_COLOR_TRC_S_LOG2:
     711   [ #  #  #  # ]:          0 :         MAP3(X >= 0 ? SLOG_A * log10f(SLOG_B * X + SLOG_C)
     712                 :            :                     : SLOG_P * X + SLOG_Q);
     713                 :            :         return;
     714                 :            :     case PL_COLOR_TRC_LINEAR:
     715                 :            :     case PL_COLOR_TRC_COUNT:
     716                 :            :         break;
     717                 :            :     }
     718                 :            : 
     719                 :          0 :     pl_unreachable();
     720                 :            : }
     721                 :            : 
     722                 :         57 : void pl_color_space_merge(struct pl_color_space *orig,
     723                 :            :                           const struct pl_color_space *new)
     724                 :            : {
     725         [ +  - ]:         57 :     if (!orig->primaries)
     726                 :         57 :         orig->primaries = new->primaries;
     727         [ +  - ]:         57 :     if (!orig->transfer)
     728                 :         57 :         orig->transfer = new->transfer;
     729                 :         57 :     pl_hdr_metadata_merge(&orig->hdr, &new->hdr);
     730                 :         57 : }
     731                 :            : 
     732                 :       2584 : bool pl_color_space_equal(const struct pl_color_space *c1,
     733                 :            :                           const struct pl_color_space *c2)
     734                 :            : {
     735                 :       5146 :     return c1->primaries == c2->primaries &&
     736   [ +  +  +  +  :       5114 :            c1->transfer  == c2->transfer &&
                   +  + ]
     737                 :       2530 :            pl_hdr_metadata_equal(&c1->hdr, &c2->hdr);
     738                 :            : }
     739                 :            : 
     740                 :            : // Estimates luminance from maxRGB by looking at how monochromatic MaxSCL is
     741                 :          2 : static void luma_from_maxrgb(const struct pl_color_space *csp,
     742                 :            :                              enum pl_hdr_scaling scaling,
     743                 :            :                              float *out_max, float *out_avg)
     744                 :            : {
     745   [ +  -  +  - ]:          4 :     const float maxscl = PL_MAX3(csp->hdr.scene_max[0],
     746                 :            :                                  csp->hdr.scene_max[1],
     747                 :            :                                  csp->hdr.scene_max[2]);
     748         [ -  + ]:          2 :     if (!maxscl)
     749                 :          0 :         return;
     750                 :            : 
     751                 :          2 :     struct pl_raw_primaries prim = csp->hdr.prim;
     752                 :          2 :     pl_raw_primaries_merge(&prim, pl_raw_primaries_get(csp->primaries));
     753                 :          2 :     const pl_matrix3x3 rgb2xyz = pl_get_rgb2xyz_matrix(&prim);
     754                 :            : 
     755                 :          2 :     const float max_luma = rgb2xyz.m[1][0] * csp->hdr.scene_max[0] +
     756                 :          2 :                            rgb2xyz.m[1][1] * csp->hdr.scene_max[1] +
     757                 :          2 :                            rgb2xyz.m[1][2] * csp->hdr.scene_max[2];
     758                 :            : 
     759                 :          2 :     const float coef = max_luma / maxscl;
     760                 :          2 :     *out_max = pl_hdr_rescale(PL_HDR_NITS, scaling, max_luma);
     761                 :          2 :     *out_avg = pl_hdr_rescale(PL_HDR_NITS, scaling, coef * csp->hdr.scene_avg);
     762                 :            : }
     763                 :            : 
     764                 :            : static inline bool metadata_compat(enum pl_hdr_metadata_type metadata,
     765                 :            :                                    enum pl_hdr_metadata_type compat)
     766                 :            : {
     767                 :     104680 :     return metadata == PL_HDR_METADATA_ANY || metadata == compat;
     768                 :            : }
     769                 :            : 
     770                 :      52340 : void pl_color_space_nominal_luma_ex(const struct pl_nominal_luma_params *params)
     771                 :            : {
     772   [ +  -  +  +  :      52340 :     if (!params || (!params->out_min && !params->out_max && !params->out_avg))
             -  +  -  - ]
     773                 :          0 :         return;
     774                 :            : 
     775                 :      52340 :     const struct pl_color_space *csp = params->color;
     776                 :      52340 :     const enum pl_hdr_scaling scaling = params->scaling;
     777                 :            : 
     778                 :      52340 :     float min_luma = 0, max_luma = 0, avg_luma = 0;
     779         [ +  + ]:      52340 :     if (params->metadata != PL_HDR_METADATA_NONE) {
     780                 :            :         // Initialize from static HDR10 metadata, in all cases
     781                 :      52334 :         min_luma = pl_hdr_rescale(PL_HDR_NITS, scaling, csp->hdr.min_luma);
     782                 :      52334 :         max_luma = pl_hdr_rescale(PL_HDR_NITS, scaling, csp->hdr.max_luma);
     783                 :            :     }
     784                 :            : 
     785   [ +  +  +  + ]:      52406 :     if (metadata_compat(params->metadata, PL_HDR_METADATA_HDR10PLUS) &&
     786                 :         66 :         pl_hdr_metadata_contains(&csp->hdr, PL_HDR_METADATA_HDR10PLUS))
     787                 :            :     {
     788                 :          2 :         luma_from_maxrgb(csp, scaling, &max_luma, &avg_luma);
     789                 :            :     }
     790                 :            : 
     791   [ +  +  +  + ]:      52406 :     if (metadata_compat(params->metadata, PL_HDR_METADATA_CIE_Y) &&
     792                 :         66 :         pl_hdr_metadata_contains(&csp->hdr, PL_HDR_METADATA_CIE_Y))
     793                 :            :     {
     794                 :         14 :         max_luma = pl_hdr_rescale(PL_HDR_PQ, scaling, csp->hdr.max_pq_y);
     795                 :         14 :         avg_luma = pl_hdr_rescale(PL_HDR_PQ, scaling, csp->hdr.avg_pq_y);
     796                 :            :     }
     797                 :            : 
     798                 :            :     // Clamp to sane value range
     799                 :      52340 :     const float hdr_min = pl_hdr_rescale(PL_HDR_NITS, scaling, PL_COLOR_HDR_BLACK);
     800                 :      52340 :     const float hdr_max = pl_hdr_rescale(PL_HDR_PQ,   scaling, 1.0f);
     801   [ +  +  +  +  :      52340 :     max_luma = max_luma ? PL_CLAMP(max_luma, hdr_min, hdr_max) : 0;
                   +  + ]
     802   [ +  +  +  +  :      52340 :     min_luma = min_luma ? PL_CLAMP(min_luma, hdr_min, hdr_max) : 0;
                   +  + ]
     803   [ +  +  +  +  :      52340 :     if ((max_luma && min_luma >= max_luma) || min_luma >= hdr_max)
                   -  + ]
     804                 :      41912 :         min_luma = max_luma = 0; // sanity
     805                 :            : 
     806                 :            :     // PQ is always scaled down to absolute black, ignoring HDR metadata
     807         [ +  + ]:      52340 :     if (csp->transfer == PL_COLOR_TRC_PQ)
     808                 :            :         min_luma = hdr_min;
     809                 :            : 
     810                 :            :     // Baseline/fallback metadata, inferred entirely from the colorspace
     811                 :            :     // description and built-in default assumptions
     812         [ +  + ]:      52340 :     if (!max_luma) {
     813         [ +  + ]:      46845 :         if (csp->transfer == PL_COLOR_TRC_HLG) {
     814                 :       2801 :             max_luma = pl_hdr_rescale(PL_HDR_NITS, scaling, PL_COLOR_HLG_PEAK);
     815                 :            :         } else {
     816                 :      44044 :             const float peak = pl_color_transfer_nominal_peak(csp->transfer);
     817                 :      44044 :             max_luma = pl_hdr_rescale(PL_HDR_NORM, scaling, peak);
     818                 :            :         }
     819                 :            :     }
     820                 :            : 
     821         [ +  + ]:      52340 :     if (!min_luma) {
     822         [ +  + ]:      43970 :         if (pl_color_transfer_is_hdr(csp->transfer)) {
     823                 :            :             min_luma = hdr_min;
     824                 :            :         } else {
     825                 :      32791 :             const float peak = pl_hdr_rescale(scaling, PL_HDR_NITS, max_luma);
     826                 :      32791 :             min_luma = pl_hdr_rescale(PL_HDR_NITS, scaling,
     827                 :            :                                       peak / PL_COLOR_SDR_CONTRAST);
     828                 :            :         }
     829                 :            :     }
     830                 :            : 
     831         [ +  + ]:      52340 :     if (avg_luma)
     832   [ +  -  +  - ]:         32 :         avg_luma = PL_CLAMP(avg_luma, min_luma, max_luma); // sanity
     833                 :            : 
     834         [ +  + ]:      52340 :     if (params->out_min)
     835                 :      50527 :         *params->out_min = min_luma;
     836         [ +  - ]:      52340 :     if (params->out_max)
     837                 :      52340 :         *params->out_max = max_luma;
     838         [ +  + ]:      52340 :     if (params->out_avg)
     839                 :         82 :         *params->out_avg = avg_luma;
     840                 :            : }
     841                 :            : 
     842                 :       6619 : void pl_color_space_infer(struct pl_color_space *space)
     843                 :            : {
     844         [ +  + ]:       6619 :     if (!space->primaries)
     845                 :         20 :         space->primaries = PL_COLOR_PRIM_BT_709;
     846         [ +  + ]:       6619 :     if (!space->transfer)
     847                 :         18 :         space->transfer = PL_COLOR_TRC_BT_1886;
     848                 :            : 
     849                 :            :     // Sanitize the static HDR metadata
     850         [ +  + ]:       6619 :     pl_color_space_nominal_luma_ex(pl_nominal_luma_params(
     851                 :            :         .color      = space,
     852                 :            :         .metadata   = PL_HDR_METADATA_HDR10,
     853                 :            :         .scaling    = PL_HDR_NITS,
     854                 :            :         .out_max    = &space->hdr.max_luma,
     855                 :            :         // Preserve tagged minimum
     856                 :            :         .out_min    = space->hdr.min_luma ? NULL : &space->hdr.min_luma,
     857                 :            :     ));
     858                 :            : 
     859                 :            :     // Default the signal color space based on the nominal raw primaries
     860         [ +  + ]:       6619 :     if (!pl_primaries_valid(&space->hdr.prim))
     861                 :       4872 :         space->hdr.prim = *pl_raw_primaries_get(space->primaries);
     862                 :       6619 : }
     863                 :            : 
     864                 :       3294 : static void infer_both_ref(struct pl_color_space *space,
     865                 :            :                            struct pl_color_space *ref)
     866                 :            : {
     867                 :       3294 :     pl_color_space_infer(ref);
     868                 :            : 
     869         [ -  + ]:       3294 :     if (!space->primaries) {
     870         [ #  # ]:          0 :         if (pl_color_primaries_is_wide_gamut(ref->primaries)) {
     871                 :          0 :             space->primaries = PL_COLOR_PRIM_BT_709;
     872                 :            :         } else {
     873                 :          0 :             space->primaries = ref->primaries;
     874                 :            :         }
     875                 :            :     }
     876                 :            : 
     877         [ -  + ]:       3294 :     if (!space->transfer) {
     878   [ #  #  #  #  :          0 :         switch (ref->transfer) {
                   #  # ]
     879                 :            :         case PL_COLOR_TRC_UNKNOWN:
     880                 :            :         case PL_COLOR_TRC_COUNT:
     881                 :          0 :             pl_unreachable();
     882                 :          0 :         case PL_COLOR_TRC_BT_1886:
     883                 :            :         case PL_COLOR_TRC_SRGB:
     884                 :            :         case PL_COLOR_TRC_GAMMA22:
     885                 :            :             // Re-use input transfer curve to avoid small adaptations
     886                 :          0 :             space->transfer = ref->transfer;
     887                 :          0 :             break;
     888                 :          0 :         case PL_COLOR_TRC_PQ:
     889                 :            :         case PL_COLOR_TRC_HLG:
     890                 :            :         case PL_COLOR_TRC_V_LOG:
     891                 :            :         case PL_COLOR_TRC_S_LOG1:
     892                 :            :         case PL_COLOR_TRC_S_LOG2:
     893                 :            :             // Pick BT.1886 model because it models SDR contrast accurately,
     894                 :            :             // and we need contrast information for tone mapping
     895                 :          0 :             space->transfer = PL_COLOR_TRC_BT_1886;
     896                 :          0 :             break;
     897                 :          0 :         case PL_COLOR_TRC_PRO_PHOTO:
     898                 :            :             // ProPhotoRGB and sRGB are both piecewise with linear slope
     899                 :          0 :             space->transfer = PL_COLOR_TRC_SRGB;
     900                 :          0 :             break;
     901                 :          0 :         case PL_COLOR_TRC_LINEAR:
     902                 :            :         case PL_COLOR_TRC_GAMMA18:
     903                 :            :         case PL_COLOR_TRC_GAMMA20:
     904                 :            :         case PL_COLOR_TRC_GAMMA24:
     905                 :            :         case PL_COLOR_TRC_GAMMA26:
     906                 :            :         case PL_COLOR_TRC_GAMMA28:
     907                 :            :         case PL_COLOR_TRC_ST428:
     908                 :            :             // Pick pure power output curve to avoid introducing black crush
     909                 :          0 :             space->transfer = PL_COLOR_TRC_GAMMA22;
     910                 :          0 :             break;
     911                 :            :         }
     912                 :            :     }
     913                 :            : 
     914                 :            :     // Infer the remaining fields after making the above choices
     915                 :       3294 :     pl_color_space_infer(space);
     916                 :       3294 : }
     917                 :            : 
     918                 :          0 : void pl_color_space_infer_ref(struct pl_color_space *space,
     919                 :            :                               const struct pl_color_space *refp)
     920                 :            : {
     921                 :            :     // Make a copy of `refp` to infer missing values first
     922                 :          0 :     struct pl_color_space ref = *refp;
     923                 :          0 :     infer_both_ref(space, &ref);
     924                 :          0 : }
     925                 :            : 
     926                 :       3294 : void pl_color_space_infer_map(struct pl_color_space *src,
     927                 :            :                               struct pl_color_space *dst)
     928                 :            : {
     929                 :       3294 :     bool unknown_src_contrast = !src->hdr.min_luma;
     930                 :       3294 :     bool unknown_dst_contrast = !dst->hdr.min_luma;
     931                 :            : 
     932                 :       3294 :     infer_both_ref(dst, src);
     933                 :            : 
     934                 :            :     // If the src has an unspecified gamma curve with dynamic black scaling,
     935                 :            :     // default it to match the dst colorspace contrast. This does not matter in
     936                 :            :     // most cases, but ensures that BT.1886 is tuned to the appropriate black
     937                 :            :     // point by default.
     938         [ +  + ]:       3294 :     bool dynamic_src_contrast = pl_color_space_is_black_scaled(src) ||
     939         [ +  + ]:         70 :                                 src->transfer == PL_COLOR_TRC_BT_1886;
     940         [ +  + ]:       3294 :     if (unknown_src_contrast && dynamic_src_contrast)
     941                 :       2415 :         src->hdr.min_luma = dst->hdr.min_luma;
     942                 :            : 
     943                 :            :     // Do the same in reverse if both src and dst are SDR curves
     944                 :       3294 :     bool src_is_sdr = !pl_color_space_is_hdr(src);
     945                 :       3294 :     bool dst_is_sdr = !pl_color_space_is_hdr(dst);
     946   [ +  +  +  - ]:       3294 :     if (unknown_dst_contrast && src_is_sdr && dst_is_sdr)
     947                 :       2399 :         dst->hdr.min_luma = src->hdr.min_luma;
     948                 :            : 
     949                 :            :     // If the src is HLG and the output is HDR, tune the HLG peak to the output
     950   [ -  +  -  - ]:       3294 :     if (src->transfer == PL_COLOR_TRC_HLG && pl_color_space_is_hdr(dst))
     951                 :          0 :         src->hdr.max_luma = dst->hdr.max_luma;
     952                 :       3294 : }
     953                 :            : 
     954                 :            : const struct pl_color_adjustment pl_color_adjustment_neutral = {
     955                 :            :     PL_COLOR_ADJUSTMENT_NEUTRAL
     956                 :            : };
     957                 :            : 
     958                 :         14 : void pl_chroma_location_offset(enum pl_chroma_location loc, float *x, float *y)
     959                 :            : {
     960                 :         14 :     *x = *y = 0;
     961                 :            : 
     962                 :            :     // This is the majority of subsampled chroma content out there
     963         [ +  - ]:         14 :     loc = PL_DEF(loc, PL_CHROMA_LEFT);
     964                 :            : 
     965         [ +  + ]:         14 :     switch (loc) {
     966                 :         12 :     case PL_CHROMA_LEFT:
     967                 :            :     case PL_CHROMA_TOP_LEFT:
     968                 :            :     case PL_CHROMA_BOTTOM_LEFT:
     969                 :         12 :         *x = -0.5;
     970                 :         12 :         break;
     971                 :            :     default: break;
     972                 :            :     }
     973                 :            : 
     974         [ +  + ]:         14 :     switch (loc) {
     975                 :         11 :     case PL_CHROMA_TOP_LEFT:
     976                 :            :     case PL_CHROMA_TOP_CENTER:
     977                 :         11 :         *y = -0.5;
     978                 :         11 :         break;
     979                 :            :     default: break;
     980                 :            :     }
     981                 :            : 
     982         [ +  + ]:         14 :     switch (loc) {
     983                 :          1 :     case PL_CHROMA_BOTTOM_LEFT:
     984                 :            :     case PL_CHROMA_BOTTOM_CENTER:
     985                 :          1 :         *y = 0.5;
     986                 :          1 :         break;
     987                 :            :     default: break;
     988                 :            :     }
     989                 :         14 : }
     990                 :            : 
     991                 :         10 : struct pl_cie_xy pl_white_from_temp(float temp)
     992                 :            : {
     993   [ +  -  +  - ]:         10 :     temp = PL_CLAMP(temp, 2500, 25000);
     994                 :            : 
     995                 :         10 :     double ti = 1000.0 / temp, ti2 = ti * ti, ti3 = ti2 * ti, x;
     996         [ +  + ]:         10 :     if (temp <= 7000) {
     997                 :          6 :         x = -4.6070 * ti3 + 2.9678 * ti2 + 0.09911 * ti + 0.244063;
     998                 :            :     } else {
     999                 :          4 :         x = -2.0064 * ti3 + 1.9018 * ti2 + 0.24748 * ti + 0.237040;
    1000                 :            :     }
    1001                 :            : 
    1002                 :         10 :     return (struct pl_cie_xy) {
    1003                 :            :         .x = x,
    1004                 :         10 :         .y = -3 * (x*x) + 2.87 * x - 0.275,
    1005                 :            :     };
    1006                 :            : }
    1007                 :            : 
    1008         [ +  + ]:       2600 : bool pl_raw_primaries_equal(const struct pl_raw_primaries *a,
    1009                 :            :                             const struct pl_raw_primaries *b)
    1010                 :            : {
    1011                 :            :     return pl_cie_xy_equal(&a->red,   &b->red)   &&
    1012                 :            :            pl_cie_xy_equal(&a->green, &b->green) &&
    1013                 :       2600 :            pl_cie_xy_equal(&a->blue,  &b->blue)  &&
    1014                 :            :            pl_cie_xy_equal(&a->white, &b->white);
    1015                 :            : }
    1016                 :            : 
    1017                 :         47 : bool pl_raw_primaries_similar(const struct pl_raw_primaries *a,
    1018                 :            :                               const struct pl_raw_primaries *b)
    1019                 :            : {
    1020                 :         47 :     float delta = fabsf(a->red.x   - b->red.x)   +
    1021                 :         47 :                   fabsf(a->red.y   - b->red.y)   +
    1022                 :         47 :                   fabsf(a->green.x - b->green.x) +
    1023                 :         47 :                   fabsf(a->green.y - b->green.y) +
    1024                 :         47 :                   fabsf(a->blue.x  - b->blue.x)  +
    1025                 :         47 :                   fabsf(a->blue.y  - b->blue.y)  +
    1026                 :         47 :                   fabsf(a->white.x - b->white.x) +
    1027                 :         47 :                   fabsf(a->white.y - b->white.y);
    1028                 :            : 
    1029                 :         47 :     return delta < 0.001;
    1030                 :            : }
    1031                 :            : 
    1032                 :         59 : void pl_raw_primaries_merge(struct pl_raw_primaries *orig,
    1033                 :            :                             const struct pl_raw_primaries *update)
    1034                 :            : {
    1035                 :            :     union {
    1036                 :            :         struct pl_raw_primaries prim;
    1037                 :            :         float raw[8];
    1038                 :            :     } *pa = (void *) orig,
    1039                 :            :       *pb = (void *) update;
    1040                 :            : 
    1041                 :            :     pl_static_assert(sizeof(*pa) == sizeof(*orig));
    1042         [ +  + ]:        531 :     for (int i = 0; i < PL_ARRAY_SIZE(pa->raw); i++)
    1043         [ +  - ]:        472 :         pa->raw[i] = PL_DEF(pa->raw[i], pb->raw[i]);
    1044                 :         59 : }
    1045                 :            : 
    1046                 :       8102 : const struct pl_raw_primaries *pl_raw_primaries_get(enum pl_color_primaries prim)
    1047                 :            : {
    1048                 :            :     /*
    1049                 :            :     Values from: ITU-R Recommendations BT.470-6, BT.601-7, BT.709-5, BT.2020-0
    1050                 :            : 
    1051                 :            :     https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.470-6-199811-S!!PDF-E.pdf
    1052                 :            :     https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.601-7-201103-I!!PDF-E.pdf
    1053                 :            :     https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.709-5-200204-I!!PDF-E.pdf
    1054                 :            :     https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.2020-0-201208-I!!PDF-E.pdf
    1055                 :            : 
    1056                 :            :     Other colorspaces from https://en.wikipedia.org/wiki/RGB_color_space#Specifications
    1057                 :            :     */
    1058                 :            : 
    1059                 :            :     // CIE standard illuminant series
    1060                 :            : #define CIE_D50 {0.3457, 0.3585}
    1061                 :            : #define CIE_D65 {0.3127, 0.3290}
    1062                 :            : #define CIE_C   {0.3100, 0.3160}
    1063                 :            : #define CIE_E   {1.0/3.0, 1.0/3.0}
    1064                 :            : #define DCI     {0.3140, 0.3510}
    1065                 :            : 
    1066                 :            :     static const struct pl_raw_primaries primaries[] = {
    1067                 :            :         [PL_COLOR_PRIM_BT_470M] = {
    1068                 :            :             .red   = {0.670, 0.330},
    1069                 :            :             .green = {0.210, 0.710},
    1070                 :            :             .blue  = {0.140, 0.080},
    1071                 :            :             .white = CIE_C,
    1072                 :            :         },
    1073                 :            : 
    1074                 :            :         [PL_COLOR_PRIM_BT_601_525] = {
    1075                 :            :             .red   = {0.630, 0.340},
    1076                 :            :             .green = {0.310, 0.595},
    1077                 :            :             .blue  = {0.155, 0.070},
    1078                 :            :             .white = CIE_D65,
    1079                 :            :         },
    1080                 :            :         [PL_COLOR_PRIM_BT_601_625] = {
    1081                 :            :             .red   = {0.640, 0.330},
    1082                 :            :             .green = {0.290, 0.600},
    1083                 :            :             .blue  = {0.150, 0.060},
    1084                 :            :             .white = CIE_D65,
    1085                 :            :         },
    1086                 :            :         [PL_COLOR_PRIM_BT_709] = {
    1087                 :            :             .red   = {0.640, 0.330},
    1088                 :            :             .green = {0.300, 0.600},
    1089                 :            :             .blue  = {0.150, 0.060},
    1090                 :            :             .white = CIE_D65,
    1091                 :            :         },
    1092                 :            :         [PL_COLOR_PRIM_BT_2020] = {
    1093                 :            :             .red   = {0.708, 0.292},
    1094                 :            :             .green = {0.170, 0.797},
    1095                 :            :             .blue  = {0.131, 0.046},
    1096                 :            :             .white = CIE_D65,
    1097                 :            :         },
    1098                 :            :         [PL_COLOR_PRIM_APPLE] = {
    1099                 :            :             .red   = {0.625, 0.340},
    1100                 :            :             .green = {0.280, 0.595},
    1101                 :            :             .blue  = {0.115, 0.070},
    1102                 :            :             .white = CIE_D65,
    1103                 :            :         },
    1104                 :            :         [PL_COLOR_PRIM_ADOBE] = {
    1105                 :            :             .red   = {0.640, 0.330},
    1106                 :            :             .green = {0.210, 0.710},
    1107                 :            :             .blue  = {0.150, 0.060},
    1108                 :            :             .white = CIE_D65,
    1109                 :            :         },
    1110                 :            :         [PL_COLOR_PRIM_PRO_PHOTO] = {
    1111                 :            :             .red   = {0.7347, 0.2653},
    1112                 :            :             .green = {0.1596, 0.8404},
    1113                 :            :             .blue  = {0.0366, 0.0001},
    1114                 :            :             .white = CIE_D50,
    1115                 :            :         },
    1116                 :            :         [PL_COLOR_PRIM_CIE_1931] = {
    1117                 :            :             .red   = {0.7347, 0.2653},
    1118                 :            :             .green = {0.2738, 0.7174},
    1119                 :            :             .blue  = {0.1666, 0.0089},
    1120                 :            :             .white = CIE_E,
    1121                 :            :         },
    1122                 :            :     // From SMPTE RP 431-2
    1123                 :            :         [PL_COLOR_PRIM_DCI_P3] = {
    1124                 :            :             .red   = {0.680, 0.320},
    1125                 :            :             .green = {0.265, 0.690},
    1126                 :            :             .blue  = {0.150, 0.060},
    1127                 :            :             .white = DCI,
    1128                 :            :         },
    1129                 :            :         [PL_COLOR_PRIM_DISPLAY_P3] = {
    1130                 :            :             .red   = {0.680, 0.320},
    1131                 :            :             .green = {0.265, 0.690},
    1132                 :            :             .blue  = {0.150, 0.060},
    1133                 :            :             .white = CIE_D65,
    1134                 :            :         },
    1135                 :            :     // From Panasonic VARICAM reference manual
    1136                 :            :         [PL_COLOR_PRIM_V_GAMUT] = {
    1137                 :            :             .red   = {0.730, 0.280},
    1138                 :            :             .green = {0.165, 0.840},
    1139                 :            :             .blue  = {0.100, -0.03},
    1140                 :            :             .white = CIE_D65,
    1141                 :            :         },
    1142                 :            :     // From Sony S-Log reference manual
    1143                 :            :         [PL_COLOR_PRIM_S_GAMUT] = {
    1144                 :            :             .red   = {0.730, 0.280},
    1145                 :            :             .green = {0.140, 0.855},
    1146                 :            :             .blue  = {0.100, -0.05},
    1147                 :            :             .white = CIE_D65,
    1148                 :            :         },
    1149                 :            :     // From FFmpeg source code
    1150                 :            :         [PL_COLOR_PRIM_FILM_C] = {
    1151                 :            :             .red   = {0.681, 0.319},
    1152                 :            :             .green = {0.243, 0.692},
    1153                 :            :             .blue  = {0.145, 0.049},
    1154                 :            :             .white = CIE_C,
    1155                 :            :         },
    1156                 :            :         [PL_COLOR_PRIM_EBU_3213] = {
    1157                 :            :             .red   = {0.630, 0.340},
    1158                 :            :             .green = {0.295, 0.605},
    1159                 :            :             .blue  = {0.155, 0.077},
    1160                 :            :             .white = CIE_D65,
    1161                 :            :         },
    1162                 :            :     // From Wikipedia
    1163                 :            :         [PL_COLOR_PRIM_ACES_AP0] = {
    1164                 :            :             .red   = {0.7347, 0.2653},
    1165                 :            :             .green = {0.0000, 1.0000},
    1166                 :            :             .blue  = {0.0001, -0.0770},
    1167                 :            :             .white = {0.32168, 0.33767},
    1168                 :            :         },
    1169                 :            :         [PL_COLOR_PRIM_ACES_AP1] = {
    1170                 :            :             .red   = {0.713, 0.293},
    1171                 :            :             .green = {0.165, 0.830},
    1172                 :            :             .blue  = {0.128, 0.044},
    1173                 :            :             .white = {0.32168, 0.33767},
    1174                 :            :         },
    1175                 :            :     };
    1176                 :            : 
    1177                 :            :     // This is the default assumption if no colorspace information could
    1178                 :            :     // be determined, eg. for files which have no video channel.
    1179         [ +  + ]:       8102 :     if (!prim)
    1180                 :            :         prim = PL_COLOR_PRIM_BT_709;
    1181                 :            : 
    1182         [ -  + ]:       7895 :     pl_assert(prim < PL_ARRAY_SIZE(primaries));
    1183                 :       8102 :     return &primaries[prim];
    1184                 :            : }
    1185                 :            : 
    1186                 :            : // Compute the RGB/XYZ matrix as described here:
    1187                 :            : // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
    1188                 :       4292 : pl_matrix3x3 pl_get_rgb2xyz_matrix(const struct pl_raw_primaries *prim)
    1189                 :            : {
    1190                 :       4292 :     pl_matrix3x3 out = {{{0}}};
    1191                 :            :     float S[3], X[4], Z[4];
    1192                 :            : 
    1193                 :       4292 :     X[0] = pl_cie_X(prim->red);
    1194                 :       4292 :     X[1] = pl_cie_X(prim->green);
    1195                 :       4292 :     X[2] = pl_cie_X(prim->blue);
    1196                 :       4292 :     X[3] = pl_cie_X(prim->white);
    1197                 :            : 
    1198                 :       4292 :     Z[0] = pl_cie_Z(prim->red);
    1199                 :       4292 :     Z[1] = pl_cie_Z(prim->green);
    1200                 :       4292 :     Z[2] = pl_cie_Z(prim->blue);
    1201                 :       4292 :     Z[3] = pl_cie_Z(prim->white);
    1202                 :            : 
    1203                 :            :     // S = XYZ^-1 * W
    1204         [ +  + ]:      17168 :     for (int i = 0; i < 3; i++) {
    1205                 :      12876 :         out.m[0][i] = X[i];
    1206                 :      12876 :         out.m[1][i] = 1;
    1207                 :      12876 :         out.m[2][i] = Z[i];
    1208                 :            :     }
    1209                 :            : 
    1210                 :       4292 :     pl_matrix3x3_invert(&out);
    1211                 :            : 
    1212         [ +  + ]:      17168 :     for (int i = 0; i < 3; i++)
    1213                 :      12876 :         S[i] = out.m[i][0] * X[3] + out.m[i][1] * 1 + out.m[i][2] * Z[3];
    1214                 :            : 
    1215                 :            :     // M = [Sc * XYZc]
    1216         [ +  + ]:      17168 :     for (int i = 0; i < 3; i++) {
    1217                 :      12876 :         out.m[0][i] = S[i] * X[i];
    1218                 :      12876 :         out.m[1][i] = S[i] * 1;
    1219                 :      12876 :         out.m[2][i] = S[i] * Z[i];
    1220                 :            :     }
    1221                 :            : 
    1222                 :       4292 :     return out;
    1223                 :            : }
    1224                 :            : 
    1225                 :          7 : pl_matrix3x3 pl_get_xyz2rgb_matrix(const struct pl_raw_primaries *prim)
    1226                 :            : {
    1227                 :            :     // For simplicity, just invert the rgb2xyz matrix
    1228                 :          7 :     pl_matrix3x3 out = pl_get_rgb2xyz_matrix(prim);
    1229                 :          7 :     pl_matrix3x3_invert(&out);
    1230                 :          7 :     return out;
    1231                 :            : }
    1232                 :            : 
    1233                 :            : // Matrix used in CAT16, a revised one-step linear transform method
    1234                 :            : static const pl_matrix3x3 m_cat16 = {{
    1235                 :            :     {  0.401288, 0.650173, -0.051461 },
    1236                 :            :     { -0.250268, 1.204414,  0.045854 },
    1237                 :            :     { -0.002079, 0.048952,  0.953127 },
    1238                 :            : }};
    1239                 :            : 
    1240                 :            : // M := M * XYZd<-XYZs
    1241                 :       1248 : static void apply_chromatic_adaptation(struct pl_cie_xy src,
    1242                 :            :                                        struct pl_cie_xy dest,
    1243                 :            :                                        pl_matrix3x3 *mat)
    1244                 :            : {
    1245                 :            :     // If the white points are nearly identical, this is a wasteful identity
    1246                 :            :     // operation.
    1247   [ +  +  -  + ]:       1248 :     if (fabs(src.x - dest.x) < 1e-6 && fabs(src.y - dest.y) < 1e-6)
    1248                 :        987 :         return;
    1249                 :            : 
    1250                 :            :     // Linear "von Kries" method, adapted from CIECAM16
    1251                 :            :     // http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html
    1252                 :            :     float C[3][2];
    1253                 :            : 
    1254         [ +  + ]:       1044 :     for (int i = 0; i < 3; i++) {
    1255                 :            :         // source cone
    1256                 :        783 :         C[i][0] = m_cat16.m[i][0] * pl_cie_X(src)
    1257                 :        783 :                 + m_cat16.m[i][1] * 1
    1258                 :        783 :                 + m_cat16.m[i][2] * pl_cie_Z(src);
    1259                 :            : 
    1260                 :            :         // dest cone
    1261                 :        783 :         C[i][1] = m_cat16.m[i][0] * pl_cie_X(dest)
    1262                 :        783 :                 + m_cat16.m[i][1] * 1
    1263                 :        783 :                 + m_cat16.m[i][2] * pl_cie_Z(dest);
    1264                 :            :     }
    1265                 :            : 
    1266                 :            :     // tmp := I * [Cd/Cs] * Ma
    1267                 :        261 :     pl_matrix3x3 tmp = {0};
    1268         [ +  + ]:       1044 :     for (int i = 0; i < 3; i++)
    1269                 :        783 :         tmp.m[i][i] = C[i][1] / C[i][0];
    1270                 :            : 
    1271                 :        261 :     pl_matrix3x3_mul(&tmp, &m_cat16);
    1272                 :            : 
    1273                 :            :     // M := M * Ma^-1 * tmp
    1274                 :        261 :     pl_matrix3x3 ma_inv = m_cat16;
    1275                 :        261 :     pl_matrix3x3_invert(&ma_inv);
    1276                 :        261 :     pl_matrix3x3_mul(mat, &ma_inv);
    1277                 :        261 :     pl_matrix3x3_mul(mat, &tmp);
    1278                 :            : }
    1279                 :            : 
    1280                 :          6 : pl_matrix3x3 pl_get_adaptation_matrix(struct pl_cie_xy src, struct pl_cie_xy dst)
    1281                 :            : {
    1282                 :            :     // Use BT.709 primaries (with chosen white point) as an XYZ reference
    1283                 :          6 :     struct pl_raw_primaries csp = *pl_raw_primaries_get(PL_COLOR_PRIM_BT_709);
    1284                 :          6 :     csp.white = src;
    1285                 :            : 
    1286                 :          6 :     pl_matrix3x3 rgb2xyz = pl_get_rgb2xyz_matrix(&csp);
    1287                 :          6 :     pl_matrix3x3 xyz2rgb = rgb2xyz;
    1288                 :          6 :     pl_matrix3x3_invert(&xyz2rgb);
    1289                 :            : 
    1290                 :          6 :     apply_chromatic_adaptation(src, dst, &xyz2rgb);
    1291                 :          6 :     pl_matrix3x3_mul(&xyz2rgb, &rgb2xyz);
    1292                 :          6 :     return xyz2rgb;
    1293                 :            : }
    1294                 :            : 
    1295                 :       1239 : pl_matrix3x3 pl_ipt_rgb2lms(const struct pl_raw_primaries *prim)
    1296                 :            : {
    1297                 :            :     static const pl_matrix3x3 hpe = {{ // HPE XYZ->LMS (D65) method
    1298                 :            :         {  0.40024f, 0.70760f, -0.08081f },
    1299                 :            :         { -0.22630f, 1.16532f,  0.04570f },
    1300                 :            :         {  0.00000f, 0.00000f,  0.91822f },
    1301                 :            :     }};
    1302                 :            : 
    1303                 :            :     const float c = 0.04; // 4% crosstalk
    1304                 :       1239 :     pl_matrix3x3 m = {{
    1305                 :            :         { 1 - 2*c,       c,       c },
    1306                 :            :         {       c, 1 - 2*c,       c },
    1307                 :            :         {       c,       c, 1 - 2*c },
    1308                 :            :     }};
    1309                 :            : 
    1310                 :       1239 :     pl_matrix3x3_mul(&m, &hpe);
    1311                 :            : 
    1312                 :            :     // Apply chromatic adaptation to D65 if the input white point differs
    1313                 :            :     static const struct pl_cie_xy d65 = CIE_D65;
    1314                 :       1239 :     apply_chromatic_adaptation(prim->white, d65, &m);
    1315                 :            : 
    1316                 :       1239 :     const pl_matrix3x3 rgb2xyz = pl_get_rgb2xyz_matrix(prim);
    1317                 :       1239 :     pl_matrix3x3_mul(&m, &rgb2xyz);
    1318                 :       1239 :     return m;
    1319                 :            : }
    1320                 :            : 
    1321                 :         67 : pl_matrix3x3 pl_ipt_lms2rgb(const struct pl_raw_primaries *prim)
    1322                 :            : {
    1323                 :         67 :     pl_matrix3x3 m = pl_ipt_rgb2lms(prim);
    1324                 :         67 :     pl_matrix3x3_invert(&m);
    1325                 :         67 :     return m;
    1326                 :            : }
    1327                 :            : 
    1328                 :            : // As standardized in Ebner & Fairchild IPT (1998)
    1329                 :            : const pl_matrix3x3 pl_ipt_lms2ipt = {{
    1330                 :            :     { 0.4000,  0.4000,  0.2000 },
    1331                 :            :     { 4.4550, -4.8510,  0.3960 },
    1332                 :            :     { 0.8056,  0.3572, -1.1628 },
    1333                 :            : }};
    1334                 :            : 
    1335                 :            : // Numerically inverted from the matrix above
    1336                 :            : const pl_matrix3x3 pl_ipt_ipt2lms = {{
    1337                 :            :     { 1.0,  0.0975689,  0.205226 },
    1338                 :            :     { 1.0, -0.1138760,  0.133217 },
    1339                 :            :     { 1.0,  0.0326151, -0.676887 },
    1340                 :            : }};
    1341                 :            : 
    1342                 :            : const struct pl_cone_params pl_vision_normal        = {PL_CONE_NONE, 1.0};
    1343                 :            : const struct pl_cone_params pl_vision_protanomaly   = {PL_CONE_L,    0.5};
    1344                 :            : const struct pl_cone_params pl_vision_protanopia    = {PL_CONE_L,    0.0};
    1345                 :            : const struct pl_cone_params pl_vision_deuteranomaly = {PL_CONE_M,    0.5};
    1346                 :            : const struct pl_cone_params pl_vision_deuteranopia  = {PL_CONE_M,    0.0};
    1347                 :            : const struct pl_cone_params pl_vision_tritanomaly   = {PL_CONE_S,    0.5};
    1348                 :            : const struct pl_cone_params pl_vision_tritanopia    = {PL_CONE_S,    0.0};
    1349                 :            : const struct pl_cone_params pl_vision_monochromacy  = {PL_CONE_LM,   0.0};
    1350                 :            : const struct pl_cone_params pl_vision_achromatopsia = {PL_CONE_LMS,  0.0};
    1351                 :            : 
    1352                 :         20 : pl_matrix3x3 pl_get_cone_matrix(const struct pl_cone_params *params,
    1353                 :            :                                 const struct pl_raw_primaries *prim)
    1354                 :            : {
    1355                 :            :     // LMS<-RGB := LMS<-XYZ * XYZ<-RGB
    1356                 :         20 :     pl_matrix3x3 rgb2lms = m_cat16;
    1357                 :         20 :     pl_matrix3x3 rgb2xyz = pl_get_rgb2xyz_matrix(prim);
    1358                 :         20 :     pl_matrix3x3_mul(&rgb2lms, &rgb2xyz);
    1359                 :            : 
    1360                 :            :     // LMS versions of the two opposing primaries, plus neutral
    1361                 :         20 :     float lms_r[3] = {1.0, 0.0, 0.0},
    1362                 :         20 :           lms_b[3] = {0.0, 0.0, 1.0},
    1363                 :         20 :           lms_w[3] = {1.0, 1.0, 1.0};
    1364                 :            : 
    1365                 :         20 :     pl_matrix3x3_apply(&rgb2lms, lms_r);
    1366                 :         20 :     pl_matrix3x3_apply(&rgb2lms, lms_b);
    1367                 :         20 :     pl_matrix3x3_apply(&rgb2lms, lms_w);
    1368                 :            : 
    1369                 :         20 :     float a, b, c = params->strength;
    1370                 :            :     pl_matrix3x3 distort;
    1371                 :            : 
    1372   [ +  +  +  +  :         20 :     switch (params->cones) {
             +  +  +  +  
                      - ]
    1373                 :          4 :     case PL_CONE_NONE:
    1374                 :          4 :         return pl_matrix3x3_identity;
    1375                 :            : 
    1376                 :          3 :     case PL_CONE_L:
    1377                 :            :         // Solve to preserve neutral and blue
    1378                 :          3 :         a = (lms_b[0] - lms_b[2] * lms_w[0] / lms_w[2]) /
    1379                 :          3 :             (lms_b[1] - lms_b[2] * lms_w[1] / lms_w[2]);
    1380                 :          3 :         b = (lms_b[0] - lms_b[1] * lms_w[0] / lms_w[1]) /
    1381                 :          3 :             (lms_b[2] - lms_b[1] * lms_w[2] / lms_w[1]);
    1382         [ -  + ]:          3 :         assert(fabs(a * lms_w[1] + b * lms_w[2] - lms_w[0]) < 1e-6);
    1383                 :            : 
    1384                 :          3 :         distort = (pl_matrix3x3) {{
    1385                 :          3 :             {            c, (1.0 - c) * a, (1.0 - c) * b},
    1386                 :            :             {          0.0,           1.0,           0.0},
    1387                 :            :             {          0.0,           0.0,           1.0},
    1388                 :            :         }};
    1389                 :          3 :         break;
    1390                 :            : 
    1391                 :          6 :     case PL_CONE_M:
    1392                 :            :         // Solve to preserve neutral and blue
    1393                 :          6 :         a = (lms_b[1] - lms_b[2] * lms_w[1] / lms_w[2]) /
    1394                 :          6 :             (lms_b[0] - lms_b[2] * lms_w[0] / lms_w[2]);
    1395                 :          6 :         b = (lms_b[1] - lms_b[0] * lms_w[1] / lms_w[0]) /
    1396                 :          6 :             (lms_b[2] - lms_b[0] * lms_w[2] / lms_w[0]);
    1397         [ -  + ]:          6 :         assert(fabs(a * lms_w[0] + b * lms_w[2] - lms_w[1]) < 1e-6);
    1398                 :            : 
    1399                 :          6 :         distort = (pl_matrix3x3) {{
    1400                 :            :             {          1.0,           0.0,           0.0},
    1401                 :          6 :             {(1.0 - c) * a,             c, (1.0 - c) * b},
    1402                 :            :             {          0.0,           0.0,           1.0},
    1403                 :            :         }};
    1404                 :          6 :         break;
    1405                 :            : 
    1406                 :          3 :     case PL_CONE_S:
    1407                 :            :         // Solve to preserve neutral and red
    1408                 :          3 :         a = (lms_r[2] - lms_r[1] * lms_w[2] / lms_w[1]) /
    1409                 :          3 :             (lms_r[0] - lms_r[1] * lms_w[0] / lms_w[1]);
    1410                 :          3 :         b = (lms_r[2] - lms_r[0] * lms_w[2] / lms_w[0]) /
    1411                 :          3 :             (lms_r[1] - lms_r[0] * lms_w[1] / lms_w[0]);
    1412         [ -  + ]:          3 :         assert(fabs(a * lms_w[0] + b * lms_w[1] - lms_w[2]) < 1e-6);
    1413                 :            : 
    1414                 :          3 :         distort = (pl_matrix3x3) {{
    1415                 :            :             {          1.0,           0.0,           0.0},
    1416                 :            :             {          0.0,           1.0,           0.0},
    1417                 :          3 :             {(1.0 - c) * a, (1.0 - c) * b,             c},
    1418                 :            :         }};
    1419                 :          3 :         break;
    1420                 :            : 
    1421                 :          1 :     case PL_CONE_LM:
    1422                 :            :         // Solve to preserve neutral
    1423                 :          1 :         a = lms_w[0] / lms_w[2];
    1424                 :          1 :         b = lms_w[1] / lms_w[2];
    1425                 :            : 
    1426                 :          1 :         distort = (pl_matrix3x3) {{
    1427                 :          1 :             {            c,           0.0, (1.0 - c) * a},
    1428                 :          1 :             {          0.0,             c, (1.0 - c) * b},
    1429                 :            :             {          0.0,           0.0,           1.0},
    1430                 :            :         }};
    1431                 :          1 :         break;
    1432                 :            : 
    1433                 :          1 :     case PL_CONE_MS:
    1434                 :            :         // Solve to preserve neutral
    1435                 :          1 :         a = lms_w[1] / lms_w[0];
    1436                 :          1 :         b = lms_w[2] / lms_w[0];
    1437                 :            : 
    1438                 :          1 :         distort = (pl_matrix3x3) {{
    1439                 :            :             {          1.0,           0.0,           0.0},
    1440                 :          1 :             {(1.0 - c) * a,             c,           0.0},
    1441                 :          1 :             {(1.0 - c) * b,           0.0,             c},
    1442                 :            :         }};
    1443                 :          1 :         break;
    1444                 :            : 
    1445                 :          1 :     case PL_CONE_LS:
    1446                 :            :         // Solve to preserve neutral
    1447                 :          1 :         a = lms_w[0] / lms_w[1];
    1448                 :          1 :         b = lms_w[2] / lms_w[1];
    1449                 :            : 
    1450                 :          1 :         distort = (pl_matrix3x3) {{
    1451                 :          1 :             {            c, (1.0 - c) * a,           0.0},
    1452                 :            :             {          0.0,           1.0,           0.0},
    1453                 :          1 :             {          0.0, (1.0 - c) * b,             c},
    1454                 :            :         }};
    1455                 :          1 :         break;
    1456                 :            : 
    1457                 :          1 :     case PL_CONE_LMS: {
    1458                 :            :         // Rod cells only, which can be modelled somewhat as a combination of
    1459                 :            :         // L and M cones. Either way, this is pushing the limits of the our
    1460                 :            :         // color model, so this is only a rough approximation.
    1461                 :          1 :         const float w[3] = {0.3605, 0.6415, -0.002};
    1462                 :            :         assert(fabs(w[0] + w[1] + w[2] - 1.0) < 1e-6);
    1463                 :            : 
    1464         [ +  + ]:          4 :         for (int i = 0; i < 3; i++) {
    1465         [ +  + ]:         12 :             for (int j = 0; j < 3; j++) {
    1466                 :          9 :                 distort.m[i][j] = (1.0 - c) * w[j] * lms_w[i] / lms_w[j];
    1467         [ +  + ]:          9 :                 if (i == j)
    1468                 :          3 :                     distort.m[i][j] += c;
    1469                 :            :             }
    1470                 :            :         }
    1471                 :            :         break;
    1472                 :            :     }
    1473                 :            : 
    1474                 :            :     default:
    1475                 :          0 :         pl_unreachable();
    1476                 :            :     }
    1477                 :            : 
    1478                 :            :     // out := RGB<-LMS * distort * LMS<-RGB
    1479                 :         16 :     pl_matrix3x3 out = rgb2lms;
    1480                 :         16 :     pl_matrix3x3_invert(&out);
    1481                 :         16 :     pl_matrix3x3_mul(&out, &distort);
    1482                 :         16 :     pl_matrix3x3_mul(&out, &rgb2lms);
    1483                 :            : 
    1484                 :         16 :     return out;
    1485                 :            : }
    1486                 :            : 
    1487                 :          3 : pl_matrix3x3 pl_get_color_mapping_matrix(const struct pl_raw_primaries *src,
    1488                 :            :                                          const struct pl_raw_primaries *dst,
    1489                 :            :                                          enum pl_rendering_intent intent)
    1490                 :            : {
    1491                 :            :     // In saturation mapping, we don't care about accuracy and just want
    1492                 :            :     // primaries to map to primaries, making this an identity transformation.
    1493         [ -  + ]:          3 :     if (intent == PL_INTENT_SATURATION)
    1494                 :          0 :         return pl_matrix3x3_identity;
    1495                 :            : 
    1496                 :            :     // RGBd<-RGBs = RGBd<-XYZd * XYZd<-XYZs * XYZs<-RGBs
    1497                 :            :     // Equations from: http://www.brucelindbloom.com/index.html?Math.html
    1498                 :            :     // Note: Perceptual is treated like relative colorimetric. There's no
    1499                 :            :     // definition for perceptual other than "make it look good".
    1500                 :            : 
    1501                 :            :     // RGBd<-XYZd matrix
    1502                 :          3 :     pl_matrix3x3 xyz2rgb_d = pl_get_xyz2rgb_matrix(dst);
    1503                 :            : 
    1504                 :            :     // Chromatic adaptation, except in absolute colorimetric intent
    1505         [ +  - ]:          3 :     if (intent != PL_INTENT_ABSOLUTE_COLORIMETRIC)
    1506                 :          3 :         apply_chromatic_adaptation(src->white, dst->white, &xyz2rgb_d);
    1507                 :            : 
    1508                 :            :     // XYZs<-RGBs
    1509                 :          3 :     pl_matrix3x3 rgb2xyz_s = pl_get_rgb2xyz_matrix(src);
    1510                 :          3 :     pl_matrix3x3_mul(&xyz2rgb_d, &rgb2xyz_s);
    1511                 :          3 :     return xyz2rgb_d;
    1512                 :            : }
    1513                 :            : 
    1514                 :            : // Test the sign of 'p' relative to the line 'ab' (barycentric coordinates)
    1515                 :            : static float test_point_line(const struct pl_cie_xy p,
    1516                 :            :                              const struct pl_cie_xy a,
    1517                 :            :                              const struct pl_cie_xy b)
    1518                 :            : {
    1519                 :       2105 :     return (p.x - b.x) * (a.y - b.y) - (a.x - b.x) * (p.y - b.y);
    1520                 :            : }
    1521                 :            : 
    1522                 :            : // Test if a point is entirely inside a gamut
    1523                 :       1919 : static float test_point_gamut(struct pl_cie_xy point,
    1524                 :            :                               const struct pl_raw_primaries *prim)
    1525                 :            : {
    1526                 :            :     float d1 = test_point_line(point, prim->red, prim->green),
    1527                 :            :           d2 = test_point_line(point, prim->green, prim->blue),
    1528                 :            :           d3 = test_point_line(point, prim->blue, prim->red);
    1529                 :            : 
    1530   [ +  +  +  +  :       1919 :     bool has_neg = d1 < -1e-6f || d2 < -1e-6f || d3 < -1e-6f,
                   +  + ]
    1531   [ +  +  +  +  :       1919 :          has_pos = d1 >  1e-6f || d2 >  1e-6f || d3 >  1e-6f;
                   -  + ]
    1532                 :            : 
    1533                 :       1919 :     return !(has_neg && has_pos);
    1534                 :            : }
    1535                 :            : 
    1536                 :        100 : bool pl_primaries_superset(const struct pl_raw_primaries *a,
    1537                 :            :                            const struct pl_raw_primaries *b)
    1538                 :            : {
    1539                 :        138 :     return test_point_gamut(b->red, a) &&
    1540   [ +  +  +  + ]:        100 :            test_point_gamut(b->green, a) &&
    1541         [ -  + ]:         34 :            test_point_gamut(b->blue, a);
    1542                 :            : }
    1543                 :            : 
    1544                 :       6619 : bool pl_primaries_valid(const struct pl_raw_primaries *prim)
    1545                 :            : {
    1546                 :            :     // Test to see if the primaries form a valid triangle (nonzero area)
    1547                 :       6619 :     float area = (prim->blue.x - prim->green.x) * (prim->red.y  - prim->green.y)
    1548                 :       6619 :                - (prim->red.x  - prim->green.x) * (prim->blue.y - prim->green.y);
    1549                 :            : 
    1550   [ +  +  -  + ]:       6619 :     return fabs(area) > 1e-6 && test_point_gamut(prim->white, prim);
    1551                 :            : }
    1552                 :            : 
    1553                 :            : static inline float xy_dist2(struct pl_cie_xy a, struct pl_cie_xy b)
    1554                 :            : {
    1555                 :        121 :     const float dx = a.x - b.x, dy = a.y - b.y;
    1556                 :        121 :     return dx * dx + dy * dy;
    1557                 :            : }
    1558                 :            : 
    1559                 :        121 : bool pl_primaries_compatible(const struct pl_raw_primaries *a,
    1560                 :            :                              const struct pl_raw_primaries *b)
    1561                 :            : {
    1562                 :            :     float RR = xy_dist2(a->red, b->red),    RG = xy_dist2(a->red, b->green),
    1563                 :            :           RB = xy_dist2(a->red, b->blue),   GG = xy_dist2(a->green, b->green),
    1564                 :            :           GB = xy_dist2(a->green, b->blue), BB = xy_dist2(a->blue, b->blue);
    1565   [ +  -  +  -  :        121 :     return RR < RG && RR < RB && GG < RG && GG < GB && BB < RB && BB < GB;
          +  -  +  -  +  
                -  -  + ]
    1566                 :            : }
    1567                 :            : 
    1568                 :            : // returns the intersection of the two lines defined by ab and cd
    1569                 :          1 : static struct pl_cie_xy intersection(struct pl_cie_xy a, struct pl_cie_xy b,
    1570                 :            :                                      struct pl_cie_xy c, struct pl_cie_xy d)
    1571                 :            : {
    1572                 :          1 :     float det = (a.x - b.x) * (c.y - d.y) - (a.y - b.y) * (c.x - d.x);
    1573                 :          1 :     float t = ((a.x - c.x) * (c.y - d.y) - (a.y - c.y) * (c.x - d.x)) / det;
    1574                 :          1 :     return (struct pl_cie_xy) {
    1575         [ +  - ]:          1 :         .x = t ? a.x + t * (b.x - a.x) : 0.0f,
    1576                 :          1 :         .y = t ? a.y + t * (b.y - a.y) : 0.0f,
    1577                 :            :     };
    1578                 :            : }
    1579                 :            : 
    1580                 :            : // x, y, z specified in clockwise order, with a, b, c being the enclosing gamut
    1581                 :            : static struct pl_cie_xy
    1582                 :        186 : clip_point(struct pl_cie_xy x, struct pl_cie_xy y, struct pl_cie_xy z,
    1583                 :            :            struct pl_cie_xy a, struct pl_cie_xy b, struct pl_cie_xy c)
    1584                 :            : {
    1585                 :            :     const float d1 = test_point_line(y, a, b);
    1586                 :            :     const float d2 = test_point_line(y, b, c);
    1587   [ +  +  +  - ]:        186 :     if (d1 <= 0.0f && d2 <= 0.0f) {
    1588                 :        168 :         return y; // already inside triangle
    1589   [ +  -  +  + ]:         18 :     } else if (d1 > 0.0f && d2 > 0.0f) {
    1590                 :         17 :         return b; // target vertex fully enclosed
    1591                 :            :     } else if (d1 > 0.0f) {
    1592                 :          1 :         return intersection(a, b, y, z);
    1593                 :            :     } else {
    1594                 :          0 :         return intersection(x, y, b, c);
    1595                 :            :     }
    1596                 :            : }
    1597                 :            : 
    1598                 :         62 : struct pl_raw_primaries pl_primaries_clip(const struct pl_raw_primaries *src,
    1599                 :            :                                           const struct pl_raw_primaries *dst)
    1600                 :            : {
    1601                 :         62 :     return (struct pl_raw_primaries) {
    1602                 :         62 :         .red   = clip_point(src->green, src->red, src->blue,
    1603                 :            :                             dst->green, dst->red, dst->blue),
    1604                 :         62 :         .green = clip_point(src->blue, src->green, src->red,
    1605                 :            :                             dst->blue, dst->green, dst->red),
    1606                 :         62 :         .blue  = clip_point(src->red, src->blue, src->green,
    1607                 :            :                             dst->red, dst->blue, dst->green),
    1608                 :            :         .white = src->white,
    1609                 :            :     };
    1610                 :            : }
    1611                 :            : 
    1612                 :            : /* Fill in the Y, U, V vectors of a yuv-to-rgb conversion matrix
    1613                 :            :  * based on the given luma weights of the R, G and B components (lr, lg, lb).
    1614                 :            :  * lr+lg+lb is assumed to equal 1.
    1615                 :            :  * This function is meant for colorspaces satisfying the following
    1616                 :            :  * conditions (which are true for common YUV colorspaces):
    1617                 :            :  * - The mapping from input [Y, U, V] to output [R, G, B] is linear.
    1618                 :            :  * - Y is the vector [1, 1, 1].  (meaning input Y component maps to 1R+1G+1B)
    1619                 :            :  * - U maps to a value with zero R and positive B ([0, x, y], y > 0;
    1620                 :            :  *   i.e. blue and green only).
    1621                 :            :  * - V maps to a value with zero B and positive R ([x, y, 0], x > 0;
    1622                 :            :  *   i.e. red and green only).
    1623                 :            :  * - U and V are orthogonal to the luma vector [lr, lg, lb].
    1624                 :            :  * - The magnitudes of the vectors U and V are the minimal ones for which
    1625                 :            :  *   the image of the set Y=[0...1],U=[-0.5...0.5],V=[-0.5...0.5] under the
    1626                 :            :  *   conversion function will cover the set R=[0...1],G=[0...1],B=[0...1]
    1627                 :            :  *   (the resulting matrix can be converted for other input/output ranges
    1628                 :            :  *   outside this function).
    1629                 :            :  * Under these conditions the given parameters lr, lg, lb uniquely
    1630                 :            :  * determine the mapping of Y, U, V to R, G, B.
    1631                 :            :  */
    1632                 :            : static pl_matrix3x3 luma_coeffs(float lr, float lg, float lb)
    1633                 :            : {
    1634                 :            :     pl_assert(fabs(lr+lg+lb - 1) < 1e-6);
    1635                 :          0 :     return (pl_matrix3x3) {{
    1636                 :            :         {1, 0,                    2 * (1-lr)          },
    1637                 :            :         {1, -2 * (1-lb) * lb/lg, -2 * (1-lr) * lr/lg  },
    1638                 :            :         {1,  2 * (1-lb),          0                   },
    1639                 :            :     }};
    1640                 :            : }
    1641                 :            : 
    1642                 :            : // Applies hue and saturation controls to a YCbCr->RGB matrix
    1643                 :        834 : static inline void apply_hue_sat(pl_matrix3x3 *m,
    1644                 :            :                                  const struct pl_color_adjustment *params)
    1645                 :            : {
    1646                 :            :     // Hue is equivalent to rotating input [U, V] subvector around the origin.
    1647                 :            :     // Saturation scales [U, V].
    1648                 :        834 :     float huecos = params->saturation * cos(params->hue);
    1649                 :        834 :     float huesin = params->saturation * sin(params->hue);
    1650         [ +  + ]:       3336 :     for (int i = 0; i < 3; i++) {
    1651                 :       2502 :         float u = m->m[i][1], v = m->m[i][2];
    1652                 :       2502 :         m->m[i][1] = huecos * u - huesin * v;
    1653                 :       2502 :         m->m[i][2] = huesin * u + huecos * v;
    1654                 :            :     }
    1655                 :        834 : }
    1656                 :            : 
    1657                 :        980 : pl_transform3x3 pl_color_repr_decode(struct pl_color_repr *repr,
    1658                 :            :                                      const struct pl_color_adjustment *params)
    1659                 :            : {
    1660         [ +  + ]:        980 :     params = PL_DEF(params, &pl_color_adjustment_neutral);
    1661                 :            : 
    1662                 :            :     pl_matrix3x3 m;
    1663   [ +  +  +  +  :        980 :     switch (repr->sys) {
          +  +  +  +  +  
             +  +  -  - ]
    1664                 :        800 :     case PL_COLOR_SYSTEM_BT_709:     m = luma_coeffs(0.2126, 0.7152, 0.0722); break;
    1665                 :          5 :     case PL_COLOR_SYSTEM_BT_601:     m = luma_coeffs(0.2990, 0.5870, 0.1140); break;
    1666                 :          5 :     case PL_COLOR_SYSTEM_SMPTE_240M: m = luma_coeffs(0.2122, 0.7013, 0.0865); break;
    1667                 :          5 :     case PL_COLOR_SYSTEM_BT_2020_NC: m = luma_coeffs(0.2627, 0.6780, 0.0593); break;
    1668                 :          4 :     case PL_COLOR_SYSTEM_BT_2020_C:
    1669                 :            :         // Note: This outputs into the [-0.5,0.5] range for chroma information.
    1670                 :          4 :         m = (pl_matrix3x3) {{
    1671                 :            :             {0, 0, 1},
    1672                 :            :             {1, 0, 0},
    1673                 :            :             {0, 1, 0},
    1674                 :            :         }};
    1675                 :          4 :         break;
    1676                 :          4 :     case PL_COLOR_SYSTEM_BT_2100_PQ: {
    1677                 :            :         // Reversed from the matrix in the spec, hard-coded for efficiency
    1678                 :            :         // and precision reasons. Exact values truncated from ITU-T H-series
    1679                 :            :         // Supplement 18.
    1680                 :            :         static const float lm_t = 0.008609, lm_p = 0.111029625;
    1681                 :          4 :         m = (pl_matrix3x3) {{
    1682                 :            :             {1.0,  lm_t,  lm_p},
    1683                 :            :             {1.0, -lm_t, -lm_p},
    1684                 :            :             {1.0, 0.560031, -0.320627},
    1685                 :            :         }};
    1686                 :            :         break;
    1687                 :            :     }
    1688                 :          4 :     case PL_COLOR_SYSTEM_BT_2100_HLG: {
    1689                 :            :         // Similar to BT.2100 PQ, exact values truncated from WolframAlpha
    1690                 :            :         static const float lm_t = 0.01571858011, lm_p = 0.2095810681;
    1691                 :          4 :         m = (pl_matrix3x3) {{
    1692                 :            :             {1.0,  lm_t,  lm_p},
    1693                 :            :             {1.0, -lm_t, -lm_p},
    1694                 :            :             {1.0, 1.02127108, -0.605274491},
    1695                 :            :         }};
    1696                 :            :         break;
    1697                 :            :     }
    1698                 :          2 :     case PL_COLOR_SYSTEM_DOLBYVISION:
    1699                 :          2 :         m = repr->dovi->nonlinear;
    1700                 :          2 :         break;
    1701                 :          5 :     case PL_COLOR_SYSTEM_YCGCO:
    1702                 :          5 :         m = (pl_matrix3x3) {{
    1703                 :            :             {1,  -1,  1},
    1704                 :            :             {1,   1,  0},
    1705                 :            :             {1,  -1, -1},
    1706                 :            :         }};
    1707                 :          5 :         break;
    1708                 :        142 :     case PL_COLOR_SYSTEM_UNKNOWN: // fall through
    1709                 :            :     case PL_COLOR_SYSTEM_RGB:
    1710                 :        142 :         m = pl_matrix3x3_identity;
    1711                 :        142 :         break;
    1712                 :          4 :     case PL_COLOR_SYSTEM_XYZ: {
    1713                 :            :         // For lack of anything saner to do, just assume the caller wants
    1714                 :            :         // DCI-P3 primaries, which is a reasonable assumption.
    1715                 :          4 :         const struct pl_raw_primaries *dst = pl_raw_primaries_get(PL_COLOR_PRIM_DCI_P3);
    1716                 :          4 :         m = pl_get_xyz2rgb_matrix(dst);
    1717                 :          4 :         break;
    1718                 :            :     }
    1719                 :            :     case PL_COLOR_SYSTEM_COUNT:
    1720                 :          0 :         pl_unreachable();
    1721                 :            :     }
    1722                 :            : 
    1723                 :            :     // Apply hue and saturation in the correct way depending on the colorspace.
    1724         [ +  + ]:        980 :     if (pl_color_system_is_ycbcr_like(repr->sys)) {
    1725                 :        834 :         apply_hue_sat(&m, params);
    1726   [ +  -  -  + ]:        146 :     } else if (params->saturation != 1.0 || params->hue != 0.0) {
    1727                 :            :         // Arbitrarily simulate hue shifts using the BT.709 YCbCr model
    1728                 :            :         pl_matrix3x3 yuv2rgb = luma_coeffs(0.2126, 0.7152, 0.0722);
    1729                 :          0 :         pl_matrix3x3 rgb2yuv = yuv2rgb;
    1730                 :          0 :         pl_matrix3x3_invert(&rgb2yuv);
    1731                 :          0 :         apply_hue_sat(&yuv2rgb, params);
    1732                 :            :         // M := RGB<-YUV * YUV<-RGB * M
    1733                 :          0 :         pl_matrix3x3_rmul(&rgb2yuv, &m);
    1734                 :          0 :         pl_matrix3x3_rmul(&yuv2rgb, &m);
    1735                 :            :     }
    1736                 :            : 
    1737                 :            :     // Apply color temperature adaptation, relative to BT.709 primaries
    1738         [ +  + ]:        980 :     if (params->temperature) {
    1739                 :          4 :         struct pl_cie_xy src = pl_white_from_temp(6500);
    1740                 :          4 :         struct pl_cie_xy dst = pl_white_from_temp(6500 + 3500 * params->temperature);
    1741                 :          4 :         pl_matrix3x3 adapt = pl_get_adaptation_matrix(src, dst);
    1742                 :          4 :         pl_matrix3x3_rmul(&adapt, &m);
    1743                 :            :     }
    1744                 :            : 
    1745                 :        980 :     pl_transform3x3 out = { .mat = m };
    1746   [ +  +  +  + ]:        980 :     int bit_depth = PL_DEF(repr->bits.sample_depth,
    1747                 :            :                     PL_DEF(repr->bits.color_depth, 8));
    1748                 :            : 
    1749                 :            :     double ymax, ymin, cmax, cmid;
    1750                 :        980 :     double scale = (1LL << bit_depth) / ((1LL << bit_depth) - 1.0);
    1751                 :            : 
    1752      [ +  +  - ]:        980 :     switch (pl_color_levels_guess(repr)) {
    1753                 :         50 :     case PL_COLOR_LEVELS_LIMITED: {
    1754                 :         50 :         ymax = 235 / 256. * scale;
    1755                 :         50 :         ymin =  16 / 256. * scale;
    1756                 :         50 :         cmax = 240 / 256. * scale;
    1757                 :         50 :         cmid = 128 / 256. * scale;
    1758                 :         50 :         break;
    1759                 :            :     }
    1760                 :        930 :     case PL_COLOR_LEVELS_FULL:
    1761                 :            :         // Note: For full-range YUV, there are multiple, subtly inconsistent
    1762                 :            :         // standards. So just pick the sanest implementation, which is to
    1763                 :            :         // assume MAX_INT == 1.0.
    1764                 :            :         ymax = 1.0;
    1765                 :            :         ymin = 0.0;
    1766                 :            :         cmax = 1.0;
    1767                 :        930 :         cmid = 128 / 256. * scale; // *not* exactly 0.5
    1768                 :        930 :         break;
    1769                 :            :     default:
    1770                 :          0 :         pl_unreachable();
    1771                 :            :     }
    1772                 :            : 
    1773                 :        980 :     double ymul = 1.0 / (ymax - ymin);
    1774                 :        980 :     double cmul = 0.5 / (cmax - cmid);
    1775                 :            : 
    1776                 :        980 :     double mul[3]   = { ymul, ymul, ymul };
    1777                 :        980 :     double black[3] = { ymin, ymin, ymin };
    1778                 :            : 
    1779                 :            : #ifdef PL_HAVE_DOVI
    1780         [ +  + ]:        980 :     if (repr->sys == PL_COLOR_SYSTEM_DOLBYVISION) {
    1781                 :            :         // The RPU matrix already includes levels normalization, but in this
    1782                 :            :         // case we also have to respect the signalled color offsets
    1783         [ +  + ]:          8 :         for (int i = 0; i < 3; i++) {
    1784                 :          6 :             mul[i] = 1.0;
    1785                 :          6 :             black[i] = repr->dovi->nonlinear_offset[i] * scale;
    1786                 :            :         }
    1787                 :            :     } else
    1788                 :            : #endif
    1789         [ +  + ]:        978 :     if (pl_color_system_is_ycbcr_like(repr->sys)) {
    1790                 :        832 :         mul[1]   = mul[2]   = cmul;
    1791                 :        832 :         black[1] = black[2] = cmid;
    1792                 :            :     }
    1793                 :            : 
    1794                 :            :     // Contrast scales the output value range (gain)
    1795                 :            :     // Brightness scales the constant output bias (black lift/boost)
    1796         [ +  + ]:       3920 :     for (int i = 0; i < 3; i++) {
    1797                 :       2940 :         mul[i]   *= params->contrast;
    1798                 :       2940 :         out.c[i] += params->brightness;
    1799                 :            :     }
    1800                 :            : 
    1801                 :            :     // Multiply in the texture multiplier and adjust `c` so that black[j] keeps
    1802                 :            :     // on mapping to RGB=0 (black to black)
    1803         [ +  + ]:       3920 :     for (int i = 0; i < 3; i++) {
    1804         [ +  + ]:      11760 :         for (int j = 0; j < 3; j++) {
    1805                 :       8820 :             out.mat.m[i][j] *= mul[j];
    1806                 :       8820 :             out.c[i] -= out.mat.m[i][j] * black[j];
    1807                 :            :         }
    1808                 :            :     }
    1809                 :            : 
    1810                 :            :     // Finally, multiply in the scaling factor required to get the color up to
    1811                 :            :     // the correct representation.
    1812                 :        980 :     pl_matrix3x3_scale(&out.mat, pl_color_repr_normalize(repr));
    1813                 :            : 
    1814                 :            :     // Update the metadata to reflect the change.
    1815                 :        980 :     repr->sys    = PL_COLOR_SYSTEM_RGB;
    1816                 :        980 :     repr->levels = PL_COLOR_LEVELS_FULL;
    1817                 :            : 
    1818                 :        980 :     return out;
    1819                 :            : }
    1820                 :            : 
    1821                 :        700 : bool pl_icc_profile_equal(const struct pl_icc_profile *p1,
    1822                 :            :                           const struct pl_icc_profile *p2)
    1823                 :            : {
    1824         [ +  - ]:        700 :     if (p1->len != p2->len)
    1825                 :            :         return false;
    1826                 :            : 
    1827                 :            :     // Ignore signatures on length-0 profiles, as a special case
    1828   [ -  +  -  - ]:       1400 :     return !p1->len || p1->signature == p2->signature;
    1829                 :            : }
    1830                 :            : 
    1831                 :          0 : void pl_icc_profile_compute_signature(struct pl_icc_profile *profile)
    1832                 :            : {
    1833         [ #  # ]:          0 :     if (!profile->len)
    1834                 :          0 :         profile->signature = 0;
    1835                 :            : 
    1836                 :            :     // In theory, we could get this value from the profile header itself if
    1837                 :            :     // lcms is available, but I'm not sure if it's even worth the trouble. Just
    1838                 :            :     // hard-code this to a pl_mem_hash(), which is decently fast anyway.
    1839                 :          0 :     profile->signature = pl_mem_hash(profile->data, profile->len);
    1840                 :          0 : }

Generated by: LCOV version 1.16