1 /*
2 * Copyright 2018 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8 #ifdef _MSC_VER
9 #define _CRT_SECURE_NO_WARNINGS
10 #endif
11
12 #include "skcms.h"
13 #include "skcms_internal.h"
14 #include "test_only.h"
15 #include <stdlib.h>
16 #include <string.h>
17
print_shortest_float(FILE * fp,float x)18 static void print_shortest_float(FILE* fp, float x) {
19 char buf[80];
20 int digits;
21 for (digits = 0; digits < 12; digits++) {
22 snprintf(buf, sizeof(buf), "%.*f", digits, x);
23 float back;
24 if (1 != sscanf(buf, "%f", &back) || back == x) {
25 break;
26 }
27 }
28 fprintf(fp, "%.*f", digits, x);
29 }
30
dump_transform_to_XYZD50(FILE * fp,const skcms_ICCProfile * profile)31 static void dump_transform_to_XYZD50(FILE* fp,
32 const skcms_ICCProfile* profile) {
33 // Interpret as RGB_888 if data color space is RGB or GRAY, RGBA_8888 if CMYK.
34 skcms_PixelFormat fmt = skcms_PixelFormat_RGB_888;
35 size_t npixels = 84;
36 if (profile->data_color_space == 0x434D594B/*CMYK*/) {
37 fmt = skcms_PixelFormat_RGBA_8888;
38 npixels = 63;
39 }
40
41 uint8_t dst[252];
42
43 if (!skcms_Transform(
44 skcms_252_random_bytes, fmt, skcms_AlphaFormat_Unpremul, profile,
45 dst, skcms_PixelFormat_RGB_888, skcms_AlphaFormat_Unpremul, skcms_XYZD50_profile(),
46 npixels)) {
47 fprintf(fp, "We can parse this profile, but not transform it to XYZD50!\n");
48 return;
49 }
50
51 fprintf(fp, "252 random bytes transformed to linear XYZD50 bytes:\n");
52 // 252 = 3 * 3 * 7 * 4, so we will print either 9 or 12 rows of 7 XYZ values here.
53 for (size_t i = 0; i < npixels; i += 7) {
54 fprintf(fp, "\t"
55 "%02x%02x%02x %02x%02x%02x %02x%02x%02x %02x%02x%02x "
56 "%02x%02x%02x %02x%02x%02x %02x%02x%02x\n",
57 dst[3*i+ 0], dst[3*i+ 1], dst[3*i+ 2],
58 dst[3*i+ 3], dst[3*i+ 4], dst[3*i+ 5],
59 dst[3*i+ 6], dst[3*i+ 7], dst[3*i+ 8],
60 dst[3*i+ 9], dst[3*i+10], dst[3*i+11],
61 dst[3*i+12], dst[3*i+13], dst[3*i+14],
62 dst[3*i+15], dst[3*i+16], dst[3*i+17],
63 dst[3*i+18], dst[3*i+19], dst[3*i+20]);
64 }
65 }
66
dump_transform_to_sRGBA(FILE * fp,const skcms_ICCProfile * profile)67 static void dump_transform_to_sRGBA(FILE* fp,
68 const skcms_ICCProfile* profile) {
69 // Let's just transform all combinations of 0x00, 0x7f, and 0xff inputs to 32-bit sRGB.
70 // This helps catch issues with alpha, and is mildly interesting on its own.
71
72 uint32_t src[81],
73 dst[81];
74 for (int i = 0; i < 81; i++) {
75 src[i] = (uint32_t)((i/1 % 3) * 127.5f) << 0
76 | (uint32_t)((i/3 % 3) * 127.5f) << 8
77 | (uint32_t)((i/9 % 3) * 127.5f) << 16
78 | (uint32_t)((i/27 % 3) * 127.5f) << 24;
79 }
80
81 // No matter profile->data_color_space, this should be fine, either RGBA itself or CMYK.
82 const skcms_PixelFormat pf = skcms_PixelFormat_RGBA_8888;
83 const skcms_AlphaFormat af = skcms_AlphaFormat_Unpremul;
84
85 if (!skcms_Transform(src, pf,af, profile,
86 dst, pf,af, skcms_sRGB_profile(), 81)) {
87 fprintf(fp, "We can parse this profile, but not transform it to sRGB!\n");
88 return;
89 }
90 fprintf(fp, "81 edge-case pixels transformed to sRGB 8888 (unpremul):\n");
91
92 for (int i = 0; i < 9; i++) {
93 fprintf(fp, "\t%08x %08x %08x %08x %08x %08x %08x %08x %08x\n",
94 dst[9*i+0], dst[9*i+1], dst[9*i+2],
95 dst[9*i+3], dst[9*i+4], dst[9*i+5],
96 dst[9*i+6], dst[9*i+7], dst[9*i+8]);
97 }
98 }
99
100
signature_to_string(uint32_t sig,char * str)101 static void signature_to_string(uint32_t sig, char* str) {
102 str[0] = (char)((sig >> 24) & 0xFF);
103 str[1] = (char)((sig >> 16) & 0xFF);
104 str[2] = (char)((sig >> 8) & 0xFF);
105 str[3] = (char)((sig >> 0) & 0xFF);
106 str[4] = 0;
107 }
108
dump_sig_field(FILE * fp,const char * name,uint32_t val)109 static void dump_sig_field(FILE* fp, const char* name, uint32_t val) {
110 char valStr[5];
111 signature_to_string(val, valStr);
112 fprintf(fp, "%20s : 0x%08X : '%s'\n", name, val, valStr);
113 }
114
dump_transfer_function(FILE * fp,const char * name,const skcms_TransferFunction * tf,float max_error)115 static void dump_transfer_function(FILE* fp, const char* name,
116 const skcms_TransferFunction* tf, float max_error) {
117 fprintf(fp, "%4s : %.7g, %.7g, %.7g, %.7g, %.7g, %.7g, %.7g", name,
118 tf->g, tf->a, tf->b, tf->c, tf->d, tf->e, tf->f);
119
120 if (max_error > 0) {
121 fprintf(fp, " (Max error: %.6g)", max_error);
122 }
123
124 if (tf->d > 0) {
125 // Has both linear and nonlinear sections, include the discontinuity at D
126 float l_at_d = (tf->c * tf->d + tf->f);
127 float n_at_d = powf_(tf->a * tf->d + tf->b, tf->g) + tf->e;
128 fprintf(fp, " (D-gap: %.6g)", (n_at_d - l_at_d));
129 }
130
131 fprintf(fp, " (f(1) = %.6g)", skcms_TransferFunction_eval(tf, 1.0f));
132
133 skcms_Curve curve;
134 curve.table_entries = 0;
135 curve.parametric = *tf;
136
137 if (skcms_AreApproximateInverses(&curve, skcms_sRGB_Inverse_TransferFunction())) {
138 fprintf(fp, " (~sRGB)");
139 } else if (skcms_AreApproximateInverses(&curve, skcms_Identity_TransferFunction())) {
140 fprintf(fp, " (~Identity)");
141 }
142 fprintf(fp, "\n");
143 }
144
dump_curve(FILE * fp,const char * name,const skcms_Curve * curve)145 static void dump_curve(FILE* fp, const char* name, const skcms_Curve* curve) {
146 if (curve->table_entries == 0) {
147 dump_transfer_function(fp, name, &curve->parametric, 0);
148 } else {
149 fprintf(fp, "%4s : %d-bit table with %u entries", name,
150 curve->table_8 ? 8 : 16, curve->table_entries);
151 if (skcms_AreApproximateInverses(curve, skcms_sRGB_Inverse_TransferFunction())) {
152 fprintf(fp, " (~sRGB)");
153 }
154 fprintf(fp, "\n");
155 float max_error;
156 skcms_TransferFunction tf;
157 if (skcms_ApproximateCurve(curve, &tf, &max_error)) {
158 dump_transfer_function(fp, "~=", &tf, max_error);
159 }
160 }
161 }
162
dump_profile(const skcms_ICCProfile * profile,FILE * fp)163 void dump_profile(const skcms_ICCProfile* profile, FILE* fp) {
164 fprintf(fp, "%20s : 0x%08X : %u\n", "Size", profile->size, profile->size);
165 dump_sig_field(fp, "Data color space", profile->data_color_space);
166 dump_sig_field(fp, "PCS", profile->pcs);
167 fprintf(fp, "%20s : 0x%08X : %u\n", "Tag count", profile->tag_count, profile->tag_count);
168
169 fprintf(fp, "\n");
170
171 fprintf(fp, " Tag : Type : Size : Offset\n");
172 fprintf(fp, " ------ : ------ : ------ : --------\n");
173 for (uint32_t i = 0; i < profile->tag_count; ++i) {
174 skcms_ICCTag tag;
175 skcms_GetTagByIndex(profile, i, &tag);
176 char tagSig[5];
177 char typeSig[5];
178 signature_to_string(tag.signature, tagSig);
179 signature_to_string(tag.type, typeSig);
180 fprintf(fp, " '%s' : '%s' : %6u : %u\n", tagSig, typeSig, tag.size,
181 (uint32_t)(tag.buf - profile->buffer));
182 }
183
184 fprintf(fp, "\n");
185
186 if (profile->has_trc) {
187 const char* trcNames[3] = { "rTRC", "gTRC", "bTRC" };
188 for (int i = 0; i < 3; ++i) {
189 dump_curve(fp, trcNames[i], &profile->trc[i]);
190 }
191 if (skcms_TRCs_AreApproximateInverse(profile, skcms_sRGB_Inverse_TransferFunction())) {
192 fprintf(fp, "TRCs ≈ sRGB\n");
193 }
194 }
195
196 skcms_ICCProfile best_single_curve = *profile;
197 if (skcms_MakeUsableAsDestinationWithSingleCurve(&best_single_curve)) {
198 dump_transfer_function(fp, "Best", &best_single_curve.trc[0].parametric, 0.0f);
199
200 skcms_TransferFunction inv;
201 if (skcms_TransferFunction_invert(&best_single_curve.trc[0].parametric, &inv)) {
202 dump_transfer_function(fp, "Inv ", &inv, 0.0f);
203
204 fprintf(fp, "Best Error: | %.6g %.6g %.6g |\n",
205 skcms_MaxRoundtripError(&profile->trc[0], &inv),
206 skcms_MaxRoundtripError(&profile->trc[1], &inv),
207 skcms_MaxRoundtripError(&profile->trc[2], &inv));
208 } else {
209 fprintf(fp, "*** could not invert Best ***\n");
210 }
211 }
212
213 if (profile->has_toXYZD50) {
214 skcms_Matrix3x3 toXYZ = profile->toXYZD50;
215
216 fprintf(fp, " XYZ : | ");
217 print_shortest_float(fp, toXYZ.vals[0][0]); fprintf(fp, " ");
218 print_shortest_float(fp, toXYZ.vals[0][1]); fprintf(fp, " ");
219 print_shortest_float(fp, toXYZ.vals[0][2]); fprintf(fp, " |\n");
220
221 fprintf(fp, " | ");
222 print_shortest_float(fp, toXYZ.vals[1][0]); fprintf(fp, " ");
223 print_shortest_float(fp, toXYZ.vals[1][1]); fprintf(fp, " ");
224 print_shortest_float(fp, toXYZ.vals[1][2]); fprintf(fp, " |\n");
225
226 fprintf(fp, " | ");
227 print_shortest_float(fp, toXYZ.vals[2][0]); fprintf(fp, " ");
228 print_shortest_float(fp, toXYZ.vals[2][1]); fprintf(fp, " ");
229 print_shortest_float(fp, toXYZ.vals[2][2]); fprintf(fp, " |\n");
230
231 float white_x = toXYZ.vals[0][0] + toXYZ.vals[0][1] + toXYZ.vals[0][2],
232 white_y = toXYZ.vals[1][0] + toXYZ.vals[1][1] + toXYZ.vals[1][2],
233 white_z = toXYZ.vals[2][0] + toXYZ.vals[2][1] + toXYZ.vals[2][2];
234 if (fabsf_(white_x - 0.964f) > 0.01f ||
235 fabsf_(white_y - 1.000f) > 0.01f ||
236 fabsf_(white_z - 0.825f) > 0.01f) {
237 fprintf(fp, " !!! This does not appear to use a D50 whitepoint, rather [%g %g %g]\n",
238 white_x, white_y, white_z);
239 }
240 }
241
242 if (profile->has_A2B) {
243 const skcms_A2B* a2b = &profile->A2B;
244 fprintf(fp, " A2B : %s%s\"B\"\n", a2b->input_channels ? "\"A\", CLUT, " : "",
245 a2b->matrix_channels ? "\"M\", Matrix, " : "");
246 if (a2b->input_channels) {
247 fprintf(fp, "%4s : %u inputs\n", "\"A\"", a2b->input_channels);
248 const char* curveNames[4] = { "A0", "A1", "A2", "A3" };
249 for (uint32_t i = 0; i < a2b->input_channels; ++i) {
250 dump_curve(fp, curveNames[i], &a2b->input_curves[i]);
251 }
252 fprintf(fp, "%4s : ", "CLUT");
253 const char* sep = "";
254 for (uint32_t i = 0; i < a2b->input_channels; ++i) {
255 fprintf(fp, "%s%u", sep, a2b->grid_points[i]);
256 sep = " x ";
257 }
258 fprintf(fp, " (%d bpp)\n", a2b->grid_8 ? 8 : 16);
259 }
260
261 if (a2b->matrix_channels) {
262 fprintf(fp, "%4s : %u inputs\n", "\"M\"", a2b->matrix_channels);
263 const char* curveNames[4] = { "M0", "M1", "M2" };
264 for (uint32_t i = 0; i < a2b->matrix_channels; ++i) {
265 dump_curve(fp, curveNames[i], &a2b->matrix_curves[i]);
266 }
267 const skcms_Matrix3x4* m = &a2b->matrix;
268 fprintf(fp, "Mtrx : | %.9g %.9g %.9g %.9g |\n"
269 " | %.9g %.9g %.9g %.9g |\n"
270 " | %.9g %.9g %.9g %.9g |\n",
271 m->vals[0][0], m->vals[0][1], m->vals[0][2], m->vals[0][3],
272 m->vals[1][0], m->vals[1][1], m->vals[1][2], m->vals[1][3],
273 m->vals[2][0], m->vals[2][1], m->vals[2][2], m->vals[2][3]);
274 }
275
276 {
277 fprintf(fp, "%4s : %u outputs\n", "\"B\"", a2b->output_channels);
278 const char* curveNames[3] = { "B0", "B1", "B2" };
279 for (uint32_t i = 0; i < a2b->output_channels; ++i) {
280 dump_curve(fp, curveNames[i], &a2b->output_curves[i]);
281 }
282 }
283 }
284
285 skcms_Matrix3x3 chad;
286 if (skcms_GetCHAD(profile, &chad)) {
287 fprintf(fp, "CHAD : | ");
288 print_shortest_float(fp, chad.vals[0][0]); fprintf(fp, " ");
289 print_shortest_float(fp, chad.vals[0][1]); fprintf(fp, " ");
290 print_shortest_float(fp, chad.vals[0][2]); fprintf(fp, " |\n");
291
292 fprintf(fp, " | ");
293 print_shortest_float(fp, chad.vals[1][0]); fprintf(fp, " ");
294 print_shortest_float(fp, chad.vals[1][1]); fprintf(fp, " ");
295 print_shortest_float(fp, chad.vals[1][2]); fprintf(fp, " |\n");
296
297 fprintf(fp, " | ");
298 print_shortest_float(fp, chad.vals[2][0]); fprintf(fp, " ");
299 print_shortest_float(fp, chad.vals[2][1]); fprintf(fp, " ");
300 print_shortest_float(fp, chad.vals[2][2]); fprintf(fp, " |\n");
301 }
302
303 float wtpt[3];
304 if (skcms_GetWTPT(profile, wtpt)) {
305 fprintf(fp, "WTPT : | ");
306 print_shortest_float(fp, wtpt[0]); fprintf(fp, " ");
307 print_shortest_float(fp, wtpt[1]); fprintf(fp, " ");
308 print_shortest_float(fp, wtpt[2]); fprintf(fp, " |\n");
309 }
310
311 dump_transform_to_XYZD50(fp, profile);
312 dump_transform_to_sRGBA (fp, profile);
313 if (skcms_ApproximatelyEqualProfiles(profile, skcms_sRGB_profile())) {
314 fprintf(fp, "This profile ≈ sRGB.\n");
315 }
316 }
317
load_file_fp(FILE * fp,void ** buf,size_t * len)318 bool load_file_fp(FILE* fp, void** buf, size_t* len) {
319 if (fseek(fp, 0L, SEEK_END) != 0) {
320 return false;
321 }
322 long size = ftell(fp);
323 if (size <= 0) {
324 return false;
325 }
326 *len = (size_t)size;
327 rewind(fp);
328
329 *buf = malloc(*len);
330 if (!*buf) {
331 return false;
332 }
333
334 if (fread(*buf, 1, *len, fp) != *len) {
335 free(*buf);
336 return false;
337 }
338 return true;
339 }
340
load_file(const char * filename,void ** buf,size_t * len)341 bool load_file(const char* filename, void** buf, size_t* len) {
342 FILE* fp = fopen(filename, "rb");
343 if (!fp) {
344 return false;
345 }
346 bool result = load_file_fp(fp, buf, len);
347 fclose(fp);
348 return result;
349 }
350
write_file(const char * filename,void * buf,size_t len)351 bool write_file(const char* filename, void* buf, size_t len) {
352 FILE* fp = fopen(filename, "wb");
353 if (!fp) {
354 return false;
355 }
356 bool result = (fwrite(buf, 1, len, fp) == len);
357 fclose(fp);
358 return result;
359 }
360