LCOV - code coverage report
Current view: top level - src - gamut_mapping.c (source / functions) Hit Total Coverage
Test: Code coverage Lines: 327 411 79.6 %
Date: 2025-03-29 09:04:10 Functions: 29 33 87.9 %
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 120 184 65.2 %

           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 "pl_thread.h"
      23                 :            : 
      24                 :            : #include <libplacebo/gamut_mapping.h>
      25                 :            : 
      26                 :            : #define fclampf(x, lo, hi) fminf(fmaxf(x, lo), hi)
      27                 :        560 : static void fix_constants(struct pl_gamut_map_constants *c)
      28                 :            : {
      29                 :        560 :     c->perceptual_deadzone = fclampf(c->perceptual_deadzone, 0.0f, 1.0f);
      30                 :        560 :     c->perceptual_strength = fclampf(c->perceptual_strength, 0.0f, 1.0f);
      31                 :        560 :     c->colorimetric_gamma  = fclampf(c->colorimetric_gamma, 0.0f, 10.0f);
      32                 :        560 :     c->softclip_knee       = fclampf(c->softclip_knee, 0.0f, 1.0f);
      33                 :        560 :     c->softclip_desat      = fclampf(c->softclip_desat, 0.0f, 1.0f);
      34                 :        560 : }
      35                 :            : 
      36                 :            : static inline bool constants_equal(const struct pl_gamut_map_constants *a,
      37                 :            :                                    const struct pl_gamut_map_constants *b)
      38                 :            : {
      39                 :            :     pl_static_assert(sizeof(*a) % sizeof(float) == 0);
      40                 :          0 :     return !memcmp(a, b, sizeof(*a));
      41                 :            : }
      42                 :            : 
      43                 :          0 : bool pl_gamut_map_params_equal(const struct pl_gamut_map_params *a,
      44                 :            :                                const struct pl_gamut_map_params *b)
      45                 :            : {
      46                 :          0 :     return a->function      == b->function      &&
      47         [ #  # ]:          0 :            a->min_luma      == b->min_luma      &&
      48         [ #  # ]:          0 :            a->max_luma      == b->max_luma      &&
      49         [ #  # ]:          0 :            a->lut_size_I    == b->lut_size_I    &&
      50         [ #  # ]:          0 :            a->lut_size_C    == b->lut_size_C    &&
      51                 :          0 :            a->lut_size_h    == b->lut_size_h    &&
      52         [ #  # ]:          0 :            a->lut_stride    == b->lut_stride    &&
      53   [ #  #  #  # ]:          0 :            constants_equal(&a->constants, &b->constants) &&
      54   [ #  #  #  # ]:          0 :            pl_raw_primaries_equal(&a->input_gamut,  &b->input_gamut) &&
      55                 :          0 :            pl_raw_primaries_equal(&a->output_gamut, &b->output_gamut);
      56                 :            : }
      57                 :            : 
      58                 :            : #define FUN(params) (params->function ? *params->function : pl_gamut_map_clip)
      59                 :            : 
      60                 :            : static void noop(float *lut, const struct pl_gamut_map_params *params);
      61                 :         58 : bool pl_gamut_map_params_noop(const struct pl_gamut_map_params *params)
      62                 :            : {
      63   [ +  -  -  + ]:         58 :     if (FUN(params).map == &noop)
      64                 :            :         return true;
      65                 :            : 
      66                 :         58 :     struct pl_raw_primaries src = params->input_gamut, dst = params->output_gamut;
      67         [ -  + ]:         58 :     if (!pl_primaries_compatible(&dst, &src))
      68                 :            :         return true;
      69                 :            : 
      70                 :         58 :     bool need_map = !pl_primaries_superset(&dst, &src);
      71                 :            :     need_map |= !pl_cie_xy_equal(&src.white, &dst.white);
      72   [ +  -  +  - ]:         58 :     if (FUN(params).bidirectional)
      73                 :         58 :         need_map |= !pl_raw_primaries_equal(&dst, &src);
      74                 :            : 
      75                 :         58 :     return !need_map;
      76                 :            : }
      77                 :            : 
      78                 :            : // For some minimal type safety, and code cleanliness
      79                 :            : struct RGB {
      80                 :            :     float R, G, B;
      81                 :            : };
      82                 :            : 
      83                 :            : struct IPT {
      84                 :            :     float I, P, T;
      85                 :            : };
      86                 :            : 
      87                 :            : struct ICh {
      88                 :            :     float I, C, h;
      89                 :            : };
      90                 :            : 
      91                 :    6566140 : static inline struct ICh ipt2ich(struct IPT c)
      92                 :            : {
      93                 :    6566140 :     return (struct ICh) {
      94                 :    6566140 :         .I = c.I,
      95                 :    6566140 :         .C = sqrtf(c.P * c.P + c.T * c.T),
      96                 :    6566140 :         .h = atan2f(c.T, c.P),
      97                 :            :     };
      98                 :            : }
      99                 :            : 
     100                 :   11825628 : static inline struct IPT ich2ipt(struct ICh c)
     101                 :            : {
     102                 :   11825628 :     return (struct IPT) {
     103                 :   11825628 :         .I = c.I,
     104                 :   11825628 :         .P = c.C * cosf(c.h),
     105                 :   11825628 :         .T = c.C * sinf(c.h),
     106                 :            :     };
     107                 :            : }
     108                 :            : 
     109                 :            : enum { PQ_LUT_SIZE = 1024 };
     110                 :            : static const float pq_eotf_lut[1024+1] = {
     111                 :            :     0.0000000e+00f, 4.0422718e-09f, 1.3111372e-08f, 2.6236826e-08f, 4.3151495e-08f, 6.3746885e-08f, 8.7982383e-08f, 1.1585362e-07f,
     112                 :            :     1.4737819e-07f, 1.8258818e-07f, 2.2152586e-07f, 2.6424098e-07f, 3.1078907e-07f, 3.6123021e-07f, 4.1562821e-07f, 4.7405001e-07f,
     113                 :            :     5.3656521e-07f, 6.0324583e-07f, 6.7416568e-07f, 7.4940095e-07f, 8.2902897e-07f, 9.1312924e-07f, 1.0017822e-06f, 1.0950702e-06f,
     114                 :            :     1.1930764e-06f, 1.2958861e-06f, 1.4035847e-06f, 1.5162600e-06f, 1.6340000e-06f, 1.7568948e-06f, 1.8850346e-06f, 2.0185119e-06f,
     115                 :            :     2.1574192e-06f, 2.3018509e-06f, 2.4519029e-06f, 2.6076704e-06f, 2.7692516e-06f, 2.9367449e-06f, 3.1102509e-06f, 3.2898690e-06f,
     116                 :            :     3.4757019e-06f, 3.6678526e-06f, 3.8664261e-06f, 4.0715262e-06f, 4.2832601e-06f, 4.5017354e-06f, 4.7270617e-06f, 4.9593473e-06f,
     117                 :            :     5.1987040e-06f, 5.4452441e-06f, 5.6990819e-06f, 5.9603301e-06f, 6.2291055e-06f, 6.5055251e-06f, 6.7897080e-06f, 7.0817717e-06f,
     118                 :            :     7.3818379e-06f, 7.6900283e-06f, 8.0064675e-06f, 8.3312774e-06f, 8.6645849e-06f, 9.0065169e-06f, 9.3572031e-06f, 9.7167704e-06f,
     119                 :            :     1.0085351e-05f, 1.0463077e-05f, 1.0850082e-05f, 1.1246501e-05f, 1.1652473e-05f, 1.2068130e-05f, 1.2493614e-05f, 1.2929066e-05f,
     120                 :            :     1.3374626e-05f, 1.3830439e-05f, 1.4296648e-05f, 1.4773401e-05f, 1.5260848e-05f, 1.5759132e-05f, 1.6268405e-05f, 1.6788821e-05f,
     121                 :            :     1.7320534e-05f, 1.7863697e-05f, 1.8418467e-05f, 1.8985004e-05f, 1.9563470e-05f, 2.0154019e-05f, 2.0756818e-05f, 2.1372031e-05f,
     122                 :            :     2.1999824e-05f, 2.2640365e-05f, 2.3293824e-05f, 2.3960372e-05f, 2.4640186e-05f, 2.5333431e-05f, 2.6040288e-05f, 2.6760935e-05f,
     123                 :            :     2.7495552e-05f, 2.8244319e-05f, 2.9007421e-05f, 2.9785041e-05f, 3.0577373e-05f, 3.1384594e-05f, 3.2206899e-05f, 3.3044481e-05f,
     124                 :            :     3.3897533e-05f, 3.4766253e-05f, 3.5650838e-05f, 3.6551487e-05f, 3.7468409e-05f, 3.8401794e-05f, 3.9351855e-05f, 4.0318799e-05f,
     125                 :            :     4.1302836e-05f, 4.2304177e-05f, 4.3323036e-05f, 4.4359629e-05f, 4.5414181e-05f, 4.6486897e-05f, 4.7578006e-05f, 4.8687732e-05f,
     126                 :            :     4.9816302e-05f, 5.0963944e-05f, 5.2130889e-05f, 5.3317369e-05f, 5.4523628e-05f, 5.5749886e-05f, 5.6996391e-05f, 5.8263384e-05f,
     127                 :            :     5.9551111e-05f, 6.0859816e-05f, 6.2189750e-05f, 6.3541162e-05f, 6.4914307e-05f, 6.6309439e-05f, 6.7726819e-05f, 6.9166705e-05f,
     128                 :            :     7.0629384e-05f, 7.2115077e-05f, 7.3624074e-05f, 7.5156646e-05f, 7.6713065e-05f, 7.8293608e-05f, 7.9898553e-05f, 8.1528181e-05f,
     129                 :            :     8.3182776e-05f, 8.4862623e-05f, 8.6568012e-05f, 8.8299235e-05f, 9.0056585e-05f, 9.1840360e-05f, 9.3650860e-05f, 9.5488388e-05f,
     130                 :            :     9.7353277e-05f, 9.9245779e-05f, 1.0116623e-04f, 1.0311496e-04f, 1.0509226e-04f, 1.0709847e-04f, 1.0913391e-04f, 1.1119889e-04f,
     131                 :            :     1.1329376e-04f, 1.1541885e-04f, 1.1757448e-04f, 1.1976100e-04f, 1.2197875e-04f, 1.2422807e-04f, 1.2650931e-04f, 1.2882282e-04f,
     132                 :            :     1.3116900e-04f, 1.3354812e-04f, 1.3596059e-04f, 1.3840676e-04f, 1.4088701e-04f, 1.4340170e-04f, 1.4595121e-04f, 1.4853593e-04f,
     133                 :            :     1.5115622e-04f, 1.5381247e-04f, 1.5650507e-04f, 1.5923442e-04f, 1.6200090e-04f, 1.6480492e-04f, 1.6764688e-04f, 1.7052718e-04f,
     134                 :            :     1.7344629e-04f, 1.7640451e-04f, 1.7940233e-04f, 1.8244015e-04f, 1.8551840e-04f, 1.8863752e-04f, 1.9179792e-04f, 1.9500006e-04f,
     135                 :            :     1.9824437e-04f, 2.0153130e-04f, 2.0486129e-04f, 2.0823479e-04f, 2.1165227e-04f, 2.1511419e-04f, 2.1862101e-04f, 2.2217319e-04f,
     136                 :            :     2.2577128e-04f, 2.2941563e-04f, 2.3310679e-04f, 2.3684523e-04f, 2.4063146e-04f, 2.4446597e-04f, 2.4834925e-04f, 2.5228182e-04f,
     137                 :            :     2.5626417e-04f, 2.6029683e-04f, 2.6438031e-04f, 2.6851514e-04f, 2.7270184e-04f, 2.7694094e-04f, 2.8123299e-04f, 2.8557852e-04f,
     138                 :            :     2.8997815e-04f, 2.9443230e-04f, 2.9894159e-04f, 3.0350657e-04f, 3.0812783e-04f, 3.1280593e-04f, 3.1754144e-04f, 3.2233495e-04f,
     139                 :            :     3.2718705e-04f, 3.3209833e-04f, 3.3706938e-04f, 3.4210082e-04f, 3.4719324e-04f, 3.5234727e-04f, 3.5756351e-04f, 3.6284261e-04f,
     140                 :            :     3.6818526e-04f, 3.7359195e-04f, 3.7906340e-04f, 3.8460024e-04f, 3.9020315e-04f, 3.9587277e-04f, 4.0160977e-04f, 4.0741483e-04f,
     141                 :            :     4.1328861e-04f, 4.1923181e-04f, 4.2524511e-04f, 4.3132921e-04f, 4.3748480e-04f, 4.4371260e-04f, 4.5001332e-04f, 4.5638768e-04f,
     142                 :            :     4.6283650e-04f, 4.6936032e-04f, 4.7595999e-04f, 4.8263624e-04f, 4.8938982e-04f, 4.9622151e-04f, 5.0313205e-04f, 5.1012223e-04f,
     143                 :            :     5.1719283e-04f, 5.2434463e-04f, 5.3157843e-04f, 5.3889502e-04f, 5.4629521e-04f, 5.5377982e-04f, 5.6134968e-04f, 5.6900560e-04f,
     144                 :            :     5.7674843e-04f, 5.8457900e-04f, 5.9249818e-04f, 6.0050682e-04f, 6.0860578e-04f, 6.1679595e-04f, 6.2507819e-04f, 6.3345341e-04f,
     145                 :            :     6.4192275e-04f, 6.5048661e-04f, 6.5914616e-04f, 6.6790231e-04f, 6.7675600e-04f, 6.8570816e-04f, 6.9475975e-04f, 7.0391171e-04f,
     146                 :            :     7.1316500e-04f, 7.2252060e-04f, 7.3197948e-04f, 7.4154264e-04f, 7.5121107e-04f, 7.6098577e-04f, 7.7086777e-04f, 7.8085807e-04f,
     147                 :            :     7.9095772e-04f, 8.0116775e-04f, 8.1148922e-04f, 8.2192318e-04f, 8.3247071e-04f, 8.4313287e-04f, 8.5391076e-04f, 8.6480548e-04f,
     148                 :            :     8.7581812e-04f, 8.8694982e-04f, 8.9820168e-04f, 9.0957485e-04f, 9.2107048e-04f, 9.3268971e-04f, 9.4443372e-04f, 9.5630368e-04f,
     149                 :            :     9.6830115e-04f, 9.8042658e-04f, 9.9268155e-04f, 1.0050673e-03f, 1.0175850e-03f, 1.0302359e-03f, 1.0430213e-03f, 1.0559425e-03f,
     150                 :            :     1.0690006e-03f, 1.0821970e-03f, 1.0955331e-03f, 1.1090100e-03f, 1.1226290e-03f, 1.1363917e-03f, 1.1502992e-03f, 1.1643529e-03f,
     151                 :            :     1.1785542e-03f, 1.1929044e-03f, 1.2074050e-03f, 1.2220573e-03f, 1.2368628e-03f, 1.2518229e-03f, 1.2669390e-03f, 1.2822125e-03f,
     152                 :            :     1.2976449e-03f, 1.3132377e-03f, 1.3289925e-03f, 1.3449105e-03f, 1.3609935e-03f, 1.3772429e-03f, 1.3936602e-03f, 1.4102470e-03f,
     153                 :            :     1.4270054e-03f, 1.4439360e-03f, 1.4610407e-03f, 1.4783214e-03f, 1.4957794e-03f, 1.5134166e-03f, 1.5312345e-03f, 1.5492348e-03f,
     154                 :            :     1.5674192e-03f, 1.5857894e-03f, 1.6043471e-03f, 1.6230939e-03f, 1.6420317e-03f, 1.6611622e-03f, 1.6804871e-03f, 1.7000083e-03f,
     155                 :            :     1.7197275e-03f, 1.7396465e-03f, 1.7597672e-03f, 1.7800914e-03f, 1.8006210e-03f, 1.8213578e-03f, 1.8423038e-03f, 1.8634608e-03f,
     156                 :            :     1.8848308e-03f, 1.9064157e-03f, 1.9282175e-03f, 1.9502381e-03f, 1.9724796e-03f, 1.9949439e-03f, 2.0176331e-03f, 2.0405492e-03f,
     157                 :            :     2.0636950e-03f, 2.0870711e-03f, 2.1106805e-03f, 2.1345250e-03f, 2.1586071e-03f, 2.1829286e-03f, 2.2074919e-03f, 2.2322992e-03f,
     158                 :            :     2.2573525e-03f, 2.2826542e-03f, 2.3082066e-03f, 2.3340118e-03f, 2.3600721e-03f, 2.3863900e-03f, 2.4129676e-03f, 2.4398074e-03f,
     159                 :            :     2.4669117e-03f, 2.4942828e-03f, 2.5219233e-03f, 2.5498355e-03f, 2.5780219e-03f, 2.6064849e-03f, 2.6352271e-03f, 2.6642509e-03f,
     160                 :            :     2.6935589e-03f, 2.7231536e-03f, 2.7530377e-03f, 2.7832137e-03f, 2.8136843e-03f, 2.8444520e-03f, 2.8755196e-03f, 2.9068898e-03f,
     161                 :            :     2.9385662e-03f, 2.9705496e-03f, 3.0028439e-03f, 3.0354517e-03f, 3.0683758e-03f, 3.1016192e-03f, 3.1351846e-03f, 3.1690750e-03f,
     162                 :            :     3.2032932e-03f, 3.2378422e-03f, 3.2727250e-03f, 3.3079445e-03f, 3.3435038e-03f, 3.3794058e-03f, 3.4156537e-03f, 3.4522505e-03f,
     163                 :            :     3.4891993e-03f, 3.5265034e-03f, 3.5641658e-03f, 3.6021897e-03f, 3.6405785e-03f, 3.6793353e-03f, 3.7184634e-03f, 3.7579661e-03f,
     164                 :            :     3.7978468e-03f, 3.8381088e-03f, 3.8787555e-03f, 3.9197904e-03f, 3.9612169e-03f, 4.0030385e-03f, 4.0452587e-03f, 4.0878810e-03f,
     165                 :            :     4.1309104e-03f, 4.1743478e-03f, 4.2181981e-03f, 4.2624651e-03f, 4.3071525e-03f, 4.3522639e-03f, 4.3978031e-03f, 4.4437739e-03f,
     166                 :            :     4.4901803e-03f, 4.5370259e-03f, 4.5843148e-03f, 4.6320508e-03f, 4.6802379e-03f, 4.7288801e-03f, 4.7779815e-03f, 4.8275461e-03f,
     167                 :            :     4.8775780e-03f, 4.9280813e-03f, 4.9790603e-03f, 5.0305191e-03f, 5.0824620e-03f, 5.1348933e-03f, 5.1878172e-03f, 5.2412382e-03f,
     168                 :            :     5.2951607e-03f, 5.3495890e-03f, 5.4045276e-03f, 5.4599811e-03f, 5.5159540e-03f, 5.5724510e-03f, 5.6294765e-03f, 5.6870353e-03f,
     169                 :            :     5.7451339e-03f, 5.8037735e-03f, 5.8629606e-03f, 5.9227001e-03f, 5.9829968e-03f, 6.0438557e-03f, 6.1052818e-03f, 6.1672799e-03f,
     170                 :            :     6.2298552e-03f, 6.2930128e-03f, 6.3567578e-03f, 6.4210953e-03f, 6.4860306e-03f, 6.5515690e-03f, 6.6177157e-03f, 6.6844762e-03f,
     171                 :            :     6.7518558e-03f, 6.8198599e-03f, 6.8884942e-03f, 6.9577641e-03f, 7.0276752e-03f, 7.0982332e-03f, 7.1694438e-03f, 7.2413127e-03f,
     172                 :            :     7.3138457e-03f, 7.3870486e-03f, 7.4609273e-03f, 7.5354878e-03f, 7.6107361e-03f, 7.6866782e-03f, 7.7633203e-03f, 7.8406684e-03f,
     173                 :            :     7.9187312e-03f, 7.9975101e-03f, 8.0770139e-03f, 8.1572490e-03f, 8.2382216e-03f, 8.3199385e-03f, 8.4024059e-03f, 8.4856307e-03f,
     174                 :            :     8.5696193e-03f, 8.6543786e-03f, 8.7399153e-03f, 8.8262362e-03f, 8.9133482e-03f, 9.0012582e-03f, 9.0899733e-03f, 9.1795005e-03f,
     175                 :            :     9.2698470e-03f, 9.3610199e-03f, 9.4530265e-03f, 9.5458741e-03f, 9.6395701e-03f, 9.7341219e-03f, 9.8295370e-03f, 9.9258231e-03f,
     176                 :            :     1.0022988e-02f, 1.0121039e-02f, 1.0219984e-02f, 1.0319830e-02f, 1.0420587e-02f, 1.0522261e-02f, 1.0624862e-02f, 1.0728396e-02f,
     177                 :            :     1.0832872e-02f, 1.0938299e-02f, 1.1044684e-02f, 1.1152036e-02f, 1.1260365e-02f, 1.1369677e-02f, 1.1479982e-02f, 1.1591288e-02f,
     178                 :            :     1.1703605e-02f, 1.1816941e-02f, 1.1931305e-02f, 1.2046706e-02f, 1.2163153e-02f, 1.2280656e-02f, 1.2399223e-02f, 1.2518864e-02f,
     179                 :            :     1.2639596e-02f, 1.2761413e-02f, 1.2884333e-02f, 1.3008365e-02f, 1.3133519e-02f, 1.3259804e-02f, 1.3387231e-02f, 1.3515809e-02f,
     180                 :            :     1.3645549e-02f, 1.3776461e-02f, 1.3908555e-02f, 1.4041841e-02f, 1.4176331e-02f, 1.4312034e-02f, 1.4448961e-02f, 1.4587123e-02f,
     181                 :            :     1.4726530e-02f, 1.4867194e-02f, 1.5009126e-02f, 1.5152336e-02f, 1.5296837e-02f, 1.5442638e-02f, 1.5589753e-02f, 1.5738191e-02f,
     182                 :            :     1.5887965e-02f, 1.6039087e-02f, 1.6191567e-02f, 1.6345419e-02f, 1.6500655e-02f, 1.6657285e-02f, 1.6815323e-02f, 1.6974781e-02f,
     183                 :            :     1.7135672e-02f, 1.7298007e-02f, 1.7461800e-02f, 1.7627063e-02f, 1.7793810e-02f, 1.7962053e-02f, 1.8131805e-02f, 1.8303080e-02f,
     184                 :            :     1.8475891e-02f, 1.8650252e-02f, 1.8826176e-02f, 1.9003676e-02f, 1.9182767e-02f, 1.9363463e-02f, 1.9545777e-02f, 1.9729724e-02f,
     185                 :            :     1.9915319e-02f, 2.0102575e-02f, 2.0291507e-02f, 2.0482131e-02f, 2.0674460e-02f, 2.0868510e-02f, 2.1064296e-02f, 2.1261833e-02f,
     186                 :            :     2.1461136e-02f, 2.1662222e-02f, 2.1865105e-02f, 2.2069802e-02f, 2.2276328e-02f, 2.2484700e-02f, 2.2694934e-02f, 2.2907045e-02f,
     187                 :            :     2.3121064e-02f, 2.3336982e-02f, 2.3554827e-02f, 2.3774618e-02f, 2.3996370e-02f, 2.4220102e-02f, 2.4445831e-02f, 2.4673574e-02f,
     188                 :            :     2.4903349e-02f, 2.5135174e-02f, 2.5369067e-02f, 2.5605046e-02f, 2.5843129e-02f, 2.6083336e-02f, 2.6325684e-02f, 2.6570192e-02f,
     189                 :            :     2.6816880e-02f, 2.7065767e-02f, 2.7316872e-02f, 2.7570215e-02f, 2.7825815e-02f, 2.8083692e-02f, 2.8343867e-02f, 2.8606359e-02f,
     190                 :            :     2.8871189e-02f, 2.9138378e-02f, 2.9407946e-02f, 2.9679914e-02f, 2.9954304e-02f, 3.0231137e-02f, 3.0510434e-02f, 3.0792217e-02f,
     191                 :            :     3.1076508e-02f, 3.1363330e-02f, 3.1652704e-02f, 3.1944653e-02f, 3.2239199e-02f, 3.2536367e-02f, 3.2836178e-02f, 3.3138657e-02f,
     192                 :            :     3.3443826e-02f, 3.3751710e-02f, 3.4062333e-02f, 3.4375718e-02f, 3.4691890e-02f, 3.5010874e-02f, 3.5332694e-02f, 3.5657377e-02f,
     193                 :            :     3.5984946e-02f, 3.6315428e-02f, 3.6648848e-02f, 3.6985233e-02f, 3.7324608e-02f, 3.7667000e-02f, 3.8012436e-02f, 3.8360942e-02f,
     194                 :            :     3.8712547e-02f, 3.9067276e-02f, 3.9425159e-02f, 3.9786223e-02f, 4.0150496e-02f, 4.0518006e-02f, 4.0888783e-02f, 4.1262855e-02f,
     195                 :            :     4.1640274e-02f, 4.2021025e-02f, 4.2405159e-02f, 4.2792707e-02f, 4.3183699e-02f, 4.3578166e-02f, 4.3976138e-02f, 4.4377647e-02f,
     196                 :            :     4.4782724e-02f, 4.5191401e-02f, 4.5603709e-02f, 4.6019681e-02f, 4.6439350e-02f, 4.6862749e-02f, 4.7289910e-02f, 4.7720867e-02f,
     197                 :            :     4.8155654e-02f, 4.8594305e-02f, 4.9036854e-02f, 4.9483336e-02f, 4.9933787e-02f, 5.0388240e-02f, 5.0846733e-02f, 5.1309301e-02f,
     198                 :            :     5.1775981e-02f, 5.2246808e-02f, 5.2721821e-02f, 5.3201056e-02f, 5.3684551e-02f, 5.4172344e-02f, 5.4664473e-02f, 5.5160978e-02f,
     199                 :            :     5.5661897e-02f, 5.6167269e-02f, 5.6677135e-02f, 5.7191535e-02f, 5.7710508e-02f, 5.8234097e-02f, 5.8762342e-02f, 5.9295285e-02f,
     200                 :            :     5.9832968e-02f, 6.0375433e-02f, 6.0922723e-02f, 6.1474882e-02f, 6.2031952e-02f, 6.2593979e-02f, 6.3161006e-02f, 6.3733078e-02f,
     201                 :            :     6.4310241e-02f, 6.4892540e-02f, 6.5480021e-02f, 6.6072730e-02f, 6.6670715e-02f, 6.7274023e-02f, 6.7882702e-02f, 6.8496800e-02f,
     202                 :            :     6.9116365e-02f, 6.9741447e-02f, 7.0372096e-02f, 7.1008361e-02f, 7.1650293e-02f, 7.2297942e-02f, 7.2951361e-02f, 7.3610602e-02f,
     203                 :            :     7.4275756e-02f, 7.4946797e-02f, 7.5623818e-02f, 7.6306873e-02f, 7.6996016e-02f, 7.7691302e-02f, 7.8392787e-02f, 7.9100526e-02f,
     204                 :            :     7.9814576e-02f, 8.0534993e-02f, 8.1261837e-02f, 8.1995163e-02f, 8.2735032e-02f, 8.3481501e-02f, 8.4234632e-02f, 8.4994483e-02f,
     205                 :            :     8.5761116e-02f, 8.6534592e-02f, 8.7314974e-02f, 8.8102323e-02f, 8.8896702e-02f, 8.9698176e-02f, 9.0506809e-02f, 9.1322665e-02f,
     206                 :            :     9.2145810e-02f, 9.2976310e-02f, 9.3814232e-02f, 9.4659643e-02f, 9.5512612e-02f, 9.6373206e-02f, 9.7241496e-02f, 9.8117550e-02f,
     207                 :            :     9.9001441e-02f, 9.9893238e-02f, 1.0079301e-01f, 1.0170084e-01f, 1.0261679e-01f, 1.0354094e-01f, 1.0447337e-01f, 1.0541414e-01f,
     208                 :            :     1.0636334e-01f, 1.0732104e-01f, 1.0828731e-01f, 1.0926225e-01f, 1.1024592e-01f, 1.1123841e-01f, 1.1223979e-01f, 1.1325016e-01f,
     209                 :            :     1.1426958e-01f, 1.1529814e-01f, 1.1633594e-01f, 1.1738304e-01f, 1.1843954e-01f, 1.1950552e-01f, 1.2058107e-01f, 1.2166627e-01f,
     210                 :            :     1.2276122e-01f, 1.2386601e-01f, 1.2498072e-01f, 1.2610544e-01f, 1.2724027e-01f, 1.2838531e-01f, 1.2954063e-01f, 1.3070635e-01f,
     211                 :            :     1.3188262e-01f, 1.3306940e-01f, 1.3426686e-01f, 1.3547509e-01f, 1.3669420e-01f, 1.3792428e-01f, 1.3916544e-01f, 1.4041778e-01f,
     212                 :            :     1.4168140e-01f, 1.4295640e-01f, 1.4424289e-01f, 1.4554098e-01f, 1.4685078e-01f, 1.4817238e-01f, 1.4950591e-01f, 1.5085147e-01f,
     213                 :            :     1.5220916e-01f, 1.5357912e-01f, 1.5496144e-01f, 1.5635624e-01f, 1.5776364e-01f, 1.5918375e-01f, 1.6061670e-01f, 1.6206260e-01f,
     214                 :            :     1.6352156e-01f, 1.6499372e-01f, 1.6647920e-01f, 1.6797811e-01f, 1.6949059e-01f, 1.7101676e-01f, 1.7255674e-01f, 1.7411067e-01f,
     215                 :            :     1.7567867e-01f, 1.7726087e-01f, 1.7885742e-01f, 1.8046844e-01f, 1.8209406e-01f, 1.8373443e-01f, 1.8538967e-01f, 1.8705994e-01f,
     216                 :            :     1.8874536e-01f, 1.9044608e-01f, 1.9216225e-01f, 1.9389401e-01f, 1.9564150e-01f, 1.9740486e-01f, 1.9918426e-01f, 2.0097984e-01f,
     217                 :            :     2.0279175e-01f, 2.0462014e-01f, 2.0646517e-01f, 2.0832699e-01f, 2.1020577e-01f, 2.1210165e-01f, 2.1401481e-01f, 2.1594540e-01f,
     218                 :            :     2.1789359e-01f, 2.1985954e-01f, 2.2184342e-01f, 2.2384540e-01f, 2.2586565e-01f, 2.2790434e-01f, 2.2996165e-01f, 2.3203774e-01f,
     219                 :            :     2.3413293e-01f, 2.3624714e-01f, 2.3838068e-01f, 2.4053372e-01f, 2.4270646e-01f, 2.4489908e-01f, 2.4711177e-01f, 2.4934471e-01f,
     220                 :            :     2.5159811e-01f, 2.5387214e-01f, 2.5616702e-01f, 2.5848293e-01f, 2.6082007e-01f, 2.6317866e-01f, 2.6555888e-01f, 2.6796095e-01f,
     221                 :            :     2.7038507e-01f, 2.7283145e-01f, 2.7530031e-01f, 2.7779186e-01f, 2.8030631e-01f, 2.8284388e-01f, 2.8540479e-01f, 2.8798927e-01f,
     222                 :            :     2.9059754e-01f, 2.9322983e-01f, 2.9588635e-01f, 2.9856736e-01f, 3.0127308e-01f, 3.0400374e-01f, 3.0675959e-01f, 3.0954086e-01f,
     223                 :            :     3.1234780e-01f, 3.1518066e-01f, 3.1803969e-01f, 3.2092512e-01f, 3.2383723e-01f, 3.2677625e-01f, 3.2974246e-01f, 3.3273611e-01f,
     224                 :            :     3.3575747e-01f, 3.3880680e-01f, 3.4188437e-01f, 3.4499045e-01f, 3.4812533e-01f, 3.5128926e-01f, 3.5448255e-01f, 3.5770546e-01f,
     225                 :            :     3.6095828e-01f, 3.6424131e-01f, 3.6755483e-01f, 3.7089914e-01f, 3.7427454e-01f, 3.7768132e-01f, 3.8111979e-01f, 3.8459027e-01f,
     226                 :            :     3.8809304e-01f, 3.9162844e-01f, 3.9519678e-01f, 3.9879837e-01f, 4.0243354e-01f, 4.0610261e-01f, 4.0980592e-01f, 4.1354380e-01f,
     227                 :            :     4.1731681e-01f, 4.2112483e-01f, 4.2496844e-01f, 4.2884798e-01f, 4.3276381e-01f, 4.3671627e-01f, 4.4070572e-01f, 4.4473253e-01f,
     228                 :            :     4.4879706e-01f, 4.5289968e-01f, 4.5704076e-01f, 4.6122068e-01f, 4.6543981e-01f, 4.6969854e-01f, 4.7399727e-01f, 4.7833637e-01f,
     229                 :            :     4.8271625e-01f, 4.8713731e-01f, 4.9159995e-01f, 4.9610458e-01f, 5.0065162e-01f, 5.0524147e-01f, 5.0987457e-01f, 5.1455133e-01f,
     230                 :            :     5.1927219e-01f, 5.2403759e-01f, 5.2884795e-01f, 5.3370373e-01f, 5.3860537e-01f, 5.4355333e-01f, 5.4854807e-01f, 5.5359004e-01f,
     231                 :            :     5.5867972e-01f, 5.6381757e-01f, 5.6900408e-01f, 5.7423972e-01f, 5.7952499e-01f, 5.8486037e-01f, 5.9024637e-01f, 5.9568349e-01f,
     232                 :            :     6.0117223e-01f, 6.0671311e-01f, 6.1230664e-01f, 6.1795336e-01f, 6.2365379e-01f, 6.2940847e-01f, 6.3521793e-01f, 6.4108273e-01f,
     233                 :            :     6.4700342e-01f, 6.5298056e-01f, 6.5901471e-01f, 6.6510643e-01f, 6.7125632e-01f, 6.7746495e-01f, 6.8373290e-01f, 6.9006078e-01f,
     234                 :            :     6.9644918e-01f, 7.0289872e-01f, 7.0941001e-01f, 7.1598366e-01f, 7.2262031e-01f, 7.2932059e-01f, 7.3608513e-01f, 7.4291460e-01f,
     235                 :            :     7.4981006e-01f, 7.5677134e-01f, 7.6379952e-01f, 7.7089527e-01f, 7.7805929e-01f, 7.8529226e-01f, 7.9259489e-01f, 7.9996786e-01f,
     236                 :            :     8.0741191e-01f, 8.1492774e-01f, 8.2251609e-01f, 8.3017769e-01f, 8.3791329e-01f, 8.4572364e-01f, 8.5360950e-01f, 8.6157163e-01f,
     237                 :            :     8.6961082e-01f, 8.7772786e-01f, 8.8592352e-01f, 8.9419862e-01f, 9.0255397e-01f, 9.1099038e-01f, 9.1950869e-01f, 9.2810973e-01f,
     238                 :            :     9.3679435e-01f, 9.4556340e-01f, 9.5441776e-01f, 9.6335829e-01f, 9.7238588e-01f, 9.8150143e-01f, 9.9070583e-01f, 1.0000000e+00f,
     239                 :            :     1.0f, // extra padding to avoid out of bounds access
     240                 :            : };
     241                 :            : 
     242                 :   54556787 : static inline float pq_eotf(float x)
     243                 :            : {
     244                 :   54556787 :     float idxf  = fminf(fmaxf(x, 0.0f), 1.0f) * (PQ_LUT_SIZE - 1);
     245                 :   54556787 :     int ipart   = floorf(idxf);
     246                 :   54556787 :     float fpart = idxf - ipart;
     247                 :   54556787 :     return PL_MIX(pq_eotf_lut[ipart], pq_eotf_lut[ipart + 1], fpart);
     248                 :            : }
     249                 :            : 
     250                 :   39396732 : static inline float pq_oetf(float x)
     251                 :            : {
     252                 :   39396732 :     x = powf(fmaxf(x, 0.0f), PQ_M1);
     253                 :   39396732 :     x = (PQ_C1 + PQ_C2 * x) / (1.0f + PQ_C3 * x);
     254                 :   39396732 :     return powf(x, PQ_M2);
     255                 :            : }
     256                 :            : 
     257                 :            : // Helper struct containing pre-computed cached values describing a gamut
     258                 :            : struct gamut {
     259                 :            :     pl_matrix3x3 lms2rgb;
     260                 :            :     pl_matrix3x3 rgb2lms;
     261                 :            :     float min_luma, max_luma;   // pq
     262                 :            :     float min_rgb,  max_rgb;    // 10k normalized
     263                 :            :     struct ICh *peak_cache;     // 1-item cache for computed peaks (per hue)
     264                 :            : };
     265                 :            : 
     266                 :            : struct cache {
     267                 :            :     struct ICh src_cache;
     268                 :            :     struct ICh dst_cache;
     269                 :            : };
     270                 :            : 
     271         [ +  - ]:        560 : static void get_gamuts(struct gamut *dst, struct gamut *src, struct cache *cache,
     272                 :            :                        const struct pl_gamut_map_params *params)
     273                 :            : {
     274                 :            :     const float epsilon = 1e-6;
     275                 :            :     memset(cache, 0, sizeof(*cache));
     276                 :            :     struct gamut base = {
     277                 :        560 :         .min_luma = params->min_luma,
     278                 :        560 :         .max_luma = params->max_luma,
     279                 :        560 :         .min_rgb  = pq_eotf(params->min_luma) - epsilon,
     280                 :        560 :         .max_rgb  = pq_eotf(params->max_luma) + epsilon,
     281                 :            :     };
     282                 :            : 
     283         [ +  - ]:        560 :     if (dst) {
     284                 :        560 :         *dst = base;
     285                 :        560 :         dst->lms2rgb = dst->rgb2lms = pl_ipt_rgb2lms(&params->output_gamut);
     286                 :        560 :         dst->peak_cache = &cache->dst_cache;
     287                 :        560 :         pl_matrix3x3_invert(&dst->lms2rgb);
     288                 :            :     }
     289                 :            : 
     290         [ +  + ]:        560 :     if (src) {
     291                 :        552 :         *src = base;
     292                 :        552 :         src->lms2rgb = src->rgb2lms = pl_ipt_rgb2lms(&params->input_gamut);
     293                 :        552 :         src->peak_cache = &cache->src_cache;
     294                 :        552 :         pl_matrix3x3_invert(&src->lms2rgb);
     295                 :            :     }
     296                 :        560 : }
     297                 :            : 
     298                 :   13132244 : static inline struct IPT rgb2ipt(struct RGB c, struct gamut gamut)
     299                 :            : {
     300                 :   13132244 :     const float L = gamut.rgb2lms.m[0][0] * c.R +
     301                 :   13132244 :                     gamut.rgb2lms.m[0][1] * c.G +
     302                 :   13132244 :                     gamut.rgb2lms.m[0][2] * c.B;
     303                 :   13132244 :     const float M = gamut.rgb2lms.m[1][0] * c.R +
     304                 :   13132244 :                     gamut.rgb2lms.m[1][1] * c.G +
     305                 :   13132244 :                     gamut.rgb2lms.m[1][2] * c.B;
     306                 :   13132244 :     const float S = gamut.rgb2lms.m[2][0] * c.R +
     307                 :   13132244 :                     gamut.rgb2lms.m[2][1] * c.G +
     308                 :   13132244 :                     gamut.rgb2lms.m[2][2] * c.B;
     309                 :   13132244 :     const float Lp = pq_oetf(L);
     310                 :   13132244 :     const float Mp = pq_oetf(M);
     311                 :   13132244 :     const float Sp = pq_oetf(S);
     312                 :   13132244 :     return (struct IPT) {
     313                 :   13132244 :         .I = 0.4000f * Lp + 0.4000f * Mp + 0.2000f * Sp,
     314                 :   13132244 :         .P = 4.4550f * Lp - 4.8510f * Mp + 0.3960f * Sp,
     315                 :   13132244 :         .T = 0.8056f * Lp + 0.3572f * Mp - 1.1628f * Sp,
     316                 :            :     };
     317                 :            : }
     318                 :            : 
     319                 :   13132196 : static inline struct RGB ipt2rgb(struct IPT c, struct gamut gamut)
     320                 :            : {
     321                 :   13132196 :     const float Lp = c.I + 0.0975689f * c.P + 0.205226f * c.T;
     322                 :   13132196 :     const float Mp = c.I - 0.1138760f * c.P + 0.133217f * c.T;
     323                 :   13132196 :     const float Sp = c.I + 0.0326151f * c.P - 0.676887f * c.T;
     324                 :   13132196 :     const float L = pq_eotf(Lp);
     325                 :   13132196 :     const float M = pq_eotf(Mp);
     326                 :   13132196 :     const float S = pq_eotf(Sp);
     327                 :   13132196 :     return (struct RGB) {
     328                 :   13132196 :         .R = gamut.lms2rgb.m[0][0] * L +
     329                 :   13132196 :              gamut.lms2rgb.m[0][1] * M +
     330                 :   13132196 :              gamut.lms2rgb.m[0][2] * S,
     331                 :   13132196 :         .G = gamut.lms2rgb.m[1][0] * L +
     332                 :   13132196 :              gamut.lms2rgb.m[1][1] * M +
     333                 :   13132196 :              gamut.lms2rgb.m[1][2] * S,
     334                 :   13132196 :         .B = gamut.lms2rgb.m[2][0] * L +
     335                 :   13132196 :              gamut.lms2rgb.m[2][1] * M +
     336                 :   13132196 :              gamut.lms2rgb.m[2][2] * S,
     337                 :            :     };
     338                 :            : }
     339                 :            : 
     340                 :    5259552 : static inline bool ingamut(struct IPT c, struct gamut gamut)
     341                 :            : {
     342                 :    5259552 :     const float Lp = c.I + 0.0975689f * c.P + 0.205226f * c.T;
     343                 :    5259552 :     const float Mp = c.I - 0.1138760f * c.P + 0.133217f * c.T;
     344                 :    5259552 :     const float Sp = c.I + 0.0326151f * c.P - 0.676887f * c.T;
     345   [ +  -  +  +  :    5259552 :     if (Lp < gamut.min_luma || Lp > gamut.max_luma ||
                   +  - ]
     346   [ +  +  +  - ]:    5253109 :         Mp < gamut.min_luma || Mp > gamut.max_luma ||
     347         [ +  + ]:    5207510 :         Sp < gamut.min_luma || Sp > gamut.max_luma)
     348                 :            :     {
     349                 :            :         // Early exit for values outside legal LMS range
     350                 :            :         return false;
     351                 :            :     }
     352                 :            : 
     353                 :    5053025 :     const float L = pq_eotf(Lp);
     354                 :    5053025 :     const float M = pq_eotf(Mp);
     355                 :    5053025 :     const float S = pq_eotf(Sp);
     356                 :            :     struct RGB rgb = {
     357                 :    5053025 :         .R = gamut.lms2rgb.m[0][0] * L +
     358                 :    5053025 :              gamut.lms2rgb.m[0][1] * M +
     359                 :    5053025 :              gamut.lms2rgb.m[0][2] * S,
     360                 :    5053025 :         .G = gamut.lms2rgb.m[1][0] * L +
     361                 :    5053025 :              gamut.lms2rgb.m[1][1] * M +
     362                 :    5053025 :              gamut.lms2rgb.m[1][2] * S,
     363                 :    5053025 :         .B = gamut.lms2rgb.m[2][0] * L +
     364                 :    5053025 :              gamut.lms2rgb.m[2][1] * M +
     365                 :    5053025 :              gamut.lms2rgb.m[2][2] * S,
     366                 :            :     };
     367   [ +  +  +  + ]:    3611677 :     return rgb.R >= gamut.min_rgb && rgb.R <= gamut.max_rgb &&
     368   [ +  +  +  + ]:    1850440 :            rgb.G >= gamut.min_rgb && rgb.G <= gamut.max_rgb &&
     369   [ +  +  +  + ]:   10169159 :            rgb.B >= gamut.min_rgb && rgb.B <= gamut.max_rgb;
     370                 :            : }
     371                 :            : 
     372                 :            : struct generate_args {
     373                 :            :     const struct pl_gamut_map_params *params;
     374                 :            :     float *out;
     375                 :            :     int start;
     376                 :            :     int count;
     377                 :            : };
     378                 :            : 
     379                 :        534 : static PL_THREAD_VOID generate(void *priv)
     380                 :            : {
     381                 :            :     const struct generate_args *args = priv;
     382                 :        534 :     const struct pl_gamut_map_params *params = args->params;
     383                 :            : 
     384                 :        534 :     float *in = args->out;
     385                 :        534 :     const int end = args->start + args->count;
     386         [ +  + ]:       4695 :     for (int h = args->start; h < end; h++) {
     387         [ +  + ]:     139458 :         for (int C = 0; C < params->lut_size_C; C++) {
     388         [ +  + ]:    6701378 :             for (int I = 0; I < params->lut_size_I; I++) {
     389                 :    6566081 :                 float Ix = (float) I / (params->lut_size_I - 1);
     390                 :    6566081 :                 float Cx = (float) C / (params->lut_size_C - 1);
     391                 :    6566081 :                 float hx = (float) h / (params->lut_size_h - 1);
     392                 :    6566081 :                 struct IPT ipt = ich2ipt((struct ICh) {
     393                 :    6566081 :                     .I = PL_MIX(params->min_luma, params->max_luma, Ix),
     394                 :    6566081 :                     .C = PL_MIX(0.0f, 0.5f, Cx),
     395                 :    6566081 :                     .h = PL_MIX(-M_PI, M_PI, hx),
     396                 :            :                 });
     397                 :    6566081 :                 in[0] = ipt.I;
     398                 :    6566081 :                 in[1] = ipt.P;
     399                 :    6566081 :                 in[2] = ipt.T;
     400                 :    6566081 :                 in += params->lut_stride;
     401                 :            :             }
     402                 :            :         }
     403                 :            :     }
     404                 :            : 
     405                 :        534 :     struct pl_gamut_map_params fixed = *params;
     406                 :        534 :     fix_constants(&fixed.constants);
     407                 :        534 :     fixed.lut_size_h = args->count;
     408         [ +  - ]:        534 :     FUN(params).map(args->out, &fixed);
     409                 :        534 :     PL_THREAD_RETURN();
     410                 :            : }
     411                 :            : 
     412                 :         17 : void pl_gamut_map_generate(float *out, const struct pl_gamut_map_params *params)
     413                 :            : {
     414                 :            :     enum { MAX_WORKERS = 32 };
     415                 :            :     struct generate_args args[MAX_WORKERS];
     416                 :            : 
     417                 :         17 :     const int num_per_worker = PL_DIV_UP(params->lut_size_h, MAX_WORKERS);
     418                 :         17 :     const int num_workers = PL_DIV_UP(params->lut_size_h, num_per_worker);
     419         [ +  + ]:        551 :     for (int i = 0; i < num_workers; i++) {
     420                 :        534 :         const int start = i * num_per_worker;
     421                 :        534 :         const int count = PL_MIN(num_per_worker, params->lut_size_h - start);
     422                 :        534 :         args[i] = (struct generate_args) {
     423                 :            :             .params = params,
     424                 :            :             .out    = out,
     425                 :            :             .start  = start,
     426                 :            :             .count  = count,
     427                 :            :         };
     428                 :        534 :         out += count * params->lut_size_C * params->lut_size_I * params->lut_stride;
     429                 :            :     }
     430                 :            : 
     431                 :         17 :     pl_thread workers[MAX_WORKERS] = {0};
     432         [ +  + ]:        551 :     for (int i = 0; i < num_workers; i++) {
     433         [ -  + ]:        534 :         if (pl_thread_create(&workers[i], generate, &args[i]) != 0)
     434                 :          0 :             generate(&args[i]); // fallback
     435                 :            :     }
     436                 :            : 
     437         [ +  + ]:        551 :     for (int i = 0; i < num_workers; i++) {
     438         [ -  + ]:        534 :         if (!workers[i])
     439                 :          0 :             continue;
     440         [ -  + ]:        534 :         if (pl_thread_join(workers[i]) != 0)
     441                 :          0 :             generate(&args[i]); // fallback
     442                 :            :     }
     443                 :         17 : }
     444                 :            : 
     445                 :         26 : void pl_gamut_map_sample(float x[3], const struct pl_gamut_map_params *params)
     446                 :            : {
     447                 :         26 :     struct pl_gamut_map_params fixed = *params;
     448                 :         26 :     fix_constants(&fixed.constants);
     449                 :         26 :     fixed.lut_size_I = fixed.lut_size_C = fixed.lut_size_h = 1;
     450                 :         26 :     fixed.lut_stride = 3;
     451                 :            : 
     452         [ +  - ]:         26 :     FUN(params).map(x, &fixed);
     453                 :         26 : }
     454                 :            : 
     455                 :            : #define LUT_SIZE(p) (p->lut_size_I * p->lut_size_C * p->lut_size_h * p->lut_stride)
     456                 :            : #define FOREACH_LUT(lut, C)                                                     \
     457                 :            :     for (struct IPT *_i = (struct IPT *) lut,                                   \
     458                 :            :                     *_end = (struct IPT *) (lut + LUT_SIZE(params)),            \
     459                 :            :                     C;                                                          \
     460                 :            :          _i < _end && ( C = *_i, 1 );                                           \
     461                 :            :          *_i = C, _i = (struct IPT *) ((float *) _i + params->lut_stride))
     462                 :            : 
     463                 :            : // Something like PL_MIX(base, c, x) but follows an exponential curve, note
     464                 :            : // that this can be used to extend 'c' outwards for x > 1
     465                 :            : static inline struct ICh mix_exp(struct ICh c, float x, float gamma, float base)
     466                 :            : {
     467                 :            :     return (struct ICh) {
     468                 :          0 :         .I = base + (c.I - base) * powf(x, gamma),
     469                 :          0 :         .C = c.C * x,
     470                 :            :         .h = c.h,
     471                 :            :     };
     472                 :            : }
     473                 :            : 
     474                 :            : // Drop gamma for colors approaching black and achromatic to avoid numerical
     475                 :            : // instabilities, and excessive brightness boosting of grain, while also
     476                 :            : // strongly boosting gamma for values exceeding the target peak
     477                 :          0 : static inline float scale_gamma(float gamma, struct ICh ich, struct ICh peak,
     478                 :            :                                 struct gamut gamut)
     479                 :            : {
     480                 :          0 :     const float Imin = gamut.min_luma;
     481                 :          0 :     const float Irel = fmaxf((ich.I - Imin) / (peak.I - Imin), 0.0f);
     482                 :          0 :     return gamma * powf(Irel, 3) * fminf(ich.C / peak.C, 1.0f);
     483                 :            : }
     484                 :            : 
     485                 :            : static const float maxDelta = 5e-5f;
     486                 :            : 
     487                 :            : // Find gamut intersection using specified bounds
     488                 :            : static inline struct ICh
     489                 :     374604 : desat_bounded(float I, float h, float Cmin, float Cmax, struct gamut gamut)
     490                 :            : {
     491         [ -  + ]:     374604 :     if (I <= gamut.min_luma)
     492                 :          0 :         return (struct ICh) { .I = gamut.min_luma, .C = 0, .h = h };
     493         [ -  + ]:     374604 :     if (I >= gamut.max_luma)
     494                 :          0 :         return (struct ICh) { .I = gamut.max_luma, .C = 0, .h = h };
     495                 :            : 
     496                 :     374604 :     const float maxDI = I * maxDelta;
     497                 :     374604 :     struct ICh res = { .I = I, .C = (Cmin + Cmax) / 2, .h = h };
     498                 :            :     do {
     499         [ +  + ]:    5259545 :         if (ingamut(ich2ipt(res), gamut)) {
     500                 :            :             Cmin = res.C;
     501                 :            :         } else {
     502                 :            :             Cmax = res.C;
     503                 :            :         }
     504                 :    5259545 :         res.C = (Cmin + Cmax) / 2;
     505         [ +  + ]:    5259545 :     } while (Cmax - Cmin > maxDI);
     506                 :            : 
     507                 :     374604 :     return res;
     508                 :            : }
     509                 :            : 
     510                 :            : // Finds maximally saturated in-gamut color (for given hue)
     511                 :   13132430 : static inline struct ICh saturate(float hue, struct gamut gamut)
     512                 :            : {
     513   [ +  +  +  + ]:   13132430 :     if (gamut.peak_cache->I && fabsf(gamut.peak_cache->h - hue) < 1e-3)
     514                 :   13115588 :         return *gamut.peak_cache;
     515                 :            : 
     516                 :            :     static const float invphi = 0.6180339887498948f;
     517                 :            :     static const float invphi2 = 0.38196601125010515f;
     518                 :            : 
     519                 :      16842 :     struct ICh lo = { .I = gamut.min_luma, .h = hue };
     520                 :      16842 :     struct ICh hi = { .I = gamut.max_luma, .h = hue };
     521                 :      16842 :     float de = hi.I - lo.I;
     522                 :      16842 :     struct ICh a = { .I = lo.I + invphi2 * de };
     523                 :      16842 :     struct ICh b = { .I = lo.I + invphi  * de };
     524                 :      16842 :     a = desat_bounded(a.I, hue, 0.0f, 0.5f, gamut);
     525                 :      16842 :     b = desat_bounded(b.I, hue, 0.0f, 0.5f, gamut);
     526                 :            : 
     527         [ +  + ]:     357762 :     while (de > maxDelta) {
     528                 :     340920 :         de *= invphi;
     529         [ +  + ]:     340920 :         if (a.C > b.C) {
     530                 :            :             hi = b;
     531                 :     105890 :             b = a;
     532                 :     105890 :             a.I = lo.I + invphi2 * de;
     533                 :     105890 :             a = desat_bounded(a.I, hue, lo.C - maxDelta, 0.5f, gamut);
     534                 :            :         } else {
     535                 :            :             lo = a;
     536                 :     235030 :             a = b;
     537                 :     235030 :             b.I = lo.I + invphi * de;
     538                 :     235030 :             b = desat_bounded(b.I, hue, hi.C - maxDelta, 0.5f, gamut);
     539                 :            :         }
     540                 :            :     }
     541                 :            : 
     542         [ +  + ]:      16842 :     struct ICh peak = a.C > b.C ? a : b;
     543                 :      16842 :     *gamut.peak_cache = peak;
     544                 :      16842 :     return peak;
     545                 :            : }
     546                 :            : 
     547                 :            : // Clip a color along the exponential curve given by `gamma`
     548                 :            : static inline struct IPT
     549                 :          8 : clip_gamma(struct IPT ipt, float gamma, struct gamut gamut)
     550                 :            : {
     551         [ +  + ]:          8 :     if (ipt.I <= gamut.min_luma)
     552                 :          3 :         return (struct IPT) { .I = gamut.min_luma };
     553         [ +  - ]:          5 :     if (ingamut(ipt, gamut))
     554                 :          5 :         return ipt;
     555                 :            : 
     556                 :          0 :     struct ICh ich = ipt2ich(ipt);
     557         [ #  # ]:          0 :     if (!gamma)
     558                 :          0 :         return ich2ipt(desat_bounded(ich.I, ich.h, 0.0f, ich.C, gamut));
     559                 :            : 
     560                 :          0 :     const float maxDI = fmaxf(ich.I * maxDelta, 1e-7f);
     561                 :          0 :     struct ICh peak = saturate(ich.h, gamut);
     562                 :          0 :     gamma = scale_gamma(gamma, ich, peak, gamut);
     563                 :            :     float lo = 0.0f, hi = 1.0f, x = 0.5f;
     564                 :            :     do {
     565                 :            :         struct ICh test = mix_exp(ich, x, gamma, peak.I);
     566         [ #  # ]:          0 :         if (ingamut(ich2ipt(test), gamut)) {
     567                 :            :             lo = x;
     568                 :            :         } else {
     569                 :            :             hi = x;
     570                 :            :         }
     571                 :          0 :         x = (lo + hi) / 2.0f;
     572         [ #  # ]:          0 :     } while (hi - lo > maxDI);
     573                 :            : 
     574                 :          0 :     return ich2ipt(mix_exp(ich, x, gamma, peak.I));
     575                 :            : }
     576                 :            : 
     577                 :   19698267 : static float softclip(float value, float source, float target,
     578                 :            :                       const struct pl_gamut_map_constants *c)
     579                 :            : {
     580         [ +  - ]:   19698267 :     if (!target)
     581                 :            :         return 0.0f;
     582                 :   19698267 :     const float peak = source / target;
     583                 :   19698267 :     const float x = fminf(value / target, peak);
     584                 :   19698267 :     const float j = c->softclip_knee;
     585   [ +  +  +  + ]:   19698267 :     if (x <= j || peak <= 1.0)
     586                 :            :         return value;
     587                 :            :     // Apply simple mobius function
     588                 :    1799938 :     const float a = -j*j * (peak - 1.0f) / (j*j - 2.0f * j + peak);
     589                 :    1799938 :     const float b = (j*j - 2.0f * j * peak + peak) /
     590                 :    1799938 :                     fmaxf(1e-6f, peak - 1.0f);
     591                 :    1799938 :     const float scale = (b*b + 2.0f * b*j + j*j) / (b - a);
     592                 :    1799938 :     return scale * (x + a) / (x + b) * target;
     593                 :            : }
     594                 :            : 
     595                 :         62 : static int cmp_float(const void *a, const void *b)
     596                 :            : {
     597                 :         62 :     float fa = *(const float*) a;
     598                 :         62 :     float fb = *(const float*) b;
     599                 :         62 :     return PL_CMP(fa, fb);
     600                 :            : }
     601                 :            : 
     602                 :            : static float wrap(float h)
     603                 :            : {
     604                 :         24 :     if (h > M_PI) {
     605                 :          0 :         return h - 2 * M_PI;
     606         [ -  + ]:         24 :     } else if (h < -M_PI) {
     607                 :          0 :         return h + 2 * M_PI;
     608                 :            :     } else {
     609                 :            :         return h;
     610                 :            :     }
     611                 :            : }
     612                 :            : 
     613                 :            : enum {
     614                 :            :     S = 12,    // number of hue shift vertices
     615                 :            :     N = S + 2, // +2 for the endpoints
     616                 :            : };
     617                 :            : 
     618                 :            : // Hue-shift helper struct
     619                 :            : struct hueshift {
     620                 :            :     float dh[N];
     621                 :            :     float dddh[N];
     622                 :            :     float K[N];
     623                 :            :     float prev_hue;
     624                 :            :     float prev_shift;
     625                 :            :     struct { float hue, delta; } hueshift[N];
     626                 :            : };
     627                 :            : 
     628                 :          2 : static void hueshift_prepare(struct hueshift *s, struct gamut src, struct gamut dst)
     629                 :            : {
     630                 :          2 :     const float O = pq_eotf(src.min_luma), X = pq_eotf(src.max_luma);
     631                 :          2 :     const float M = (O + X) / 2.0f;
     632                 :          2 :     const struct RGB refpoints[S] = {
     633                 :            :         {X, O, O}, {O, X, O}, {O, O, X},
     634                 :            :         {O, X, X}, {X, O, X}, {X, X, O},
     635                 :            :         {O, X, M}, {X, O, M}, {X, M, O},
     636                 :            :         {O, M, X}, {M, O, X}, {M, X, O},
     637                 :            :     };
     638                 :            : 
     639                 :            :     memset(s, 0, sizeof(*s));
     640         [ +  + ]:         26 :     for (int i = 0; i < S; i++) {
     641                 :         24 :         struct ICh ich_src = ipt2ich(rgb2ipt(refpoints[i], src));
     642                 :         24 :         struct ICh ich_dst = ipt2ich(rgb2ipt(refpoints[i], dst));
     643         [ -  + ]:         24 :         const float delta = wrap(ich_dst.h - ich_src.h);
     644                 :         24 :         s->hueshift[i+1].hue = ich_src.h;
     645                 :         24 :         s->hueshift[i+1].delta = delta;
     646                 :            :     }
     647                 :            : 
     648                 :            :     // Sort and wrap endpoints
     649                 :          2 :     qsort(s->hueshift + 1, S, sizeof(*s->hueshift), cmp_float);
     650                 :          2 :     s->hueshift[0]   = s->hueshift[S];
     651                 :          2 :     s->hueshift[S+1] = s->hueshift[1];
     652                 :          2 :     s->hueshift[0].hue   -= 2 * M_PI;
     653                 :          2 :     s->hueshift[S+1].hue += 2 * M_PI;
     654                 :            : 
     655                 :            :     // Construction of cubic spline coefficients
     656                 :          2 :     float tmp[N][N] = {0};
     657         [ +  + ]:         28 :     for (int i = N - 1; i > 0; i--) {
     658                 :         26 :         s->dh[i-1] = s->hueshift[i].hue - s->hueshift[i-1].hue;
     659                 :         26 :         s->dddh[i] = (s->hueshift[i].delta - s->hueshift[i-1].delta) / s->dh[i-1];
     660                 :            :     }
     661         [ +  + ]:         26 :     for (int i = 1; i < N - 1; i++) {
     662                 :         24 :         tmp[i][i] = 2 * (s->dh[i-1] + s->dh[i]);
     663         [ +  + ]:         24 :         if (i != 1)
     664                 :         22 :             tmp[i][i-1] = tmp[i-1][i] = s->dh[i-1];
     665                 :         24 :         tmp[i][N-1] = 6 * (s->dddh[i+1] - s->dddh[i]);
     666                 :            :     }
     667         [ +  + ]:         24 :     for (int i = 1; i < N - 2; i++) {
     668                 :         22 :         const float q = (tmp[i+1][i] / tmp[i][i]);
     669         [ +  + ]:        308 :         for (int j = 1; j <= N - 1; j++)
     670                 :        286 :             tmp[i+1][j] -= q * tmp[i][j];
     671                 :            :     }
     672         [ +  + ]:         26 :     for (int i = N - 2; i > 0; i--) {
     673                 :            :         float sum = 0.0f;
     674         [ +  + ]:        180 :         for (int j = i; j <= N - 2; j++)
     675                 :        156 :             sum += tmp[i][j] * s->K[j];
     676                 :         24 :         s->K[i] = (tmp[i][N-1] - sum) / tmp[i][i];
     677                 :            :     }
     678                 :            : 
     679                 :          2 :     s->prev_hue = -10.0f;
     680                 :          2 : }
     681                 :            : 
     682                 :          0 : static struct ICh hueshift_apply(struct hueshift *s, struct ICh ich)
     683                 :            : {
     684         [ #  # ]:          0 :     if (fabsf(ich.h - s->prev_hue) < 1e-6f)
     685                 :          0 :         goto done;
     686                 :            : 
     687                 :            :     // Determine perceptual hue shift delta by interpolation of refpoints
     688         [ #  # ]:          0 :     for (int i = 0; i < N - 1; i++) {
     689         [ #  # ]:          0 :         if (s->hueshift[i+1].hue > ich.h) {
     690         [ #  # ]:          0 :             pl_assert(s->hueshift[i].hue <= ich.h);
     691                 :          0 :             float a = (s->K[i+1] - s->K[i]) / (6 * s->dh[i]);
     692                 :          0 :             float b = s->K[i] / 2;
     693                 :          0 :             float c = s->dddh[i+1] - (2 * s->dh[i] * s->K[i] + s->K[i+1] * s->dh[i]) / 6;
     694                 :          0 :             float d = s->hueshift[i].delta;
     695                 :          0 :             float x = ich.h - s->hueshift[i].hue;
     696                 :          0 :             float delta = ((a * x + b) * x + c) * x + d;
     697                 :          0 :             s->prev_shift = ich.h + delta;
     698                 :          0 :             s->prev_hue = ich.h;
     699                 :          0 :             break;
     700                 :            :         }
     701                 :            :     }
     702                 :            : 
     703                 :          0 : done:
     704                 :          0 :     return (struct ICh) {
     705                 :          0 :         .I = ich.I,
     706                 :          0 :         .C = ich.C,
     707                 :          0 :         .h = s->prev_shift,
     708                 :            :     };
     709                 :            : }
     710                 :            : 
     711                 :        542 : static void perceptual(float *lut, const struct pl_gamut_map_params *params)
     712                 :            : {
     713                 :        542 :     const struct pl_gamut_map_constants *c = &params->constants;
     714                 :            :     struct cache cache;
     715                 :            :     struct gamut dst, src;
     716                 :        542 :     get_gamuts(&dst, &src, &cache, params);
     717                 :            : 
     718         [ +  + ]:    6566631 :     FOREACH_LUT(lut, ipt) {
     719                 :    6566089 :         struct ICh ich = ipt2ich(ipt);
     720                 :    6566089 :         struct ICh src_peak = saturate(ich.h, src);
     721                 :    6566089 :         struct ICh dst_peak = saturate(ich.h, dst);
     722                 :    6566089 :         struct IPT mapped = rgb2ipt(ipt2rgb(ipt, src), dst);
     723                 :            : 
     724                 :            :         // Protect in gamut region
     725                 :    6566089 :         const float maxC = fmaxf(src_peak.C, dst_peak.C);
     726         [ -  + ]:    6566089 :         float k = pl_smoothstep(c->perceptual_deadzone, 1.0f, ich.C / maxC);
     727                 :    6566089 :         k *= c->perceptual_strength;
     728                 :    6566089 :         ipt.I = PL_MIX(ipt.I, mapped.I, k);
     729                 :    6566089 :         ipt.P = PL_MIX(ipt.P, mapped.P, k);
     730                 :    6566089 :         ipt.T = PL_MIX(ipt.T, mapped.T, k);
     731                 :            : 
     732                 :    6566089 :         struct RGB rgb = ipt2rgb(ipt, dst);
     733                 :    6566089 :         const float maxRGB = fmaxf(rgb.R, fmaxf(rgb.G, rgb.B));
     734                 :    6566089 :         rgb.R = fmaxf(softclip(rgb.R, maxRGB, dst.max_rgb, c), dst.min_rgb);
     735                 :    6566089 :         rgb.G = fmaxf(softclip(rgb.G, maxRGB, dst.max_rgb, c), dst.min_rgb);
     736                 :    6566089 :         rgb.B = fmaxf(softclip(rgb.B, maxRGB, dst.max_rgb, c), dst.min_rgb);
     737                 :    6566089 :         ipt = rgb2ipt(rgb, dst);
     738                 :            :     }
     739                 :        542 : }
     740                 :            : 
     741                 :            : const struct pl_gamut_map_function pl_gamut_map_perceptual = {
     742                 :            :     .name = "perceptual",
     743                 :            :     .description = "Perceptual mapping",
     744                 :            :     .bidirectional = true,
     745                 :            :     .map = perceptual,
     746                 :            : };
     747                 :            : 
     748                 :          2 : static void softclip_map(float *lut, const struct pl_gamut_map_params *params)
     749                 :            : {
     750                 :          2 :     const struct pl_gamut_map_constants *c = &params->constants;
     751                 :            : 
     752                 :            :     // Separate cache after hueshift, because this invalidates previous cache
     753                 :            :     struct cache cache_pre, cache_post;
     754                 :            :     struct gamut dst_pre, src_pre, src_post, dst_post;
     755                 :            :     struct hueshift hueshift;
     756                 :          2 :     get_gamuts(&dst_pre, &src_pre, &cache_pre, params);
     757                 :          2 :     get_gamuts(&dst_post, &src_post, &cache_post, params);
     758                 :          2 :     hueshift_prepare(&hueshift, src_pre, dst_pre);
     759                 :            : 
     760         [ +  + ]:          4 :     FOREACH_LUT(lut, ipt) {
     761                 :          2 :         struct gamut src = src_pre;
     762                 :          2 :         struct gamut dst = dst_pre;
     763                 :            : 
     764         [ +  + ]:          2 :         if (ipt.I <= dst.min_luma) {
     765                 :            :             ipt.P = ipt.T = 0.0f;
     766                 :          2 :             continue;
     767                 :            :         }
     768                 :            : 
     769                 :          1 :         struct ICh ich = ipt2ich(ipt);
     770         [ +  - ]:          1 :         if (ich.C <= 1e-2f)
     771                 :          1 :             continue; // Fast path for achromatic colors
     772                 :            : 
     773                 :            :         float margin = 1.0f;
     774                 :          0 :         struct ICh shifted = hueshift_apply(&hueshift, ich);
     775         [ #  # ]:          0 :         if (fabsf(shifted.h - ich.h) >= 1e-3f) {
     776                 :          0 :             struct ICh src_border = desat_bounded(ich.I, ich.h, 0.0f, 0.5f, src);
     777                 :          0 :             struct ICh dst_border = desat_bounded(ich.I, ich.h, 0.0f, 0.5f, dst);
     778         [ #  # ]:          0 :             const float k = pl_smoothstep(dst_border.C * c->softclip_knee,
     779                 :            :                                           src_border.C, ich.C);
     780                 :          0 :             ich.h = PL_MIX(ich.h, shifted.h, k);
     781                 :          0 :             src = src_post;
     782                 :          0 :             dst = dst_post;
     783                 :            : 
     784                 :            :             // Expand/contract chromaticity margin to correspond to the altered
     785                 :            :             // size of the hue leaf after applying the hue delta
     786                 :          0 :             struct ICh shift_border = desat_bounded(ich.I, ich.h, 0.0f, 0.5f, src);
     787                 :          0 :             margin *= fmaxf(1.0f, src_border.C / shift_border.C);
     788                 :            :         }
     789                 :            : 
     790                 :            :         // Determine intersections with source and target gamuts, and
     791                 :            :         // apply softclip to the chromaticity
     792                 :          0 :         struct ICh source = saturate(ich.h, src);
     793                 :          0 :         struct ICh target = saturate(ich.h, dst);
     794                 :          0 :         struct ICh border = desat_bounded(ich.I, ich.h, 0.0f, target.C, dst);
     795                 :          0 :         const float chromaticity = PL_MIX(target.C, border.C, c->softclip_desat);
     796                 :          0 :         ich.C = softclip(ich.C, margin * source.C, chromaticity, c);
     797                 :            : 
     798                 :            :         // Soft-clip the resulting RGB color. This will generally distort
     799                 :            :         // hues slightly, but hopefully in an aesthetically pleasing way.
     800                 :          0 :         struct ICh saturated = { ich.I, chromaticity, ich.h };
     801                 :          0 :         struct RGB peak = ipt2rgb(ich2ipt(saturated), dst);
     802                 :          0 :         struct RGB rgb = ipt2rgb(ich2ipt(ich), dst);
     803                 :          0 :         rgb.R = fmaxf(softclip(rgb.R, peak.R, dst.max_rgb, c), dst.min_rgb);
     804                 :          0 :         rgb.G = fmaxf(softclip(rgb.G, peak.G, dst.max_rgb, c), dst.min_rgb);
     805                 :          0 :         rgb.B = fmaxf(softclip(rgb.B, peak.B, dst.max_rgb, c), dst.min_rgb);
     806                 :          0 :         ipt = rgb2ipt(rgb, dst);
     807                 :            :     }
     808                 :          2 : }
     809                 :            : 
     810                 :            : const struct pl_gamut_map_function pl_gamut_map_softclip = {
     811                 :            :     .name = "softclip",
     812                 :            :     .description = "Soft clipping",
     813                 :            :     .map = softclip_map,
     814                 :            : };
     815                 :            : 
     816                 :          2 : static void relative(float *lut, const struct pl_gamut_map_params *params)
     817                 :            : {
     818                 :            :     const struct pl_gamut_map_constants *c = &params->constants;
     819                 :            :     struct cache cache;
     820                 :            :     struct gamut dst;
     821                 :          2 :     get_gamuts(&dst, NULL, &cache, params);
     822                 :            : 
     823         [ +  + ]:          4 :     FOREACH_LUT(lut, ipt)
     824                 :          2 :         ipt = clip_gamma(ipt, c->colorimetric_gamma, dst);
     825                 :          2 : }
     826                 :            : 
     827                 :            : const struct pl_gamut_map_function pl_gamut_map_relative = {
     828                 :            :     .name = "relative",
     829                 :            :     .description = "Colorimetric clip",
     830                 :            :     .map = relative,
     831                 :            : };
     832                 :            : 
     833                 :          2 : static void desaturate(float *lut, const struct pl_gamut_map_params *params)
     834                 :            : {
     835                 :            :     struct cache cache;
     836                 :            :     struct gamut dst;
     837                 :          2 :     get_gamuts(&dst, NULL, &cache, params);
     838                 :            : 
     839         [ +  + ]:          4 :     FOREACH_LUT(lut, ipt)
     840                 :          2 :         ipt = clip_gamma(ipt, 0.0f, dst);
     841                 :          2 : }
     842                 :            : 
     843                 :            : const struct pl_gamut_map_function pl_gamut_map_desaturate = {
     844                 :            :     .name = "desaturate",
     845                 :            :     .description = "Desaturating clip",
     846                 :            :     .map = desaturate,
     847                 :            : };
     848                 :            : 
     849                 :          2 : static void saturation(float *lut, const struct pl_gamut_map_params *params)
     850                 :            : {
     851                 :            :     struct cache cache;
     852                 :            :     struct gamut dst, src;
     853                 :          2 :     get_gamuts(&dst, &src, &cache, params);
     854                 :            : 
     855         [ +  + ]:          4 :     FOREACH_LUT(lut, ipt)
     856                 :          2 :         ipt = rgb2ipt(ipt2rgb(ipt, src), dst);
     857                 :          2 : }
     858                 :            : 
     859                 :            : const struct pl_gamut_map_function pl_gamut_map_saturation = {
     860                 :            :     .name = "saturation",
     861                 :            :     .description = "Saturation mapping",
     862                 :            :     .bidirectional = true,
     863                 :            :     .map = saturation,
     864                 :            : };
     865                 :            : 
     866                 :          2 : static void absolute(float *lut, const struct pl_gamut_map_params *params)
     867                 :            : {
     868                 :            :     const struct pl_gamut_map_constants *c = &params->constants;
     869                 :            :     struct cache cache;
     870                 :            :     struct gamut dst;
     871                 :          2 :     get_gamuts(&dst, NULL, &cache, params);
     872                 :          2 :     pl_matrix3x3 m = pl_get_adaptation_matrix(params->output_gamut.white,
     873                 :            :                                               params->input_gamut.white);
     874                 :            : 
     875         [ +  + ]:          4 :     FOREACH_LUT(lut, ipt) {
     876                 :          2 :         struct RGB rgb = ipt2rgb(ipt, dst);
     877                 :          2 :         pl_matrix3x3_apply(&m, (float *) &rgb);
     878                 :          2 :         ipt = rgb2ipt(rgb, dst);
     879                 :          2 :         ipt = clip_gamma(ipt, c->colorimetric_gamma, dst);
     880                 :            :     }
     881                 :          2 : }
     882                 :            : 
     883                 :            : const struct pl_gamut_map_function pl_gamut_map_absolute = {
     884                 :            :     .name = "absolute",
     885                 :            :     .description = "Absolute colorimetric clip",
     886                 :            :     .map = absolute,
     887                 :            : };
     888                 :            : 
     889                 :          2 : static void highlight(float *lut, const struct pl_gamut_map_params *params)
     890                 :            : {
     891                 :            :     struct cache cache;
     892                 :            :     struct gamut dst;
     893                 :          2 :     get_gamuts(&dst, NULL, &cache, params);
     894                 :            : 
     895         [ +  + ]:          4 :     FOREACH_LUT(lut, ipt) {
     896         [ -  + ]:          2 :         if (!ingamut(ipt, dst)) {
     897                 :          0 :             ipt.I = fminf(ipt.I + 0.1f, 1.0f);
     898                 :          0 :             ipt.P = fclampf(-1.2f * ipt.P, -0.5f, 0.5f);
     899                 :          0 :             ipt.T = fclampf(-1.2f * ipt.T, -0.5f, 0.5f);
     900                 :            :         }
     901                 :            :     }
     902                 :          2 : }
     903                 :            : 
     904                 :            : const struct pl_gamut_map_function pl_gamut_map_highlight = {
     905                 :            :     .name = "highlight",
     906                 :            :     .description = "Highlight out-of-gamut pixels",
     907                 :            :     .map = highlight,
     908                 :            : };
     909                 :            : 
     910                 :          2 : static void linear(float *lut, const struct pl_gamut_map_params *params)
     911                 :            : {
     912                 :            :     struct cache cache;
     913                 :            :     struct gamut dst, src;
     914                 :          2 :     get_gamuts(&dst, &src, &cache, params);
     915                 :            : 
     916                 :            :     float gain = 1.0f;
     917         [ +  + ]:        128 :     for (float hue = -M_PI; hue < M_PI; hue += 0.1f)
     918                 :        126 :         gain = fminf(gain, saturate(hue, dst).C / saturate(hue, src).C);
     919                 :            : 
     920         [ +  + ]:          4 :     FOREACH_LUT(lut, ipt) {
     921                 :          2 :         struct ICh ich = ipt2ich(ipt);
     922                 :          2 :         ich.C *= gain;
     923                 :          2 :         ipt = ich2ipt(ich);
     924                 :            :     }
     925                 :          2 : }
     926                 :            : 
     927                 :            : const struct pl_gamut_map_function pl_gamut_map_linear = {
     928                 :            :     .name = "linear",
     929                 :            :     .description = "Linear desaturate",
     930                 :            :     .map = linear,
     931                 :            : };
     932                 :            : 
     933                 :          2 : static void darken(float *lut, const struct pl_gamut_map_params *params)
     934                 :            : {
     935                 :            :     const struct pl_gamut_map_constants *c = &params->constants;
     936                 :            :     struct cache cache;
     937                 :            :     struct gamut dst, src;
     938                 :          2 :     get_gamuts(&dst, &src, &cache, params);
     939                 :            : 
     940                 :            :     static const struct RGB points[6] = {
     941                 :            :         {1, 0, 0}, {0, 1, 0}, {0, 0, 1},
     942                 :            :         {0, 1, 1}, {1, 0, 1}, {1, 1, 0},
     943                 :            :     };
     944                 :            : 
     945                 :            :     float gain = 1.0f;
     946         [ +  + ]:         14 :     for (int i = 0; i < PL_ARRAY_SIZE(points); i++) {
     947                 :         12 :         const struct RGB p = ipt2rgb(rgb2ipt(points[i], src), dst);
     948   [ +  +  +  + ]:         18 :         const float maxRGB = PL_MAX3(p.R, p.G, p.B);
     949                 :         12 :         gain = fminf(gain, 1.0 / maxRGB);
     950                 :            :     }
     951                 :            : 
     952         [ +  + ]:          4 :     FOREACH_LUT(lut, ipt) {
     953                 :          2 :         struct RGB rgb = ipt2rgb(ipt, dst);
     954                 :          2 :         rgb.R *= gain;
     955                 :          2 :         rgb.G *= gain;
     956                 :          2 :         rgb.B *= gain;
     957                 :          2 :         ipt = rgb2ipt(rgb, dst);
     958                 :          2 :         ipt = clip_gamma(ipt, c->colorimetric_gamma, dst);
     959                 :            :     }
     960                 :          2 : }
     961                 :            : 
     962                 :            : const struct pl_gamut_map_function pl_gamut_map_darken = {
     963                 :            :     .name = "darken",
     964                 :            :     .description = "Darken and clip",
     965                 :            :     .map = darken,
     966                 :            : };
     967                 :            : 
     968                 :          2 : static void noop(float *lut, const struct pl_gamut_map_params *params)
     969                 :            : {
     970                 :          2 :     return;
     971                 :            : }
     972                 :            : 
     973                 :            : const struct pl_gamut_map_function pl_gamut_map_clip = {
     974                 :            :     .name = "clip",
     975                 :            :     .description = "No gamut mapping (hard clip)",
     976                 :            :     .map = noop,
     977                 :            : };
     978                 :            : 
     979                 :            : const struct pl_gamut_map_function * const pl_gamut_map_functions[] = {
     980                 :            :     &pl_gamut_map_clip,
     981                 :            :     &pl_gamut_map_perceptual,
     982                 :            :     &pl_gamut_map_softclip,
     983                 :            :     &pl_gamut_map_relative,
     984                 :            :     &pl_gamut_map_saturation,
     985                 :            :     &pl_gamut_map_absolute,
     986                 :            :     &pl_gamut_map_desaturate,
     987                 :            :     &pl_gamut_map_darken,
     988                 :            :     &pl_gamut_map_highlight,
     989                 :            :     &pl_gamut_map_linear,
     990                 :            :     NULL
     991                 :            : };
     992                 :            : 
     993                 :            : const int pl_num_gamut_map_functions = PL_ARRAY_SIZE(pl_gamut_map_functions) - 1;
     994                 :            : 
     995                 :          0 : const struct pl_gamut_map_function *pl_find_gamut_map_function(const char *name)
     996                 :            : {
     997         [ #  # ]:          0 :     for (int i = 0; i < pl_num_gamut_map_functions; i++) {
     998         [ #  # ]:          0 :         if (strcmp(name, pl_gamut_map_functions[i]->name) == 0)
     999                 :          0 :             return pl_gamut_map_functions[i];
    1000                 :            :     }
    1001                 :            : 
    1002                 :            :     return NULL;
    1003                 :            : }

Generated by: LCOV version 1.16