/* * Copyright 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef ULTRAHDR_ICC_H #define ULTRAHDR_ICC_H #include #ifndef USE_BIG_ENDIAN_IN_ICC #define USE_BIG_ENDIAN_IN_ICC true #endif #undef Endian_SwapBE32 #undef Endian_SwapBE16 #if USE_BIG_ENDIAN_IN_ICC #define Endian_SwapBE32(n) EndianSwap32(n) #define Endian_SwapBE16(n) EndianSwap16(n) #else #define Endian_SwapBE32(n) (n) #define Endian_SwapBE16(n) (n) #endif #include "ultrahdr/jpegr.h" #include "ultrahdr/gainmapmath.h" #include "ultrahdr/jpegrutils.h" namespace ultrahdr { typedef int32_t Fixed; #define Fixed1 (1 << 16) #define MaxS32FitsInFloat 2147483520 #define MinS32FitsInFloat (-MaxS32FitsInFloat) #define FixedToFloat(x) ((x)*1.52587890625e-5f) typedef struct Matrix3x3 { float vals[3][3]; } Matrix3x3; // A transfer function mapping encoded values to linear values, // represented by this 7-parameter piecewise function: // // linear = sign(encoded) * (c*|encoded| + f) , 0 <= |encoded| < d // = sign(encoded) * ((a*|encoded| + b)^g + e), d <= |encoded| // // (A simple gamma transfer function sets g to gamma and a to 1.) typedef struct TransferFunction { float g, a, b, c, d, e, f; } TransferFunction; static constexpr TransferFunction kSRGB_TransFun = { 2.4f, (float)(1 / 1.055), (float)(0.055 / 1.055), (float)(1 / 12.92), 0.04045f, 0.0f, 0.0f}; static constexpr TransferFunction kLinear_TransFun = {1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}; // The D50 illuminant. constexpr float kD50_x = 0.9642f; constexpr float kD50_y = 1.0000f; constexpr float kD50_z = 0.8249f; enum { // data_color_space Signature_CMYK = 0x434D594B, Signature_Gray = 0x47524159, Signature_RGB = 0x52474220, // pcs Signature_Lab = 0x4C616220, Signature_XYZ = 0x58595A20, }; typedef uint32_t FourByteTag; static inline constexpr FourByteTag SetFourByteTag(char a, char b, char c, char d) { return (((uint32_t)a << 24) | ((uint32_t)b << 16) | ((uint32_t)c << 8) | (uint32_t)d); } static constexpr char kICCIdentifier[] = "ICC_PROFILE"; // 12 for the actual identifier, +2 for the chunk count and chunk index which // will always follow. static constexpr size_t kICCIdentifierSize = 14; // This is equal to the header size according to the ICC specification (128) // plus the size of the tag count (4). We include the tag count since we // always require it to be present anyway. static constexpr size_t kICCHeaderSize = 132; // Contains a signature (4), offset (4), and size (4). static constexpr size_t kICCTagTableEntrySize = 12; // size should be 20; 4 bytes for type descriptor, 4 bytes reserved, 12 // bytes for a single XYZ number type (4 bytes per coordinate). static constexpr size_t kColorantTagSize = 20; static constexpr uint32_t kDisplay_Profile = SetFourByteTag('m', 'n', 't', 'r'); static constexpr uint32_t kRGB_ColorSpace = SetFourByteTag('R', 'G', 'B', ' '); static constexpr uint32_t kXYZ_PCSSpace = SetFourByteTag('X', 'Y', 'Z', ' '); static constexpr uint32_t kACSP_Signature = SetFourByteTag('a', 'c', 's', 'p'); static constexpr uint32_t kTAG_desc = SetFourByteTag('d', 'e', 's', 'c'); static constexpr uint32_t kTAG_TextType = SetFourByteTag('m', 'l', 'u', 'c'); static constexpr uint32_t kTAG_rXYZ = SetFourByteTag('r', 'X', 'Y', 'Z'); static constexpr uint32_t kTAG_gXYZ = SetFourByteTag('g', 'X', 'Y', 'Z'); static constexpr uint32_t kTAG_bXYZ = SetFourByteTag('b', 'X', 'Y', 'Z'); static constexpr uint32_t kTAG_wtpt = SetFourByteTag('w', 't', 'p', 't'); static constexpr uint32_t kTAG_rTRC = SetFourByteTag('r', 'T', 'R', 'C'); static constexpr uint32_t kTAG_gTRC = SetFourByteTag('g', 'T', 'R', 'C'); static constexpr uint32_t kTAG_bTRC = SetFourByteTag('b', 'T', 'R', 'C'); static constexpr uint32_t kTAG_cicp = SetFourByteTag('c', 'i', 'c', 'p'); static constexpr uint32_t kTAG_cprt = SetFourByteTag('c', 'p', 'r', 't'); static constexpr uint32_t kTAG_A2B0 = SetFourByteTag('A', '2', 'B', '0'); static constexpr uint32_t kTAG_B2A0 = SetFourByteTag('B', '2', 'A', '0'); static constexpr uint32_t kTAG_CurveType = SetFourByteTag('c', 'u', 'r', 'v'); static constexpr uint32_t kTAG_mABType = SetFourByteTag('m', 'A', 'B', ' '); static constexpr uint32_t kTAG_mBAType = SetFourByteTag('m', 'B', 'A', ' '); static constexpr uint32_t kTAG_ParaCurveType = SetFourByteTag('p', 'a', 'r', 'a'); static constexpr Matrix3x3 kSRGB = {{ // ICC fixed-point (16.16) representation, taken from skcms. Please keep them exactly in sync. // 0.436065674f, 0.385147095f, 0.143066406f, // 0.222488403f, 0.716873169f, 0.060607910f, // 0.013916016f, 0.097076416f, 0.714096069f, {FixedToFloat(0x6FA2), FixedToFloat(0x6299), FixedToFloat(0x24A0)}, {FixedToFloat(0x38F5), FixedToFloat(0xB785), FixedToFloat(0x0F84)}, {FixedToFloat(0x0390), FixedToFloat(0x18DA), FixedToFloat(0xB6CF)}, }}; static constexpr Matrix3x3 kDisplayP3 = {{ {0.515102f, 0.291965f, 0.157153f}, {0.241182f, 0.692236f, 0.0665819f}, {-0.00104941f, 0.0418818f, 0.784378f}, }}; static constexpr Matrix3x3 kRec2020 = {{ {0.673459f, 0.165661f, 0.125100f}, {0.279033f, 0.675338f, 0.0456288f}, {-0.00193139f, 0.0299794f, 0.797162f}, }}; static constexpr uint32_t kCICPPrimariesSRGB = 1; static constexpr uint32_t kCICPPrimariesP3 = 12; static constexpr uint32_t kCICPPrimariesRec2020 = 9; static constexpr uint32_t kCICPTrfnSRGB = 1; static constexpr uint32_t kCICPTrfnLinear = 8; static constexpr uint32_t kCICPTrfnPQ = 16; static constexpr uint32_t kCICPTrfnHLG = 18; enum ParaCurveType { kExponential_ParaCurveType = 0, kGAB_ParaCurveType = 1, kGABC_ParaCurveType = 2, kGABDE_ParaCurveType = 3, kGABCDEF_ParaCurveType = 4, }; /** * Return the closest int for the given float. Returns MaxS32FitsInFloat for NaN. */ static inline int float_saturate2int(float x) { x = x < MaxS32FitsInFloat ? x : MaxS32FitsInFloat; x = x > MinS32FitsInFloat ? x : MinS32FitsInFloat; return (int)x; } static inline Fixed float_round_to_fixed(float x) { return float_saturate2int((float)floor((double)x * Fixed1 + 0.5)); } static inline uint16_t float_round_to_unorm16(float x) { x = x * 65535.f + 0.5f; if (x > 65535) return 65535; if (x < 0) return 0; return static_cast(x); } static inline void float_to_table16(const float f, uint8_t* table_16) { *reinterpret_cast(table_16) = Endian_SwapBE16(float_round_to_unorm16(f)); } static inline bool isfinitef_(float x) { return 0 == x * 0; } struct ICCHeader { // Size of the profile (computed) uint32_t size; // Preferred CMM type (ignored) uint32_t cmm_type = 0; // Version 4.3 or 4.4 if CICP is included. uint32_t version = Endian_SwapBE32(0x04300000); // Display device profile uint32_t profile_class = Endian_SwapBE32(kDisplay_Profile); // RGB input color space; uint32_t data_color_space = Endian_SwapBE32(kRGB_ColorSpace); // Profile connection space. uint32_t pcs = Endian_SwapBE32(kXYZ_PCSSpace); // Date and time (ignored) uint8_t creation_date_time[12] = {0}; // Profile signature uint32_t signature = Endian_SwapBE32(kACSP_Signature); // Platform target (ignored) uint32_t platform = 0; // Flags: not embedded, can be used independently uint32_t flags = 0x00000000; // Device manufacturer (ignored) uint32_t device_manufacturer = 0; // Device model (ignored) uint32_t device_model = 0; // Device attributes (ignored) uint8_t device_attributes[8] = {0}; // Relative colorimetric rendering intent uint32_t rendering_intent = Endian_SwapBE32(1); // D50 standard illuminant (X, Y, Z) uint32_t illuminant_X = Endian_SwapBE32(float_round_to_fixed(kD50_x)); uint32_t illuminant_Y = Endian_SwapBE32(float_round_to_fixed(kD50_y)); uint32_t illuminant_Z = Endian_SwapBE32(float_round_to_fixed(kD50_z)); // Profile creator (ignored) uint32_t creator = 0; // Profile id checksum (ignored) uint8_t profile_id[16] = {0}; // Reserved (ignored) uint8_t reserved[28] = {0}; // Technically not part of header, but required uint32_t tag_count = 0; }; class IccHelper { private: static constexpr uint32_t kTrcTableSize = 65; static constexpr uint32_t kGridSize = 17; static constexpr size_t kNumChannels = 3; static std::shared_ptr write_text_tag(const char* text); static std::string get_desc_string(const uhdr_color_transfer_t tf, const uhdr_color_gamut_t gamut); static std::shared_ptr write_xyz_tag(float x, float y, float z); static std::shared_ptr write_trc_tag(const int table_entries, const void* table_16); static std::shared_ptr write_trc_tag(const TransferFunction& fn); static float compute_tone_map_gain(const uhdr_color_transfer_t tf, float L); static std::shared_ptr write_cicp_tag(uint32_t color_primaries, uint32_t transfer_characteristics); static std::shared_ptr write_mAB_or_mBA_tag(uint32_t type, bool has_a_curves, const uint8_t* grid_points, const uint8_t* grid_16); static void compute_lut_entry(const Matrix3x3& src_to_XYZD50, float rgb[3]); static std::shared_ptr write_clut(const uint8_t* grid_points, const uint8_t* grid_16); // Checks if a set of xyz tags is equivalent to a 3x3 Matrix. Each input // tag buffer assumed to be at least kColorantTagSize in size. static bool tagsEqualToMatrix(const Matrix3x3& matrix, const uint8_t* red_tag, const uint8_t* green_tag, const uint8_t* blue_tag); public: // Output includes JPEG embedding identifier and chunk information, but not // APPx information. static std::shared_ptr writeIccProfile(const uhdr_color_transfer_t tf, const uhdr_color_gamut_t gamut); // NOTE: this function is not robust; it can infer gamuts that IccHelper // writes out but should not be considered a reference implementation for // robust parsing of ICC profiles or their gamuts. static uhdr_color_gamut_t readIccColorGamut(void* icc_data, size_t icc_size); }; } // namespace ultrahdr #endif // ULTRAHDR_ICC_H