libjxl

FORK: libjxl patches used on blog
git clone https://git.neptards.moe/blog/libjxl.git
Log | Files | Refs | Submodules | README | LICENSE

color_encoding_cms.h (19960B)


      1 // Copyright (c) the JPEG XL Project Authors. All rights reserved.
      2 //
      3 // Use of this source code is governed by a BSD-style
      4 // license that can be found in the LICENSE file.
      5 
      6 #ifndef LIB_JXL_CMS_COLOR_ENCODING_CMS_H_
      7 #define LIB_JXL_CMS_COLOR_ENCODING_CMS_H_
      8 
      9 #include <jxl/cms_interface.h>
     10 #include <jxl/color_encoding.h>
     11 #include <jxl/types.h>
     12 
     13 #include <cmath>
     14 #include <cstdint>
     15 #include <cstring>
     16 #include <utility>
     17 #include <vector>
     18 
     19 #include "lib/jxl/base/status.h"
     20 
     21 namespace jxl {
     22 namespace cms {
     23 
     24 using IccBytes = std::vector<uint8_t>;
     25 
     26 // Returns whether the two inputs are approximately equal.
     27 static inline bool ApproxEq(const double a, const double b,
     28                             double max_l1 = 1E-3) {
     29   // Threshold should be sufficient for ICC's 15-bit fixed-point numbers.
     30   // We have seen differences of 7.1E-5 with lcms2 and 1E-3 with skcms.
     31   return std::abs(a - b) <= max_l1;
     32 }
     33 
     34 // (All CIE units are for the standard 1931 2 degree observer)
     35 
     36 // Color space the color pixel data is encoded in. The color pixel data is
     37 // 3-channel in all cases except in case of kGray, where it uses only 1 channel.
     38 // This also determines the amount of channels used in modular encoding.
     39 enum class ColorSpace : uint32_t {
     40   // Trichromatic color data. This also includes CMYK if a kBlack
     41   // ExtraChannelInfo is present. This implies, if there is an ICC profile, that
     42   // the ICC profile uses a 3-channel color space if no kBlack extra channel is
     43   // present, or uses color space 'CMYK' if a kBlack extra channel is present.
     44   kRGB,
     45   // Single-channel data. This implies, if there is an ICC profile, that the ICC
     46   // profile also represents single-channel data and has the appropriate color
     47   // space ('GRAY').
     48   kGray,
     49   // Like kRGB, but implies fixed values for primaries etc.
     50   kXYB,
     51   // For non-RGB/gray data, e.g. from non-electro-optical sensors. Otherwise
     52   // the same conditions as kRGB apply.
     53   kUnknown
     54   // NB: don't forget to update EnumBits!
     55 };
     56 
     57 // Values from CICP ColourPrimaries.
     58 enum class WhitePoint : uint32_t {
     59   kD65 = 1,     // sRGB/BT.709/Display P3/BT.2020
     60   kCustom = 2,  // Actual values encoded in separate fields
     61   kE = 10,      // XYZ
     62   kDCI = 11,    // DCI-P3
     63   // NB: don't forget to update EnumBits!
     64 };
     65 
     66 // Values from CICP ColourPrimaries
     67 enum class Primaries : uint32_t {
     68   kSRGB = 1,    // Same as BT.709
     69   kCustom = 2,  // Actual values encoded in separate fields
     70   k2100 = 9,    // Same as BT.2020
     71   kP3 = 11,
     72   // NB: don't forget to update EnumBits!
     73 };
     74 
     75 // Values from CICP TransferCharacteristics
     76 enum class TransferFunction : uint32_t {
     77   k709 = 1,
     78   kUnknown = 2,
     79   kLinear = 8,
     80   kSRGB = 13,
     81   kPQ = 16,   // from BT.2100
     82   kDCI = 17,  // from SMPTE RP 431-2 reference projector
     83   kHLG = 18,  // from BT.2100
     84   // NB: don't forget to update EnumBits!
     85 };
     86 
     87 enum class RenderingIntent : uint32_t {
     88   // Values match ICC sRGB encodings.
     89   kPerceptual = 0,  // good for photos, requires a profile with LUT.
     90   kRelative,        // good for logos.
     91   kSaturation,      // perhaps useful for CG with fully saturated colors.
     92   kAbsolute,        // leaves white point unchanged; good for proofing.
     93   // NB: don't forget to update EnumBits!
     94 };
     95 
     96 // Chromaticity (Y is omitted because it is 1 for white points and implicit for
     97 // primaries)
     98 struct CIExy {
     99   double x = 0.0;
    100   double y = 0.0;
    101 };
    102 
    103 struct PrimariesCIExy {
    104   CIExy r;
    105   CIExy g;
    106   CIExy b;
    107 };
    108 
    109 // Serializable form of CIExy.
    110 struct Customxy {
    111   static constexpr uint32_t kMul = 1000000;
    112   static constexpr double kRoughLimit = 4.0;
    113   static constexpr int32_t kMin = -0x200000;
    114   static constexpr int32_t kMax = 0x1FFFFF;
    115 
    116   int32_t x = 0;
    117   int32_t y = 0;
    118 
    119   CIExy GetValue() const {
    120     CIExy xy;
    121     xy.x = x * (1.0 / kMul);
    122     xy.y = y * (1.0 / kMul);
    123     return xy;
    124   }
    125 
    126   Status SetValue(const CIExy& xy) {
    127     bool ok = (std::abs(xy.x) < kRoughLimit) && (std::abs(xy.y) < kRoughLimit);
    128     if (!ok) return JXL_FAILURE("X or Y is out of bounds");
    129     x = static_cast<int32_t>(roundf(xy.x * kMul));
    130     if (x < kMin || x > kMax) return JXL_FAILURE("X is out of bounds");
    131     y = static_cast<int32_t>(roundf(xy.y * kMul));
    132     if (y < kMin || y > kMax) return JXL_FAILURE("Y is out of bounds");
    133     return true;
    134   }
    135 
    136   bool IsSame(const Customxy& other) const {
    137     return (x == other.x) && (y == other.y);
    138   }
    139 };
    140 
    141 static inline Status WhitePointFromExternal(const JxlWhitePoint external,
    142                                             WhitePoint* out) {
    143   switch (external) {
    144     case JXL_WHITE_POINT_D65:
    145       *out = WhitePoint::kD65;
    146       return true;
    147     case JXL_WHITE_POINT_CUSTOM:
    148       *out = WhitePoint::kCustom;
    149       return true;
    150     case JXL_WHITE_POINT_E:
    151       *out = WhitePoint::kE;
    152       return true;
    153     case JXL_WHITE_POINT_DCI:
    154       *out = WhitePoint::kDCI;
    155       return true;
    156   }
    157   return JXL_FAILURE("Invalid WhitePoint enum value %d",
    158                      static_cast<int>(external));
    159 }
    160 
    161 static inline Status PrimariesFromExternal(const JxlPrimaries external,
    162                                            Primaries* out) {
    163   switch (external) {
    164     case JXL_PRIMARIES_SRGB:
    165       *out = Primaries::kSRGB;
    166       return true;
    167     case JXL_PRIMARIES_CUSTOM:
    168       *out = Primaries::kCustom;
    169       return true;
    170     case JXL_PRIMARIES_2100:
    171       *out = Primaries::k2100;
    172       return true;
    173     case JXL_PRIMARIES_P3:
    174       *out = Primaries::kP3;
    175       return true;
    176   }
    177   return JXL_FAILURE("Invalid Primaries enum value");
    178 }
    179 
    180 static inline Status RenderingIntentFromExternal(
    181     const JxlRenderingIntent external, RenderingIntent* out) {
    182   switch (external) {
    183     case JXL_RENDERING_INTENT_PERCEPTUAL:
    184       *out = RenderingIntent::kPerceptual;
    185       return true;
    186     case JXL_RENDERING_INTENT_RELATIVE:
    187       *out = RenderingIntent::kRelative;
    188       return true;
    189     case JXL_RENDERING_INTENT_SATURATION:
    190       *out = RenderingIntent::kSaturation;
    191       return true;
    192     case JXL_RENDERING_INTENT_ABSOLUTE:
    193       *out = RenderingIntent::kAbsolute;
    194       return true;
    195   }
    196   return JXL_FAILURE("Invalid RenderingIntent enum value");
    197 }
    198 
    199 struct CustomTransferFunction {
    200   // Highest reasonable value for the gamma of a transfer curve.
    201   static constexpr uint32_t kMaxGamma = 8192;
    202   static constexpr uint32_t kGammaMul = 10000000;
    203 
    204   bool have_gamma = false;
    205 
    206   // OETF exponent to go from linear to gamma-compressed.
    207   uint32_t gamma = 0;  // Only used if have_gamma_.
    208 
    209   // Can be kUnknown.
    210   TransferFunction transfer_function =
    211       TransferFunction::kSRGB;  // Only used if !have_gamma_.
    212 
    213   TransferFunction GetTransferFunction() const {
    214     JXL_ASSERT(!have_gamma);
    215     return transfer_function;
    216   }
    217   void SetTransferFunction(const TransferFunction tf) {
    218     have_gamma = false;
    219     transfer_function = tf;
    220   }
    221 
    222   bool IsUnknown() const {
    223     return !have_gamma && (transfer_function == TransferFunction::kUnknown);
    224   }
    225   bool IsSRGB() const {
    226     return !have_gamma && (transfer_function == TransferFunction::kSRGB);
    227   }
    228   bool IsLinear() const {
    229     return !have_gamma && (transfer_function == TransferFunction::kLinear);
    230   }
    231   bool IsPQ() const {
    232     return !have_gamma && (transfer_function == TransferFunction::kPQ);
    233   }
    234   bool IsHLG() const {
    235     return !have_gamma && (transfer_function == TransferFunction::kHLG);
    236   }
    237   bool Is709() const {
    238     return !have_gamma && (transfer_function == TransferFunction::k709);
    239   }
    240   bool IsDCI() const {
    241     return !have_gamma && (transfer_function == TransferFunction::kDCI);
    242   }
    243 
    244   double GetGamma() const {
    245     JXL_ASSERT(have_gamma);
    246     return gamma * (1.0 / kGammaMul);  // (0, 1)
    247   }
    248   Status SetGamma(double new_gamma) {
    249     if (new_gamma < (1.0 / kMaxGamma) || new_gamma > 1.0) {
    250       return JXL_FAILURE("Invalid gamma %f", new_gamma);
    251     }
    252 
    253     have_gamma = false;
    254     if (ApproxEq(new_gamma, 1.0)) {
    255       transfer_function = TransferFunction::kLinear;
    256       return true;
    257     }
    258     if (ApproxEq(new_gamma, 1.0 / 2.6)) {
    259       transfer_function = TransferFunction::kDCI;
    260       return true;
    261     }
    262     // Don't translate 0.45.. to kSRGB nor k709 - that might change pixel
    263     // values because those curves also have a linear part.
    264 
    265     have_gamma = true;
    266     gamma = roundf(new_gamma * kGammaMul);
    267     transfer_function = TransferFunction::kUnknown;
    268     return true;
    269   }
    270 
    271   bool IsSame(const CustomTransferFunction& other) const {
    272     if (have_gamma != other.have_gamma) {
    273       return false;
    274     }
    275     if (have_gamma) {
    276       if (gamma != other.gamma) {
    277         return false;
    278       }
    279     } else {
    280       if (transfer_function != other.transfer_function) {
    281         return false;
    282       }
    283     }
    284     return true;
    285   }
    286 };
    287 
    288 static inline Status ConvertExternalToInternalTransferFunction(
    289     const JxlTransferFunction external, TransferFunction* internal) {
    290   switch (external) {
    291     case JXL_TRANSFER_FUNCTION_709:
    292       *internal = TransferFunction::k709;
    293       return true;
    294     case JXL_TRANSFER_FUNCTION_UNKNOWN:
    295       *internal = TransferFunction::kUnknown;
    296       return true;
    297     case JXL_TRANSFER_FUNCTION_LINEAR:
    298       *internal = TransferFunction::kLinear;
    299       return true;
    300     case JXL_TRANSFER_FUNCTION_SRGB:
    301       *internal = TransferFunction::kSRGB;
    302       return true;
    303     case JXL_TRANSFER_FUNCTION_PQ:
    304       *internal = TransferFunction::kPQ;
    305       return true;
    306     case JXL_TRANSFER_FUNCTION_DCI:
    307       *internal = TransferFunction::kDCI;
    308       return true;
    309     case JXL_TRANSFER_FUNCTION_HLG:
    310       *internal = TransferFunction::kHLG;
    311       return true;
    312     case JXL_TRANSFER_FUNCTION_GAMMA:
    313       return JXL_FAILURE("Gamma should be handled separately");
    314   }
    315   return JXL_FAILURE("Invalid TransferFunction enum value");
    316 }
    317 
    318 // Compact encoding of data required to interpret and translate pixels to a
    319 // known color space. Stored in Metadata. Thread-compatible.
    320 struct ColorEncoding {
    321   // Only valid if HaveFields()
    322   WhitePoint white_point = WhitePoint::kD65;
    323   Primaries primaries = Primaries::kSRGB;  // Only valid if HasPrimaries()
    324   RenderingIntent rendering_intent = RenderingIntent::kRelative;
    325 
    326   // When false, fields such as white_point and tf are invalid and must not be
    327   // used. This occurs after setting a raw bytes-only ICC profile, only the
    328   // ICC bytes may be used. The color_space_ field is still valid.
    329   bool have_fields = true;
    330 
    331   IccBytes icc;  // Valid ICC profile
    332 
    333   ColorSpace color_space = ColorSpace::kRGB;  // Can be kUnknown
    334   bool cmyk = false;
    335 
    336   // "late sync" fields
    337   CustomTransferFunction tf;
    338   Customxy white;  // Only used if white_point == kCustom
    339   Customxy red;    // Only used if primaries == kCustom
    340   Customxy green;  // Only used if primaries == kCustom
    341   Customxy blue;   // Only used if primaries == kCustom
    342 
    343   // Returns false if the field is invalid and unusable.
    344   bool HasPrimaries() const {
    345     return (color_space != ColorSpace::kGray) &&
    346            (color_space != ColorSpace::kXYB);
    347   }
    348 
    349   size_t Channels() const { return (color_space == ColorSpace::kGray) ? 1 : 3; }
    350 
    351   PrimariesCIExy GetPrimaries() const {
    352     JXL_DASSERT(have_fields);
    353     JXL_ASSERT(HasPrimaries());
    354     PrimariesCIExy xy;
    355     switch (primaries) {
    356       case Primaries::kCustom:
    357         xy.r = red.GetValue();
    358         xy.g = green.GetValue();
    359         xy.b = blue.GetValue();
    360         return xy;
    361 
    362       case Primaries::kSRGB:
    363         xy.r.x = 0.639998686;
    364         xy.r.y = 0.330010138;
    365         xy.g.x = 0.300003784;
    366         xy.g.y = 0.600003357;
    367         xy.b.x = 0.150002046;
    368         xy.b.y = 0.059997204;
    369         return xy;
    370 
    371       case Primaries::k2100:
    372         xy.r.x = 0.708;
    373         xy.r.y = 0.292;
    374         xy.g.x = 0.170;
    375         xy.g.y = 0.797;
    376         xy.b.x = 0.131;
    377         xy.b.y = 0.046;
    378         return xy;
    379 
    380       case Primaries::kP3:
    381         xy.r.x = 0.680;
    382         xy.r.y = 0.320;
    383         xy.g.x = 0.265;
    384         xy.g.y = 0.690;
    385         xy.b.x = 0.150;
    386         xy.b.y = 0.060;
    387         return xy;
    388     }
    389     JXL_UNREACHABLE("Invalid Primaries %u", static_cast<uint32_t>(primaries));
    390   }
    391 
    392   Status SetPrimaries(const PrimariesCIExy& xy) {
    393     JXL_DASSERT(have_fields);
    394     JXL_ASSERT(HasPrimaries());
    395     if (xy.r.x == 0.0 || xy.r.y == 0.0 || xy.g.x == 0.0 || xy.g.y == 0.0 ||
    396         xy.b.x == 0.0 || xy.b.y == 0.0) {
    397       return JXL_FAILURE("Invalid primaries %f %f %f %f %f %f", xy.r.x, xy.r.y,
    398                          xy.g.x, xy.g.y, xy.b.x, xy.b.y);
    399     }
    400 
    401     if (ApproxEq(xy.r.x, 0.64) && ApproxEq(xy.r.y, 0.33) &&
    402         ApproxEq(xy.g.x, 0.30) && ApproxEq(xy.g.y, 0.60) &&
    403         ApproxEq(xy.b.x, 0.15) && ApproxEq(xy.b.y, 0.06)) {
    404       primaries = Primaries::kSRGB;
    405       return true;
    406     }
    407 
    408     if (ApproxEq(xy.r.x, 0.708) && ApproxEq(xy.r.y, 0.292) &&
    409         ApproxEq(xy.g.x, 0.170) && ApproxEq(xy.g.y, 0.797) &&
    410         ApproxEq(xy.b.x, 0.131) && ApproxEq(xy.b.y, 0.046)) {
    411       primaries = Primaries::k2100;
    412       return true;
    413     }
    414     if (ApproxEq(xy.r.x, 0.680) && ApproxEq(xy.r.y, 0.320) &&
    415         ApproxEq(xy.g.x, 0.265) && ApproxEq(xy.g.y, 0.690) &&
    416         ApproxEq(xy.b.x, 0.150) && ApproxEq(xy.b.y, 0.060)) {
    417       primaries = Primaries::kP3;
    418       return true;
    419     }
    420 
    421     primaries = Primaries::kCustom;
    422     JXL_RETURN_IF_ERROR(red.SetValue(xy.r));
    423     JXL_RETURN_IF_ERROR(green.SetValue(xy.g));
    424     JXL_RETURN_IF_ERROR(blue.SetValue(xy.b));
    425     return true;
    426   }
    427 
    428   CIExy GetWhitePoint() const {
    429     JXL_DASSERT(have_fields);
    430     CIExy xy;
    431     switch (white_point) {
    432       case WhitePoint::kCustom:
    433         return white.GetValue();
    434 
    435       case WhitePoint::kD65:
    436         xy.x = 0.3127;
    437         xy.y = 0.3290;
    438         return xy;
    439 
    440       case WhitePoint::kDCI:
    441         // From https://ieeexplore.ieee.org/document/7290729 C.2 page 11
    442         xy.x = 0.314;
    443         xy.y = 0.351;
    444         return xy;
    445 
    446       case WhitePoint::kE:
    447         xy.x = xy.y = 1.0 / 3;
    448         return xy;
    449     }
    450     JXL_UNREACHABLE("Invalid WhitePoint %u",
    451                     static_cast<uint32_t>(white_point));
    452   }
    453 
    454   Status SetWhitePoint(const CIExy& xy) {
    455     JXL_DASSERT(have_fields);
    456     if (xy.x == 0.0 || xy.y == 0.0) {
    457       return JXL_FAILURE("Invalid white point %f %f", xy.x, xy.y);
    458     }
    459     if (ApproxEq(xy.x, 0.3127) && ApproxEq(xy.y, 0.3290)) {
    460       white_point = WhitePoint::kD65;
    461       return true;
    462     }
    463     if (ApproxEq(xy.x, 1.0 / 3) && ApproxEq(xy.y, 1.0 / 3)) {
    464       white_point = WhitePoint::kE;
    465       return true;
    466     }
    467     if (ApproxEq(xy.x, 0.314) && ApproxEq(xy.y, 0.351)) {
    468       white_point = WhitePoint::kDCI;
    469       return true;
    470     }
    471     white_point = WhitePoint::kCustom;
    472     return white.SetValue(xy);
    473   }
    474 
    475   // Checks if the color spaces (including white point / primaries) are the
    476   // same, but ignores the transfer function, rendering intent and ICC bytes.
    477   bool SameColorSpace(const ColorEncoding& other) const {
    478     if (color_space != other.color_space) return false;
    479 
    480     if (white_point != other.white_point) return false;
    481     if (white_point == WhitePoint::kCustom) {
    482       if (!white.IsSame(other.white)) {
    483         return false;
    484       }
    485     }
    486 
    487     if (HasPrimaries() != other.HasPrimaries()) return false;
    488     if (HasPrimaries()) {
    489       if (primaries != other.primaries) return false;
    490       if (primaries == Primaries::kCustom) {
    491         if (!red.IsSame(other.red)) return false;
    492         if (!green.IsSame(other.green)) return false;
    493         if (!blue.IsSame(other.blue)) return false;
    494       }
    495     }
    496     return true;
    497   }
    498 
    499   // Checks if the color space and transfer function are the same, ignoring
    500   // rendering intent and ICC bytes
    501   bool SameColorEncoding(const ColorEncoding& other) const {
    502     return SameColorSpace(other) && tf.IsSame(other.tf);
    503   }
    504 
    505   // Returns true if all fields have been initialized (possibly to kUnknown).
    506   // Returns false if the ICC profile is invalid or decoding it fails.
    507   Status SetFieldsFromICC(IccBytes&& new_icc, const JxlCmsInterface& cms) {
    508     // In case parsing fails, mark the ColorEncoding as invalid.
    509     JXL_ASSERT(!new_icc.empty());
    510     color_space = ColorSpace::kUnknown;
    511     tf.transfer_function = TransferFunction::kUnknown;
    512     icc.clear();
    513 
    514     JxlColorEncoding external;
    515     JXL_BOOL new_cmyk;
    516     JXL_RETURN_IF_ERROR(cms.set_fields_from_icc(cms.set_fields_data,
    517                                                 new_icc.data(), new_icc.size(),
    518                                                 &external, &new_cmyk));
    519     cmyk = static_cast<bool>(new_cmyk);
    520     JXL_RETURN_IF_ERROR(FromExternal(external));
    521     icc = std::move(new_icc);
    522     return true;
    523   }
    524 
    525   JxlColorEncoding ToExternal() const {
    526     JxlColorEncoding external = {};
    527     if (!have_fields) {
    528       external.color_space = JXL_COLOR_SPACE_UNKNOWN;
    529       external.primaries = JXL_PRIMARIES_CUSTOM;
    530       external.rendering_intent = JXL_RENDERING_INTENT_PERCEPTUAL;  //?
    531       external.transfer_function = JXL_TRANSFER_FUNCTION_UNKNOWN;
    532       external.white_point = JXL_WHITE_POINT_CUSTOM;
    533       return external;
    534     }
    535     external.color_space = static_cast<JxlColorSpace>(color_space);
    536 
    537     external.white_point = static_cast<JxlWhitePoint>(white_point);
    538 
    539     CIExy wp = GetWhitePoint();
    540     external.white_point_xy[0] = wp.x;
    541     external.white_point_xy[1] = wp.y;
    542 
    543     if (external.color_space == JXL_COLOR_SPACE_RGB ||
    544         external.color_space == JXL_COLOR_SPACE_UNKNOWN) {
    545       external.primaries = static_cast<JxlPrimaries>(primaries);
    546       PrimariesCIExy p = GetPrimaries();
    547       external.primaries_red_xy[0] = p.r.x;
    548       external.primaries_red_xy[1] = p.r.y;
    549       external.primaries_green_xy[0] = p.g.x;
    550       external.primaries_green_xy[1] = p.g.y;
    551       external.primaries_blue_xy[0] = p.b.x;
    552       external.primaries_blue_xy[1] = p.b.y;
    553     }
    554 
    555     if (tf.have_gamma) {
    556       external.transfer_function = JXL_TRANSFER_FUNCTION_GAMMA;
    557       external.gamma = tf.GetGamma();
    558     } else {
    559       external.transfer_function =
    560           static_cast<JxlTransferFunction>(tf.GetTransferFunction());
    561       external.gamma = 0;
    562     }
    563 
    564     external.rendering_intent =
    565         static_cast<JxlRenderingIntent>(rendering_intent);
    566     return external;
    567   }
    568 
    569   // NB: does not create ICC.
    570   Status FromExternal(const JxlColorEncoding& external) {
    571     // TODO(eustas): update non-serializable on call-site
    572     color_space = static_cast<ColorSpace>(external.color_space);
    573 
    574     JXL_RETURN_IF_ERROR(
    575         WhitePointFromExternal(external.white_point, &white_point));
    576     if (external.white_point == JXL_WHITE_POINT_CUSTOM) {
    577       CIExy wp;
    578       wp.x = external.white_point_xy[0];
    579       wp.y = external.white_point_xy[1];
    580       JXL_RETURN_IF_ERROR(SetWhitePoint(wp));
    581     }
    582 
    583     if (external.color_space == JXL_COLOR_SPACE_RGB ||
    584         external.color_space == JXL_COLOR_SPACE_UNKNOWN) {
    585       JXL_RETURN_IF_ERROR(
    586           PrimariesFromExternal(external.primaries, &primaries));
    587       if (external.primaries == JXL_PRIMARIES_CUSTOM) {
    588         PrimariesCIExy primaries;
    589         primaries.r.x = external.primaries_red_xy[0];
    590         primaries.r.y = external.primaries_red_xy[1];
    591         primaries.g.x = external.primaries_green_xy[0];
    592         primaries.g.y = external.primaries_green_xy[1];
    593         primaries.b.x = external.primaries_blue_xy[0];
    594         primaries.b.y = external.primaries_blue_xy[1];
    595         JXL_RETURN_IF_ERROR(SetPrimaries(primaries));
    596       }
    597     }
    598     CustomTransferFunction tf;
    599     if (external.transfer_function == JXL_TRANSFER_FUNCTION_GAMMA) {
    600       JXL_RETURN_IF_ERROR(tf.SetGamma(external.gamma));
    601     } else {
    602       TransferFunction tf_enum;
    603       // JXL_TRANSFER_FUNCTION_GAMMA is not handled by this function since
    604       // there's no internal enum value for it.
    605       JXL_RETURN_IF_ERROR(ConvertExternalToInternalTransferFunction(
    606           external.transfer_function, &tf_enum));
    607       tf.SetTransferFunction(tf_enum);
    608     }
    609     this->tf = tf;
    610 
    611     JXL_RETURN_IF_ERROR(RenderingIntentFromExternal(external.rendering_intent,
    612                                                     &rendering_intent));
    613 
    614     icc.clear();
    615 
    616     return true;
    617   }
    618 };
    619 
    620 }  // namespace cms
    621 }  // namespace jxl
    622 
    623 #endif  // LIB_JXL_CMS_COLOR_ENCODING_CMS_H_