Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
common/colorchecker.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2025-2026 Guillaume Stutin.
4
5 darktable is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 darktable is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with darktable. If not, see <http://www.gnu.org/licenses/>.
17*/
18
31#include "system/mem_alloc.h"
32#include "common/paths.h" // DT_PATH_MAX
33#include "colorchecker.h"
35#include "file_location.h"
36
37#include <glib.h>
38#include <inttypes.h>
39#include <lcms2.h>
40#include "common/logging.h"
41
42// In some environments ERROR is already defined, ie: WIN32
43#if defined(ERROR)
44#undef ERROR
45#endif // defined (ERROR)
46
47#define ERROR \
48 { \
49 lineno = __LINE__; \
50 goto error; \
51 }
52
62
63typedef struct cht_box_t {
64 char key_letter; // 'D', 'X', or 'Y'
69 float width;
70 float height;
71 float x_origin;
72 float y_origin;
76
77typedef struct cht_box_F_t {
78 float ax; // top left corner
79 float ay; // top left corner
80 float bx; // top right corner
81 float by; // top right corner
82 float cx; // bottom left corner
83 float cy; // bottom left corner
84 float dx; // bottom right corner
85 float dy; // bottom right corner
86 float width; // width of the frame
87 float height; // height of the frame
89
90#define TWO_SQRT2f 2.8284271247461900976f // sqrt(2) * 2
91
92/* The only consumers of these two tables live in this file. They used to be defined in
93 * common/colorchecker.h, i.e. once per translation unit that included it -- which was
94 * invisible while every IOP was its own shared object, and a duplicate definition once
95 * the modules were linked into lib_ansel. A header declares; it does not define. */
96static const char *CGATS_types[CGATS_TYPE_UNKOWN] = {
97 "IT8.7/1", // transparent
98 "IT8.7/2", // opaque
99 "CTI3" // opaque
100};
101
103 "Transparent",
104 "Opaque"
105};
106
108{
109 if(IS_NULL_PTR(dest) || IS_NULL_PTR(src)) return;
110
111 dest->name = g_strdup(src->name);
112 dest->x = src->x;
113 dest->y = src->y;
114 dest->Lab[0] = src->Lab[0];
115 dest->Lab[1] = src->Lab[1];
116 dest->Lab[2] = src->Lab[2];
117}
118
120{
121 if(IS_NULL_PTR(dest) || IS_NULL_PTR(src)) return;
122
123 dest->name = g_strdup(src->name);
124 dest->author = g_strdup(src->author);
125 dest->date = g_strdup(src->date);
126 dest->manufacturer = g_strdup(src->manufacturer);
127 dest->type = src->type;
128 dest->radius = src->radius;
129 dest->ratio = src->ratio;
130 dest->patches = src->patches;
131 dest->size[0] = src->size[0];
132 dest->size[1] = src->size[1];
133 dest->middle_grey = src->middle_grey;
134 dest->white = src->white;
135 dest->black = src->black;
136
137 if(!IS_NULL_PTR(src->values))
138 {
140 if(IS_NULL_PTR(dest->values))
141 {
142 fprintf(stderr, "Error: Memory allocation failed for color checker values.\n");
143 return;
144 }
145
146 for(int i = 0; i < src->patches; i++)
147 {
149 }
150 }
151 else
152 {
153 dest->values = NULL;
154 }
155 dest->finished = TRUE;
156}
157
164static cht_box_F_t *_dt_cht_extract_F(const char **tokens)
165{
166 cht_box_F_t *frame_coordinates = (cht_box_F_t *)malloc(sizeof(cht_box_F_t));
167 if(IS_NULL_PTR(frame_coordinates)) return NULL;
168
169 size_t index = 0;
170 float extracted_coords[8] = { 0.f };
171 for(size_t i = 0; tokens[i] != NULL; i++)
172 {
173 if(index >= 8) break; // Prevent overflow
174
175 if(g_ascii_isdigit(tokens[i][0])) // note : always positive numbers
176 {
177 extracted_coords[index] = (float)g_ascii_strtod(tokens[i], NULL);
178 index++;
179 }
180 }
181
182 // copy the extracted coordinates to the frame_coordinates structure
183 frame_coordinates->ax = extracted_coords[0];
184 frame_coordinates->ay = extracted_coords[1];
185 frame_coordinates->bx = extracted_coords[2];
186 frame_coordinates->by = extracted_coords[3];
187 frame_coordinates->cx = extracted_coords[4];
188 frame_coordinates->cy = extracted_coords[5];
189 frame_coordinates->dx = extracted_coords[6];
190 frame_coordinates->dy = extracted_coords[7];
191
192 // Compute the width and height of the frame
193 frame_coordinates->width = extracted_coords[2] - extracted_coords[0];
194 frame_coordinates->height = extracted_coords[5] - extracted_coords[1];
195
196 return frame_coordinates;
197}
198
200{
202 if(IS_NULL_PTR(result)) return NULL;
203
204 result->type = NULL;
205 result->radius = 0.f;
206 result->ratio = 0.f;
207 result->size[0] = 0;
208 result->size[1] = 0;
209 result->middle_grey = 0;
210 result->white = 0;
211 result->black = 0;
212 result->num_patches = 0;
213 result->colums = 0;
214 result->rows = 0;
215 result->patch_width = FLT_MAX;
216 result->patch_height = FLT_MAX;
217 result->patch_offset_x = 0.f;
218 result->patch_offset_y = 0.f;
219 result->guide_size[0] = 0.f;
220 result->guide_size[1] = 0.f;
221 result->patches = NULL;
222
223 return result;
224}
225
227{
228 if(IS_NULL_PTR(chart_spec)) return;
229
230 dt_free(chart_spec->type);
231
232 // Free the patches' gslist
233 if(chart_spec->patches)
234 g_slist_free_full(chart_spec->patches, dt_colorchecker_patch_cleanup_list);
235
236 dt_free(chart_spec);
237}
238
240{
241 // dt_alloc_align: dt_color_checker_patch is 64-aligned (its array sibling above already
242 // allocates aligned); dt_colorchecker_patch_cleanup_list() frees with dt_free_align.
244 if(IS_NULL_PTR(patch)) return NULL;
245
246 patch->name = NULL;
247 patch->Lab[0] = 0.f;
248 patch->Lab[1] = 0.f;
249 patch->Lab[2] = 0.f;
250 patch->x = -1.f;
251 patch->y = -1.f;
252
253 return patch;
254}
255
256static void _dt_cht_box_cleanup(void *data)
257{
258 cht_box_t *box = (cht_box_t *)data;
259 if(IS_NULL_PTR(box)) return;
260
262 dt_free(box->label_x_end);
264 dt_free(box->label_y_end);
265 dt_free(box);
266}
267
268static cht_box_t *_dt_cht_box_extract(const char **tokens)
269{
270 cht_box_t *box = (cht_box_t *)calloc(1, sizeof(cht_box_t));
271 if(IS_NULL_PTR(box)) return NULL;
272
273 size_t index = 0;
274 size_t i = 0;
275 while(!IS_NULL_PTR(tokens[i]) && index <= 10)
276 {
277 if(tokens[i][0] != '\0')
278 {
279 float value = 0;
280 const char *string = tokens[i];
281
282 // Check if the token is a digit or a negative number before converting the string to float
283 if(g_ascii_isdigit(tokens[i][0]) || (tokens[i][0] == '-' && g_ascii_isdigit(tokens[i][1])))
284 value = (float)g_ascii_strtod(tokens[i], NULL);
285
286 switch(index)
287 {
288 case 0: box->key_letter = tokens[i][0]; index++; break; // 'D', 'X', or 'Y'
289 case 1: box->label_x_start = g_strdup(string); index++; break;
290 case 2: box->label_x_end = g_strdup(string); index++; break;
291 case 3: box->label_y_start = g_strdup(string); index++; break;
292 case 4: box->label_y_end = g_strdup(string); index++; break;
293 case 5: box->width = value; index++; break;
294 case 6: box->height = value; index++; break;
295 case 7: box->x_origin = value; index++; break;
296 case 8: box->y_origin = value; index++; break;
297 case 9: box->x_increment = value; index++; break;
298 case 10: box->y_increment = value; index++; break;
299 default: fprintf(stderr, "Unexpected token in cht box extraction: %s\n", tokens[i]);
301 return NULL;
302 }
303 }
304 i++;
305 }
306
307 return box;
308}
309
316static char *_increment_string(const gchar *in)
317{
318 if (IS_NULL_PTR(in) || *in == '\0') return NULL;
319
320 gchar *result = g_strdup(in);
321 if(IS_NULL_PTR(result)) return NULL;
322
323 size_t len = strlen(result);
324
325 if(len == 0)
326 {
327 dt_free(result);
328 return NULL;
329 }
330
331 for(int i = (int)len - 1; i >= 0; i--)
332 {
333 // for numbers
334 if(g_ascii_isdigit(result[i]))
335 {
336 if(result[i] == '9')
337 {
338 result[i] = '0';
339 continue;
340 }
341 result[i]++;
342 break;
343 }
344 // for letters
345 else if(g_ascii_isalpha(result[i]))
346 {
347 if(result[i] == 'z' || result[i] == 'Z')
348 {
349 result[i] = (result[i] == 'z') ? 'a' : 'A';
350 continue;
351 }
352 result[i]++;
353 break;
354 }
355 // there should not be other cases
356 else
357 {
358 break;
359 }
360 }
361
362 return result;
363}
364
371static inline const char *_remove_leading_zeros(const char *in)
372{
373 if(IS_NULL_PTR(in) || *in == '\0') return "";
374 const char *start = in;
375 while(*start == '0') start++;
376
377 return start;
378}
379
389static gboolean _dt_cht_generate_patch_list(dt_colorchecker_chart_spec_t *chart, const cht_box_t *cht_patch, const cht_box_F_t *F_box)
390{
391 gboolean result = FALSE;
392 int lineno = 0;
393
394 gchar *current_colum = NULL;
395 gchar *current_row = NULL;
396 gchar *last_label = NULL;
397 GSList *patch_tail = NULL;
398
399 // Input validation
400 if(IS_NULL_PTR(cht_patch))
401 {
402 fprintf(stderr, "Invalid cht_patch");
403 ERROR;
404 }
405
406 if(IS_NULL_PTR(chart))
407 {
408 fprintf(stderr, "Invalid chart");
409 ERROR;
410 }
411
412 // The key letter determines the axes to begin to iterate
413 gboolean swap_axes = (cht_patch->key_letter == 'Y') ? TRUE : FALSE;
414
415 // Unpack strings from cht_patch
416 const char *start_colum = swap_axes ? cht_patch->label_y_start : cht_patch->label_x_start;
417 const char *end_colum = swap_axes ? cht_patch->label_y_end : cht_patch->label_x_end;
418
419 const char *start_row = swap_axes ? cht_patch->label_x_start : cht_patch->label_y_start;
420 const char *end_row = swap_axes ? cht_patch->label_x_end : cht_patch->label_y_end;
421
422 // start shouldn't be greater than end
423 if(g_strcmp0(start_colum, end_colum) > 0 || g_strcmp0(start_row, end_row) > 0)
424 ERROR
425
426 // we want the center of the patch.
427 const float patch_w = cht_patch->width / 2;
428 const float patch_h = cht_patch->height / 2;
429
430 // Prepare the initial x and y coordinates
431 float origin_x = cht_patch->x_origin - (chart->guide_size[0] / 2) + patch_w - F_box->ax;
432 float origin_y = cht_patch->y_origin - (chart->guide_size[1] / 2) + patch_h - F_box->ay;
433
434 // build the last label name, for comparison
435 const char *last_label_colum = (end_colum[0] != '_') ? _remove_leading_zeros(end_colum) : NULL;
436 const char *last_label_row = (end_row[0] != '_') ? _remove_leading_zeros(end_row) : NULL;
437 last_label = g_strconcat(last_label_colum ? last_label_colum : "", last_label_row ? last_label_row : "", NULL);
438 if(IS_NULL_PTR(last_label)) ERROR
439
440 // Copy string for manipulation
441 current_colum = g_strdup(start_colum);
442 if(IS_NULL_PTR(current_colum)) ERROR
443 const char *colum_last = swap_axes ? cht_patch->label_y_end : cht_patch->label_x_end;
444 const char *row_last = swap_axes ? cht_patch->label_x_end : cht_patch->label_y_end;
445 const float inv_frame_width = 1.f / (F_box->width - chart->guide_size[0]);
446 const float inv_frame_height = 1.f / (F_box->height - chart->guide_size[1]);
447
448 patch_tail = g_slist_last(chart->patches);
449
450 // Iterate over chart columns and rows, creating one patch per label until the CHT end label is reached.
451 int index_colum = 0;
452 while(g_strcmp0(current_colum, colum_last) <= 0)
453 {
454 current_row = g_strdup(start_row);
455 if(IS_NULL_PTR(current_row)) ERROR
456 int index_row = 0;
457
458 while(g_strcmp0(current_row, row_last) <= 0)
459 {
460 // Create the label
461 const char *label_colum = current_colum[0] != '_' ? _remove_leading_zeros(current_colum) : NULL;
462 const char *label_row = current_row[0] != '_' ? _remove_leading_zeros(current_row) : NULL;
463
464 gchar *label = g_strconcat(label_colum ? label_colum : "", label_row ? label_row : "", NULL);
465 if(IS_NULL_PTR(label)) ERROR
466
467 // Create the patch
469 if(IS_NULL_PTR(patch))
470 {
471 dt_free(label);
472 ERROR
473 }
474
475 // Set the patch properties
476 patch->name = label;
477 label = NULL;
478
479 int index_x = swap_axes ? index_row : index_colum;
480 float temp_x = (origin_x + (cht_patch->x_increment * index_x)) * inv_frame_width;
481
482 int index_y = swap_axes ? index_colum : index_row;
483 float temp_y = (origin_y + (cht_patch->y_increment * index_y)) * inv_frame_height;
484
485 patch->x = temp_x;
486 patch->y = temp_y;
487
488 // Add the node explicitly at the current tail: g_slist_append() would rescan
489 // the whole list for every patch and turn large charts into quadratic work.
490 GSList *patch_node = g_slist_alloc();
491 patch_node->data = patch;
492 patch_node->next = NULL;
493 if(!IS_NULL_PTR(patch_tail))
494 patch_tail->next = patch_node;
495 else
496 chart->patches = patch_node;
497 patch_tail = patch_node;
498
499 const gboolean last_patch_reached = !g_strcmp0(patch->name, last_label);
500 if(last_patch_reached) goto out;
501 if(!g_strcmp0(current_row, "_")) break;
502
503 // increment x in a new string and pass the ownership to current_row
504 gchar *temp = _increment_string(current_row);
505 dt_free(current_row)
506 current_row = temp;
507
508 chart->colums = MAX(chart->colums, index_row + 1);
509 index_row++;
510 }
511
512 dt_free(current_row)
513 current_row = NULL;
514
515 // increment y in a new string and pass the ownership to current_colum
516 gchar *temp = _increment_string(current_colum);
517 dt_free(current_colum)
518 current_colum = temp;
519
520 chart->rows = MAX(chart->rows, index_colum + 1);
521 index_colum++;
522 }
523
524out:
525 result = TRUE;
526 goto end;
527
528error:
529 fprintf(stderr, "error parsing CHT file, in %s %s:%d\n", __FUNCTION__, __FILE__, lineno);
530
531end:
532 dt_free(last_label)
533 dt_free(current_row)
534 dt_free(current_colum)
535 return result;
536}
537
544static GList *_parse_cht(const char *filename)
545{
546 GList *result = NULL;
547
548 if(IS_NULL_PTR(filename))
549 {
550 fprintf(stderr, "Invalid filename for CHT parsing");
551 return NULL;
552 }
553
554 int lineno = 0;
555 GIOChannel *fp = g_io_channel_new_file(filename, "r", NULL);
556 if(IS_NULL_PTR(fp))
557 {
558 fprintf(stderr, "Error opening '%s'\n", filename);
559 return NULL;
560 }
561
562 // parser control
563 GString *line = g_string_new(NULL);
564 parser_state_t last_block = BLOCK_NONE;
565 int skip_block = 0;
566
567 // main loop over the input file
568 while(g_io_channel_read_line_string(fp, line, NULL, NULL) == G_IO_STATUS_NORMAL)
569 {
570 if(line->len == 0)
571 {
572 skip_block = 0;
573 continue;
574 }
575 if(skip_block) continue;
576
577 // we should be at the start of a block now
578 const char *c = line->str;
579 if(IS_NULL_PTR(c)) continue;
580
581 while(*c == ' ') c++; // skip leading spaces
582 gchar **line_tokens = g_strsplit(c, " ", 0);
583
584 if(!IS_NULL_PTR(line_tokens[0]) && !g_strcmp0(line_tokens[0], "BOXES") && last_block < BLOCK_BOXES)
585 {
586 last_block = BLOCK_BOXES;
587
588 // let's have another loop reading from the file.
589 while(g_io_channel_read_line_string(fp, line, NULL, NULL) == G_IO_STATUS_NORMAL)
590 {
591 if(line->len == 0) break;
592
593 c = line->str;
594 while(*c == ' ') c++; // skip leading spaces
595
596 gchar **box_tokens = g_strsplit(c, " ", 0);
597 if(!IS_NULL_PTR(box_tokens[0])
598 && (!g_strcmp0(box_tokens[0], "F")
599 || !g_strcmp0(box_tokens[0], "D")
600 || !g_strcmp0(box_tokens[0], "X")
601 || !g_strcmp0(box_tokens[0], "Y")))
602 {
603 result = g_list_append(result, box_tokens);
604 }
605 else
606 {
607 g_strfreev(box_tokens);
608 }
609 }
610 }
611
612 if(!IS_NULL_PTR(line_tokens[0]) && !g_strcmp0(line_tokens[0], "BOX_SHRINK") && last_block < BLOCK_BOX_SHRINK)
613 {
614 last_block = BLOCK_BOX_SHRINK;
615 skip_block = 1;
616 }
617
618 g_strfreev(line_tokens);
619 }
620
621 if(last_block == BLOCK_NONE)
622 ERROR
623
624 goto end;
625
626error:
627 fprintf(stderr, "error parsing CHT file, in %s %s:%d\n", __FUNCTION__, __FILE__, lineno);
628
629end:
630 if(line) g_string_free(line, TRUE);
631 if(fp) g_io_channel_unref(fp);
632 return result;
633}
634
635// according to cht_format.html from argyll:
636// "The keywords and associated data must be used in the following order: BOXES, BOX_SHRINK, REF_ROTATION,
637// XLIST, YLIST and EXPECTED."
638static gboolean _dispatch_cht_data(GList **boxes, dt_colorchecker_chart_spec_t *chart_spec)
639{
640 gboolean result = FALSE;
641 int lineno = 0;
642
643 // data gathered from the CHT file
644 cht_box_F_t *F_box = NULL;
645 GList *boxes_list = NULL;
646
647 float chart_radius = -1.f;
648
649 if(IS_NULL_PTR(boxes) || IS_NULL_PTR(chart_spec))
650 {
651 fprintf(stderr, "Invalid input to dispatch cht data");
652 ERROR
653 }
654
655 // Gather the frame box and every patch-row/column box before deriving the chart geometry.
656 for(GList *lines = *boxes; lines; lines = g_list_next(lines))
657 {
658 const char **tokens = (const char **)lines->data;
659 if(IS_NULL_PTR(tokens)) ERROR
660
661 const char letter = tokens[0][0];
662 if(letter == 'F')
663 {
664 // A CHT file describes exactly one frame, but nothing here parses a trusted file --
665 // releasing any previous one keeps a malformed input with several F lines from leaking
666 // all but the last. dt_free() on the NULL first pass is a no-op.
667 dt_free(F_box);
668 F_box = _dt_cht_extract_F(tokens);
669 }
670
671 else if(letter == 'D' || letter == 'X' || letter == 'Y')
672 {
673 cht_box_t *box = _dt_cht_box_extract(tokens);
674 if(IS_NULL_PTR(box)) ERROR
675
676 boxes_list = g_list_append(boxes_list, box);
677 }
678 }
679
680 if(IS_NULL_PTR(F_box)) ERROR
681
682 // Fill the colorchecker spec structure
683 chart_spec->ratio = F_box->height / F_box->width;
684 chart_radius = hypotf(F_box->height, F_box->width);
685
686 for(GList *iter = boxes_list; iter; iter = g_list_next(iter))
687 {
688 cht_box_t *box = (cht_box_t *)iter->data;
689 if(IS_NULL_PTR(box)) ERROR
690
691 if(box->key_letter == 'D')
692 {
693 // Save the guide corner sizes when they are specified, to changes the patches area size in consequence.
694 if(!g_strcmp0(box->label_x_start,"MARK")) chart_spec->guide_size[0] = box->width - box->x_origin;
695 if(!g_strcmp0(box->label_x_start,"MARK")) chart_spec->guide_size[1] = box->height - box->y_origin;
696 }
697
698 else if(box->key_letter == 'X' || box->key_letter == 'Y')
699 {
700 chart_spec->patch_width = MIN(chart_spec->patch_width, box->width);
701 chart_spec->patch_height = MIN(chart_spec->patch_height, box->height);
702
703 if(!_dt_cht_generate_patch_list(chart_spec, box, F_box))
704 {
705 ERROR
706 }
707 }
708 }
709
710 chart_spec->num_patches = g_slist_length(chart_spec->patches);
711 chart_spec->size[0] = (size_t)chart_spec->colums;
712 chart_spec->size[1] = (size_t)chart_spec->rows;
713 const float patch_radius = hypotf(chart_spec->patch_width, chart_spec->patch_height) / TWO_SQRT2f;
714 chart_spec->radius = patch_radius / chart_radius;
715
716 result = TRUE;
717 goto end;
718
719error:
720 fprintf(stderr, "Error dispatching CHT file, in %s %s:%d\n", __FUNCTION__, __FILE__, lineno);
721
722end:
723 dt_free(F_box);
724 if(!IS_NULL_PTR(boxes_list)) g_list_free_full(boxes_list, _dt_cht_box_cleanup);
725
726 return result;
727}
728
736static gboolean _dt_colorchecker_open_cht(const char *filename, dt_colorchecker_chart_spec_t *chart_spec)
737{
738 if(IS_NULL_PTR(filename) || IS_NULL_PTR(chart_spec))
739 {
740 fprintf(stderr, "[_dt_colorchecker_open_cht] Error: Invalid input parameters.\n");
741 return FALSE;
742 }
743
744 GList *boxes = _parse_cht(filename);
745 if(IS_NULL_PTR(boxes))
746 {
747 fprintf(stderr, "[_dt_colorchecker_open_cht] Error parsing CHT file '%s'\n", filename);
748 return FALSE;
749 }
750
751 if(!_dispatch_cht_data(&boxes, chart_spec))
752 {
753 fprintf(stderr, "[_dt_colorchecker_open_cht] Error dispatching CHT data from '%s'\n", filename);
754 g_list_free_full(boxes, (GDestroyNotify)g_strfreev);
755 return FALSE;
756 }
757
758 chart_spec->type = g_path_get_basename(filename);
759
760 g_list_free_full(boxes, (GDestroyNotify)g_strfreev);
761
762 return TRUE;
763}
764
766
768{
769 if(IS_NULL_PTR(*hIT8))
770 {
771 fprintf(stderr, "[_dt_colorchecker_IT8_get_material_type] Error: Invalid IT8 handle provided.\n");
773 }
774
775 const int CGATS_type_value = _dt_CGATS_get_type_value(cmsIT8GetSheetType(*hIT8));
776 switch(CGATS_type_value)
777 {
780
782 case CGATS_TYPE_CTI3:
784
786 default:
788 }
789
791}
792
793
802{
804 return colorchecker_material_types[material];
805
806 // else
807 fprintf(stderr, "[_dt_colorchecker_get_material_string] Error: Unknown material type.\n");
808 return NULL;
809}
810
812{
814
815 // Scan only supported names and return the sentinel for unsupported metadata.
817 {
818 if(!g_strcmp0(type, CGATS_types[t])) return t;
819 }
820
821 return CGATS_TYPE_UNKOWN;
822}
823
830static gchar *_dt_colorchecker_get_standard_type(const char *type)
831{
832 gchar *result = NULL;
833
834 if(IS_NULL_PTR(type))
835 {
836 fprintf(stderr, "[_dt_colorchecker_get_standard_type] Error: Invalid CGATS type provided.\n");
837 result = g_strdup("Unknown Type");
838 }
839 else
840 {
843 result = g_strdup("IT8"); // make a shorter title for the IT8 types
844 else if(t == CGATS_TYPE_CTI3)
845 result = g_strdup("CTI3");
846 else
847 {
848 dt_print(DT_DEBUG_VERBOSE, "[_dt_colorchecker_get_standard_type] Unknown CGATS type: %s\n", type);
849 result = g_strdup(type);
850 }
851 }
852
853 if(IS_NULL_PTR(result))
854 {
855 fprintf(stderr, "[_dt_colorchecker_get_standard_type] Error: Memory allocation failed for standard type string.\n");
856 return NULL;
857 }
858
859 return result;
860}
861
869static gboolean _dt_CGATS_is_supported(const cmsHANDLE *hIT8)
870{
871 if(IS_NULL_PTR(hIT8) || IS_NULL_PTR(*hIT8))
872 {
873 fprintf(stderr, "[_dt_CGATS_is_supported] Error: Invalid IT8 handle provided.\n");
874 return FALSE;
875 }
876
877 const char *CGATS_type = cmsIT8GetSheetType(*hIT8);
878 // The CGATS property stores the file syntax version, for example
879 // "CGATS.17". The sheet type stores the target family we support,
880 // for example "IT8.7/1" or "IT8.7/2".
882 {
883 dt_print(DT_DEBUG_VERBOSE, "[_dt_CGATS_is_supported] type '%s' is not supported by Ansel.\n",
884 !IS_NULL_PTR(CGATS_type) ? CGATS_type : "(null)");
885 return FALSE;
886 }
887
888 int column_SAMPLE_ID = -1;
889 int column_X = -1;
890 int column_Y = -1;
891 int column_Z = -1;
892 int column_L = -1;
893 int column_a = -1;
894 int column_b = -1;
895 char **sample_names = NULL;
896 int n_columns = cmsIT8EnumDataFormat(*hIT8, &sample_names);
897
898 if(n_columns == -1)
899 {
900 fprintf(stderr, "[_dt_CGATS_is_supported] Error with the CGATS file, can't get column types\n");
901 return FALSE;
902 }
903
904 if(!IS_NULL_PTR(sample_names))
905 for(int i = 0; i < n_columns; i++)
906 {
907 if(!g_strcmp0(sample_names[i], "SAMPLE_ID") || !g_strcmp0(sample_names[i], "SAMPLE_LOC"))
908 column_SAMPLE_ID = i;
909 else if(!g_strcmp0(sample_names[i], "XYZ_X"))
910 column_X = i;
911 else if(!g_strcmp0(sample_names[i], "XYZ_Y"))
912 column_Y = i;
913 else if(!g_strcmp0(sample_names[i], "XYZ_Z"))
914 column_Z = i;
915 else if(!g_strcmp0(sample_names[i], "LAB_L"))
916 column_L = i;
917 else if(!g_strcmp0(sample_names[i], "LAB_A"))
918 column_a = i;
919 else if(!g_strcmp0(sample_names[i], "LAB_B"))
920 column_b = i;
921 }
922
923 if(column_SAMPLE_ID == -1)
924 {
925 fprintf(stderr, "[_dt_CGATS_is_supported] Error: can't find the SAMPLE_ID column in the CGATS file.\n");
926 return FALSE;
927 }
928
929 if(column_X + column_Y + column_Z + column_L + column_a + column_b == -1)
930 {
931 fprintf(stderr, "[_dt_CGATS_is_supported] Error: No XYZ or Lab columns found in the CGATS file.\n");
932 return FALSE;
933 }
934
935 uint32_t table_count = cmsIT8TableCount(*hIT8);
936 if(table_count != 1)
937 {
938 dt_print(DT_DEBUG_VERBOSE, "[_dt_CGATS_is_supported] the CGATS file contains %u tables but only one table is supported at the moment.\n",
939 table_count);
940 return FALSE;
941 }
942
943 return TRUE;
944}
945
946static inline const char *_dt_CGATS_get_author(const cmsHANDLE *hIT8)
947{
948 if(IS_NULL_PTR(hIT8) || IS_NULL_PTR(*hIT8))
949 {
950 fprintf(stderr, "[_dt_CGATS_get_author] Error: Invalid IT8 handle provided.\n");
951 return "Unknown Author";
952 }
953 const char *author = cmsIT8GetProperty(*hIT8, "ORIGINATOR");
954
955 return !IS_NULL_PTR(author) ? author : "Unknown Author";
956}
957
964static inline const char *_dt_CGATS_get_date(const cmsHANDLE *hIT8)
965{
966 if(IS_NULL_PTR(hIT8) || IS_NULL_PTR(*hIT8))
967 {
968 fprintf(stderr, "[_dt_CGATS_get_date] Error: Invalid IT8 handle provided.\n");
969 return "Unknown Date";
970 }
971
972 // in CGATS.17, the date in PROD_DATE is stored in the format YYYY:MM
973 const char *date = cmsIT8GetProperty(*hIT8, "PROD_DATE");
974
975 return !IS_NULL_PTR(date) ? date : "Unknown Date";
976}
977
978static inline const char *_dt_CGATS_get_manufacturer(const cmsHANDLE *hIT8)
979{
980 if(IS_NULL_PTR(hIT8) || IS_NULL_PTR(*hIT8))
981 {
982 fprintf(stderr, "[_dt_CGATS_get_manufacturer] Error: Invalid IT8 handle provided.\n");
983 return "Unknown Manufacturer";
984 }
985 const char *manufacturer = cmsIT8GetProperty(*hIT8, "MANUFACTURER");
986 return !IS_NULL_PTR(manufacturer) ? manufacturer : "Unknown Manufacturer";
987}
988
995static inline gchar *_dt_get_builtin_colorchecker_name(const dt_color_checker_targets target_type)
996{
997 dt_color_checker_t *color_checker = dt_get_color_checker(target_type, NULL, NULL);
998 if(IS_NULL_PTR(color_checker))
999 {
1000 fprintf(stderr, "[_dt_get_builtin_colorchecker_name] Error: Unable to get the color checker %d.\n", target_type);
1001 return g_strdup("Unknown name");
1002 }
1003 gchar *name = g_strdup(color_checker->name);
1004
1005 dt_colorchecker_cleanup(color_checker);
1006 return !IS_NULL_PTR(name) ? name : g_strdup("Unknown name");
1007}
1008
1016{
1017 dt_color_checker_t *color_checker = dt_get_color_checker(target_type, NULL, NULL);
1018 if(IS_NULL_PTR(color_checker))
1019 {
1020 fprintf(stderr, "[_dt_get_builtin_colorchecker_patch_nb] Error: Unable to get the color checker %d.\n", target_type);
1021 return 0;
1022 }
1023 const int patch_nb = color_checker->patches;
1024
1025 dt_colorchecker_cleanup(color_checker);
1026 return patch_nb;
1027}
1028
1037{
1038 if(IS_NULL_PTR(label))
1039 {
1040 fprintf(stderr, "[_dt_colorchecker_label_build_name] Error: Invalid label provided.\n");
1041 return g_strdup("Unknown Color Checker");
1042 }
1043 const gchar *type = !IS_NULL_PTR(label->type) && g_strcmp0(label->type, "") != 0 ? label->type : "?";
1044 // material if any
1045 gchar *tmp_material = !IS_NULL_PTR(label->material) && g_strcmp0(label->material, "") != 0
1046 ? g_strdup_printf(" (%s)", label->material)
1047 : g_strdup("");
1048 // Description if any
1049 gchar *tmp_description = !IS_NULL_PTR(label->description) && g_strcmp0(label->description, "") != 0
1050 ? g_strdup(label->description)
1051 : g_strdup("Unknown");
1052
1053 // Compose: filename
1054 gchar *name = g_strdup_printf("%s%s - %s", type, tmp_material, tmp_description);
1055
1056 // Clean up
1057 dt_free(tmp_material)
1058 dt_free(tmp_description)
1059
1060 return name;
1061}
1062
1071static inline char *_dt_CGATS_get_name(const cmsHANDLE *hIT8, const char *filename)
1072{
1073 gchar *result = NULL;
1074 gchar *basename = NULL;
1075
1076 if(!IS_NULL_PTR(filename) && g_strcmp0(filename, "") != 0)
1077 {
1078 basename = g_path_get_basename(filename);
1079 char *dot = g_strrstr(basename, ".");
1080 if(!IS_NULL_PTR(dot))
1081 {
1082 // remove the file extension
1083 *dot = '\0';
1084 }
1085 }
1086
1087 if(IS_NULL_PTR(hIT8) || IS_NULL_PTR(*hIT8))
1088 {
1089 fprintf(stderr, "[_dt_CGATS_get_name] Error: Invalid CGATS handle provided.\n");
1090 result = g_strdup(!IS_NULL_PTR(basename) ? basename : "Unnamed CGATS");
1091 }
1092 else
1093 {
1094 // Get other useful information from the CGATS file
1095 gchar *chart_type = _dt_colorchecker_get_standard_type(cmsIT8GetSheetType(*hIT8));
1096 const char *description = cmsIT8GetProperty(*hIT8, "DESCRIPTOR");
1098 gchar *material_str = g_strdup(_dt_colorchecker_get_material_string(material));
1099
1100 if(IS_NULL_PTR(chart_type) && IS_NULL_PTR(description) && IS_NULL_PTR(material_str))
1101 {
1102 dt_print(DT_DEBUG_VERBOSE, "[_dt_CGATS_get_name] no useful metadata found in the CGATS file to build a name, using filename instead.\n");
1103 result = (!IS_NULL_PTR(basename) && g_strcmp0(basename, "") != 0) ? g_strdup(basename) : g_strdup("Unnamed CGATS");
1104 }
1105 else
1106 {
1108 .type = chart_type,
1109 .description = !IS_NULL_PTR(description) ? description : NULL,
1110 .material = material_str }; //can be NULL
1111
1112 gchar *name = _dt_colorchecker_label_build_name(&label);
1113
1114 if(!IS_NULL_PTR(name) && g_strcmp0(name, "") != 0)
1115 result = name;
1116 else
1117 {
1118 result = (!IS_NULL_PTR(basename) && g_strcmp0(basename, "") != 0) ? g_strdup(basename) : g_strdup("Unnamed CGATS");
1119 dt_free(name)
1120 }
1121 }
1122
1123 dt_free(chart_type)
1124 dt_free(material_str)
1125 }
1126
1127 dt_free(basename)
1128
1129 return result;
1130}
1131
1138static int _dt_colorchecker_cht_get_patch_nb(const char *filepath)
1139{
1140 int result = 0;
1141 dt_colorchecker_chart_spec_t *chart_spec = NULL;
1142
1143 if(IS_NULL_PTR(filepath) || g_strcmp0(filepath, "") == 0)
1144 {
1145 fprintf(stderr, "Error: Invalid file path provided for CHT file.\n");
1146 goto end;
1147 }
1148
1149 chart_spec = _dt_colorchecker_chart_spec_init();
1150 if(IS_NULL_PTR(chart_spec))
1151 {
1152 fprintf(stderr, "Error: cannot allocate memory for the chart spec.\n");
1153 goto end;
1154 }
1155
1156 if(!_dt_colorchecker_open_cht(filepath, chart_spec))
1157 {
1158 fprintf(stderr, "Error: cannot open the cht file '%s'.\n", filepath);
1159 goto end;
1160 }
1161
1162 if(!chart_spec->num_patches)
1163 fprintf(stderr, "Error: no patches found in the cht file '%s'.\n", filepath);
1164 else result = chart_spec->num_patches;
1165
1166 end:
1167 if(!IS_NULL_PTR(chart_spec)) _dt_colorchecker_chart_spec_cleanup(chart_spec);
1168 return result;
1169}
1170
1171static float dE_1976(const float a, const float b, const float c)
1172{
1173 return sqrtf(sqf(a) + sqf(b) + sqf(c));
1174}
1175
1176static inline void _dt_CGATS_find_whitest_blackest_greyest(const dt_color_checker_patch *const values, size_t *bwg, const size_t patch)
1177{
1178 if(IS_NULL_PTR(values) || IS_NULL_PTR(bwg))
1179 {
1180 fprintf(stderr, "[_dt_CGATS_find_whitest_blackest_greyest] Error: Invalid input parameters.\n");
1181 return;
1182 }
1183
1184 for(int i = 0; i < 3; i++)
1185 {
1186 float target = 50.f * i;
1187 float delta_current = dE_1976(values[bwg[i]].Lab[0] - target, values[bwg[i]].Lab[1], values[bwg[i]].Lab[2]);
1188 float delta_patch = dE_1976(values[patch].Lab[0] - target, values[patch].Lab[1], values[patch].Lab[2]);
1189 if(delta_patch < delta_current)
1190 bwg[i] = patch;
1191 }
1192}
1193
1204static dt_color_checker_patch *_dt_colorchecker_CGATS_fill_patch_values(const cmsHANDLE hIT8, size_t *bwg, const dt_colorchecker_chart_spec_t *chart_spec, const size_t num_patches)
1205{
1206 if(IS_NULL_PTR(hIT8) || IS_NULL_PTR(bwg) || IS_NULL_PTR(chart_spec) || num_patches == 0)
1207 {
1208 fprintf(stderr, "Error: Invalid input parameters for filling patch values from CGATS file.\n");
1209 return NULL;
1210 }
1211
1212 int column_SAMPLE_ID = -1;
1213 int column_X = -1;
1214 int column_Y = -1;
1215 int column_Z = -1;
1216 int column_L = -1;
1217 int column_a = -1;
1218 int column_b = -1;
1219 char **sample_names = NULL;
1220 int n_columns = cmsIT8EnumDataFormat(hIT8, &sample_names);
1221
1223 if(IS_NULL_PTR(values))
1224 {
1225 fprintf(stderr, "Error: Memory allocation failed for values array.\n");
1226 goto error;
1227 }
1228
1229 gboolean use_XYZ = FALSE;
1230 if(n_columns == -1)
1231 {
1232 fprintf(stderr, "Error with the CGATS file, can't get column types\n");
1233 goto error;
1234 }
1235
1236 for(int i = 0; i < n_columns; i++)
1237 {
1238 if(!g_strcmp0(sample_names[i], "SAMPLE_ID") || !g_strcmp0(sample_names[i], "SAMPLE_LOC"))
1239 column_SAMPLE_ID = i;
1240 else if(!g_strcmp0(sample_names[i], "XYZ_X"))
1241 column_X = i;
1242 else if(!g_strcmp0(sample_names[i], "XYZ_Y"))
1243 column_Y = i;
1244 else if(!g_strcmp0(sample_names[i], "XYZ_Z"))
1245 column_Z = i;
1246 else if(!g_strcmp0(sample_names[i], "LAB_L"))
1247 column_L = i;
1248 else if(!g_strcmp0(sample_names[i], "LAB_A"))
1249 column_a = i;
1250 else if(!g_strcmp0(sample_names[i], "LAB_B"))
1251 column_b = i;
1252 }
1253
1254 if(column_SAMPLE_ID == -1)
1255 {
1256 fprintf(stderr, "Error: can't find the SAMPLE_ID column in the CGATS file.\n");
1257 goto error;
1258 }
1259
1260 if(column_X + column_Y + column_Z + column_L + column_a + column_b == -1)
1261 {
1262 fprintf(stderr, "Error: No XYZ or Lab columns found in the CGATS file.\n");
1263 goto error;
1264 }
1265
1266 int columns[3] = { -1, -1, -1 };
1267 if(column_L != -1 && column_a != -1 && column_b != -1)
1268 {
1269 columns[0] = cmsIT8FindDataFormat(hIT8, "LAB_L");
1270 columns[1] = cmsIT8FindDataFormat(hIT8, "LAB_A");
1271 columns[2] = cmsIT8FindDataFormat(hIT8, "LAB_B");
1272 }
1273 // In case no Lab column is found, we assume the IT8 file has XYZ data
1274 else if(column_X != -1 && column_Y != -1 && column_Z != -1)
1275 {
1276 use_XYZ = TRUE;
1277 columns[0] = cmsIT8FindDataFormat(hIT8, "XYZ_X");
1278 columns[1] = cmsIT8FindDataFormat(hIT8, "XYZ_Y");
1279 columns[2] = cmsIT8FindDataFormat(hIT8, "XYZ_Z");
1280 }
1281 else
1282 {
1283 fprintf(stderr, "Error: can't find XYZ or Lab columns in the CGATS file\n");
1284 goto error;
1285 }
1286
1287 for(size_t patch_iter = 0; patch_iter < num_patches; patch_iter++)
1288 {
1289 // Ensure the CGATS row exists before binding its values to the chart patch
1290 // at the same index. The display name comes from the chart geometry below,
1291 // so we must not allocate a temporary row name that gets overwritten.
1292 const char *sample_name = cmsIT8GetDataRowCol(hIT8, patch_iter, 0);
1293 if(IS_NULL_PTR(sample_name))
1294 {
1295 fprintf(stderr, "Error : can't find sample '%" G_GSIZE_FORMAT "' in CGATS file\n", patch_iter);
1296 goto error;
1297 }
1298
1299 // set patch position
1300 // The position of the patch is given by the chart specification
1301 if(IS_NULL_PTR(chart_spec->patches))
1302 {
1303 fprintf(stderr, "Error: no patches found in the chart specification.\n");
1304 goto error;
1305 }
1306
1307 const dt_color_checker_patch *p = (dt_color_checker_patch*)g_slist_nth_data(chart_spec->patches, (guint)patch_iter);
1308 if(IS_NULL_PTR(p))
1309 {
1310 fprintf(stderr, "Error: patch %" G_GSIZE_FORMAT " not found in chart specification.\n", patch_iter);
1311 goto error;
1312 }
1313 _dt_colorchecker_copy_patch(&values[patch_iter], p);
1314 if(IS_NULL_PTR(values[patch_iter].name))
1315 {
1316 fprintf(stderr, "Error: patch %" G_GSIZE_FORMAT " has no name in chart specification.\n", patch_iter);
1317 goto error;
1318 }
1319
1320 // Copy color values
1321 const double patchdbl[3] = { cmsIT8GetDataRowColDbl(hIT8, (int)patch_iter, columns[0]),
1322 cmsIT8GetDataRowColDbl(hIT8, (int)patch_iter, columns[1]),
1323 cmsIT8GetDataRowColDbl(hIT8, (int)patch_iter, columns[2]) };
1324
1325 // Convert to Lab when it's in XYZ
1326 if(use_XYZ)
1327 {
1328 const dt_aligned_pixel_t patch_color = { (float)patchdbl[0] * 0.01, (float)patchdbl[1] *0.01, (float)patchdbl[2] * 0.01, 0.0f };
1329 dt_XYZ_to_Lab(patch_color, values[patch_iter].Lab);
1330 }
1331 else
1332 {
1333 values[patch_iter].Lab[0] = (float)patchdbl[0];
1334 values[patch_iter].Lab[1] = (float)patchdbl[1];
1335 values[patch_iter].Lab[2] = (float)patchdbl[2];
1336 }
1337
1338 _dt_CGATS_find_whitest_blackest_greyest(values, bwg, patch_iter);
1339 }
1340
1341 goto end;
1342
1343error:
1344 if(!IS_NULL_PTR(values))
1345 {
1346 for(size_t i = 0; i < num_patches; i++)
1347 {
1349 }
1350 dt_free_align(values);
1351 }
1352 values = NULL;
1353
1354end:
1355 return values;
1356}
1357
1358dt_color_checker_t *dt_colorchecker_user_ref_create(const char *color_filename, const char *cht_filename)
1359{
1360 dt_colorchecker_chart_spec_t *chart_spec = NULL;
1361 dt_color_checker_t *checker = NULL;
1362
1363 int lineno = 0;
1364
1365 if(IS_NULL_PTR(color_filename) || g_strcmp0(color_filename, "") == 0)
1366 {
1367 fprintf(stderr, "Error: Invalid color filename provided.\n");
1368 return NULL;
1369 }
1370
1371 if(!g_file_test(color_filename, G_FILE_TEST_IS_REGULAR))
1372 {
1373 fprintf(stderr, "Error: the color file '%s' does not exist or is not a regular file.\n", color_filename);
1374 return NULL;
1375 }
1376
1377 cmsHANDLE hIT8 = cmsIT8LoadFromFile(NULL, color_filename);
1378
1379 if(!_dt_CGATS_is_supported(&hIT8))
1380 {
1381 fprintf(stderr, "Ansel cannot load the CGATS file '%s'\n", color_filename);
1382 ERROR
1383 }
1384
1385 chart_spec = _dt_colorchecker_chart_spec_init();
1386 if(IS_NULL_PTR(chart_spec))
1387 {
1388 fprintf(stderr, "Error: cannot allocate memory for the chart spec.\n");
1389 ERROR
1390 }
1391 // load the cht file if any
1392 if(!IS_NULL_PTR(cht_filename) && g_file_test(cht_filename, G_FILE_TEST_IS_REGULAR))
1393 {
1394 if(!_dt_colorchecker_open_cht(cht_filename, chart_spec))
1395 {
1396 fprintf(stderr, "Error: cannot open the cht file '%s'.\n", cht_filename);
1397 ERROR
1398 }
1399 }
1400 else dt_print(DT_DEBUG_VERBOSE, "invalid cht file '%s'.\n", cht_filename);
1401
1402 // Check if the CGATS file contains the expected number of patches
1403 const int num_patches_it8 = (const int)cmsIT8GetPropertyDbl(hIT8, "NUMBER_OF_SETS");
1404
1405 if(chart_spec->num_patches > 0 && num_patches_it8 != chart_spec->num_patches)
1406 {
1407 dt_print(DT_DEBUG_VERBOSE, "the number of patches in the CGATS file (%i) does not match the expected number (%i) in the cht file.\n",
1408 num_patches_it8, chart_spec->num_patches);
1409 }
1410
1411 // Limit the number of patches to the minimum between the CGATS file and the chart specification to avoid overflow.
1412 const size_t num_patches = MIN(num_patches_it8, chart_spec->num_patches);
1413 dt_print(DT_DEBUG_VERBOSE, "%" PRIu64 " patches will be added to the chart\n", (uint64_t)num_patches);
1414
1415 checker = dt_colorchecker_init();
1416 if(IS_NULL_PTR(checker))
1417 {
1418 fprintf(stderr, "Error: cannot allocate memory for the color checker.\n");
1419 ERROR
1420 }
1421
1422 checker->name = _dt_CGATS_get_name(&hIT8, color_filename);
1423 checker->author = g_strdup(_dt_CGATS_get_author(&hIT8));
1424 checker->date = g_strdup(_dt_CGATS_get_date(&hIT8));
1425 checker->manufacturer = g_strdup(_dt_CGATS_get_manufacturer(&hIT8));
1426 checker->type = COLOR_CHECKER_USER_REF;
1427 checker->radius = chart_spec->radius;
1428 checker->ratio = chart_spec->ratio;
1429 checker->patches = num_patches;
1430 checker->size[0] = chart_spec->size[0];
1431 checker->size[1] = chart_spec->size[1];
1432 checker->middle_grey = chart_spec->middle_grey;
1433 checker->white = chart_spec->white;
1434 checker->black = chart_spec->black;
1435
1436 // blackest, whitest and greyest patches will be found while filling the color values
1437 size_t bwg[3] = { 0, 0, 0 };
1438 checker->values = _dt_colorchecker_CGATS_fill_patch_values(hIT8, bwg, chart_spec, num_patches);
1439 if(IS_NULL_PTR(checker->values))
1440 {
1441 fprintf(stderr, "Error: cannot fill the color values from the CGATS file.\n");
1442 ERROR
1443 }
1444
1445 checker->black = bwg[0];
1446 checker->white = bwg[1];
1447 checker->middle_grey = bwg[2];
1448 dt_print(DT_DEBUG_VERBOSE, _("blackest patch: %s, middle grey patch: %s, white patch: %s\n"),
1449 checker->values[bwg[0]].name, checker->values[bwg[1]].name, checker->values[bwg[2]].name);
1450
1451 dt_print(DT_DEBUG_VERBOSE, _("it8 '%s' done\n"), color_filename);
1452 goto end;
1453
1454error:
1455 fprintf(stderr, "Error creating user ref checker, in %s %s:%d\n", __FUNCTION__, __FILE__, lineno);
1456 dt_colorchecker_cleanup(checker);
1457 checker = NULL;
1458
1459 end:
1460 if(!IS_NULL_PTR(chart_spec)) _dt_colorchecker_chart_spec_cleanup(chart_spec); // only allocated chart will be freed
1461 if(!IS_NULL_PTR(hIT8)) cmsIT8Free(hIT8);
1462 return checker;
1463}
1464
1465static dt_colorchecker_label_t *_dt_colorchecker_user_ref_make_label(const gchar *filename, const gchar *user_it8_dir)
1466{
1467 dt_colorchecker_label_t *result = NULL;
1468
1469 if(IS_NULL_PTR(filename) || g_strcmp0(filename, "") == 0 || IS_NULL_PTR(user_it8_dir) || g_strcmp0(user_it8_dir, "") == 0)
1470 {
1471 fprintf(stderr, "Error: Invalid filename or user IT8 directory provided for making CGATS label.\n");
1472 return NULL;
1473 }
1474
1475 gchar *filepath = g_build_filename(user_it8_dir, filename, NULL);
1476 if(g_file_test(filepath, G_FILE_TEST_IS_REGULAR))
1477 {
1478 cmsHANDLE hIT8 = cmsIT8LoadFromFile(NULL, filepath);
1479 if(!IS_NULL_PTR(hIT8) && _dt_CGATS_is_supported(&hIT8))
1480 {
1481 const int patch_nb = (int)cmsIT8GetPropertyDbl(hIT8, "NUMBER_OF_SETS");
1482 if(patch_nb > 0)
1483 {
1484 gchar *label = _dt_CGATS_get_name(&hIT8, filename);
1485 dt_colorchecker_label_t *CGATS_label = dt_colorchecker_label_init(label, COLOR_CHECKER_USER_REF, filepath, patch_nb);
1486
1487 dt_free(label);
1488 result = CGATS_label;
1489 }
1490 }
1491 if(!IS_NULL_PTR(hIT8)) cmsIT8Free(hIT8);
1492 }
1493 dt_free(filepath)
1494
1495 if(IS_NULL_PTR(result))
1496 return NULL;
1497
1498 else return result;
1499}
1500
1501static dt_colorchecker_label_t *_dt_colorchecker_cht_make_label(const gchar *filename, const gchar *user_it8_dir)
1502{
1503 if(IS_NULL_PTR(filename) || g_strcmp0(filename, "") == 0 || IS_NULL_PTR(user_it8_dir) || g_strcmp0(user_it8_dir, "") == 0)
1504 {
1505 fprintf(stderr, "Error: Invalid filename or user IT8 directory provided for making CHT label.\n");
1506 return NULL;
1507 }
1508
1509 dt_colorchecker_label_t *cht_label = NULL;
1510
1511 gchar *filepath = g_build_filename(user_it8_dir, filename, NULL);
1512 if(g_file_test(filepath, G_FILE_TEST_IS_REGULAR))
1513 {
1514 gchar *basename = g_path_get_basename(filename);
1515 char *dot = g_strrstr(basename, ".");
1516 if(!IS_NULL_PTR(dot)) *dot = '\0'; // removes the file extension in basename
1517 const int patch_nb = _dt_colorchecker_cht_get_patch_nb(filepath);
1518
1519 if(patch_nb > 0) // only create a label if the CHT file has patches
1520 cht_label = dt_colorchecker_label_init(basename, COLOR_CHECKER_USER_REF, filepath, patch_nb);
1521
1522 dt_free(basename);
1523 }
1524 dt_free(filepath);
1525
1526 return cht_label;
1527}
1528
1529int dt_colorchecker_find_builtin(GList **colorcheckers_label)
1530{
1531 int nb = 0;
1532 for(int k = 0; k < COLOR_CHECKER_USER_REF; k++)
1533 {
1535 const int patch_nb = _dt_get_builtin_colorchecker_patch_nb(k);
1536 if(patch_nb <= 0)
1537 {
1538 dt_free(name)
1539 continue; // skip color checkers with no patches
1540 }
1541
1542 dt_colorchecker_label_t *builtin_label = dt_colorchecker_label_init(name, k, NULL, patch_nb);
1543 dt_free(name)
1544
1545 if(IS_NULL_PTR(builtin_label))
1546 {
1547 fprintf(stderr, "Error: failed to allocate memory for builtin colorchecker label %d\n", k);
1548 continue;
1549 }
1550 else
1551 {
1552 *colorcheckers_label = g_list_append(*colorcheckers_label, builtin_label);
1553 nb++;
1554 }
1555 }
1556 return nb;
1557}
1558
1559int dt_colorchecker_find_CGATS_reference_files(GList **ref_colorcheckers_files)
1560{
1561 int nb = 0;
1562 char confdir[DT_PATH_MAX] = { 0 };
1563 dt_loc_get_user_config_dir(confdir, sizeof(confdir));
1564 gchar *user_it8_dir = g_build_filename(confdir, "color", "checker", NULL);
1565
1566 GDir *dir = g_dir_open(user_it8_dir, 0, NULL);
1567 if(!IS_NULL_PTR(dir))
1568 {
1569 const char *filename;
1570 while(!IS_NULL_PTR(filename = g_dir_read_name(dir)))
1571 {
1572 const char *dot = g_strrstr(filename, ".");
1573 if(!IS_NULL_PTR(dot) && g_ascii_strcasecmp(dot, ".cht") == 0)
1574 continue; // skip .cht files
1575
1576 dt_colorchecker_label_t *CGATS_label = _dt_colorchecker_user_ref_make_label(filename, user_it8_dir);
1577 if(!IS_NULL_PTR(CGATS_label))
1578 {
1579 *ref_colorcheckers_files = g_list_append(*ref_colorcheckers_files, CGATS_label);
1580 nb++;
1581 }
1582 else
1583 dt_print(DT_DEBUG_VERBOSE, "failed to load CGATS file '%s' in %s\n", filename, user_it8_dir);
1584 }
1585 g_dir_close(dir);
1586 }
1587 dt_free(user_it8_dir)
1588
1589 return nb;
1590}
1591
1593{
1594 int nb = 0;
1595 char confdir[DT_PATH_MAX] = { 0 };
1596 dt_loc_get_user_config_dir(confdir, sizeof(confdir));
1597 gchar *user_it8_dir = g_build_filename(confdir, "color", "checker", NULL);
1598
1599 GDir *dir = g_dir_open(user_it8_dir, 0, NULL);
1600 if(!IS_NULL_PTR(dir))
1601 {
1602 const char *filename;
1603 while(!IS_NULL_PTR(filename = g_dir_read_name(dir)))
1604 {
1605 const char *dot = g_strrstr(filename, ".");
1606 if(IS_NULL_PTR(dot) || g_ascii_strcasecmp(dot, ".cht") != 0)
1607 continue; // skip files that are not .cht
1608
1609 dt_colorchecker_label_t *cht_label = _dt_colorchecker_cht_make_label(filename, user_it8_dir);
1610 if(!IS_NULL_PTR(cht_label))
1611 {
1612 *chts = g_list_append(*chts, cht_label);
1613 nb++;
1614 }
1615 }
1616 g_dir_close(dir);
1617 }
1618 dt_free(user_it8_dir);
1619
1620 return nb;
1621}
1622
1623// clang-format off
1624// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1625// vim: shiftwidth=2 expandtab tabstop=2 cindent
1626// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1627// clang-format on
const char ** description(struct dt_iop_module_t *self)
Definition ashift.c:168
static void error(char *msg)
Definition ashift_lsd.c:202
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
static dt_colorchecker_label_t * dt_colorchecker_label_init(const char *label, const dt_color_checker_targets type, const char *path, const int patch_nb)
dt_color_checker_targets
@ COLOR_CHECKER_USER_REF
static void dt_colorchecker_cleanup(dt_color_checker_t *checker)
dt_colorchecker_material_types
@ COLOR_CHECKER_MATERIAL_TRANSPARENT
@ COLOR_CHECKER_MATERIAL_UNKNOWN
@ COLOR_CHECKER_MATERIAL_OPAQUE
static void dt_colorchecker_patch_cleanup(dt_color_checker_patch *patch)
dt_colorchecker_CGATS_types
@ CGATS_TYPE_IT8_7_1
@ CGATS_TYPE_UNKOWN
@ CGATS_TYPE_IT8_7_2
@ CGATS_TYPE_CTI3
static void dt_colorchecker_patch_cleanup_list(void *_patch)
static dt_color_checker_t * dt_get_color_checker(const dt_color_checker_targets target_type, const char *cht_filename, const char *color_filename)
Build a color checker of the given type.
static dt_color_checker_t * dt_colorchecker_init()
static dt_color_checker_patch * dt_colorchecker_patch_array_init(const size_t num_patches)
const int t
static dt_aligned_pixel_t Lab
const dt_colormatrix_t dt_aligned_pixel_t out
dt_XYZ_to_Lab(XYZ, Lab)
int dt_colorchecker_find_CGATS_reference_files(GList **ref_colorcheckers_files)
Find all CGAT files in the user config/color/it8 directory.
static int _dt_get_builtin_colorchecker_patch_nb(const dt_color_checker_targets target_type)
Get the number of patches in a built-in colorchecker.
static const char * _dt_CGATS_get_manufacturer(const cmsHANDLE *hIT8)
static char * _increment_string(const gchar *in)
Increments a string alphanumerically.
static const char * _dt_CGATS_get_author(const cmsHANDLE *hIT8)
static float dE_1976(const float a, const float b, const float c)
int dt_colorchecker_find_cht_files(GList **chts)
Find all .cht files in the user config/color/it8 directory.
static dt_color_checker_patch * _dt_colorchecker_CGATS_fill_patch_values(const cmsHANDLE hIT8, size_t *bwg, const dt_colorchecker_chart_spec_t *chart_spec, const size_t num_patches)
fills the patch values from the CGATS file, converts to Lab if needed. The number of patches to be fi...
static const char * _remove_leading_zeros(const char *in)
Removes leading zeros from a string.
static void _dt_cht_box_cleanup(void *data)
#define TWO_SQRT2f
static dt_colorchecker_label_t * _dt_colorchecker_cht_make_label(const gchar *filename, const gchar *user_it8_dir)
static void _dt_colorchecker_chart_spec_cleanup(dt_colorchecker_chart_spec_t *chart_spec)
static char * _dt_CGATS_get_name(const cmsHANDLE *hIT8, const char *filename)
Get the name of the colorchecker from the CGATS file. The resulting string must be freed by the calle...
static gchar * _dt_get_builtin_colorchecker_name(const dt_color_checker_targets target_type)
Get the name of a built-in color checker.
static dt_colorchecker_chart_spec_t * _dt_colorchecker_chart_spec_init()
static dt_colorchecker_CGATS_types _dt_CGATS_get_type_value(const char *type)
static cht_box_t * _dt_cht_box_extract(const char **tokens)
static const char * _dt_CGATS_get_date(const cmsHANDLE *hIT8)
Get the production date of the CGATS file.
void dt_colorchecker_copy(dt_color_checker_t *dest, const dt_color_checker_t *src)
Copy the content of a color checker from source to destination.
static const char * colorchecker_material_types[COLOR_CHECKER_MATERIAL_UNKNOWN]
dt_color_checker_t * dt_colorchecker_user_ref_create(const char *color_filename, const char *cht_filename)
Creates a color checker from a reference file (CGATS format).
static gboolean _dispatch_cht_data(GList **boxes, dt_colorchecker_chart_spec_t *chart_spec)
static gboolean _dt_colorchecker_open_cht(const char *filename, dt_colorchecker_chart_spec_t *chart_spec)
Opens a CHT file and parses its content to fill the chart_spec structure.
#define ERROR
static const char * _dt_colorchecker_get_material_string(const dt_colorchecker_material_types material)
Gets the string representation of the material type ("Transparent" or "Opaque") to be used in label n...
static void _dt_CGATS_find_whitest_blackest_greyest(const dt_color_checker_patch *const values, size_t *bwg, const size_t patch)
static GList * _parse_cht(const char *filename)
Parses a CHT file and extracts the boxes data.
static gchar * _dt_colorchecker_get_standard_type(const char *type)
Get the standard type name from a CGATS type.
static void _dt_colorchecker_copy_patch(dt_color_checker_patch *dest, const dt_color_checker_patch *src)
int dt_colorchecker_find_builtin(GList **colorcheckers_label)
Find all builtin colorcheckers.
@ BLOCK_BOX_SHRINK
@ BLOCK_NONE
@ BLOCK_YLIST
@ BLOCK_REF_ROTATION
@ BLOCK_XLIST
@ BLOCK_BOXES
@ BLOCK_EXPECTED
static dt_colorchecker_material_types _dt_colorchecker_IT8_get_material_type(const cmsHANDLE *hIT8)
static gboolean _dt_cht_generate_patch_list(dt_colorchecker_chart_spec_t *chart, const cht_box_t *cht_patch, const cht_box_F_t *F_box)
Generates a list of patches from the provided cht_patch structure. Patche's positions are calculated ...
static const char * CGATS_types[CGATS_TYPE_UNKOWN]
static dt_colorchecker_label_t * _dt_colorchecker_user_ref_make_label(const gchar *filename, const gchar *user_it8_dir)
static dt_color_checker_patch * _dt_colorchecker_patch_init()
static int _dt_colorchecker_cht_get_patch_nb(const char *filepath)
Get the number of patches in a CHT file.
static gboolean _dt_CGATS_is_supported(const cmsHANDLE *hIT8)
Test if the file is a CGATS.17 file and if it contains one table of patch only.
static gchar * _dt_colorchecker_label_build_name(const dt_colorchecker_CGATS_label_make_name_t *label)
build a name for the colorchecker. The returned string must be freed by the caller.
static cht_box_F_t * _dt_cht_extract_F(const char **tokens)
Extracts the frame coordinates from the tokens, computes the width and height.
void * dt_alloc_align(size_t size)
Allocate cacheline-aligned memory.
Definition darktable.c:508
void dt_loc_get_user_config_dir(char *configdir, size_t bufsize)
_lib_location_type_t type
Definition location.c:1
@ DT_DEBUG_VERBOSE
Definition logging.h:78
void dt_print(dt_debug_thread_t thread, const char *msg,...) __attribute__((format(printf
Print to stdout when thread is enabled, prefixed with seconds since startup.
float *const restrict const size_t k
#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 macros.h:96
#define dt_free_align(ptr)
Release memory from dt_alloc_align() and set ptr to NULL.
Definition mem_alloc.h:214
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
#define DT_PATH_MAX
Buffer size for a filesystem path anywhere in Ansel.
Definition paths.h:57
const char * name
Definition pdf.h:90
DT_ALIGNED_PIXEL float dt_aligned_pixel_t[4]
Definition simd.h:53
static const dt_aligned_pixel_simd_t value
Definition simd.h:144
unsigned __int64 uint64_t
Definition strptime.c:75
dt_aligned_pixel_t Lab
dt_color_checker_targets type
dt_color_checker_patch * values
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29