color_encoding_internal.h (12509B)
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 <jxl/cms_interface.h> 12 #include <jxl/color_encoding.h> 13 #include <stddef.h> 14 #include <stdint.h> 15 16 #include <array> 17 #include <cstdlib> // free 18 #include <ostream> 19 #include <string> 20 #include <utility> 21 22 #include "lib/jxl/base/compiler_specific.h" 23 #include "lib/jxl/base/status.h" 24 #include "lib/jxl/cms/color_encoding_cms.h" 25 #include "lib/jxl/cms/jxl_cms_internal.h" 26 #include "lib/jxl/field_encodings.h" 27 28 namespace jxl { 29 30 using IccBytes = ::jxl::cms::IccBytes; 31 using ColorSpace = ::jxl::cms::ColorSpace; 32 using WhitePoint = ::jxl::cms::WhitePoint; 33 using Primaries = ::jxl::cms::Primaries; 34 using TransferFunction = ::jxl::cms::TransferFunction; 35 using RenderingIntent = ::jxl::cms::RenderingIntent; 36 using CIExy = ::jxl::cms::CIExy; 37 using PrimariesCIExy = ::jxl::cms::PrimariesCIExy; 38 39 namespace cms { 40 41 static inline const char* EnumName(ColorSpace /*unused*/) { 42 return "ColorSpace"; 43 } 44 static inline constexpr uint64_t EnumBits(ColorSpace /*unused*/) { 45 using CS = ColorSpace; 46 return MakeBit(CS::kRGB) | MakeBit(CS::kGray) | MakeBit(CS::kXYB) | 47 MakeBit(CS::kUnknown); 48 } 49 50 static inline const char* EnumName(WhitePoint /*unused*/) { 51 return "WhitePoint"; 52 } 53 static inline constexpr uint64_t EnumBits(WhitePoint /*unused*/) { 54 return MakeBit(WhitePoint::kD65) | MakeBit(WhitePoint::kCustom) | 55 MakeBit(WhitePoint::kE) | MakeBit(WhitePoint::kDCI); 56 } 57 58 static inline const char* EnumName(Primaries /*unused*/) { return "Primaries"; } 59 static inline constexpr uint64_t EnumBits(Primaries /*unused*/) { 60 using Pr = Primaries; 61 return MakeBit(Pr::kSRGB) | MakeBit(Pr::kCustom) | MakeBit(Pr::k2100) | 62 MakeBit(Pr::kP3); 63 } 64 65 static inline const char* EnumName(TransferFunction /*unused*/) { 66 return "TransferFunction"; 67 } 68 69 static inline constexpr uint64_t EnumBits(TransferFunction /*unused*/) { 70 using TF = TransferFunction; 71 return MakeBit(TF::k709) | MakeBit(TF::kLinear) | MakeBit(TF::kSRGB) | 72 MakeBit(TF::kPQ) | MakeBit(TF::kDCI) | MakeBit(TF::kHLG) | 73 MakeBit(TF::kUnknown); 74 } 75 76 static inline const char* EnumName(RenderingIntent /*unused*/) { 77 return "RenderingIntent"; 78 } 79 static inline constexpr uint64_t EnumBits(RenderingIntent /*unused*/) { 80 using RI = RenderingIntent; 81 return MakeBit(RI::kPerceptual) | MakeBit(RI::kRelative) | 82 MakeBit(RI::kSaturation) | MakeBit(RI::kAbsolute); 83 } 84 85 } // namespace cms 86 87 struct ColorEncoding; 88 89 // Serializable form of CIExy. 90 struct Customxy : public Fields { 91 Customxy(); 92 JXL_FIELDS_NAME(Customxy) 93 94 Status VisitFields(Visitor* JXL_RESTRICT visitor) override; 95 96 private: 97 friend struct ColorEncoding; 98 ::jxl::cms::Customxy storage_; 99 }; 100 101 struct CustomTransferFunction : public Fields { 102 CustomTransferFunction(); 103 JXL_FIELDS_NAME(CustomTransferFunction) 104 105 // Sets fields and returns true if nonserialized_color_space has an implicit 106 // transfer function, otherwise leaves fields unchanged and returns false. 107 bool SetImplicit(); 108 109 Status VisitFields(Visitor* JXL_RESTRICT visitor) override; 110 111 // Must be set before calling VisitFields! 112 ColorSpace nonserialized_color_space = ColorSpace::kRGB; 113 114 private: 115 friend struct ColorEncoding; 116 ::jxl::cms::CustomTransferFunction storage_; 117 }; 118 119 // Compact encoding of data required to interpret and translate pixels to a 120 // known color space. Stored in Metadata. Thread-compatible. 121 struct ColorEncoding : public Fields { 122 ColorEncoding(); 123 JXL_FIELDS_NAME(ColorEncoding) 124 125 // Returns ready-to-use color encodings (initialized on-demand). 126 static const ColorEncoding& SRGB(bool is_gray = false); 127 static const ColorEncoding& LinearSRGB(bool is_gray = false); 128 129 // Returns true if an ICC profile was successfully created from fields. 130 // Must be called after modifying fields. Defined in color_management.cc. 131 Status CreateICC() { 132 storage_.icc.clear(); 133 const JxlColorEncoding external = ToExternal(); 134 if (!MaybeCreateProfile(external, &storage_.icc)) { 135 storage_.icc.clear(); 136 return JXL_FAILURE("Failed to create ICC profile"); 137 } 138 return true; 139 } 140 141 // Returns non-empty and valid ICC profile, unless: 142 // - WantICC() == true and SetICC() was not yet called; 143 // - after a failed call to SetSRGB(), SetICC(), or CreateICC(). 144 const IccBytes& ICC() const { return storage_.icc; } 145 146 // Returns true if `icc` is assigned and decoded successfully. If so, 147 // subsequent WantICC() will return true until DecideIfWantICC() changes it. 148 // Returning false indicates data has been lost. 149 Status SetICC(IccBytes&& icc, const JxlCmsInterface* cms) { 150 JXL_ASSERT(cms != nullptr); 151 JXL_ASSERT(!icc.empty()); 152 want_icc_ = storage_.SetFieldsFromICC(std::move(icc), *cms); 153 return want_icc_; 154 } 155 156 // Sets the raw ICC profile bytes, without parsing the ICC, and without 157 // updating the direct fields such as whitepoint, primaries and color 158 // space. Functions to get and set fields, such as SetWhitePoint, cannot be 159 // used anymore after this and functions such as IsSRGB return false no matter 160 // what the contents of the icc profile. 161 void SetICCRaw(IccBytes&& icc) { 162 JXL_ASSERT(!icc.empty()); 163 storage_.icc = std::move(icc); 164 storage_.have_fields = false; 165 want_icc_ = true; 166 } 167 168 // Returns whether to send the ICC profile in the codestream. 169 bool WantICC() const { return want_icc_; } 170 171 // Return whether the direct fields are set, if false but ICC is set, only 172 // raw ICC bytes are known. 173 bool HaveFields() const { return storage_.have_fields; } 174 175 // Causes WantICC() to return false if ICC() can be reconstructed from fields. 176 void DecideIfWantICC(const JxlCmsInterface& cms); 177 178 bool IsGray() const { return storage_.color_space == ColorSpace::kGray; } 179 bool IsCMYK() const { return storage_.cmyk; } 180 size_t Channels() const { return storage_.Channels(); } 181 182 // Returns false if the field is invalid and unusable. 183 bool HasPrimaries() const { return storage_.HasPrimaries(); } 184 185 // Returns true after setting the field to a value defined by color_space, 186 // otherwise false and leaves the field unchanged. 187 bool ImplicitWhitePoint() { 188 // TODO(eustas): inline 189 if (storage_.color_space == ColorSpace::kXYB) { 190 storage_.white_point = WhitePoint::kD65; 191 return true; 192 } 193 return false; 194 } 195 196 // Returns whether the color space is known to be sRGB. If a raw unparsed ICC 197 // profile is set without the fields being set, this returns false, even if 198 // the content of the ICC profile would match sRGB. 199 bool IsSRGB() const { 200 if (!storage_.have_fields) return false; 201 if (!IsGray() && storage_.color_space != ColorSpace::kRGB) return false; 202 if (storage_.white_point != WhitePoint::kD65) return false; 203 if (storage_.primaries != Primaries::kSRGB) return false; 204 if (!storage_.tf.IsSRGB()) return false; 205 return true; 206 } 207 208 // Returns whether the color space is known to be linear sRGB. If a raw 209 // unparsed ICC profile is set without the fields being set, this returns 210 // false, even if the content of the ICC profile would match linear sRGB. 211 bool IsLinearSRGB() const { 212 if (!storage_.have_fields) return false; 213 if (!IsGray() && storage_.color_space != ColorSpace::kRGB) return false; 214 if (storage_.white_point != WhitePoint::kD65) return false; 215 if (storage_.primaries != Primaries::kSRGB) return false; 216 if (!storage_.tf.IsLinear()) return false; 217 return true; 218 } 219 220 Status SetSRGB(const ColorSpace cs, 221 const RenderingIntent ri = RenderingIntent::kRelative) { 222 storage_.icc.clear(); 223 JXL_ASSERT(cs == ColorSpace::kGray || cs == ColorSpace::kRGB); 224 storage_.color_space = cs; 225 storage_.white_point = WhitePoint::kD65; 226 storage_.primaries = Primaries::kSRGB; 227 storage_.tf.transfer_function = TransferFunction::kSRGB; 228 storage_.rendering_intent = ri; 229 return CreateICC(); 230 } 231 232 Status VisitFields(Visitor* JXL_RESTRICT visitor) override; 233 234 // Accessors ensure tf.nonserialized_color_space is updated at the same time. 235 ColorSpace GetColorSpace() const { return storage_.color_space; } 236 void SetColorSpace(const ColorSpace cs) { storage_.color_space = cs; } 237 CIExy GetWhitePoint() const { return storage_.GetWhitePoint(); } 238 239 WhitePoint GetWhitePointType() const { return storage_.white_point; } 240 Status SetWhitePointType(const WhitePoint& wp); 241 PrimariesCIExy GetPrimaries() const { return storage_.GetPrimaries(); } 242 243 Primaries GetPrimariesType() const { return storage_.primaries; } 244 Status SetPrimariesType(const Primaries& p); 245 246 jxl::cms::CustomTransferFunction& Tf() { return storage_.tf; } 247 const jxl::cms::CustomTransferFunction& Tf() const { return storage_.tf; } 248 249 RenderingIntent GetRenderingIntent() const { 250 return storage_.rendering_intent; 251 } 252 void SetRenderingIntent(const RenderingIntent& ri) { 253 storage_.rendering_intent = ri; 254 } 255 256 bool SameColorEncoding(const ColorEncoding& other) const { 257 return storage_.SameColorEncoding(other.storage_); 258 } 259 260 mutable bool all_default; 261 262 JxlColorEncoding ToExternal() const { return storage_.ToExternal(); } 263 Status FromExternal(const JxlColorEncoding& external) { 264 JXL_RETURN_IF_ERROR(storage_.FromExternal(external)); 265 (void)CreateICC(); 266 return true; 267 } 268 const jxl::cms::ColorEncoding& View() const { return storage_; } 269 std::string Description() const; 270 271 private: 272 static std::array<ColorEncoding, 2> CreateC2(Primaries pr, 273 TransferFunction tf); 274 275 // If true, the codestream contains an ICC profile and we do not serialize 276 // fields. Otherwise, fields are serialized and we create an ICC profile. 277 bool want_icc_; 278 279 ::jxl::cms::ColorEncoding storage_; 280 // Only used if white_point == kCustom. 281 Customxy white_; 282 283 // Only valid if HaveFields() 284 CustomTransferFunction tf_; 285 286 // Only used if primaries == kCustom. 287 Customxy red_; 288 Customxy green_; 289 Customxy blue_; 290 }; 291 292 static inline std::string Description(const ColorEncoding& c) { 293 const JxlColorEncoding external = c.View().ToExternal(); 294 return ColorEncodingDescription(external); 295 } 296 297 static inline std::ostream& operator<<(std::ostream& os, 298 const ColorEncoding& c) { 299 return os << Description(c); 300 } 301 302 class ColorSpaceTransform { 303 public: 304 explicit ColorSpaceTransform(const JxlCmsInterface& cms) : cms_(cms) {} 305 ~ColorSpaceTransform() { 306 if (cms_data_ != nullptr) { 307 cms_.destroy(cms_data_); 308 } 309 } 310 311 // Cannot copy. 312 ColorSpaceTransform(const ColorSpaceTransform&) = delete; 313 ColorSpaceTransform& operator=(const ColorSpaceTransform&) = delete; 314 315 Status Init(const ColorEncoding& c_src, const ColorEncoding& c_dst, 316 float intensity_target, size_t xsize, size_t num_threads) { 317 JxlColorProfile input_profile; 318 icc_src_ = c_src.ICC(); 319 input_profile.icc.data = icc_src_.data(); 320 input_profile.icc.size = icc_src_.size(); 321 input_profile.color_encoding = c_src.ToExternal(); 322 input_profile.num_channels = c_src.IsCMYK() ? 4 : c_src.Channels(); 323 JxlColorProfile output_profile; 324 icc_dst_ = c_dst.ICC(); 325 output_profile.icc.data = icc_dst_.data(); 326 output_profile.icc.size = icc_dst_.size(); 327 output_profile.color_encoding = c_dst.ToExternal(); 328 if (c_dst.IsCMYK()) 329 return JXL_FAILURE("Conversion to CMYK is not supported"); 330 output_profile.num_channels = c_dst.Channels(); 331 cms_data_ = cms_.init(cms_.init_data, num_threads, xsize, &input_profile, 332 &output_profile, intensity_target); 333 JXL_RETURN_IF_ERROR(cms_data_ != nullptr); 334 return true; 335 } 336 337 float* BufSrc(const size_t thread) const { 338 return cms_.get_src_buf(cms_data_, thread); 339 } 340 341 float* BufDst(const size_t thread) const { 342 return cms_.get_dst_buf(cms_data_, thread); 343 } 344 345 Status Run(const size_t thread, const float* buf_src, float* buf_dst, 346 size_t xsize) { 347 // TODO(eustas): convert false to Status? 348 return FROM_JXL_BOOL(cms_.run(cms_data_, thread, buf_src, buf_dst, xsize)); 349 } 350 351 private: 352 JxlCmsInterface cms_; 353 void* cms_data_ = nullptr; 354 // The interface may retain pointers into these. 355 IccBytes icc_src_; 356 IccBytes icc_dst_; 357 }; 358 359 } // namespace jxl 360 361 #endif // LIB_JXL_COLOR_ENCODING_INTERNAL_H_