libjxl

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

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_