/src/libavif/apps/shared/iccmaker.c
Line | Count | Source |
1 | | // Copyright 2023 Yuan Tong. All rights reserved. |
2 | | // SPDX-License-Identifier: BSD-2-Clause |
3 | | |
4 | | #include "iccmaker.h" |
5 | | |
6 | | #include <math.h> |
7 | | #include <string.h> |
8 | | |
9 | | // ICCv2 profile specification: https://www.color.org/icc32.pdf |
10 | | |
11 | | /** |
12 | | * Color Profile Structure |
13 | | * |
14 | | * Header: |
15 | | * size = 376 bytes (*1) |
16 | | * CMM = 'lcms' (*2) |
17 | | * Version = 2.2.0 |
18 | | * Device Class = Display |
19 | | * Color Space = RGB |
20 | | * Conn. Space = XYZ |
21 | | * Date, Time = 1 Jan 2000, 0:00:00 |
22 | | * Platform = Microsoft |
23 | | * Flags = Not Embedded Profile, Use anywhere |
24 | | * Dev. Mnfctr. = 0x0 |
25 | | * Dev. Model = 0x0 |
26 | | * Dev. Attrbts = Reflective, Glossy, Positive, Color |
27 | | * Rndrng Intnt = Perceptual |
28 | | * Illuminant = 0.96420288, 1.00000000, 0.82490540 [Lab 100.000000, 0.000000, 0.000000] |
29 | | * Creator = 'avif' |
30 | | * |
31 | | * Profile Tags: |
32 | | * Tag ID Offset Size Value |
33 | | * ---- ------ ------ ---- ----- |
34 | | * profileDescriptionTag 'desc' 240 95 avif |
35 | | * mediaWhitePointTag 'wtpt' 268 (*3) 20 (to be filled) |
36 | | * redColorantTag 'rXYZ' 288 20 (to be filled) |
37 | | * greenColorantTag 'gXYZ' 308 20 (to be filled) |
38 | | * blueColorantTag 'bXYZ' 328 20 (to be filled) |
39 | | * redTRCTag 'rTRC' 348 (*4) 16 (to be filled) |
40 | | * greenTRCTag 'gTRC' 348 16 (to be filled) |
41 | | * blueTRCTag 'bTRC' 348 16 (to be filled) |
42 | | * copyrightTag 'cprt' 364 12 CC0 |
43 | | * |
44 | | * (*1): The template data is padded to 448 bytes according to MD5 specification, so that computation can be applied |
45 | | * directly on it. The actual ICC profile data is the first 376 bytes. |
46 | | * (*2): 6.1.2 CMM Type: The signatures must be registered in order to avoid conflicts. |
47 | | * The registry can be found at https://www.color.org/signatures2.xalter (Private and ICC tag and CMM registry) |
48 | | * Therefore we are using the signature of Little CMS. |
49 | | * (*3): The profileDescriptionTag requires 95 bytes of data, but with some trick, the content of the last 67 bytes |
50 | | * can be anything. Therefore we are placing the following tags in this region to reduce profile size. |
51 | | * (*4): The transfer characteristic (gamma) of the 3 channels are the same, so the data can be shared. |
52 | | */ |
53 | | |
54 | | static const uint8_t iccColorTemplate[448] = { |
55 | | 0x00, 0x00, 0x01, 0x78, 0x6c, 0x63, 0x6d, 0x73, 0x02, 0x20, 0x00, 0x00, 0x6d, 0x6e, 0x74, 0x72, 0x52, 0x47, 0x42, 0x20, 0x58, |
56 | | 0x59, 0x5a, 0x20, 0x07, 0xd0, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0x63, 0x73, 0x70, 0x4d, 0x53, |
57 | | 0x46, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
58 | | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf6, 0xd6, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xd3, 0x2d, 0x61, 0x76, 0x69, 0x66, |
59 | | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
60 | | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
61 | | 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x64, 0x65, 0x73, 0x63, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x5f, 0x77, 0x74, 0x70, |
62 | | 0x74, 0x00, 0x00, 0x01, 0x0c, 0x00, 0x00, 0x00, 0x14, 0x72, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x01, 0x20, 0x00, 0x00, 0x00, 0x14, |
63 | | 0x67, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x01, 0x34, 0x00, 0x00, 0x00, 0x14, 0x62, 0x58, 0x59, 0x5a, 0x00, 0x00, 0x01, 0x48, 0x00, |
64 | | 0x00, 0x00, 0x14, 0x72, 0x54, 0x52, 0x43, 0x00, 0x00, 0x01, 0x5c, 0x00, 0x00, 0x00, 0x10, 0x67, 0x54, 0x52, 0x43, 0x00, 0x00, |
65 | | 0x01, 0x5c, 0x00, 0x00, 0x00, 0x10, 0x62, 0x54, 0x52, 0x43, 0x00, 0x00, 0x01, 0x5c, 0x00, 0x00, 0x00, 0x10, 0x63, 0x70, 0x72, |
66 | | 0x74, 0x00, 0x00, 0x01, 0x6c, 0x00, 0x00, 0x00, 0x0c, 0x64, 0x65, 0x73, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, |
67 | | 0x61, 0x76, 0x69, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x59, 0x5a, 0x20, 0x00, |
68 | | 0x00, 0x00, 0x00, 0x00, 0x00, 0xf3, 0x54, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x16, 0xc9, 0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, |
69 | | 0x00, 0x00, 0x00, 0x00, 0x6f, 0xa0, 0x00, 0x00, 0x38, 0xf2, 0x00, 0x00, 0x03, 0x8f, 0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, |
70 | | 0x00, 0x00, 0x00, 0x62, 0x96, 0x00, 0x00, 0xb7, 0x89, 0x00, 0x00, 0x18, 0xda, 0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00, |
71 | | 0x00, 0x00, 0x24, 0xa0, 0x00, 0x00, 0x0f, 0x85, 0x00, 0x00, 0xb6, 0xc4, 0x63, 0x75, 0x72, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, |
72 | | 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x74, 0x65, 0x78, 0x74, 0x00, 0x00, 0x00, 0x00, 0x43, 0x43, 0x30, 0x00, 0x80, 0x00, |
73 | | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
74 | | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
75 | | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, |
76 | | 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
77 | | }; |
78 | | |
79 | | static const size_t iccColorLength = 376; |
80 | | |
81 | | static const ptrdiff_t colorWhiteOffset = 0x114; |
82 | | static const ptrdiff_t colorRedOffset = 0x128; |
83 | | static const ptrdiff_t colorGreenOffset = 0x13c; |
84 | | static const ptrdiff_t colorBlueOffset = 0x150; |
85 | | static const ptrdiff_t colorGammaOffset = 0x168; |
86 | | |
87 | | /** |
88 | | * Gray Profile Structure |
89 | | * |
90 | | * Header: |
91 | | * size = 275 bytes |
92 | | * CMM = 'lcms' |
93 | | * Version = 2.2.0 |
94 | | * Device Class = Display |
95 | | * Color Space = Gray |
96 | | * Conn. Space = XYZ |
97 | | * Date, Time = 1 Jan 2000, 0:00:00 |
98 | | * Platform = Microsoft |
99 | | * Flags = Not Embedded Profile, Use anywhere |
100 | | * Dev. Mnfctr. = 0x0 |
101 | | * Dev. Model = 0x0 |
102 | | * Dev. Attrbts = Reflective, Glossy, Positive, Color |
103 | | * Rndrng Intnt = Perceptual |
104 | | * Illuminant = 0.96420288, 1.00000000, 0.82490540 [Lab 100.000000, 0.000000, 0.000000] |
105 | | * Creator = 'avif' |
106 | | * |
107 | | * Profile Tags: |
108 | | * Tag ID Offset Size Value |
109 | | * ---- ------ ------ ---- ----- |
110 | | * profileDescriptionTag 'desc' 180 95 avif |
111 | | * mediaWhitePointTag 'wtpt' 208 20 (to be filled) |
112 | | * grayTRCTag 'kTRC' 228 16 (to be filled) |
113 | | * copyrightTag 'cprt' 244 12 CC0 |
114 | | */ |
115 | | |
116 | | static const uint8_t iccGrayTemplate[320] = { |
117 | | 0x00, 0x00, 0x01, 0x13, 0x6c, 0x63, 0x6d, 0x73, 0x02, 0x20, 0x00, 0x00, 0x6d, 0x6e, 0x74, 0x72, 0x47, 0x52, 0x41, 0x59, |
118 | | 0x58, 0x59, 0x5a, 0x20, 0x07, 0xd0, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0x63, 0x73, 0x70, |
119 | | 0x4d, 0x53, 0x46, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
120 | | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf6, 0xd6, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xd3, 0x2d, |
121 | | 0x61, 0x76, 0x69, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
122 | | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
123 | | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x64, 0x65, 0x73, 0x63, 0x00, 0x00, 0x00, 0xb4, |
124 | | 0x00, 0x00, 0x00, 0x5f, 0x77, 0x74, 0x70, 0x74, 0x00, 0x00, 0x00, 0xd0, 0x00, 0x00, 0x00, 0x14, 0x6b, 0x54, 0x52, 0x43, |
125 | | 0x00, 0x00, 0x00, 0xe4, 0x00, 0x00, 0x00, 0x10, 0x63, 0x70, 0x72, 0x74, 0x00, 0x00, 0x00, 0xf4, 0x00, 0x00, 0x00, 0x0c, |
126 | | 0x64, 0x65, 0x73, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x61, 0x76, 0x69, 0x66, 0x00, 0x00, 0x00, 0x00, |
127 | | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x59, 0x5a, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf3, 0x54, |
128 | | 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x16, 0xc9, 0x63, 0x75, 0x72, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, |
129 | | 0x01, 0x00, 0x00, 0x00, 0x74, 0x65, 0x78, 0x74, 0x00, 0x00, 0x00, 0x00, 0x43, 0x43, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, |
130 | | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, |
131 | | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
132 | | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x98, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
133 | | }; |
134 | | |
135 | | static const size_t iccGrayLength = 275; |
136 | | |
137 | | static const ptrdiff_t grayWhiteOffset = 0xd8; |
138 | | static const ptrdiff_t grayGammaOffset = 0xf0; |
139 | | |
140 | | static const ptrdiff_t checksumOffset = 0x54; |
141 | | |
142 | | static const double small = 1e-12; |
143 | | |
144 | | static uint32_t readLittleEndianU32(const uint8_t * data) |
145 | 0 | { |
146 | 0 | return ((uint32_t)data[0] << 0) | ((uint32_t)data[1] << 8) | ((uint32_t)data[2] << 16) | ((uint32_t)data[3] << 24); |
147 | 0 | } |
148 | | |
149 | | static void writeLittleEndianU32(uint8_t * data, uint32_t value) |
150 | 0 | { |
151 | 0 | data[0] = (value >> 0) & 0xff; |
152 | 0 | data[1] = (value >> 8) & 0xff; |
153 | 0 | data[2] = (value >> 16) & 0xff; |
154 | 0 | data[3] = (value >> 24) & 0xff; |
155 | 0 | } |
156 | | |
157 | | static void writeBigEndianU16(uint8_t * data, uint16_t value) |
158 | 0 | { |
159 | 0 | data[0] = (value >> 8) & 0xff; |
160 | 0 | data[1] = (value >> 0) & 0xff; |
161 | 0 | } |
162 | | |
163 | | static void writeBigEndianU32(uint8_t * data, uint32_t value) |
164 | 0 | { |
165 | 0 | data[0] = (value >> 24) & 0xff; |
166 | 0 | data[1] = (value >> 16) & 0xff; |
167 | 0 | data[2] = (value >> 8) & 0xff; |
168 | 0 | data[3] = (value >> 0) & 0xff; |
169 | 0 | } |
170 | | |
171 | | static avifBool putS15Fixed16(uint8_t * data, double value) |
172 | 0 | { |
173 | 0 | value = round(value * 65536); |
174 | 0 | if (value > INT32_MAX || value < INT32_MIN) { |
175 | 0 | return AVIF_FALSE; |
176 | 0 | } |
177 | | |
178 | 0 | int32_t fixed = (int32_t)value; |
179 | | // reinterpret into uint32_t to ensure the exact bits are written. |
180 | 0 | writeBigEndianU32(data, *(uint32_t *)&fixed); |
181 | |
|
182 | 0 | return AVIF_TRUE; |
183 | 0 | } |
184 | | |
185 | | static avifBool putU8Fixed8(uint8_t * data, float value) |
186 | 0 | { |
187 | 0 | value = roundf(value * 256); |
188 | 0 | if (value > UINT16_MAX || value < 1) { |
189 | 0 | return AVIF_FALSE; |
190 | 0 | } |
191 | | |
192 | 0 | uint16_t fixed = (uint16_t)value; |
193 | 0 | writeBigEndianU16(data, fixed); |
194 | 0 | return AVIF_TRUE; |
195 | 0 | } |
196 | | |
197 | | static avifBool putColorant(uint8_t * data, const double XYZ[3]) |
198 | 0 | { |
199 | 0 | if (!putS15Fixed16(data, XYZ[0])) { |
200 | 0 | return AVIF_FALSE; |
201 | 0 | } |
202 | | |
203 | 0 | if (!putS15Fixed16(data + 4, XYZ[1])) { |
204 | 0 | return AVIF_FALSE; |
205 | 0 | } |
206 | | |
207 | 0 | if (!putS15Fixed16(data + 8, XYZ[2])) { |
208 | 0 | return AVIF_FALSE; |
209 | 0 | } |
210 | | |
211 | 0 | return AVIF_TRUE; |
212 | 0 | } |
213 | | |
214 | | static avifBool xyToXYZ(const float xy[2], double XYZ[3]) |
215 | 0 | { |
216 | 0 | if (fabsf(xy[1]) < small) { |
217 | 0 | return AVIF_FALSE; |
218 | 0 | } |
219 | | |
220 | 0 | const double factor = 1.0 / xy[1]; |
221 | 0 | XYZ[0] = xy[0] * factor; |
222 | 0 | XYZ[1] = 1; |
223 | 0 | XYZ[2] = (1 - xy[0] - xy[1]) * factor; |
224 | |
|
225 | 0 | return AVIF_TRUE; |
226 | 0 | } |
227 | | |
228 | | // Computes I = M^-1. Returns false if M seems to be singular. |
229 | | static avifBool matInv(const double M[3][3], double I[3][3]) |
230 | 0 | { |
231 | 0 | double det = M[0][0] * (M[1][1] * M[2][2] - M[2][1] * M[1][2]) - M[0][1] * (M[1][0] * M[2][2] - M[1][2] * M[2][0]) + |
232 | 0 | M[0][2] * (M[1][0] * M[2][1] - M[1][1] * M[2][0]); |
233 | 0 | if (fabs(det) < small) { |
234 | 0 | return AVIF_FALSE; |
235 | 0 | } |
236 | 0 | det = 1 / det; |
237 | |
|
238 | 0 | I[0][0] = (M[1][1] * M[2][2] - M[2][1] * M[1][2]) * det; |
239 | 0 | I[0][1] = (M[0][2] * M[2][1] - M[0][1] * M[2][2]) * det; |
240 | 0 | I[0][2] = (M[0][1] * M[1][2] - M[0][2] * M[1][1]) * det; |
241 | 0 | I[1][0] = (M[1][2] * M[2][0] - M[1][0] * M[2][2]) * det; |
242 | 0 | I[1][1] = (M[0][0] * M[2][2] - M[0][2] * M[2][0]) * det; |
243 | 0 | I[1][2] = (M[1][0] * M[0][2] - M[0][0] * M[1][2]) * det; |
244 | 0 | I[2][0] = (M[1][0] * M[2][1] - M[2][0] * M[1][1]) * det; |
245 | 0 | I[2][1] = (M[2][0] * M[0][1] - M[0][0] * M[2][1]) * det; |
246 | 0 | I[2][2] = (M[0][0] * M[1][1] - M[1][0] * M[0][1]) * det; |
247 | |
|
248 | 0 | return AVIF_TRUE; |
249 | 0 | } |
250 | | |
251 | | // Computes C = A*B |
252 | | static void matMul(const double A[3][3], const double B[3][3], double C[3][3]) |
253 | 0 | { |
254 | 0 | C[0][0] = A[0][0] * B[0][0] + A[0][1] * B[1][0] + A[0][2] * B[2][0]; |
255 | 0 | C[0][1] = A[0][0] * B[0][1] + A[0][1] * B[1][1] + A[0][2] * B[2][1]; |
256 | 0 | C[0][2] = A[0][0] * B[0][2] + A[0][1] * B[1][2] + A[0][2] * B[2][2]; |
257 | 0 | C[1][0] = A[1][0] * B[0][0] + A[1][1] * B[1][0] + A[1][2] * B[2][0]; |
258 | 0 | C[1][1] = A[1][0] * B[0][1] + A[1][1] * B[1][1] + A[1][2] * B[2][1]; |
259 | 0 | C[1][2] = A[1][0] * B[0][2] + A[1][1] * B[1][2] + A[1][2] * B[2][2]; |
260 | 0 | C[2][0] = A[2][0] * B[0][0] + A[2][1] * B[1][0] + A[2][2] * B[2][0]; |
261 | 0 | C[2][1] = A[2][0] * B[0][1] + A[2][1] * B[1][1] + A[2][2] * B[2][1]; |
262 | 0 | C[2][2] = A[2][0] * B[0][2] + A[2][1] * B[1][2] + A[2][2] * B[2][2]; |
263 | 0 | } |
264 | | |
265 | | // Set M to have values of d on the leading diagonal, and zero elsewhere. |
266 | | static void matDiag(const double d[3], double M[3][3]) |
267 | 0 | { |
268 | 0 | M[0][0] = d[0]; |
269 | 0 | M[0][1] = 0; |
270 | 0 | M[0][2] = 0; |
271 | 0 | M[1][0] = 0; |
272 | 0 | M[1][1] = d[1]; |
273 | 0 | M[1][2] = 0; |
274 | 0 | M[2][0] = 0; |
275 | 0 | M[2][1] = 0; |
276 | 0 | M[2][2] = d[2]; |
277 | 0 | } |
278 | | |
279 | | static void swap(double * a, double * b) |
280 | 0 | { |
281 | 0 | double tmp = *a; |
282 | 0 | *a = *b; |
283 | 0 | *b = tmp; |
284 | 0 | } |
285 | | |
286 | | // Transpose M |
287 | | static void matTrans(double M[3][3]) |
288 | 0 | { |
289 | 0 | swap(&M[0][1], &M[1][0]); |
290 | 0 | swap(&M[0][2], &M[2][0]); |
291 | 0 | swap(&M[1][2], &M[2][1]); |
292 | 0 | } |
293 | | |
294 | | // Computes y = M.x |
295 | | static void vecMul(const double M[3][3], const double x[3], double y[3]) |
296 | 0 | { |
297 | 0 | y[0] = M[0][0] * x[0] + M[0][1] * x[1] + M[0][2] * x[2]; |
298 | 0 | y[1] = M[1][0] * x[0] + M[1][1] * x[1] + M[1][2] * x[2]; |
299 | 0 | y[2] = M[2][0] * x[0] + M[2][1] * x[1] + M[2][2] * x[2]; |
300 | 0 | } |
301 | | |
302 | | // MD5 algorithm. See https://www.ietf.org/rfc/rfc1321.html#appendix-A.3 |
303 | | // This function writes the MD5 checksum in place at offset `checksumOffset` of `data`. |
304 | | // This function shall only be called with a copy of iccColorTemplate or iccGrayTemplate, and sizeof(icc*Template). |
305 | | static void computeMD5(uint8_t * data, size_t length) |
306 | 0 | { |
307 | 0 | static const uint32_t sineparts[64] = { |
308 | 0 | 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, 0x698098d8, 0x8b44f7af, |
309 | 0 | 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, |
310 | 0 | 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, |
311 | 0 | 0x676f02d9, 0x8d2a4c8a, 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, |
312 | 0 | 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, 0xf4292244, 0x432aff97, |
313 | 0 | 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, |
314 | 0 | 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391, |
315 | 0 | }; |
316 | 0 | static const uint8_t shift[64] = { |
317 | 0 | 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, |
318 | 0 | 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, |
319 | 0 | }; |
320 | |
|
321 | 0 | uint32_t a0 = 0x67452301, b0 = 0xefcdab89, c0 = 0x98badcfe, d0 = 0x10325476; |
322 | |
|
323 | 0 | for (uint32_t i = 0; i < length; i += 64) { |
324 | 0 | uint32_t a = a0, b = b0, c = c0, d = d0, f, g; |
325 | 0 | for (uint32_t j = 0; j < 64; j++) { |
326 | 0 | if (j < 16) { |
327 | 0 | f = (b & c) | ((~b) & d); |
328 | 0 | g = j; |
329 | 0 | } else if (j < 32) { |
330 | 0 | f = (d & b) | ((~d) & c); |
331 | 0 | g = (5 * j + 1) & 0xf; |
332 | 0 | } else if (j < 48) { |
333 | 0 | f = b ^ c ^ d; |
334 | 0 | g = (3 * j + 5) & 0xf; |
335 | 0 | } else { |
336 | 0 | f = c ^ (b | (~d)); |
337 | 0 | g = (7 * j) & 0xf; |
338 | 0 | } |
339 | 0 | uint32_t u = readLittleEndianU32(data + i + g * 4); |
340 | 0 | f += a + sineparts[j] + u; |
341 | 0 | a = d; |
342 | 0 | d = c; |
343 | 0 | c = b; |
344 | 0 | b += (f << shift[j]) | (f >> (32u - shift[j])); |
345 | 0 | } |
346 | 0 | a0 += a; |
347 | 0 | b0 += b; |
348 | 0 | c0 += c; |
349 | 0 | d0 += d; |
350 | 0 | } |
351 | |
|
352 | 0 | uint8_t * output = data + checksumOffset; |
353 | 0 | writeLittleEndianU32(output, a0); |
354 | 0 | writeLittleEndianU32(output + 4, b0); |
355 | 0 | writeLittleEndianU32(output + 8, c0); |
356 | 0 | writeLittleEndianU32(output + 12, d0); |
357 | 0 | } |
358 | | |
359 | | // Bradford chromatic adaptation matrix |
360 | | // from https://www.researchgate.net/publication/253799640_A_uniform_colour_space_based_upon_CIECAM97s |
361 | | static const double bradford[3][3] = { |
362 | | { 0.8951, 0.2664, -0.1614 }, |
363 | | { -0.7502, 1.7135, 0.0367 }, |
364 | | { 0.0389, -0.0685, 1.0296 }, |
365 | | }; |
366 | | |
367 | | // LMS values for D50 whitepoint |
368 | | static const double lmsD50[3] = { 0.996284, 1.02043, 0.818644 }; |
369 | | |
370 | | avifBool avifGenerateRGBICC(avifRWData * icc, float gamma, const float primaries[8]) |
371 | 0 | { |
372 | 0 | uint8_t buffer[sizeof(iccColorTemplate)]; |
373 | 0 | memcpy(buffer, iccColorTemplate, sizeof(iccColorTemplate)); |
374 | |
|
375 | 0 | double whitePointXYZ[3]; |
376 | 0 | if (!xyToXYZ(&primaries[6], whitePointXYZ)) { |
377 | 0 | return AVIF_FALSE; |
378 | 0 | } |
379 | | |
380 | 0 | if (!putColorant(buffer + colorWhiteOffset, whitePointXYZ)) { |
381 | 0 | return AVIF_FALSE; |
382 | 0 | } |
383 | | |
384 | 0 | const double rgbPrimaries[3][3] = { |
385 | 0 | { primaries[0], primaries[2], primaries[4] }, |
386 | 0 | { primaries[1], primaries[3], primaries[5] }, |
387 | 0 | { 1.0 - primaries[0] - primaries[1], 1.0 - primaries[2] - primaries[3], 1.0 - primaries[4] - primaries[5] } |
388 | 0 | }; |
389 | |
|
390 | 0 | double rgbPrimariesInv[3][3]; |
391 | 0 | if (!matInv(rgbPrimaries, rgbPrimariesInv)) { |
392 | 0 | return AVIF_FALSE; |
393 | 0 | } |
394 | | |
395 | 0 | double rgbCoefficients[3]; |
396 | 0 | vecMul(rgbPrimariesInv, whitePointXYZ, rgbCoefficients); |
397 | |
|
398 | 0 | double rgbCoefficientsMat[3][3]; |
399 | 0 | matDiag(rgbCoefficients, rgbCoefficientsMat); |
400 | |
|
401 | 0 | double rgbXYZ[3][3]; |
402 | 0 | matMul(rgbPrimaries, rgbCoefficientsMat, rgbXYZ); |
403 | | |
404 | | // ICC stores primaries XYZ under PCS. |
405 | | // Adapt using linear bradford transform |
406 | | // from https://onlinelibrary.wiley.com/doi/pdf/10.1002/9781119021780.app3 |
407 | 0 | double lms[3]; |
408 | 0 | vecMul(bradford, whitePointXYZ, lms); |
409 | 0 | for (int i = 0; i < 3; ++i) { |
410 | 0 | if (fabs(lms[i]) < small) { |
411 | 0 | return AVIF_FALSE; |
412 | 0 | } |
413 | 0 | lms[i] = lmsD50[i] / lms[i]; |
414 | 0 | } |
415 | | |
416 | 0 | double adaptation[3][3]; |
417 | 0 | matDiag(lms, adaptation); |
418 | |
|
419 | 0 | double tmp[3][3]; |
420 | 0 | matMul(adaptation, bradford, tmp); |
421 | |
|
422 | 0 | double bradfordInv[3][3]; |
423 | 0 | if (!matInv(bradford, bradfordInv)) { |
424 | 0 | return AVIF_FALSE; |
425 | 0 | } |
426 | 0 | matMul(bradfordInv, tmp, adaptation); |
427 | |
|
428 | 0 | double rgbXYZD50[3][3]; |
429 | 0 | matMul(adaptation, rgbXYZ, rgbXYZD50); |
430 | 0 | matTrans(rgbXYZD50); |
431 | |
|
432 | 0 | if (!putColorant(buffer + colorRedOffset, rgbXYZD50[0])) { |
433 | 0 | return AVIF_FALSE; |
434 | 0 | } |
435 | | |
436 | 0 | if (!putColorant(buffer + colorGreenOffset, rgbXYZD50[1])) { |
437 | 0 | return AVIF_FALSE; |
438 | 0 | } |
439 | | |
440 | 0 | if (!putColorant(buffer + colorBlueOffset, rgbXYZD50[2])) { |
441 | 0 | return AVIF_FALSE; |
442 | 0 | } |
443 | | |
444 | 0 | if (!putU8Fixed8(buffer + colorGammaOffset, gamma)) { |
445 | 0 | return AVIF_FALSE; |
446 | 0 | } |
447 | | |
448 | 0 | computeMD5(buffer, sizeof(iccColorTemplate)); |
449 | 0 | if (avifRWDataSet(icc, buffer, iccColorLength) != AVIF_RESULT_OK) { |
450 | 0 | return AVIF_FALSE; |
451 | 0 | } |
452 | | |
453 | 0 | return AVIF_TRUE; |
454 | 0 | } |
455 | | |
456 | | avifBool avifGenerateGrayICC(avifRWData * icc, float gamma, const float white[2]) |
457 | 0 | { |
458 | 0 | uint8_t buffer[sizeof(iccGrayTemplate)]; |
459 | 0 | memcpy(buffer, iccGrayTemplate, sizeof(iccGrayTemplate)); |
460 | |
|
461 | 0 | double whitePointXYZ[3]; |
462 | 0 | if (!xyToXYZ(white, whitePointXYZ)) { |
463 | 0 | return AVIF_FALSE; |
464 | 0 | } |
465 | | |
466 | 0 | if (!putColorant(buffer + grayWhiteOffset, whitePointXYZ)) { |
467 | 0 | return AVIF_FALSE; |
468 | 0 | } |
469 | | |
470 | 0 | if (!putU8Fixed8(buffer + grayGammaOffset, gamma)) { |
471 | 0 | return AVIF_FALSE; |
472 | 0 | } |
473 | | |
474 | 0 | computeMD5(buffer, sizeof(iccGrayTemplate)); |
475 | 0 | if (avifRWDataSet(icc, buffer, iccGrayLength) != AVIF_RESULT_OK) { |
476 | 0 | return AVIF_FALSE; |
477 | 0 | } |
478 | | |
479 | 0 | return AVIF_TRUE; |
480 | 0 | } |