/src/libultrahdr/lib/include/ultrahdr/icc.h
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright 2022 The Android Open Source Project |
3 | | * |
4 | | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | | * you may not use this file except in compliance with the License. |
6 | | * You may obtain a copy of the License at |
7 | | * |
8 | | * http://www.apache.org/licenses/LICENSE-2.0 |
9 | | * |
10 | | * Unless required by applicable law or agreed to in writing, software |
11 | | * distributed under the License is distributed on an "AS IS" BASIS, |
12 | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 | | * See the License for the specific language governing permissions and |
14 | | * limitations under the License. |
15 | | */ |
16 | | |
17 | | #ifndef ULTRAHDR_ICC_H |
18 | | #define ULTRAHDR_ICC_H |
19 | | |
20 | | #include <memory> |
21 | | |
22 | | #ifndef USE_BIG_ENDIAN_IN_ICC |
23 | | #define USE_BIG_ENDIAN_IN_ICC true |
24 | | #endif |
25 | | |
26 | | #undef Endian_SwapBE32 |
27 | | #undef Endian_SwapBE16 |
28 | | #if USE_BIG_ENDIAN_IN_ICC |
29 | 152k | #define Endian_SwapBE32(n) EndianSwap32(n) |
30 | 0 | #define Endian_SwapBE16(n) EndianSwap16(n) |
31 | | #else |
32 | | #define Endian_SwapBE32(n) (n) |
33 | | #define Endian_SwapBE16(n) (n) |
34 | | #endif |
35 | | |
36 | | #include "ultrahdr/jpegr.h" |
37 | | #include "ultrahdr/gainmapmath.h" |
38 | | #include "ultrahdr/jpegrutils.h" |
39 | | |
40 | | namespace ultrahdr { |
41 | | |
42 | | typedef int32_t Fixed; |
43 | 0 | #define Fixed1 (1 << 16) |
44 | 0 | #define MaxS32FitsInFloat 2147483520 |
45 | 0 | #define MinS32FitsInFloat (-MaxS32FitsInFloat) |
46 | 27 | #define FixedToFloat(x) ((x)*1.52587890625e-5f) |
47 | | |
48 | | typedef struct Matrix3x3 { |
49 | | float vals[3][3]; |
50 | | } Matrix3x3; |
51 | | |
52 | | // A transfer function mapping encoded values to linear values, |
53 | | // represented by this 7-parameter piecewise function: |
54 | | // |
55 | | // linear = sign(encoded) * (c*|encoded| + f) , 0 <= |encoded| < d |
56 | | // = sign(encoded) * ((a*|encoded| + b)^g + e), d <= |encoded| |
57 | | // |
58 | | // (A simple gamma transfer function sets g to gamma and a to 1.) |
59 | | typedef struct TransferFunction { |
60 | | float g, a, b, c, d, e, f; |
61 | | } TransferFunction; |
62 | | |
63 | | static constexpr TransferFunction kSRGB_TransFun = { |
64 | | 2.4f, (float)(1 / 1.055), (float)(0.055 / 1.055), (float)(1 / 12.92), 0.04045f, 0.0f, 0.0f}; |
65 | | |
66 | | static constexpr TransferFunction kLinear_TransFun = {1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}; |
67 | | |
68 | | // The D50 illuminant. |
69 | | constexpr float kD50_x = 0.9642f; |
70 | | constexpr float kD50_y = 1.0000f; |
71 | | constexpr float kD50_z = 0.8249f; |
72 | | |
73 | | enum { |
74 | | // data_color_space |
75 | | Signature_CMYK = 0x434D594B, |
76 | | Signature_Gray = 0x47524159, |
77 | | Signature_RGB = 0x52474220, |
78 | | |
79 | | // pcs |
80 | | Signature_Lab = 0x4C616220, |
81 | | Signature_XYZ = 0x58595A20, |
82 | | }; |
83 | | |
84 | | typedef uint32_t FourByteTag; |
85 | 0 | static inline constexpr FourByteTag SetFourByteTag(char a, char b, char c, char d) { |
86 | 0 | return (((uint32_t)a << 24) | ((uint32_t)b << 16) | ((uint32_t)c << 8) | (uint32_t)d); |
87 | 0 | } Unexecuted instantiation: jpegr.cpp:ultrahdr::SetFourByteTag(char, char, char, char) Unexecuted instantiation: icc.cpp:ultrahdr::SetFourByteTag(char, char, char, char) |
88 | | |
89 | | static constexpr char kICCIdentifier[] = "ICC_PROFILE"; |
90 | | // 12 for the actual identifier, +2 for the chunk count and chunk index which |
91 | | // will always follow. |
92 | | static constexpr size_t kICCIdentifierSize = 14; |
93 | | |
94 | | // This is equal to the header size according to the ICC specification (128) |
95 | | // plus the size of the tag count (4). We include the tag count since we |
96 | | // always require it to be present anyway. |
97 | | static constexpr size_t kICCHeaderSize = 132; |
98 | | |
99 | | // Contains a signature (4), offset (4), and size (4). |
100 | | static constexpr size_t kICCTagTableEntrySize = 12; |
101 | | |
102 | | // size should be 20; 4 bytes for type descriptor, 4 bytes reserved, 12 |
103 | | // bytes for a single XYZ number type (4 bytes per coordinate). |
104 | | static constexpr size_t kColorantTagSize = 20; |
105 | | |
106 | | // size should be 12; 4 bytes for type descriptor, 4 bytes reserved, one |
107 | | // byte each for primaries, transfer, matrix, range. |
108 | | static constexpr size_t kCicpTagSize = 12; |
109 | | |
110 | | static constexpr uint32_t kDisplay_Profile = SetFourByteTag('m', 'n', 't', 'r'); |
111 | | static constexpr uint32_t kRGB_ColorSpace = SetFourByteTag('R', 'G', 'B', ' '); |
112 | | static constexpr uint32_t kXYZ_PCSSpace = SetFourByteTag('X', 'Y', 'Z', ' '); |
113 | | static constexpr uint32_t kACSP_Signature = SetFourByteTag('a', 'c', 's', 'p'); |
114 | | |
115 | | static constexpr uint32_t kTAG_desc = SetFourByteTag('d', 'e', 's', 'c'); |
116 | | static constexpr uint32_t kTAG_TextType = SetFourByteTag('m', 'l', 'u', 'c'); |
117 | | static constexpr uint32_t kTAG_rXYZ = SetFourByteTag('r', 'X', 'Y', 'Z'); |
118 | | static constexpr uint32_t kTAG_gXYZ = SetFourByteTag('g', 'X', 'Y', 'Z'); |
119 | | static constexpr uint32_t kTAG_bXYZ = SetFourByteTag('b', 'X', 'Y', 'Z'); |
120 | | static constexpr uint32_t kTAG_wtpt = SetFourByteTag('w', 't', 'p', 't'); |
121 | | static constexpr uint32_t kTAG_rTRC = SetFourByteTag('r', 'T', 'R', 'C'); |
122 | | static constexpr uint32_t kTAG_gTRC = SetFourByteTag('g', 'T', 'R', 'C'); |
123 | | static constexpr uint32_t kTAG_bTRC = SetFourByteTag('b', 'T', 'R', 'C'); |
124 | | static constexpr uint32_t kTAG_cicp = SetFourByteTag('c', 'i', 'c', 'p'); |
125 | | static constexpr uint32_t kTAG_cprt = SetFourByteTag('c', 'p', 'r', 't'); |
126 | | static constexpr uint32_t kTAG_A2B0 = SetFourByteTag('A', '2', 'B', '0'); |
127 | | static constexpr uint32_t kTAG_B2A0 = SetFourByteTag('B', '2', 'A', '0'); |
128 | | |
129 | | static constexpr uint32_t kTAG_CurveType = SetFourByteTag('c', 'u', 'r', 'v'); |
130 | | static constexpr uint32_t kTAG_mABType = SetFourByteTag('m', 'A', 'B', ' '); |
131 | | static constexpr uint32_t kTAG_mBAType = SetFourByteTag('m', 'B', 'A', ' '); |
132 | | static constexpr uint32_t kTAG_ParaCurveType = SetFourByteTag('p', 'a', 'r', 'a'); |
133 | | |
134 | | static constexpr Matrix3x3 kSRGB = {{ |
135 | | // ICC fixed-point (16.16) representation, taken from skcms. Please keep them exactly in sync. |
136 | | // 0.436065674f, 0.385147095f, 0.143066406f, |
137 | | // 0.222488403f, 0.716873169f, 0.060607910f, |
138 | | // 0.013916016f, 0.097076416f, 0.714096069f, |
139 | | {FixedToFloat(0x6FA2), FixedToFloat(0x6299), FixedToFloat(0x24A0)}, |
140 | | {FixedToFloat(0x38F5), FixedToFloat(0xB785), FixedToFloat(0x0F84)}, |
141 | | {FixedToFloat(0x0390), FixedToFloat(0x18DA), FixedToFloat(0xB6CF)}, |
142 | | }}; |
143 | | |
144 | | static constexpr Matrix3x3 kDisplayP3 = {{ |
145 | | {0.515102f, 0.291965f, 0.157153f}, |
146 | | {0.241182f, 0.692236f, 0.0665819f}, |
147 | | {-0.00104941f, 0.0418818f, 0.784378f}, |
148 | | }}; |
149 | | |
150 | | static constexpr Matrix3x3 kRec2020 = {{ |
151 | | {0.673459f, 0.165661f, 0.125100f}, |
152 | | {0.279033f, 0.675338f, 0.0456288f}, |
153 | | {-0.00193139f, 0.0299794f, 0.797162f}, |
154 | | }}; |
155 | | |
156 | | static constexpr uint32_t kCICPPrimariesUnSpecified = 2; |
157 | | static constexpr uint32_t kCICPPrimariesSRGB = 1; |
158 | | static constexpr uint32_t kCICPPrimariesP3 = 12; |
159 | | static constexpr uint32_t kCICPPrimariesRec2020 = 9; |
160 | | |
161 | | static constexpr uint32_t kCICPTrfnUnSpecified = 2; |
162 | | static constexpr uint32_t kCICPTrfnSRGB = 1; |
163 | | static constexpr uint32_t kCICPTrfnLinear = 8; |
164 | | static constexpr uint32_t kCICPTrfnPQ = 16; |
165 | | static constexpr uint32_t kCICPTrfnHLG = 18; |
166 | | |
167 | | enum ParaCurveType { |
168 | | kExponential_ParaCurveType = 0, |
169 | | kGAB_ParaCurveType = 1, |
170 | | kGABC_ParaCurveType = 2, |
171 | | kGABDE_ParaCurveType = 3, |
172 | | kGABCDEF_ParaCurveType = 4, |
173 | | }; |
174 | | |
175 | | /** |
176 | | * Return the closest int for the given float. Returns MaxS32FitsInFloat for NaN. |
177 | | */ |
178 | 0 | static inline int float_saturate2int(float x) { |
179 | 0 | x = x < MaxS32FitsInFloat ? x : MaxS32FitsInFloat; |
180 | 0 | x = x > MinS32FitsInFloat ? x : MinS32FitsInFloat; |
181 | 0 | return (int)x; |
182 | 0 | } Unexecuted instantiation: jpegr.cpp:ultrahdr::float_saturate2int(float) Unexecuted instantiation: icc.cpp:ultrahdr::float_saturate2int(float) |
183 | | |
184 | 0 | static inline Fixed float_round_to_fixed(float x) { |
185 | 0 | return float_saturate2int((float)floor((double)x * Fixed1 + 0.5)); |
186 | 0 | } Unexecuted instantiation: jpegr.cpp:ultrahdr::float_round_to_fixed(float) Unexecuted instantiation: icc.cpp:ultrahdr::float_round_to_fixed(float) |
187 | | |
188 | 0 | static inline uint16_t float_round_to_unorm16(float x) { |
189 | 0 | x = x * 65535.f + 0.5f; |
190 | 0 | if (x > 65535) return 65535; |
191 | 0 | if (x < 0) return 0; |
192 | 0 | return static_cast<uint16_t>(x); |
193 | 0 | } Unexecuted instantiation: jpegr.cpp:ultrahdr::float_round_to_unorm16(float) Unexecuted instantiation: icc.cpp:ultrahdr::float_round_to_unorm16(float) |
194 | | |
195 | 0 | static inline void float_to_table16(const float f, uint8_t* table_16) { |
196 | 0 | *reinterpret_cast<uint16_t*>(table_16) = Endian_SwapBE16(float_round_to_unorm16(f)); |
197 | 0 | } Unexecuted instantiation: jpegr.cpp:ultrahdr::float_to_table16(float, unsigned char*) Unexecuted instantiation: icc.cpp:ultrahdr::float_to_table16(float, unsigned char*) |
198 | | |
199 | 0 | static inline bool isfinitef_(float x) { return 0 == x * 0; } Unexecuted instantiation: jpegr.cpp:ultrahdr::isfinitef_(float) Unexecuted instantiation: icc.cpp:ultrahdr::isfinitef_(float) |
200 | | |
201 | | struct ICCHeader { |
202 | | // Size of the profile (computed) |
203 | | uint32_t size; |
204 | | // Preferred CMM type (ignored) |
205 | | uint32_t cmm_type = 0; |
206 | | // Version 4.3 or 4.4 if CICP is included. |
207 | | uint32_t version = Endian_SwapBE32(0x04300000); |
208 | | // Display device profile |
209 | | uint32_t profile_class = Endian_SwapBE32(kDisplay_Profile); |
210 | | // RGB input color space; |
211 | | uint32_t data_color_space = Endian_SwapBE32(kRGB_ColorSpace); |
212 | | // Profile connection space. |
213 | | uint32_t pcs = Endian_SwapBE32(kXYZ_PCSSpace); |
214 | | // Date and time (ignored) |
215 | | uint8_t creation_date_time[12] = {0}; |
216 | | // Profile signature |
217 | | uint32_t signature = Endian_SwapBE32(kACSP_Signature); |
218 | | // Platform target (ignored) |
219 | | uint32_t platform = 0; |
220 | | // Flags: not embedded, can be used independently |
221 | | uint32_t flags = 0x00000000; |
222 | | // Device manufacturer (ignored) |
223 | | uint32_t device_manufacturer = 0; |
224 | | // Device model (ignored) |
225 | | uint32_t device_model = 0; |
226 | | // Device attributes (ignored) |
227 | | uint8_t device_attributes[8] = {0}; |
228 | | // Relative colorimetric rendering intent |
229 | | uint32_t rendering_intent = Endian_SwapBE32(1); |
230 | | // D50 standard illuminant (X, Y, Z) |
231 | | uint32_t illuminant_X = Endian_SwapBE32(float_round_to_fixed(kD50_x)); |
232 | | uint32_t illuminant_Y = Endian_SwapBE32(float_round_to_fixed(kD50_y)); |
233 | | uint32_t illuminant_Z = Endian_SwapBE32(float_round_to_fixed(kD50_z)); |
234 | | // Profile creator (ignored) |
235 | | uint32_t creator = 0; |
236 | | // Profile id checksum (ignored) |
237 | | uint8_t profile_id[16] = {0}; |
238 | | // Reserved (ignored) |
239 | | uint8_t reserved[28] = {0}; |
240 | | // Technically not part of header, but required |
241 | | uint32_t tag_count = 0; |
242 | | }; |
243 | | |
244 | | class IccHelper { |
245 | | private: |
246 | | static constexpr uint32_t kTrcTableSize = 65; |
247 | | static constexpr uint32_t kGridSize = 17; |
248 | | static constexpr size_t kNumChannels = 3; |
249 | | |
250 | | static std::shared_ptr<DataStruct> write_text_tag(const char* text); |
251 | | static std::string get_desc_string(const uhdr_color_transfer_t tf, |
252 | | const uhdr_color_gamut_t gamut); |
253 | | static std::shared_ptr<DataStruct> write_xyz_tag(float x, float y, float z); |
254 | | static std::shared_ptr<DataStruct> write_trc_tag(const int table_entries, const void* table_16); |
255 | | static std::shared_ptr<DataStruct> write_trc_tag(const TransferFunction& fn); |
256 | | static float compute_tone_map_gain(const uhdr_color_transfer_t tf, float L); |
257 | | static std::shared_ptr<DataStruct> write_cicp_tag(uint32_t color_primaries, |
258 | | uint32_t transfer_characteristics); |
259 | | static std::shared_ptr<DataStruct> write_mAB_or_mBA_tag(uint32_t type, bool has_a_curves, |
260 | | const uint8_t* grid_points, |
261 | | const uint8_t* grid_16); |
262 | | static void compute_lut_entry(const Matrix3x3& src_to_XYZD50, float rgb[3]); |
263 | | static std::shared_ptr<DataStruct> write_clut(const uint8_t* grid_points, const uint8_t* grid_16); |
264 | | |
265 | | // Checks if a set of xyz tags is equivalent to a 3x3 Matrix. Each input |
266 | | // tag buffer assumed to be at least kColorantTagSize in size. |
267 | | static bool tagsEqualToMatrix(const Matrix3x3& matrix, const uint8_t* red_tag, |
268 | | const uint8_t* green_tag, const uint8_t* blue_tag); |
269 | | |
270 | | public: |
271 | | // Output includes JPEG embedding identifier and chunk information, but not |
272 | | // APPx information. |
273 | | static std::shared_ptr<DataStruct> writeIccProfile(const uhdr_color_transfer_t tf, |
274 | | const uhdr_color_gamut_t gamut); |
275 | | // NOTE: this function is not robust; it can infer gamuts that IccHelper |
276 | | // writes out but should not be considered a reference implementation for |
277 | | // robust parsing of ICC profiles or their gamuts. |
278 | | static uhdr_color_gamut_t readIccColorGamut(void* icc_data, size_t icc_size); |
279 | | }; |
280 | | |
281 | | } // namespace ultrahdr |
282 | | |
283 | | #endif // ULTRAHDR_ICC_H |