Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
colorchart.c
Go to the documentation of this file.
1/*
2 * This file is part of darktable,
3 * Copyright (C) 2016 johannes hanika.
4 * Copyright (C) 2016-2017 Peter Budai.
5 * Copyright (C) 2016 Roman Lebedev.
6 * Copyright (C) 2016-2017 Tobias Ellinghaus.
7 * Copyright (C) 2018 jothalha.
8 * Copyright (C) 2020, 2023 Aurélien PIERRE.
9 * Copyright (C) 2020 Heiko Bauke.
10 * Copyright (C) 2020-2021 Pascal Obry.
11 * Copyright (C) 2021 Ralf Brown.
12 * Copyright (C) 2022 Martin Bařinka.
13 *
14 * darktable is free software: you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation, either version 3 of the License, or
17 * (at your option) any later version.
18 *
19 * darktable is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * You should have received a copy of the GNU General Public License
25 * along with darktable. If not, see <http://www.gnu.org/licenses/>.
26 */
27
28#include "common/darktable.h"
29#include <lcms2.h>
30#include <stdio.h>
31#include <stdlib.h>
32#include <string.h>
33
34#pragma GCC diagnostic ignored "-Wshadow"
35
36#include "colorchart.h"
37
38#define MAX_LINE_LENGTH 512
39
49
50void free_chart(chart_t *chart)
51{
52 if(IS_NULL_PTR(chart)) return;
53 g_list_free_full(chart->f_list, dt_free_gpointer);
54 chart->f_list = NULL;
55 if(chart->d_table) g_hash_table_unref(chart->d_table);
56 if(chart->box_table) g_hash_table_unref(chart->box_table);
57 if(chart->patch_sets) g_hash_table_unref(chart->patch_sets);
58 dt_free(chart);
59}
60
61static char *parse_string(char **c)
62{
63 while(**c == ' ' || **c == '\t') (*c)++;
64 char *result = *c;
65 while(**c != ' ' && **c != '\t' && **c != '\0' && **c != '\n') (*c)++;
66 *(*c)++ = '\0';
67 return result;
68}
69
70static double parse_double(char **c)
71{
72 while(**c == ' ' || **c == '\t') (*c)++;
73 double result = g_ascii_strtod(*c, c);
74 *((*c) - 1) = '\0';
75 return result;
76}
77
78// this is not the code from argyll but a rewrite!
79static int strinc(char *label, size_t buffer_size)
80{
81 size_t label_len = strlen(label);
82 char *c = label + label_len - 1;
83 while(c >= label)
84 {
85 char carry_over = 0;
86 switch(*c)
87 {
88 case 'z':
89 case 'Z':
90 *c -= 25;
91 carry_over = *c;
92 break;
93 case '9':
94 *c = '0';
95 carry_over = '1';
96 break;
97 default:
98 (*c)++;
99 }
100 if(IS_NULL_PTR(carry_over))
101 break;
102 else if(c == label)
103 {
104 if(label_len + 1 >= buffer_size) return 0;
105 memmove(c + 1, c, label_len + 1);
106 *c = carry_over;
107 }
108 c--;
109 }
110 return 1;
111}
112
114{
116 box->color[0] = c0;
117 box->color[1] = c1;
118 box->color[2] = c2;
119
120 dt_aligned_pixel_t Lab = { c0, c1, c2 };
121 dt_aligned_pixel_t XYZ = { c0 * 0.01, c1 * 0.01, c2 * 0.01 };
122
123 switch(color_space)
124 {
125 default:
127 for(int c = 0; c < 3; c++) box->rgb[c] = 0.0;
128 break;
132 dt_XYZ_to_sRGB_clipped(XYZ, box->rgb);
133 break;
134 }
135}
136
137static void free_labels_list(gpointer data)
138{
139 g_list_free_full((GList *)data, dt_free_gpointer);
140}
141
142// In some environments ERROR is already defined, ie: WIN32
143#if defined(ERROR)
144#undef ERROR
145#endif // defined (ERROR)
146
147#define ERROR \
148 { \
149 lineno = __LINE__; \
150 goto error; \
151 }
152// according to cht_format.html from argyll:
153// "The keywords and associated data must be used in the following order: BOXES, BOX_SHRINK, REF_ROTATION,
154// XLIST, YLIST and EXPECTED."
155chart_t *parse_cht(const char *filename)
156{
157 chart_t *result = (chart_t *)calloc(1, sizeof(chart_t));
158 int lineno = 0;
159
160 FILE *fp = g_fopen(filename, "rb");
161 if(IS_NULL_PTR(fp))
162 {
163 fprintf(stderr, "error opening `%s'\n", filename);
164 ERROR;
165 }
166
167 // parser control
168 char line[MAX_LINE_LENGTH] = { 0 };
169 parser_state_t last_block = BLOCK_NONE;
170 int skip_block = 0;
171
172 // data gathered from the CHT file
173 unsigned int n_boxes;
174 result->d_table = g_hash_table_new_full(g_str_hash, g_str_equal, dt_free_gpointer, dt_free_gpointer);
175 result->box_table = g_hash_table_new_full(g_str_hash, g_str_equal, dt_free_gpointer, dt_free_gpointer);
176 result->patch_sets = g_hash_table_new_full(g_str_hash, g_str_equal, dt_free_gpointer, free_labels_list);
177
178 float x_min = FLT_MAX, x_max = FLT_MIN, y_min = FLT_MAX, y_max = FLT_MIN;
179
180 // main loop over the input file
181 while(fgets(line, MAX_LINE_LENGTH, fp))
182 {
183 if(line[0] == '\0' || line[0] == '\n')
184 {
185 skip_block = 0;
186 continue;
187 }
188 if(skip_block) continue;
189
190 // we should be at the start of a block now
191 char *c = line;
192 ssize_t len = strlen(line);
193 char *keyword = parse_string(&c);
194
195 if(!g_strcmp0(keyword, "BOXES") && last_block < BLOCK_BOXES)
196 {
197 last_block = BLOCK_BOXES;
198 if(c - line >= len) ERROR;
199 n_boxes = parse_double(&c);
200
201 // let's have another loop reading from the file.
202 while(fgets(line, MAX_LINE_LENGTH, fp))
203 {
204 if(line[0] == '\0' || line[0] == '\n') break;
205
206 char *c = line;
207 ssize_t len = strlen(line);
208 while(*c == ' ') c++;
209 if(*c == 'F')
210 {
211 float x0, y0, x1, y1, x2, y2, x3, y3;
212 // using sscanf would be nice, but parsing floats does only work with LANG=C
213 // if(sscanf(line, " F _ _ %f %f %f %f %f %f %f %f", &x0, &y0, &x1, &y1, &x2, &y2, &x3, &y3) != 8)
214 // ERROR;
215 c++;
216 while(*c == ' ') c++;
217 if(*c++ != '_') ERROR;
218 while(*c == ' ') c++;
219 if(*c++ != '_') ERROR;
220 while(*c == ' ') c++;
221 if(c - line >= len) ERROR;
222 x0 = parse_double(&c);
223 if(c - line >= len) ERROR;
224 y0 = parse_double(&c);
225 if(c - line >= len) ERROR;
226 x1 = parse_double(&c);
227 if(c - line >= len) ERROR;
228 y1 = parse_double(&c);
229 if(c - line >= len) ERROR;
230 x2 = parse_double(&c);
231 if(c - line >= len) ERROR;
232 y2 = parse_double(&c);
233 if(c - line >= len) ERROR;
234 x3 = parse_double(&c);
235 if(c - line >= len) ERROR;
236 y3 = parse_double(&c);
237
238 x_min = MIN(x_min, x0);
239 x_min = MIN(x_min, x1);
240 x_min = MIN(x_min, x2);
241 x_min = MIN(x_min, x3);
242
243 y_min = MIN(y_min, y0);
244 y_min = MIN(y_min, y1);
245 y_min = MIN(y_min, y2);
246 y_min = MIN(y_min, y3);
247
248 x_max = MAX(x_max, x0);
249 x_max = MAX(x_max, x1);
250 x_max = MAX(x_max, x2);
251 x_max = MAX(x_max, x3);
252
253 y_max = MAX(y_max, y0);
254 y_max = MAX(y_max, y1);
255 y_max = MAX(y_max, y2);
256 y_max = MAX(y_max, y3);
257
258 f_line_t *l = (f_line_t *)malloc(sizeof(f_line_t));
259
260 l->p[0].x = x0;
261 l->p[0].y = y0;
262 l->p[1].x = x1;
263 l->p[1].y = y1;
264 l->p[2].x = x2;
265 l->p[2].y = y2;
266 l->p[3].x = x3;
267 l->p[3].y = y3;
268
269 result->f_list = g_list_append(result->f_list, l);
270 }
271 // these get parsed the same way
272 else if((*c == 'D') || (*c == 'X') || (*c == 'Y'))
273 {
274 char kl, *lxs, *lxe, *lys, *lye;
275 float w, h, xo, yo, xi, yi;
276 kl = *c;
277 *c++ = '\0';
278
279 if(c - line >= len) ERROR;
280 lxs = parse_string(&c);
281 if(c - line >= len) ERROR;
282 lxe = parse_string(&c);
283 if(c - line >= len) ERROR;
284 lys = parse_string(&c);
285 if(c - line >= len) ERROR;
286 lye = parse_string(&c);
287
288 if(c - line >= len) ERROR;
289 w = parse_double(&c);
290 if(c - line >= len) ERROR;
291 h = parse_double(&c);
292 if(c - line >= len) ERROR;
293 xo = parse_double(&c);
294 if(c - line >= len) ERROR;
295 yo = parse_double(&c);
296 if(c - line >= len) ERROR;
297 xi = parse_double(&c);
298 if(c - line >= len) ERROR;
299 yi = parse_double(&c);
300
301 x_min = MIN(x_min, xo);
302 y_min = MIN(y_min, yo);
303
304 int y_steps = 1;
305 size_t lxs_len = strlen(lxs), lxe_len = strlen(lxe), lys_len = strlen(lys), lye_len = strlen(lye);
306 if(lxs_len > lxe_len || lys_len > lye_len) ERROR;
307
308 // make sure there is enough room to add another char in the beginning
309 const size_t x_label_size = lxe_len + 1;
310 const size_t y_label_size = lye_len + 1;
311
312 char *x_label = malloc(x_label_size);
313 char *y_label = malloc(y_label_size);
314
315 char *first_label = NULL, *last_label = NULL;
316 GList *labels = NULL;
317
318 float y = yo;
319 memcpy(y_label, lys, lys_len + 1);
320 while(1)
321 {
322 float x = xo;
323 memcpy(x_label, lxs, lxs_len + 1);
324 while(1)
325 {
326 // build the label of the box
327 char *label;
328 if(!g_strcmp0(x_label, "_"))
329 label = g_strdup(y_label);
330 else if(!g_strcmp0(y_label, "_"))
331 label = g_strdup(x_label);
332 else
333 {
334 if(kl == 'Y')
335 label = g_strconcat(y_label, x_label, NULL);
336 else
337 label = g_strconcat(x_label, y_label, NULL);
338 }
339
340 if(IS_NULL_PTR(first_label)) first_label = label;
341 dt_free(last_label);
342 last_label = label;
343
344 // store it
345 box_t *box = calloc(1, sizeof(box_t));
346 box->p.x = x;
347 box->p.y = y;
348 box->w = w;
349 box->h = h;
350 box->color_space = DT_COLORSPACE_NONE; // no color for this box yet
351 if(kl == 'D')
352 g_hash_table_insert(result->d_table, label, box);
353 else
354 g_hash_table_insert(result->box_table, label, box);
355 if(kl == 'X' || kl == 'Y') labels = g_list_append(labels, g_strdup(label));
356
357 // increment in x direction
358 if(!g_strcmp0(x_label, lxe)) break;
359 x += xi;
360 if(!strinc(x_label, x_label_size))
361 {
362 dt_free(y_label);
363 dt_free(x_label);
364 ERROR;
365 }
366 }
367 x_max = MAX(x_max, x + w);
368 // increment in y direction
369 if(!g_strcmp0(y_label, lye)) break;
370 y += yi;
371 y_steps++;
372 if(!strinc(y_label, y_label_size))
373 {
374 dt_free(y_label);
375 dt_free(x_label);
376 ERROR;
377 }
378 }
379 y_max = MAX(y_max, y + h);
380 if((kl == 'X' || kl == 'Y') && first_label && last_label)
381 g_hash_table_insert(result->patch_sets, g_strdup_printf("%s .. %s", first_label, last_label), labels);
382
383 dt_free(last_label);
384 dt_free(y_label);
385 dt_free(x_label);
386 }
387 else
388 ERROR;
389 }
390
391 if(n_boxes != g_hash_table_size(result->d_table) + g_hash_table_size(result->box_table)) ERROR;
392
393 // all the box lines are read and we know the bounding box,
394 // so let's scale all the values to have a bounding box with the longer side having length 1 and start
395 // at (0, 0)
396
397 result->bb_w = x_max - x_min;
398 result->bb_h = y_max - y_min;
399
400#define SCALE_X(x) x = (x - x_min) / result->bb_w
401#define SCALE_Y(y) y = (y - y_min) / result->bb_h
402
403 for(GList *iter = result->f_list; iter; iter = g_list_next(iter))
404 {
405 f_line_t *f = iter->data;
406 for(int i = 0; i < 4; i++)
407 {
408 SCALE_X(f->p[i].x);
409 SCALE_Y(f->p[i].y);
410 }
411 }
412
413 GHashTableIter table_iter;
414 gpointer key, value;
415
416 g_hash_table_iter_init(&table_iter, result->d_table);
417 while(g_hash_table_iter_next(&table_iter, &key, &value))
418 {
419 box_t *box = (box_t *)value;
420 SCALE_X(box->p.x);
421 SCALE_Y(box->p.y);
422 box->w /= result->bb_w;
423 box->h /= result->bb_h;
424 }
425
426 g_hash_table_iter_init(&table_iter, result->box_table);
427 while(g_hash_table_iter_next(&table_iter, &key, &value))
428 {
429 box_t *box = (box_t *)value;
430 SCALE_X(box->p.x);
431 SCALE_Y(box->p.y);
432 box->w /= result->bb_w;
433 box->h /= result->bb_h;
434 }
435
436#undef SCALE_X
437#undef SCALE_Y
438 }
439 else if(!g_strcmp0(keyword, "BOX_SHRINK") && last_block < BLOCK_BOX_SHRINK)
440 {
441 last_block = BLOCK_BOX_SHRINK;
442 if(c - line >= len) ERROR;
443 result->box_shrink = parse_double(&c);
444 }
445 else if(!g_strcmp0(keyword, "REF_ROTATION") && last_block < BLOCK_REF_ROTATION)
446 {
447 last_block = BLOCK_REF_ROTATION;
448 if(c - line >= len) ERROR;
449 result->ref_rotation = parse_double(&c);
450 }
451 else if(!g_strcmp0(keyword, "XLIST") && last_block < BLOCK_XLIST)
452 {
453 last_block = BLOCK_XLIST;
454 // skip until empty line, we don't care about these
455 skip_block = 1;
456 }
457 else if(!g_strcmp0(keyword, "YLIST") && last_block < BLOCK_YLIST)
458 {
459 last_block = BLOCK_YLIST;
460 // skip until empty line, we don't care about these
461 skip_block = 1;
462 }
463 else if(!g_strcmp0(keyword, "EXPECTED") && last_block < BLOCK_EXPECTED)
464 {
465 last_block = BLOCK_EXPECTED;
467 if(c - line >= len) ERROR;
468 char *cs = parse_string(&c);
469 if(c - line >= len) ERROR;
470 unsigned int n_colors = parse_double(&c);
471
472 if(!g_strcmp0(cs, "XYZ"))
474 else if(!g_strcmp0(cs, "LAB"))
476 else
477 ERROR;
478
479 // read and store the numbers.
480 // we use them 1) to draw visual hints on the grid and 2) as a fallback reference set
481
482 // let's have another loop reading from the file.
483 while(fgets(line, MAX_LINE_LENGTH, fp))
484 {
485 if(line[0] == '\0' || line[0] == '\n') break;
486 n_colors--;
487 ssize_t len = strlen(line);
488 char *c = line;
489
490 char *label = parse_string(&c);
491 box_t *box = (box_t *)g_hash_table_lookup(result->box_table, label);
492 if(IS_NULL_PTR(box)) ERROR;
493
494 if(c - line >= len) ERROR;
495 float c0 = parse_double(&c);
496 if(c - line >= len) ERROR;
497 float c1 = parse_double(&c);
498 if(c - line >= len) ERROR;
499 float c2 = parse_double(&c);
501 }
502 if(n_colors != 0) ERROR;
503 }
504 else
505 {
506 fprintf(stderr, "unknown keyword `%s'\n", keyword);
507 ERROR;
508 }
509 }
510
511 fprintf(stderr, "cht `%s' done\n", filename);
512 goto end;
513
514error:
515 fprintf(stderr, "error parsing CHT file, (%s:%d)\n", __FUNCTION__, lineno);
516 // clean up
517 free_chart(result);
518 result = NULL;
519
520end:
521 if(fp) fclose(fp);
522 return result;
523}
524
525int parse_it8(const char *filename, chart_t *chart)
526{
527 int result = 1;
528 cmsHANDLE hIT8 = cmsIT8LoadFromFile(NULL, filename);
529 if(IS_NULL_PTR(hIT8))
530 {
531 fprintf(stderr, "error loading IT8 file `%s'\n", filename);
532 goto error;
533 }
534
535 if(cmsIT8TableCount(hIT8) != 1)
536 {
537 fprintf(stderr, "error with the IT8 file, we only support files with one table at the moment\n");
538 goto error;
539 }
540
542 int column_SAMPLE_ID = -1, column_X = -1, column_Y = -1, column_Z = -1, column_L = -1, column_a = -1,
543 column_b = -1;
544 char **sample_names = NULL;
545 int n_columns = cmsIT8EnumDataFormat(hIT8, &sample_names);
546
547 if(n_columns == -1)
548 {
549 fprintf(stderr, "error with the IT8 file, can't get column types\n");
550 goto error;
551 }
552
553 for(int i = 0; i < n_columns; i++)
554 {
555 if(!g_strcmp0(sample_names[i], "SAMPLE_ID"))
556 column_SAMPLE_ID = i;
557 else if(!g_strcmp0(sample_names[i], "XYZ_X"))
558 column_X = i;
559 else if(!g_strcmp0(sample_names[i], "XYZ_Y"))
560 column_Y = i;
561 else if(!g_strcmp0(sample_names[i], "XYZ_Z"))
562 column_Z = i;
563 else if(!g_strcmp0(sample_names[i], "LAB_L"))
564 column_L = i;
565 else if(!g_strcmp0(sample_names[i], "LAB_A"))
566 column_a = i;
567 else if(!g_strcmp0(sample_names[i], "LAB_B"))
568 column_b = i;
569 }
570
571 if(column_SAMPLE_ID == -1)
572 {
573 fprintf(stderr, "error with the IT8 file, can't find the SAMPLE_ID column\n");
574 goto error;
575 }
576
577 char *columns[3] = { 0 };
578 if(column_X != -1 && column_Y != -1 && column_Z != -1)
579 {
581 columns[0] = "XYZ_X";
582 columns[1] = "XYZ_Y";
583 columns[2] = "XYZ_Z";
584 }
585 else if(column_L != -1 && column_a != -1 && column_b != -1)
586 {
588 columns[0] = "LAB_L";
589 columns[1] = "LAB_A";
590 columns[2] = "LAB_B";
591 }
592 else
593 {
594 fprintf(stderr, "error with the IT8 file, can't find XYZ or Lab columns\n");
595 goto error;
596 }
597
598 GHashTableIter table_iter;
599 gpointer key, value;
600
601 g_hash_table_iter_init(&table_iter, chart->box_table);
602 while(g_hash_table_iter_next(&table_iter, &key, &value))
603 {
604 box_t *box = (box_t *)value;
605
606 if(cmsIT8GetData(hIT8, key, "SAMPLE_ID") == NULL)
607 {
608 fprintf(stderr, "error with the IT8 file, can't find sample `%s'\n", (char *)key);
609 goto error;
610 }
611
612 checker_set_color(box, color_space, cmsIT8GetDataDbl(hIT8, key, columns[0]), cmsIT8GetDataDbl(hIT8, key, columns[1]),
613 cmsIT8GetDataDbl(hIT8, key, columns[2]));
614 }
615
616 fprintf(stderr, "it8 `%s' done\n", filename);
617 goto end;
618
619error:
620 result = 0;
621end:
622 if(hIT8) cmsIT8Free(hIT8);
623 return result;
624}
625
626#undef MAX_LINE_LENGTH
627
628// clang-format off
629// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
630// vim: shiftwidth=2 expandtab tabstop=2 cindent
631// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
632// clang-format on
static void error(char *msg)
Definition ashift_lsd.c:202
static char * parse_string(char **c)
Definition colorchart.c:61
static int strinc(char *label, size_t buffer_size)
Definition colorchart.c:79
static double parse_double(char **c)
Definition colorchart.c:70
chart_t * parse_cht(const char *filename)
Definition colorchart.c:155
void free_chart(chart_t *chart)
Definition colorchart.c:50
static void free_labels_list(gpointer data)
Definition colorchart.c:137
int parse_it8(const char *filename, chart_t *chart)
Definition colorchart.c:525
#define ERROR
Definition colorchart.c:147
void checker_set_color(box_t *box, dt_colorspaces_color_profile_type_t color_space, float c0, float c1, float c2)
Definition colorchart.c:113
#define SCALE_X(x)
#define SCALE_Y(y)
parser_state_t
Definition colorchart.c:40
@ BLOCK_BOX_SHRINK
Definition colorchart.c:43
@ BLOCK_NONE
Definition colorchart.c:41
@ BLOCK_YLIST
Definition colorchart.c:46
@ BLOCK_REF_ROTATION
Definition colorchart.c:44
@ BLOCK_XLIST
Definition colorchart.c:45
@ BLOCK_BOXES
Definition colorchart.c:42
@ BLOCK_EXPECTED
Definition colorchart.c:47
#define MAX_LINE_LENGTH
Definition colorchart.c:38
dt_colorspaces_color_profile_type_t
Definition colorspaces.h:81
@ DT_COLORSPACE_LAB
Definition colorspaces.h:89
@ DT_COLORSPACE_NONE
Definition colorspaces.h:82
@ DT_COLORSPACE_XYZ
Definition colorspaces.h:88
dt_Lab_to_XYZ(Lab, XYZ)
const dt_aligned_pixel_t f
static dt_aligned_pixel_t XYZ
static dt_aligned_pixel_t Lab
char * key
static void dt_free_gpointer(gpointer ptr)
Definition darktable.h:463
#define dt_free(ptr)
Definition darktable.h:456
static const dt_aligned_pixel_simd_t value
Definition darktable.h:577
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition darktable.h:281
static const float x
dt_colorspaces_color_profile_type_t color_space
Definition mipmap_cache.c:5
float dt_aligned_pixel_t[4]
float h
Definition colorchart.h:45
float w
Definition colorchart.h:45
dt_colorspaces_color_profile_type_t color_space
Definition colorchart.h:47
dt_aligned_pixel_t color
Definition colorchart.h:48
dt_aligned_pixel_t rgb
Definition colorchart.h:49
point_t p
Definition colorchart.h:44
float bb_w
Definition colorchart.h:63
GHashTable * patch_sets
Definition colorchart.h:61
float bb_h
Definition colorchart.h:63
float box_shrink
Definition colorchart.h:65
GHashTable * box_table
Definition colorchart.h:57
GHashTable * d_table
Definition colorchart.h:57
float ref_rotation
Definition colorchart.h:65
GList * f_list
Definition colorchart.h:55
point_t p[4]
Definition colorchart.h:38
float y
Definition colorchart.h:33
float x
Definition colorchart.h:33
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29