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