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