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_COLOR_ENCODING_INTERNAL_H_
7 #define LIB_JXL_COLOR_ENCODING_INTERNAL_H_
8 
9 // Metadata for color space conversions.
10 
11 #include <stddef.h>
12 #include <stdint.h>
13 #include <stdlib.h>
14 
15 #include <cmath>  // std::abs
16 #include <ostream>
17 #include <string>
18 #include <vector>
19 
20 #include "jxl/color_encoding.h"
21 #include "lib/jxl/base/compiler_specific.h"
22 #include "lib/jxl/base/padded_bytes.h"
23 #include "lib/jxl/base/status.h"
24 #include "lib/jxl/field_encodings.h"
25 
26 namespace jxl {
27 
28 // (All CIE units are for the standard 1931 2 degree observer)
29 
30 // Color space the color pixel data is encoded in. The color pixel data is
31 // 3-channel in all cases except in case of kGray, where it uses only 1 channel.
32 // This also determines the amount of channels used in modular encoding.
33 enum class ColorSpace : uint32_t {
34   // Trichromatic color data. This also includes CMYK if a kBlack
35   // ExtraChannelInfo is present. This implies, if there is an ICC profile, that
36   // the ICC profile uses a 3-channel color space if no kBlack extra channel is
37   // present, or uses color space 'CMYK' if a kBlack extra channel is present.
38   kRGB,
39   // Single-channel data. This implies, if there is an ICC profile, that the ICC
40   // profile also represents single-channel data and has the appropriate color
41   // space ('GRAY').
42   kGray,
43   // Like kRGB, but implies fixed values for primaries etc.
44   kXYB,
45   // For non-RGB/gray data, e.g. from non-electro-optical sensors. Otherwise
46   // the same conditions as kRGB apply.
47   kUnknown
48 };
49 
EnumName(ColorSpace)50 static inline const char* EnumName(ColorSpace /*unused*/) {
51   return "ColorSpace";
52 }
EnumBits(ColorSpace)53 static inline constexpr uint64_t EnumBits(ColorSpace /*unused*/) {
54   using CS = ColorSpace;
55   return MakeBit(CS::kRGB) | MakeBit(CS::kGray) | MakeBit(CS::kXYB) |
56          MakeBit(CS::kUnknown);
57 }
58 
59 // Values from CICP ColourPrimaries.
60 enum class WhitePoint : uint32_t {
61   kD65 = 1,     // sRGB/BT.709/Display P3/BT.2020
62   kCustom = 2,  // Actual values encoded in separate fields
63   kE = 10,      // XYZ
64   kDCI = 11,    // DCI-P3
65 };
66 
EnumName(WhitePoint)67 static inline const char* EnumName(WhitePoint /*unused*/) {
68   return "WhitePoint";
69 }
EnumBits(WhitePoint)70 static inline constexpr uint64_t EnumBits(WhitePoint /*unused*/) {
71   return MakeBit(WhitePoint::kD65) | MakeBit(WhitePoint::kCustom) |
72          MakeBit(WhitePoint::kE) | MakeBit(WhitePoint::kDCI);
73 }
74 
75 // Values from CICP ColourPrimaries
76 enum class Primaries : uint32_t {
77   kSRGB = 1,    // Same as BT.709
78   kCustom = 2,  // Actual values encoded in separate fields
79   k2100 = 9,    // Same as BT.2020
80   kP3 = 11,
81 };
82 
EnumName(Primaries)83 static inline const char* EnumName(Primaries /*unused*/) { return "Primaries"; }
EnumBits(Primaries)84 static inline constexpr uint64_t EnumBits(Primaries /*unused*/) {
85   using Pr = Primaries;
86   return MakeBit(Pr::kSRGB) | MakeBit(Pr::kCustom) | MakeBit(Pr::k2100) |
87          MakeBit(Pr::kP3);
88 }
89 
90 // Values from CICP TransferCharacteristics
91 enum TransferFunction : uint32_t {
92   k709 = 1,
93   kUnknown = 2,
94   kLinear = 8,
95   kSRGB = 13,
96   kPQ = 16,   // from BT.2100
97   kDCI = 17,  // from SMPTE RP 431-2 reference projector
98   kHLG = 18,  // from BT.2100
99 };
100 
EnumName(TransferFunction)101 static inline const char* EnumName(TransferFunction /*unused*/) {
102   return "TransferFunction";
103 }
EnumBits(TransferFunction)104 static inline constexpr uint64_t EnumBits(TransferFunction /*unused*/) {
105   using TF = TransferFunction;
106   return MakeBit(TF::k709) | MakeBit(TF::kLinear) | MakeBit(TF::kSRGB) |
107          MakeBit(TF::kPQ) | MakeBit(TF::kDCI) | MakeBit(TF::kHLG) |
108          MakeBit(TF::kUnknown);
109 }
110 
111 enum class RenderingIntent : uint32_t {
112   // Values match ICC sRGB encodings.
113   kPerceptual = 0,  // good for photos, requires a profile with LUT.
114   kRelative,        // good for logos.
115   kSaturation,      // perhaps useful for CG with fully saturated colors.
116   kAbsolute,        // leaves white point unchanged; good for proofing.
117 };
118 
EnumName(RenderingIntent)119 static inline const char* EnumName(RenderingIntent /*unused*/) {
120   return "RenderingIntent";
121 }
EnumBits(RenderingIntent)122 static inline constexpr uint64_t EnumBits(RenderingIntent /*unused*/) {
123   using RI = RenderingIntent;
124   return MakeBit(RI::kPerceptual) | MakeBit(RI::kRelative) |
125          MakeBit(RI::kSaturation) | MakeBit(RI::kAbsolute);
126 }
127 
128 // Chromaticity (Y is omitted because it is 1 for primaries/white points)
129 struct CIExy {
130   double x = 0.0;
131   double y = 0.0;
132 };
133 
134 struct PrimariesCIExy {
135   CIExy r;
136   CIExy g;
137   CIExy b;
138 };
139 
140 // Serializable form of CIExy.
141 struct Customxy : public Fields {
142   Customxy();
NameCustomxy143   const char* Name() const override { return "Customxy"; }
144 
145   Status VisitFields(Visitor* JXL_RESTRICT visitor) override;
146 
147   CIExy Get() const;
148   // Returns false if x or y do not fit in the encoding.
149   Status Set(const CIExy& xy);
150 
151   int32_t x;
152   int32_t y;
153 };
154 
155 struct CustomTransferFunction : public Fields {
156   CustomTransferFunction();
NameCustomTransferFunction157   const char* Name() const override { return "CustomTransferFunction"; }
158 
159   // Sets fields and returns true if nonserialized_color_space has an implicit
160   // transfer function, otherwise leaves fields unchanged and returns false.
161   bool SetImplicit();
162 
163   // Gamma: only used for PNG inputs
IsGammaCustomTransferFunction164   bool IsGamma() const { return have_gamma_; }
GetGammaCustomTransferFunction165   double GetGamma() const {
166     JXL_ASSERT(IsGamma());
167     return gamma_ * 1E-7;  // (0, 1)
168   }
169   Status SetGamma(double gamma);
170 
GetTransferFunctionCustomTransferFunction171   TransferFunction GetTransferFunction() const {
172     JXL_ASSERT(!IsGamma());
173     return transfer_function_;
174   }
SetTransferFunctionCustomTransferFunction175   void SetTransferFunction(const TransferFunction tf) {
176     have_gamma_ = false;
177     transfer_function_ = tf;
178   }
179 
IsUnknownCustomTransferFunction180   bool IsUnknown() const {
181     return !have_gamma_ && (transfer_function_ == TransferFunction::kUnknown);
182   }
IsSRGBCustomTransferFunction183   bool IsSRGB() const {
184     return !have_gamma_ && (transfer_function_ == TransferFunction::kSRGB);
185   }
IsLinearCustomTransferFunction186   bool IsLinear() const {
187     return !have_gamma_ && (transfer_function_ == TransferFunction::kLinear);
188   }
IsPQCustomTransferFunction189   bool IsPQ() const {
190     return !have_gamma_ && (transfer_function_ == TransferFunction::kPQ);
191   }
IsHLGCustomTransferFunction192   bool IsHLG() const {
193     return !have_gamma_ && (transfer_function_ == TransferFunction::kHLG);
194   }
Is709CustomTransferFunction195   bool Is709() const {
196     return !have_gamma_ && (transfer_function_ == TransferFunction::k709);
197   }
IsDCICustomTransferFunction198   bool IsDCI() const {
199     return !have_gamma_ && (transfer_function_ == TransferFunction::kDCI);
200   }
IsSameCustomTransferFunction201   bool IsSame(const CustomTransferFunction& other) const {
202     if (have_gamma_ != other.have_gamma_) return false;
203     if (have_gamma_) {
204       if (gamma_ != other.gamma_) return false;
205     } else {
206       if (transfer_function_ != other.transfer_function_) return false;
207     }
208     return true;
209   }
210 
211   Status VisitFields(Visitor* JXL_RESTRICT visitor) override;
212 
213   // Must be set before calling VisitFields!
214   ColorSpace nonserialized_color_space = ColorSpace::kRGB;
215 
216  private:
217   static constexpr uint32_t kGammaMul = 10000000;
218 
219   bool have_gamma_;
220 
221   // OETF exponent to go from linear to gamma-compressed.
222   uint32_t gamma_;  // Only used if have_gamma_.
223 
224   // Can be kUnknown.
225   TransferFunction transfer_function_;  // Only used if !have_gamma_.
226 };
227 
228 // Compact encoding of data required to interpret and translate pixels to a
229 // known color space. Stored in Metadata. Thread-compatible.
230 struct ColorEncoding : public Fields {
231   ColorEncoding();
NameColorEncoding232   const char* Name() const override { return "ColorEncoding"; }
233 
234   // Returns ready-to-use color encodings (initialized on-demand).
235   static const ColorEncoding& SRGB(bool is_gray = false);
236   static const ColorEncoding& LinearSRGB(bool is_gray = false);
237 
238   // Returns true if an ICC profile was successfully created from fields.
239   // Must be called after modifying fields. Defined in color_management.cc.
240   Status CreateICC();
241 
242   // Returns non-empty and valid ICC profile, unless:
243   // - between calling InternalRemoveICC() and CreateICC() in tests;
244   // - WantICC() == true and SetICC() was not yet called;
245   // - after a failed call to SetSRGB(), SetICC(), or CreateICC().
ICCColorEncoding246   const PaddedBytes& ICC() const { return icc_; }
247 
248   // Internal only, do not call except from tests.
InternalRemoveICCColorEncoding249   void InternalRemoveICC() { icc_.clear(); }
250 
251   // Returns true if `icc` is assigned and decoded successfully. If so,
252   // subsequent WantICC() will return true until DecideIfWantICC() changes it.
253   // Returning false indicates data has been lost.
SetICCColorEncoding254   Status SetICC(PaddedBytes&& icc) {
255     if (icc.empty()) return false;
256     icc_ = std::move(icc);
257 
258     if (!SetFieldsFromICC()) {
259       InternalRemoveICC();
260       return false;
261     }
262 
263     want_icc_ = true;
264     return true;
265   }
266 
267   // Sets the raw ICC profile bytes, without parsing the ICC, and without
268   // updating the direct fields such as whitepoint, primaries and color
269   // space. Functions to get and set fields, such as SetWhitePoint, cannot be
270   // used anymore after this and functions such as IsSRGB return false no matter
271   // what the contents of the icc profile.
SetICCRawColorEncoding272   Status SetICCRaw(PaddedBytes&& icc) {
273     if (icc.empty()) return false;
274     icc_ = std::move(icc);
275 
276     want_icc_ = true;
277     have_fields_ = false;
278     return true;
279   }
280 
281   // Returns whether to send the ICC profile in the codestream.
WantICCColorEncoding282   bool WantICC() const { return want_icc_; }
283 
284   // Return whether the direct fields are set, if false but ICC is set, only
285   // raw ICC bytes are known.
HaveFieldsColorEncoding286   bool HaveFields() const { return have_fields_; }
287 
288   // Causes WantICC() to return false if ICC() can be reconstructed from fields.
289   // Defined in color_management.cc.
290   void DecideIfWantICC();
291 
IsGrayColorEncoding292   bool IsGray() const { return color_space_ == ColorSpace::kGray; }
ChannelsColorEncoding293   size_t Channels() const { return IsGray() ? 1 : 3; }
294 
295   // Returns false if the field is invalid and unusable.
HasPrimariesColorEncoding296   bool HasPrimaries() const {
297     return !IsGray() && color_space_ != ColorSpace::kXYB;
298   }
299 
300   // Returns true after setting the field to a value defined by color_space,
301   // otherwise false and leaves the field unchanged.
ImplicitWhitePointColorEncoding302   bool ImplicitWhitePoint() {
303     if (color_space_ == ColorSpace::kXYB) {
304       white_point = WhitePoint::kD65;
305       return true;
306     }
307     return false;
308   }
309 
310   // Returns whether the color space is known to be sRGB. If a raw unparsed ICC
311   // profile is set without the fields being set, this returns false, even if
312   // the content of the ICC profile would match sRGB.
IsSRGBColorEncoding313   bool IsSRGB() const {
314     if (!have_fields_) return false;
315     if (!IsGray() && color_space_ != ColorSpace::kRGB) return false;
316     if (white_point != WhitePoint::kD65) return false;
317     if (primaries != Primaries::kSRGB) return false;
318     if (!tf.IsSRGB()) return false;
319     return true;
320   }
321 
322   // Returns whether the color space is known to be linear sRGB. If a raw
323   // unparsed ICC profile is set without the fields being set, this returns
324   // false, even if the content of the ICC profile would match linear sRGB.
IsLinearSRGBColorEncoding325   bool IsLinearSRGB() const {
326     if (!have_fields_) return false;
327     if (!IsGray() && color_space_ != ColorSpace::kRGB) return false;
328     if (white_point != WhitePoint::kD65) return false;
329     if (primaries != Primaries::kSRGB) return false;
330     if (!tf.IsLinear()) return false;
331     return true;
332   }
333 
334   Status SetSRGB(const ColorSpace cs,
335                  const RenderingIntent ri = RenderingIntent::kRelative) {
336     InternalRemoveICC();
337     JXL_ASSERT(cs == ColorSpace::kGray || cs == ColorSpace::kRGB);
338     color_space_ = cs;
339     white_point = WhitePoint::kD65;
340     primaries = Primaries::kSRGB;
341     tf.SetTransferFunction(TransferFunction::kSRGB);
342     rendering_intent = ri;
343     return CreateICC();
344   }
345 
346   Status VisitFields(Visitor* JXL_RESTRICT visitor) override;
347 
348   // Accessors ensure tf.nonserialized_color_space is updated at the same time.
GetColorSpaceColorEncoding349   ColorSpace GetColorSpace() const { return color_space_; }
SetColorSpaceColorEncoding350   void SetColorSpace(const ColorSpace cs) {
351     color_space_ = cs;
352     tf.nonserialized_color_space = cs;
353   }
354 
355   CIExy GetWhitePoint() const;
356   Status SetWhitePoint(const CIExy& xy);
357 
358   PrimariesCIExy GetPrimaries() const;
359   Status SetPrimaries(const PrimariesCIExy& xy);
360 
361   // Checks if the color spaces (including white point / primaries) are the
362   // same, but ignores the transfer function, rendering intent and ICC bytes.
SameColorSpaceColorEncoding363   bool SameColorSpace(const ColorEncoding& other) const {
364     if (color_space_ != other.color_space_) return false;
365 
366     if (white_point != other.white_point) return false;
367     if (white_point == WhitePoint::kCustom) {
368       if (white_.x != other.white_.x || white_.y != other.white_.y)
369         return false;
370     }
371 
372     if (HasPrimaries() != other.HasPrimaries()) return false;
373     if (HasPrimaries()) {
374       if (primaries != other.primaries) return false;
375       if (primaries == Primaries::kCustom) {
376         if (red_.x != other.red_.x || red_.y != other.red_.y) return false;
377         if (green_.x != other.green_.x || green_.y != other.green_.y)
378           return false;
379         if (blue_.x != other.blue_.x || blue_.y != other.blue_.y) return false;
380       }
381     }
382     return true;
383   }
384 
385   // Checks if the color space and transfer function are the same, ignoring
386   // rendering intent and ICC bytes
SameColorEncodingColorEncoding387   bool SameColorEncoding(const ColorEncoding& other) const {
388     return SameColorSpace(other) && tf.IsSame(other.tf);
389   }
390 
391   mutable bool all_default;
392 
393   // Only valid if HaveFields()
394   WhitePoint white_point;
395   Primaries primaries;  // Only valid if HasPrimaries()
396   CustomTransferFunction tf;
397   RenderingIntent rendering_intent;
398 
399  private:
400   // Returns true if all fields have been initialized (possibly to kUnknown).
401   // Returns false if the ICC profile is invalid or decoding it fails.
402   // Defined in color_management.cc.
403   Status SetFieldsFromICC();
404 
405   // If true, the codestream contains an ICC profile and we do not serialize
406   // fields. Otherwise, fields are serialized and we create an ICC profile.
407   bool want_icc_;
408 
409   // When false, fields such as white_point and tf are invalid and must not be
410   // used. This occurs after setting a raw bytes-only ICC profile, only the
411   // ICC bytes may be used. The color_space_ field is still valid.
412   bool have_fields_ = true;
413 
414   PaddedBytes icc_;  // Valid ICC profile
415 
416   ColorSpace color_space_;  // Can be kUnknown
417 
418   // Only used if white_point == kCustom.
419   Customxy white_;
420 
421   // Only used if primaries == kCustom.
422   Customxy red_;
423   Customxy green_;
424   Customxy blue_;
425 };
426 
427 // Returns whether the two inputs are approximately equal.
428 static inline bool ApproxEq(const double a, const double b,
429 #if JPEGXL_ENABLE_SKCMS
430                             double max_l1 = 1E-3) {
431 #else
432                             double max_l1 = 8E-5) {
433 #endif
434   // Threshold should be sufficient for ICC's 15-bit fixed-point numbers.
435   // We have seen differences of 7.1E-5 with lcms2 and 1E-3 with skcms.
436   return std::abs(a - b) <= max_l1;
437 }
438 
439 // Returns a representation of the ColorEncoding fields (not icc).
440 // Example description: "RGB_D65_SRG_Rel_Lin"
441 std::string Description(const ColorEncoding& c);
442 static inline std::ostream& operator<<(std::ostream& os,
443                                        const ColorEncoding& c) {
444   return os << Description(c);
445 }
446 
447 void ConvertInternalToExternalColorEncoding(const jxl::ColorEncoding& internal,
448                                             JxlColorEncoding* external);
449 
450 Status ConvertExternalToInternalColorEncoding(const JxlColorEncoding& external,
451                                               jxl::ColorEncoding* internal);
452 
453 Status PrimariesToXYZD50(float rx, float ry, float gx, float gy, float bx,
454                          float by, float wx, float wy, float matrix[9]);
455 Status AdaptToXYZD50(float wx, float wy, float matrix[9]);
456 
457 }  // namespace jxl
458 
459 #endif  // LIB_JXL_COLOR_ENCODING_INTERNAL_H_
460