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