Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
ansel-chart/main.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2016-2018 johannes hanika.
4 Copyright (C) 2016 Roman Lebedev.
5 Copyright (C) 2016-2018, 2020 Tobias Ellinghaus.
6 Copyright (C) 2017, 2019 Heiko Bauke.
7 Copyright (C) 2017 luzpaz.
8 Copyright (C) 2019 Andreas Schneider.
9 Copyright (C) 2019-2020 parafin.
10 Copyright (C) 2020 Aurélien PIERRE.
11 Copyright (C) 2020 Hubert Kowalski.
12 Copyright (C) 2020-2021 Pascal Obry.
13 Copyright (C) 2021 Ralf Brown.
14 Copyright (C) 2021 Sakari Kapanen.
15 Copyright (C) 2022 Martin Bařinka.
16
17 darktable is free software: you can redistribute it and/or modify
18 it under the terms of the GNU General Public License as published by
19 the Free Software Foundation, either version 3 of the License, or
20 (at your option) any later version.
21
22 darktable is distributed in the hope that it will be useful,
23 but WITHOUT ANY WARRANTY; without even the implied warranty of
24 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 GNU General Public License for more details.
26
27 You should have received a copy of the GNU General Public License
28 along with darktable. If not, see <http://www.gnu.org/licenses/>.
29*/
30
31#include <gtk/gtk.h>
32
33#include "system/macros.h"
34#include "system/mem_alloc.h"
35#include "system/openmp.h"
36#include "system/simd.h"
44#include "metadata/exif.h"
45
46#include <glib/gstdio.h>
47#include <stdio.h>
48#include <stdlib.h>
49#include <string.h>
50
51#ifdef __APPLE__
52#include "osx/osx.h"
53#endif
54
55#ifdef _WIN32
56#include "win/main_wrapper.h"
57#endif
58
59const double thrs = 200.0;
60
61static const point_t bb_ref[] = {{.x=.0, .y=.0}, {.x=1., .y=0.}, {.x=1., .y=1.}, {.x=0., .y=1.}};
62
63enum
64{
74};
75
95
96// boring helper functions
97static void init_image(dt_lut_t *self, image_t *image, GCallback motion_cb);
98static void image_lab_to_xyz(float *image, const int width, const int height);
99static void map_boundingbox_to_view(image_t *image, point_t *bb);
101static void get_xyz_sample_from_image(const image_t *const image, float shrink, box_t *box, float *xyz);
102static void add_column(GtkTreeView *treeview, const char *title, int column_id, int sort_column);
103static void update_table(dt_lut_t *self);
104static void init_table(dt_lut_t *self);
105static void get_Lab_from_box(box_t *box, float *Lab);
106static void collect_source_patches(dt_lut_t *self);
107static void collect_source_patches_foreach(gpointer key, gpointer value, gpointer user_data);
108static void collect_reference_patches(dt_lut_t *self);
109static void collect_reference_patches_foreach(gpointer key, gpointer value, gpointer user_data);
110static box_t *find_patch(GHashTable *table, gpointer key);
111static void get_boundingbox(const image_t *const image, point_t *bb);
112static box_t get_sample_box(chart_t *chart, box_t *outer_box, float shrink);
113static void get_corners(const float *homography, box_t *box, point_t *corners);
114static void get_pixel_region(const image_t *const image, const point_t *const corners, int *x_start, int *y_start,
115 int *x_end, int *y_end);
116static void reset_bb(image_t *image);
117static void free_image(image_t *image);
118static gboolean handle_motion(GtkWidget *widget, GdkEventMotion *event, dt_lut_t *self, image_t *image);
119static int find_closest_corner(point_t *bb, float x, float y);
120static void map_mouse_to_0_1(GtkWidget *widget, GdkEventMotion *event, image_t *image, float *x, float *y);
121static void update_corner(image_t *image, int which, float *x, float *y);
122static gboolean open_source_image(dt_lut_t *self, const char *filename);
123static gboolean open_reference_image(dt_lut_t *self, const char *filename);
124static gboolean open_image(image_t *image, const char *filename);
125static gboolean open_cht(dt_lut_t *self, const char *filename);
126static gboolean open_it8(dt_lut_t *self, const char *filename);
127
128static void size_allocate_callback(GtkWidget *widget, GdkRectangle *allocation, gpointer user_data)
129{
130 image_t *image = (image_t *)user_data;
131 set_offset_and_scale(image, allocation->width, allocation->height);
132}
133
134static gboolean draw_image_callback(GtkWidget *widget, cairo_t *cr, gpointer user_data)
135{
136 image_t *image = (image_t *)user_data;
137 chart_t *chart = *(image->chart);
138
140
141 // done when no image is loaded
142 if(IS_NULL_PTR(image->image))
143 {
144 draw_no_image(cr, widget);
145 return FALSE;
146 }
147
148 center_image(cr, image);
149
150 draw_image(cr, image);
151
152 // done when no chart was loaded
153 if(IS_NULL_PTR(chart)) return FALSE;
154
155 // draw overlay
156 point_t bb[4];
157 float homography[9];
158 map_boundingbox_to_view(image, bb);
159 // calculating the homography takes hardly any time, so we do it here instead of the move handler.
160 // the benefits are that the window size is taken into account and image->bb can't disagree with the cached homography
162
163 draw_boundingbox(cr, bb);
164 draw_f_boxes(cr, homography, chart);
165 draw_d_boxes(cr, homography, chart);
167
168 stroke_boxes(cr, 1.0);
169
170 draw_color_boxes_inside(cr, homography, chart, image->shrink, 2.0, image->draw_colored);
171
172 return FALSE;
173}
174
176{
177 for(int i = 0; i < 4; i++) bb[i] = map_point_to_view(image, image->bb[i]);
178}
179
181{
182 point_t result;
183
184 result.x = p.x * image->width / image->scale;
185 result.y = p.y * image->height / image->scale;
186
187 return result;
188}
189
190static gboolean motion_notify_callback_source(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
191{
192 dt_lut_t *self = (dt_lut_t *)user_data;
193 const gboolean res = handle_motion(widget, event, self, &self->source);
194 if(res)
195 {
197 update_table(self);
198 }
199 return res;
200}
201
202static gboolean motion_notify_callback_reference(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
203{
204 dt_lut_t *self = (dt_lut_t *)user_data;
205 const gboolean res = handle_motion(widget, event, self, &self->reference);
206 if(res)
207 {
209 update_table(self);
210 }
211 return res;
212}
213
214static gboolean handle_motion(GtkWidget *widget, GdkEventMotion *event, dt_lut_t *self, image_t *image)
215{
216 if(!(event->state & GDK_BUTTON1_MASK) || IS_NULL_PTR(image->image)) return FALSE;
217
218 // mouse -> 0..1
219 float x, y;
220 map_mouse_to_0_1(widget, event, image, &x, &y);
221
222 // dragging the crosses is hard when they are not in a cornerof the bb but sprinkled over the chart
223 int closest_corner = find_closest_corner(image->bb, x, y);
224
225 update_corner(image, closest_corner, &x, &y);
226
227 // check if the shape would turn concave by testing if the new location
228 // is inside the triangle formed by the other three. google barycentric coordinates to see how it's done.
229 const int prev_corner = (closest_corner + 3) % 4;
230 const int opposite_corner = (closest_corner + 2) % 4;
231 const int next_corner = (closest_corner + 1) % 4;
232
233 const float x1 = image->bb[prev_corner].x;
234 const float y1 = image->bb[prev_corner].y;
235 const float x2 = image->bb[next_corner].x;
236 const float y2 = image->bb[next_corner].y;
237 const float x3 = image->bb[opposite_corner].x;
238 const float y3 = image->bb[opposite_corner].y;
239
240 const float denom = (y2 - y3) * (x1 - x3) + (x3 - x2) * (y1 - y3);
241 const float l1 = ((y2 - y3) * (x - x3) + (x3 - x2) * (y - y3)) / denom;
242 const float l2 = ((y3 - y1) * (x - x3) + (x1 - x3) * (y - y3)) / denom;
243 const float l3 = 1.0 - l1 - l2;
244
245 if(l1 < 0.0 || l2 < 0.0 || l3 < 0.0)
246 {
247 image->bb[closest_corner].x = x;
248 image->bb[closest_corner].y = y;
249 }
250
251 gtk_widget_queue_draw(widget);
252
253 return TRUE;
254}
255
256static int find_closest_corner(point_t *bb, float x, float y)
257{
258 int closest_corner = 0;
259 float distance = G_MAXFLOAT;
260 for(int i = 0; i < 4; i++)
261 {
262 const float d_x = (x - bb[i].x);
263 const float d_y = (y - bb[i].y);
264 float d = d_x * d_x + d_y * d_y;
265 if(d < distance)
266 {
267 distance = d;
269 }
270 }
271 // TODO: only react when the distance < some threshold?
272 return closest_corner;
273}
274
275static void map_mouse_to_0_1(GtkWidget *widget, GdkEventMotion *event, image_t *image, float *x, float *y)
276{
279
280 *x = (event->x - image->offset_x) / (width - 2.0 * image->offset_x);
281 *y = (event->y - image->offset_y) / (height - 2.0 * image->offset_y);
282}
283
284static void update_corner(image_t *image, int which, float *x, float *y)
285{
286 // keep the corners in clockwise order
287 if(which == TOP_LEFT)
288 {
289 *x = CLAMP(*x, 0.0, image->bb[TOP_RIGHT].x);
290 *y = CLAMP(*y, 0.0, image->bb[BOTTOM_LEFT].y);
291 }
292 else if(which == TOP_RIGHT)
293 {
294 *x = CLAMP(*x, image->bb[TOP_LEFT].x, 1.0);
295 *y = CLAMP(*y, 0.0, image->bb[BOTTOM_RIGHT].y);
296 }
297 else if(which == BOTTOM_RIGHT)
298 {
299 *x = CLAMP(*x, image->bb[BOTTOM_LEFT].x, 1.0);
300 *y = CLAMP(*y, image->bb[TOP_RIGHT].y, 1.0);
301 }
302 else if(which == BOTTOM_LEFT)
303 {
304 *x = CLAMP(*x, 0.0, image->bb[BOTTOM_RIGHT].x);
305 *y = CLAMP(*y, image->bb[TOP_LEFT].y, 1.0);
306 }
307}
308
309static void source_image_changed_callback(GtkFileChooserButton *widget, gpointer user_data)
310{
311 dt_lut_t *self = (dt_lut_t *)user_data;
315}
316
317static gboolean open_source_image(dt_lut_t *self, const char *filename)
318{
319 gboolean res = open_image(&self->source, filename);
323
324 return res;
325}
326
327static void ref_image_changed_callback(GtkFileChooserButton *widget, gpointer user_data)
328{
329 dt_lut_t *self = (dt_lut_t *)user_data;
333}
334
335static char *get_filename_base(const char *filename)
336{
337 char *last_slash = g_strrstr(filename, "/");
338 if(last_slash)
339 return g_strdup(last_slash + 1);
340 else
341 return g_strdup(filename);
342}
343
344static gboolean open_reference_image(dt_lut_t *self, const char *filename)
345{
346 const gboolean initial_loading = (IS_NULL_PTR(self->reference.xyz));
347 const gboolean res = open_image(&self->reference, filename);
351 if(IS_NULL_PTR(res))
353 else
354 {
356 {
357 // copy over the bounding box from the source image.
358 // when matching raw to jpeg this is in general what the user wants
359 memcpy(self->reference.bb, self->source.bb, sizeof(self->reference.bb));
360 }
362 update_table(self);
364 self->reference_filename = get_filename_base(filename);
365 }
367 return res;
368}
369
370static gboolean open_image(image_t *image, const char *filename)
371{
372 int width, height;
373
374 free_image(image);
375
376 if(IS_NULL_PTR(filename)) return FALSE;
377
378 float *pfm = read_pfm(filename, &width, &height);
379
380 if(IS_NULL_PTR(pfm))
381 {
382 fprintf(stderr, "error reading image `%s'\n", filename);
383 return FALSE;
384 }
385
386 // we want the image in XYZ to average patches
388
390
391 if(cairo_surface_status(image_surface) != CAIRO_STATUS_SUCCESS)
392 {
393 fprintf(stderr, "error creating cairo surface from `%s'\n", filename);
394 cairo_surface_destroy(image_surface);
395 dt_free(pfm);
396 return FALSE;
397 }
398 image->surface = image_surface;
399 image->image = cairo_pattern_create_for_surface(image_surface);
400 image->width = width;
401 image->height = height;
402 image->xyz = pfm;
403
404 // at init time this can fail once
405 if(GTK_IS_WIDGET(image->drawing_area))
406 {
407 guint widget_width = gtk_widget_get_allocated_width(image->drawing_area);
408 guint widget_height = gtk_widget_get_allocated_height(image->drawing_area);
409 set_offset_and_scale(image, widget_width, widget_height);
410 }
411
412 return TRUE;
413}
414
415static void cht_changed_callback(GtkFileChooserButton *widget, gpointer user_data)
416{
417 dt_lut_t *self = (dt_lut_t *)user_data;
419 open_cht(self, new_filename);
421}
422
423static gboolean open_cht(dt_lut_t *self, const char *filename)
424{
425 if(self->chart) free_chart(self->chart);
426 const gboolean res = ((self->chart = parse_cht(filename)) != NULL);
427
428 reset_bb(&self->source);
429 reset_bb(&self->reference);
430
432 if(res) collect_source_patches(self);
433 init_table(self);
434
435 // reset it8/reference entry
439
440 if(res)
441 {
442 self->source.shrink = self->chart->box_shrink;
443 self->reference.shrink = self->chart->box_shrink;
446 }
447
453
456
457 return res;
458}
459
460static void reference_mode_changed_callback(GtkComboBox *widget, gpointer user_data)
461{
462 dt_lut_t *self = (dt_lut_t *)user_data;
463 const int selected = gtk_combo_box_get_active(widget);
464 if(selected == 0)
465 {
466 // it8
471 g_signal_emit_by_name(self->it8_button, "file-set", user_data);
472 }
473 else
474 {
475 // image
481 g_signal_emit_by_name(self->reference_image_button, "file-set", user_data);
482 }
483}
484
485static void it8_changed_callback(GtkFileChooserButton *widget, gpointer user_data)
486{
487 dt_lut_t *self = (dt_lut_t *)user_data;
489 open_it8(self, new_filename);
491}
492
493static gboolean open_it8(dt_lut_t *self, const char *filename)
494{
495 if(IS_NULL_PTR(self->chart) || !filename) return FALSE;
496 const gboolean res = parse_it8(filename, self->chart);
498 update_table(self);
499
503 if(IS_NULL_PTR(res))
505 else
506 {
508 self->reference_filename = get_filename_base(filename);
510 }
512
513 return res;
514}
515
516static char *get_export_filename(dt_lut_t *self, const char *extension, char **name, char **description,
517 gboolean *basecurve, gboolean *colorchecker, gboolean *colorin, gboolean *tonecurve)
518{
520 GtkWidget *dialog
522 _("_cancel"), GTK_RESPONSE_CANCEL, _("_save"), GTK_RESPONSE_ACCEPT, NULL);
523
525
526 char *reference_filename = g_strdup(self->reference_filename);
527 char *last_dot = g_strrstr(reference_filename, ".");
528 if(last_dot)
529 {
530 *last_dot = '\0';
531 char *new_filename = g_strconcat(reference_filename, extension, NULL);
534 }
535 dt_free(reference_filename);
536
537 GtkWidget *grid = gtk_grid_new();
541
543 *description = g_strdup_printf("fitted LUT style from %s", self->reference_filename);
544 char *name_dot = g_strrstr(*name, ".");
545 if(name_dot) *name_dot = '\0';
546
549
552 dt_free(*name);
554 *name = NULL;
555 *description = NULL;
556
557 GtkWidget *label;
558 label = gtk_label_new("style name");
560 gtk_grid_attach(GTK_GRID(grid), label, 0, 0, 1, 1);
561 gtk_grid_attach(GTK_GRID(grid), name_entry, 1, 0, 1, 1);
562 label = gtk_label_new("style description");
564 gtk_grid_attach(GTK_GRID(grid), label, 0, 1, 1, 1);
565 gtk_grid_attach(GTK_GRID(grid), description_entry, 1, 1, 1, 1);
566
567 // allow the user to decide what modules to include in the style
568 label = gtk_label_new("modules included in the style:");
570 g_object_set(label, "margin-left", 50, NULL);
571
574 GtkWidget *cb_colorin = gtk_check_button_new_with_label("input color profile");
576
581
582 if(basecurve)
583 {
584 gtk_grid_attach(GTK_GRID(grid), label, 2, 0, 1, 1);
589 }
590
592
594
595 char *filename = NULL;
596 int res = gtk_dialog_run(GTK_DIALOG(dialog));
598 {
602 if(basecurve)
603 {
604 // either request all of them or none ...
609 }
610 }
611 gtk_widget_destroy(dialog);
612
613 return filename;
614}
615
616static void print_patches(dt_lut_t *self, FILE *fd, GList *patch_names)
617{
619 {
620 char s[64];
621 char *key = (char *)iter->data;
625 {
626 fprintf(stderr, "error: missing patch `%s'\n", key);
627 continue;
628 }
629
630 dt_aligned_pixel_t source_Lab = { 0.0 }, reference_Lab = { 0.0 };
631 get_Lab_from_box(source_patch, source_Lab);
633
634 fprintf(fd, "%s", key);
635 for(int i = 0; i < 3; i++) fprintf(fd, ";%s", g_ascii_dtostr(s, sizeof(s), source_Lab[i]));
636 for(int i = 0; i < 3; i++) fprintf(fd, ";%s", g_ascii_dtostr(s, sizeof(s), reference_Lab[i]));
637 fprintf(fd, "\n");
638 }
639}
640
641static void print_xml_plugin(FILE *fd, int num, int op_version, const char *operation, const char *op_params,
642 gboolean enabled)
643{
644 fprintf(fd, " <plugin>\n");
645 fprintf(fd, " <num>%d</num>\n", num);
646 fprintf(fd, " <module>%d</module>\n", op_version);
647 fprintf(fd, " <operation>%s</operation>\n", operation);
648 fprintf(fd, " <op_params>%s</op_params>\n", op_params);
649 fprintf(fd, " <enabled>%d</enabled>\n", enabled);
650 fprintf(fd, " <blendop_params>gz12eJxjYGBgkGAAgRNODESDBnsIHll8ANNSGQM=</blendop_params>\n");
651 fprintf(fd, " <blendop_version>7</blendop_version>\n");
652 fprintf(fd, " <multi_priority>0</multi_priority>\n");
653 fprintf(fd, " <multi_name></multi_name>\n");
654 fprintf(fd, " </plugin>\n");
655}
656
657static void export_style(dt_lut_t *self, const char *filename, const char *name, const char *description,
658 gboolean include_basecurve, gboolean include_colorchecker, gboolean include_colorin,
659 gboolean include_tonecurve)
660{
661 int num = 0;
662
663 FILE *fd = g_fopen(filename, "w");
664 if(IS_NULL_PTR(fd)) return;
665
666 fprintf(fd, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
667 fprintf(fd, "<darktable_style version=\"1.0\">\n");
668 fprintf(fd, "<info>\n");
669 fprintf(fd, " <name>%s</name>\n", name);
670 fprintf(fd, " <description>%s</description>\n", description);
671 fprintf(fd, "</info>\n");
672 fprintf(fd, "<style>\n");
673
674 // 0: disable basecurve
676 {
677 print_xml_plugin(fd, num++, 2, "basecurve",
678 "gz09eJxjYIAAM6vnNnqyn22E9n235b6aa3cy6rVdRaK9/Y970fYf95bbMzA0QPEoGEqADYnNhMQGAO0WEJo=", FALSE);
679 }
680 // 1: set colorin to standard matrix
682 {
683 // print_xml_plugin(fd, num++, 4, "colorin", "gz10eJzjZqA/AAAFcAAM", TRUE); // no gamut clipping
684 // and enable gamut clipping. the it8 knows nothing about colours outside
685 // rec2020 (only reflectances, no neon lights for instance)
686 print_xml_plugin(fd, num++, 4, "colorin", "gz09eJzjZqAfYIHSAAWQABA=", TRUE); // gamut clipping to rec2020
687 }
688 // 2: add tonecurve
690 {
691 print_xml_plugin(fd, num++, 4, "tonecurve", self->tonecurve_encoded, TRUE);
692 }
693 // 3: add lut
695 {
696 print_xml_plugin(fd, num++, 2, "colorchecker", self->colorchecker_encoded, TRUE);
697 }
698
699 fprintf(fd, "</style>\n");
700 fprintf(fd, "</darktable_style>\n");
701
702 fclose(fd);
703}
704
705static void export_raw(dt_lut_t *self, char *filename, char *name, char *description)
706{
708 gpointer key, value;
709
710 FILE *fd = g_fopen(filename, "w");
711 if(IS_NULL_PTR(fd)) return;
712
714
715 fprintf(fd, "name;%s\n", name);
716 fprintf(fd, "description;%s\n", description);
717 fprintf(fd, "num_gray; 0\n");
718
719 fprintf(fd, "patch;L_source;a_source;b_source;L_reference;a_reference;b_reference\n");
720 // iterate over all known patches in the chart
723 {
725 print_patches(self, fd, patch_names);
726 }
727 fclose(fd);
728}
729
730static void export_raw_button_clicked_callback(GtkButton *button, gpointer user_data)
731{
732 dt_lut_t *self = (dt_lut_t *)user_data;
733 if(IS_NULL_PTR(self->chart)) return;
734
735 char *name = NULL, *description = NULL;
736 char *filename = get_export_filename(self, ".csv", &name, &description, NULL, NULL, NULL, NULL);
737 if(filename) export_raw(self, filename, name, description);
738 dt_free(name);
740 dt_free(filename);
741}
742
743static void export_button_clicked_callback(GtkButton *button, gpointer user_data)
744{
745 dt_lut_t *self = (dt_lut_t *)user_data;
746 if(!self->tonecurve_encoded || IS_NULL_PTR(self->colorchecker_encoded)) return;
747
748 char *name = NULL, *description = NULL;
750 char *filename = get_export_filename(self, ".dtstyle", &name, &description,
752 if(filename) export_style(self, filename, name, description,
754 dt_free(name);
756 dt_free(filename);
757}
758
759static void add_patches_to_array(dt_lut_t *self, GList *patch_names, int *N, int *i, double *target_L,
760 double *target_a, double *target_b, double *colorchecker_Lab)
761{
762
764 {
765 const char *key = (char *)iter->data;
769 {
770 fprintf(stderr, "error: missing patch `%s'\n", key);
771 continue;
772 }
773
774 dt_aligned_pixel_t source_Lab = { 0.0 }, reference_Lab = { 0.0 };
775 get_Lab_from_box(source_patch, source_Lab);
777
778 for(int j = 0; j < 3; j++) colorchecker_Lab[3 * (*i) + j] = source_Lab[j];
779 target_L[*i] = reference_Lab[0];
780 target_a[*i] = reference_Lab[1];
781 target_b[*i] = reference_Lab[2];
782
783 const double deltaE = dt_colorspaces_deltaE_1976(source_Lab, reference_Lab);
784 if(deltaE > thrs)
785 {
786 fprintf(stderr, "warning: ignoring patch %s with large difference deltaE %g!\n", key, deltaE);
787 fprintf(stderr, " %g %g %g -- %g %g %g\n", source_Lab[0], source_Lab[1], source_Lab[2],
789 (*N)--; // ignore this patch.
790 (*i)--;
791 }
792 (*i)++;
793 }
794}
795
796static void add_hdr_patches(int *N, double **target_L, double **target_a, double **target_b,
797 double **colorchecker_Lab)
798{
799 gboolean need_hdr00 = TRUE, need_hdr01 = TRUE;
800 int n_extra_patches = 0;
802
803 for(int j = 0; j < *N; j++)
804 {
805 if((*target_L)[j] == 100.0 && (*target_a)[j] == 0.0 && (*target_b)[j] == 0.0 && (*colorchecker_Lab)[j * 3] == 100.0
806 && (*colorchecker_Lab)[j * 3 + 1] == 0.0 && (*colorchecker_Lab)[j * 3 + 2] == 0.0)
807 {
809 }
810 else if((*target_L)[j] == 200.0 && (*target_a)[j] == 0.0 && (*target_b)[j] == 0.0 && (*colorchecker_Lab)[j * 3] == 200.0
811 && (*colorchecker_Lab)[j * 3 + 1] == 0.0 && (*colorchecker_Lab)[j * 3 + 2] == 0.0)
812 {
814 }
815 }
816
817 if(need_hdr00)
818 {
826 }
827
828 if(need_hdr01)
829 {
837 }
838
839 if(n_extra_patches > 0)
840 {
841 *target_L = realloc(*target_L, sizeof(double) * (*N + n_extra_patches + 4));
842 *target_a = realloc(*target_a, sizeof(double) * (*N + n_extra_patches + 4));
843 *target_b = realloc(*target_b, sizeof(double) * (*N + n_extra_patches + 4));
844 // Same 4-patch slack as the three target_* arrays above. This one holds 3 doubles per
845 // patch, so the slack is 3 * 4 doubles -- the point is to match the sibling arrays
846 // patch-for-patch, not byte-for-byte.
847 *colorchecker_Lab = realloc(*colorchecker_Lab, sizeof(double) * 3 * (*N + n_extra_patches + 4));
848
849 memmove(&(*target_L)[n_extra_patches], *target_L, sizeof(double) * *N);
850 memmove(&(*target_a)[n_extra_patches], *target_a, sizeof(double) * *N);
851 memmove(&(*target_b)[n_extra_patches], *target_b, sizeof(double) * *N);
852 memmove(&(*colorchecker_Lab)[3 * n_extra_patches], *colorchecker_Lab, sizeof(double) * 3 * *N);
853
854 memcpy(*target_L, extra_target_L, sizeof(double) * n_extra_patches);
855 memcpy(*target_a, extra_target_a, sizeof(double) * n_extra_patches);
856 memcpy(*target_b, extra_target_b, sizeof(double) * n_extra_patches);
858
859 *N += n_extra_patches;
860 }
861}
862
863static char *encode_tonecurve(const tonecurve_t *c)
864{
865 // hardcoded params v4 from tonecurve:
866 typedef struct dt_iop_tonecurve_node_t
867 {
868 float x;
869 float y;
871 typedef struct dt_iop_tonecurve_params_t
872 {
873 dt_iop_tonecurve_node_t tonecurve[3][20]; // three curves (L, a, b) with max number
874 // of nodes
875 int tonecurve_nodes[3];
876 int tonecurve_type[3];
881
883 memset(&params, 0, sizeof(params));
884 params.tonecurve_autoscale_ab = 3; // prophoto rgb
885
886 params.tonecurve_type[0] = 2; // MONOTONE_HERMITE
887 params.tonecurve_nodes[0] = 20;
888 for(int k = 0; k < 20; k++)
889 {
890 const double x = (k / 19.0) * (k / 19.0);
891 params.tonecurve[0][k].x = x;
892 params.tonecurve[0][k].y = tonecurve_apply(c, 100.0 * x) / 100.0;
893 }
894
895 params.tonecurve_type[1] = 2; // MONOTONE_HERMITE
896 params.tonecurve_nodes[1] = 2;
897 params.tonecurve[1][0].x = 0.0f;
898 params.tonecurve[1][0].y = 0.0f;
899 params.tonecurve[1][1].x = 1.0f;
900 params.tonecurve[1][1].y = 1.0f;
901
902 params.tonecurve_type[2] = 2; // MONOTONE_HERMITE
903 params.tonecurve_nodes[2] = 2;
904 params.tonecurve[2][0].x = 0.0f;
905 params.tonecurve[2][0].y = 0.0f;
906 params.tonecurve[2][1].x = 1.0f;
907 params.tonecurve[2][1].y = 1.0f;
908
909 return dt_exif_xmp_encode_internal((uint8_t *)&params, sizeof(params), NULL, FALSE);
910}
911
912static char *encode_colorchecker(int num, const double *point, const double **target, int *permutation)
913{
914// hardcoded v2 of the module
915#define MAX_PATCHES 49
916 typedef struct dt_iop_colorchecker_params_t
917 {
918 float source_L[MAX_PATCHES];
919 float source_a[MAX_PATCHES];
920 float source_b[MAX_PATCHES];
921 float target_L[MAX_PATCHES];
922 float target_a[MAX_PATCHES];
923 float target_b[MAX_PATCHES];
924 int32_t num_patches;
926
928 memset(&params, 0, sizeof(params));
929 num = MIN(MAX_PATCHES, num);
930 // assert(num <= MAX_PATCHES);
931 params.num_patches = num;
932
933 for(int k = 0; k < num; k++)
934 {
935 params.source_L[k] = point[3 * permutation[k]];
936 params.source_a[k] = point[3 * permutation[k] + 1];
937 params.source_b[k] = point[3 * permutation[k] + 2];
938 params.target_L[k] = target[0][permutation[k]];
939 params.target_a[k] = target[1][permutation[k]];
940 params.target_b[k] = target[2][permutation[k]];
941 }
942
943#define SWAP(a, b) \
944 { \
945 const float tmp = (a); \
946 (a) = (b); \
947 (b) = tmp; \
948 }
949 // bubble sort by octant and brightness:
950 for(int k = 0; k < num - 1; k++)
951 for(int j = 0; j < num - k - 1; j++)
952 {
953 if(thinplate_color_pos(params.source_L[j], params.source_a[j], params.source_b[j])
954 < thinplate_color_pos(params.source_L[j + 1], params.source_a[j + 1], params.source_b[j + 1]))
955 {
956 SWAP(params.source_L[j], params.source_L[j + 1]);
957 SWAP(params.source_a[j], params.source_a[j + 1]);
958 SWAP(params.source_b[j], params.source_b[j + 1]);
959 SWAP(params.target_L[j], params.target_L[j + 1]);
960 SWAP(params.target_a[j], params.target_a[j + 1]);
961 SWAP(params.target_b[j], params.target_b[j + 1]);
962 }
963 }
964#undef SWAP
965#undef MAX_PATCHES
966
967 return dt_exif_xmp_encode_internal((uint8_t *)&params, sizeof(params), NULL, FALSE);
968}
969
970static int compare_L_source(const void *x_, const void *y_)
971{
972 const double x = *(const double *)x_;
973 const double y = *(const double *)y_;
974 return x < y ? -1 : (x > y ? +1 : 0);
975}
976
977static void process_data(dt_lut_t *self, double *target_L, double *target_a, double *target_b,
978 double *colorchecker_Lab, int N, int sparsity)
979{
980 // get all the memory, just in case:
981 double *cx = malloc(sizeof(double)*N);
982 double *cy = malloc(sizeof(double)*N);
983 double *grays = malloc(sizeof(double) * 6 * N);
984 tonecurve_t tonecurve;
985 int num_tonecurve = 0;
986 {
987 int cnt = 0;
988
989 for(int i=0;i<N;i++)
990 {
991 const double sat_in =
994 const double sat_out =
995 target_a[i] * target_a[i] +
996 target_b[i] * target_b[i];
997 // we'll allow some artistic tint or one due to illuminants (note square scale)
998 if(sat_in < 15.0 && sat_out < 15.0)
999 {
1000 cnt++; // store as gray patch:
1001 grays[6*cnt + 0] = colorchecker_Lab[3*i+0];
1002 grays[6*cnt + 1] = colorchecker_Lab[3*i+1];
1003 grays[6*cnt + 2] = colorchecker_Lab[3*i+2];
1004 grays[6*cnt + 3] = target_L[i];
1005 grays[6*cnt + 4] = target_a[i];
1006 grays[6*cnt + 5] = target_b[i];
1007 }
1008 }
1009 fprintf(stderr, "detected %d/%d as gray patches for tonecurve computation\n", cnt, N);
1010 // sort entries by source L
1011 qsort(grays, cnt, sizeof(double) * 6, compare_L_source);
1012
1013 // put entries with fixed black and white into cx, cy
1014 cx[0] = cy[0] = 0.0; // fix black
1015 cx[cnt+1] = cy[cnt+1] = 100.0; // fix white
1016
1017 // construct a tone curve from the grays plus pure black and pure white
1018 num_tonecurve = cnt + 2;
1019 for(int k = 0; k < cnt; k++) cx[k + 1] = grays[6*k+0];
1020 for(int k = 0; k < cnt; k++) cy[k + 1] = grays[6*k+3];
1021 tonecurve_create(&tonecurve, cx, cy, num_tonecurve);
1022 }
1023
1024#if 0 // quiet.
1025 for(int k = 0; k < num_tonecurve; k++)
1026 fprintf(stderr, "L[%g] = %g\n", 100.0 * k / (num_tonecurve - 1.0),
1027 tonecurve_apply(&tonecurve, 100.0 * k / (num_tonecurve - 1.0)));
1028#endif
1029
1030#if 0 // Lab tonecurve on L only
1031 // unapply from target data, we will apply it later in the pipe and want to match the colours only:
1032 for(int k = 0; k < N; k++) target_L[k] = tonecurve_unapply(&tonecurve, target_L[k]);
1033#else // rgb tonecurve affecting colours, too
1035 // ownership transferred to tonecurve object, so we just alloc without free here:
1036 cx = malloc(sizeof(double)*num_tonecurve);
1037 cy = malloc(sizeof(double)*num_tonecurve);
1038 cx[0] = cy[0] = 0.0; // fix black
1039 cx[num_tonecurve - 1] = cy[num_tonecurve - 1] = 100.0; // fix white
1040 for(int k = 1; k < num_tonecurve-1; k++)
1041 {
1042 dt_aligned_pixel_t rgb, Lab = { 0.0f, 0.0f, 0.0f };
1043 Lab[0] = grays[6*k+0];
1045 cx[k] = rgb[0];
1046 Lab[0] = tonecurve_apply(&tonecurve, Lab[0]);
1048 cy[k] = rgb[0];
1049 }
1051 dt_free(grays);
1052
1053 // now unapply the curve:
1054 for(int k = 0; k < N; k++)
1055 {
1056 dt_aligned_pixel_t rgb, Lab = { 0.0f, 0.0f, 0.0f };
1057 Lab[0] = target_L[k];
1058 Lab[1] = target_a[k];
1059 Lab[2] = target_b[k];
1061 rgb[0] = tonecurve_unapply(&rgbcurve, rgb[0]);
1062 rgb[1] = tonecurve_unapply(&rgbcurve, rgb[1]);
1063 rgb[2] = tonecurve_unapply(&rgbcurve, rgb[2]);
1065 target_L[k] = Lab[0];
1066 target_a[k] = Lab[1];
1067 target_b[k] = Lab[2];
1068 }
1070#endif
1071
1072 const double *target[3] = { target_L, target_a, target_b };
1073 double *coeff_L = malloc(sizeof(double) * (N + 4) );
1074 double *coeff_a = malloc(sizeof(double) * (N + 4) );
1075 double *coeff_b = malloc(sizeof(double) * (N + 4) );
1076 double *coeff[] = { coeff_L, coeff_a, coeff_b };
1077 int *perm = malloc(sizeof(int) * (N + 4));
1078 double avgerr, maxerr;
1079 sparsity = thinplate_match(&tonecurve, 3, N, colorchecker_Lab, target, sparsity, perm, coeff, &avgerr, &maxerr);
1080
1081 if (!IS_NULL_PTR(self->result_label))
1082 {
1083 // TODO: is the rank interesting, too?
1084 char *result_string = g_strdup_printf(_("average dE: %.02f\nmax dE: %.02f"), avgerr, maxerr);
1087 }
1088
1089 dt_free(coeff_b);
1090 dt_free(coeff_a);
1091 dt_free(coeff_L);
1092
1093 int sp = 0;
1094 int cperm[300] = { 0 };
1095 for(int k = 0; k < sparsity; k++)
1096 if(perm[k] < N) // skip polynomial parts
1097 cperm[sp++] = perm[k];
1098
1099#if 0 // quiet.
1100 fprintf(stderr, "found %d basis functions:\n", sp);
1101 for(int k = 0; k < sp; k++)
1102 fprintf(stderr, "perm[%d] = %d source %g %g %g\n", k, cperm[k], colorchecker_Lab[3 * cperm[k]],
1103 colorchecker_Lab[3 * cperm[k] + 1], colorchecker_Lab[3 * cperm[k] + 2]);
1104#endif
1105
1106 dt_free(perm);
1107 self->tonecurve_encoded = encode_tonecurve(&tonecurve);
1109
1110 tonecurve_delete(&tonecurve);
1111}
1112
1113static void process_button_clicked_callback(GtkButton *button, gpointer user_data)
1114{
1115 dt_lut_t *self = (dt_lut_t *)user_data;
1116
1120 self->tonecurve_encoded = NULL;
1121 self->colorchecker_encoded = NULL;
1122
1123 if(IS_NULL_PTR(self->chart)) return;
1124
1125 int i = 0;
1126 int N = g_hash_table_size(self->chart->box_table);
1127
1128 double *target_L = (double *)calloc(sizeof(double), (N + 4));
1129 double *target_a = (double *)calloc(sizeof(double), (N + 4));
1130 double *target_b = (double *)calloc(sizeof(double), (N + 4));
1131 double *colorchecker_Lab = (double *)calloc(sizeof(double) * 3, N);
1132
1134 gpointer set_key, value;
1135
1138 {
1140 add_patches_to_array(self, patch_names, &N, &i, target_L, target_a, target_b, colorchecker_Lab);
1141 }
1142
1143 add_hdr_patches(&N, &target_L, &target_a, &target_b, &colorchecker_Lab);
1144
1146
1147 process_data(self, target_L, target_a, target_b, colorchecker_Lab, N, sparsity);
1148
1151
1152 dt_free(target_L);
1153 dt_free(target_a);
1154 dt_free(target_b);
1156}
1157
1158static void cht_state_callback(GtkWidget *widget, GtkStateFlags flags, gpointer user_data)
1159{
1160 dt_lut_t *self = (dt_lut_t *)user_data;
1161 // cht not sensitive -> no reference or export
1163 {
1169 }
1170}
1171
1172static void shrink_changed_callback(GtkRange *range, gpointer user_data)
1173{
1174 image_t *image = (image_t *)user_data;
1175 image->shrink = gtk_range_get_value(range);
1176
1178}
1179
1181{
1183
1185 gtk_box_pack_start(GTK_BOX(page), hbox, FALSE, TRUE, 0);
1186
1187 GtkWidget *image_button = gtk_file_chooser_button_new("image of a color chart", GTK_FILE_CHOOSER_ACTION_OPEN);
1188 g_signal_connect(image_button, "file-set", G_CALLBACK(source_image_changed_callback), self);
1189
1190 GtkWidget *cht_button
1191 = gtk_file_chooser_button_new("description of a color chart", GTK_FILE_CHOOSER_ACTION_OPEN);
1192 g_signal_connect(cht_button, "file-set", G_CALLBACK(cht_changed_callback), self);
1193
1194 GtkWidget *source_shrink = gtk_scale_new_with_range(GTK_ORIENTATION_HORIZONTAL, 0.5, 2.0, 0.01);
1196 g_signal_connect(source_shrink, "value-changed", G_CALLBACK(shrink_changed_callback), &self->source);
1197
1198 gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new("image:"), FALSE, TRUE, 0);
1199 gtk_box_pack_start(GTK_BOX(hbox), image_button, TRUE, TRUE, 0);
1200 gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new("chart:"), FALSE, TRUE, 0);
1201 gtk_box_pack_start(GTK_BOX(hbox), cht_button, TRUE, TRUE, 0);
1202 gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new("size:"), FALSE, TRUE, 0);
1203 gtk_box_pack_start(GTK_BOX(hbox), source_shrink, TRUE, TRUE, 0);
1204
1206 self->source.draw_colored = TRUE;
1208
1209 g_signal_connect(cht_button, "state-flags-changed", G_CALLBACK(cht_state_callback), self);
1210
1211 self->image_button = image_button;
1212 self->cht_button = cht_button;
1213 self->source_shrink = source_shrink;
1214
1215 return page;
1216}
1217
1219{
1221
1223 gtk_box_pack_start(GTK_BOX(page), hbox, FALSE, TRUE, 0);
1224
1225 GtkWidget *reference_mode = gtk_combo_box_text_new();
1226 gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(reference_mode), NULL, "cie/it8 file");
1227 gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(reference_mode), NULL, "color chart image");
1228 gtk_combo_box_set_active(GTK_COMBO_BOX(reference_mode), 0);
1229 g_signal_connect(reference_mode, "changed", G_CALLBACK(reference_mode_changed_callback), self);
1230
1231 GtkWidget *it8_button
1232 = gtk_file_chooser_button_new("reference data of a color chart", GTK_FILE_CHOOSER_ACTION_OPEN);
1233 g_signal_connect(it8_button, "file-set", G_CALLBACK(it8_changed_callback), self);
1234
1235 GtkWidget *reference_image_button
1236 = gtk_file_chooser_button_new("image of a color chart", GTK_FILE_CHOOSER_ACTION_OPEN);
1237 g_signal_connect(reference_image_button, "file-set", G_CALLBACK(ref_image_changed_callback), self);
1238
1239 GtkWidget *reference_shrink = gtk_scale_new_with_range(GTK_ORIENTATION_HORIZONTAL, 0.5, 2.0, 0.01);
1241 g_signal_connect(reference_shrink, "value-changed", G_CALLBACK(shrink_changed_callback), &self->reference);
1242
1243 gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new("mode:"), FALSE, TRUE, 0);
1244 gtk_box_pack_start(GTK_BOX(hbox), reference_mode, TRUE, TRUE, 0);
1245
1247 gtk_box_pack_start(GTK_BOX(reference_it8_box), gtk_label_new("reference it8:"), FALSE, TRUE, 0);
1248 gtk_box_pack_start(GTK_BOX(reference_it8_box), it8_button, TRUE, TRUE, 0);
1249 gtk_box_pack_start(GTK_BOX(hbox), reference_it8_box, TRUE, TRUE, 0);
1250
1252 gtk_box_pack_start(GTK_BOX(reference_image_box), gtk_label_new("reference image:"), FALSE, TRUE, 0);
1253 gtk_box_pack_start(GTK_BOX(reference_image_box), reference_image_button, TRUE, TRUE, 0);
1254 gtk_box_pack_start(GTK_BOX(reference_image_box), gtk_label_new("size:"), FALSE, TRUE, 0);
1255 gtk_box_pack_start(GTK_BOX(reference_image_box), reference_shrink, TRUE, TRUE, 0);
1256 gtk_box_pack_start(GTK_BOX(hbox), reference_image_box, TRUE, TRUE, 0);
1257
1261
1262 gtk_widget_show_all(reference_it8_box);
1263 gtk_widget_show_all(reference_image_box);
1265 gtk_widget_hide(reference_image_box);
1267 gtk_widget_set_no_show_all(reference_it8_box, TRUE);
1268 gtk_widget_set_no_show_all(reference_image_box, TRUE);
1270
1271 self->reference_mode = reference_mode;
1272 self->it8_button = it8_button;
1273 self->reference_image_button = reference_image_button;
1274 self->reference_it8_box = reference_it8_box;
1275 self->reference_image_box = reference_image_box;
1276 self->reference_shrink = reference_shrink;
1277
1278 return page;
1279}
1280
1282{
1283 GtkWidget *page = gtk_grid_new();
1286
1287 int line = 0;
1288
1289 // TODO: it might make sense to limit this to a smaller range and/or use a slider
1290 // 49 is the current max in the lut iop
1291 GtkWidget *number_patches = gtk_spin_button_new_with_range(0, 49, 1);
1292 gtk_spin_button_set_value(GTK_SPIN_BUTTON(number_patches), 24);
1293 gtk_grid_attach(GTK_GRID(page), gtk_label_new("number of final patches"), 0, line, 1, 1);
1294 gtk_grid_attach(GTK_GRID(page), number_patches, 1, line++, 1, 1);
1295
1296 GtkWidget *process_button = gtk_button_new_with_label("process");
1297 GtkWidget *export_button = gtk_button_new_with_label("export");
1298 GtkWidget *export_raw_button = gtk_button_new_with_label("export raw data as csv");
1299 gtk_grid_attach(GTK_GRID(page), process_button, 1, line, 1, 1);
1300 gtk_grid_attach(GTK_GRID(page), export_button, 2, line, 1, 1);
1301 gtk_grid_attach(GTK_GRID(page), export_raw_button, 3, line++, 1, 1);
1302
1305 gtk_grid_attach(GTK_GRID(page), self->result_label, 1, line++, 3, 1);
1306
1307 g_signal_connect(process_button, "clicked", G_CALLBACK(process_button_clicked_callback), self);
1308 g_signal_connect(export_button, "clicked", G_CALLBACK(export_button_clicked_callback), self);
1309 g_signal_connect(export_raw_button, "clicked", G_CALLBACK(export_raw_button_clicked_callback), self);
1310
1311 self->number_patches = number_patches;
1312 self->process_button = process_button;
1313 self->export_button = export_button;
1314 self->export_raw_button = export_raw_button;
1315
1316 return page;
1317}
1318
1320{
1321 // the notebook with 2 tabs for input
1323
1324 // first tab: input image + cht file
1326 gtk_label_new("source image"));
1327
1328 // second tab: mode + either reference image or cie file
1330 gtk_label_new("reference values"));
1331
1332 // third tab: analyze data and process it
1334
1335 return notebook;
1336}
1337
1339{
1344 // gtk_paned_pack2(GTK_PANED(vpaned), scrolled_window, TRUE, FALSE);
1345
1347 G_TYPE_STRING, // COLUMN_NAME
1348 G_TYPE_STRING, // COLUMN_RGB_IN
1349 G_TYPE_STRING, // COLUMN_LAB_IN
1350 G_TYPE_STRING, // COLUMN_LAB_REF
1351 G_TYPE_STRING, // COLUMN_DE_1976
1352 G_TYPE_FLOAT, // COLUMN_DE_1976_FLOAT
1353 G_TYPE_STRING, // COLUMN_DE_2000
1354 G_TYPE_FLOAT // COLUMN_DE_2000_FLOAT
1355 ));
1359
1363 add_column(GTK_TREE_VIEW(self->treeview), "Lab (reference)", COLUMN_LAB_REF, COLUMN_LAB_REF);
1366
1367 return scrolled_window;
1368}
1369
1370static void add_column(GtkTreeView *treeview, const char *title, int column_id, int sort_column)
1371{
1372 GtkCellRenderer *renderer;
1374 renderer = gtk_cell_renderer_text_new();
1378}
1379
1380// only change the numbers, don't re-fill the table!
1381static void update_table(dt_lut_t *self)
1382{
1384 gboolean valid = gtk_tree_model_get_iter_first(self->model, &iter);
1385
1386 while(valid)
1387 {
1388 char *name;
1389
1391
1393 if(box)
1394 {
1395 dt_aligned_pixel_t Lab = { 0.0 };
1397 float deltaE_1976 = 0.0, deltaE_2000 = 0.0;
1398
1399 get_Lab_from_box(box, Lab);
1400
1402 if(patch)
1403 {
1404 dt_aligned_pixel_t in_Lab = { 0.0 };
1405 get_Lab_from_box(patch, in_Lab);
1406 s_RGB_in = g_strdup_printf("%d; %d; %d", (int)(patch->rgb[0] * 255 + 0.5),
1407 (int)(patch->rgb[1] * 255 + 0.5), (int)(patch->rgb[2] * 255 + 0.5));
1408 s_Lab_in = g_strdup_printf("%.02f; %.02f; %.02f", in_Lab[0], in_Lab[1], in_Lab[2]);
1413 }
1414 else
1415 {
1416 s_Lab_in = g_strdup("?");
1417 s_RGB_in = g_strdup("?");
1418 s_deltaE_1976 = g_strdup("-");
1419 s_deltaE_2000 = g_strdup("-");
1420 }
1421 char *s_Lab_ref = g_strdup_printf("%.02f; %.02f; %.02f", Lab[0], Lab[1], Lab[2]);
1422
1431 } // if(box)
1432
1433 dt_free(name);
1434 valid = gtk_tree_model_iter_next(self->model, &iter);
1435 } // while(valid)
1436}
1437
1438static void get_Lab_from_box(box_t *box, float *Lab)
1439{
1440 switch(box->color_space)
1441 {
1442 case DT_COLORSPACE_XYZ:
1443 {
1445 for(int i = 0; i < 3; i++) XYZ[i] = box->color[i] * 0.01;
1447 break;
1448 }
1449 case DT_COLORSPACE_LAB:
1450 for(int i = 0; i < 3; i++) Lab[i] = box->color[i];
1451 break;
1452 default:
1453 break;
1454 }
1455}
1456
1457static void init_table(dt_lut_t *self)
1458{
1460
1462
1463 if(IS_NULL_PTR(self->chart)) return;
1464
1468 {
1470 gtk_list_store_set(GTK_LIST_STORE(self->model), &iter, COLUMN_NAME, (char *)name->data, -1);
1471 }
1473 patch_names = NULL;
1474
1475 update_table(self);
1476}
1477
1482
1487
1488static void collect_source_patches_foreach(gpointer key, gpointer value, gpointer user_data)
1489{
1490 dt_lut_t *self = (dt_lut_t *)user_data;
1491 box_t *box = (box_t *)value;
1493
1494 box_t *patch = find_patch(self->picked_source_patches, key);
1495
1496 get_xyz_sample_from_image(&self->source, self->source.shrink, box, xyz);
1497
1498 checker_set_color(patch, DT_COLORSPACE_XYZ, xyz[0] * 100.0, xyz[1] * 100.0, xyz[2] * 100.0);
1499}
1500
1501static void collect_reference_patches_foreach(gpointer key, gpointer value, gpointer user_data)
1502{
1503 dt_lut_t *self = (dt_lut_t *)user_data;
1504 box_t *patch = (box_t *)value;
1506
1507 get_xyz_sample_from_image(&self->reference, self->reference.shrink, patch, xyz);
1508
1509 checker_set_color(patch, DT_COLORSPACE_XYZ, xyz[0] * 100.0, xyz[1] * 100.0, xyz[2] * 100.0);
1510}
1511
1512static box_t *find_patch(GHashTable *table, gpointer key)
1513{
1514 box_t *patch = (box_t *)g_hash_table_lookup(table, key);
1515 if(IS_NULL_PTR(patch))
1516 {
1517 // the patch won't be found in the first pass
1518 patch = (box_t *)calloc(1, sizeof(box_t));
1519 g_hash_table_insert(table, g_strdup(key), patch);
1520 }
1521 return patch;
1522}
1523
1524static void get_xyz_sample_from_image(const image_t *const image, float shrink, box_t *box, float *xyz)
1525{
1526 point_t bb[4];
1527 float homography[9];
1528 point_t corners[4];
1530 int x_start, y_start, x_end, y_end;
1531
1532 xyz[0] = xyz[1] = xyz[2] = 0.0;
1533
1534 if(IS_NULL_PTR(box)) return;
1535
1536 get_boundingbox(image, bb);
1538 inner_box = get_sample_box(*(image->chart), box, shrink);
1541
1542 const float delta_x_top = corners[TOP_RIGHT].x - corners[TOP_LEFT].x;
1543 const float delta_y_top = corners[TOP_RIGHT].y - corners[TOP_LEFT].y;
1546 const float delta_x_left = corners[BOTTOM_LEFT].x - corners[TOP_LEFT].x;
1547 const float delta_y_left = corners[BOTTOM_LEFT].y - corners[TOP_LEFT].y;
1548 const float delta_x_right = corners[BOTTOM_RIGHT].x - corners[TOP_RIGHT].x;
1549 const float delta_y_right = corners[BOTTOM_RIGHT].y - corners[TOP_RIGHT].y;
1550
1551 double sample_x = 0.0, sample_y = 0.0, sample_z = 0.0;
1552 size_t n_samples = 0;
1554 for(int y = y_start; y < y_end; y++)
1555 for(int x = x_start; x < x_end; x++)
1556 {
1561 {
1562 float *pixel = &image->xyz[(x + y * image->width) * 3];
1563 sample_x += pixel[0];
1564 sample_y += pixel[1];
1565 sample_z += pixel[2];
1566 n_samples++;
1567 }
1568 }
1569
1570 xyz[0] = sample_x / n_samples;
1571 xyz[1] = sample_y / n_samples;
1572 xyz[2] = sample_z / n_samples;
1573}
1574
1575static void get_boundingbox(const image_t *const image, point_t *bb)
1576{
1577 for(int i = 0; i < 4; i++)
1578 {
1579 bb[i].x = image->bb[i].x * image->width;
1580 bb[i].y = image->bb[i].y * image->height;
1581 }
1582}
1583
1584static box_t get_sample_box(chart_t *chart, box_t *outer_box, float shrink)
1585{
1587 float x_shrink = shrink * chart->box_shrink / chart->bb_w, y_shrink = shrink * chart->box_shrink / chart->bb_h;
1588 inner_box.p.x += x_shrink;
1589 inner_box.p.y += y_shrink;
1590 inner_box.w -= 2.0 * x_shrink;
1591 inner_box.h -= 2.0 * y_shrink;
1592 return inner_box;
1593}
1594
1595static void get_corners(const float *homography, box_t *box, point_t *corners)
1596{
1598 corners[TOP_RIGHT].x += box->w;
1599 corners[BOTTOM_RIGHT].x += box->w;
1600 corners[BOTTOM_RIGHT].y += box->h;
1601 corners[BOTTOM_LEFT].y += box->h;
1602
1603 for(int i = 0; i < 4; i++) corners[i] = apply_homography(corners[i], homography);
1604}
1605
1606static void get_pixel_region(const image_t *const image, const point_t *const corners, int *x_start, int *y_start,
1607 int *x_end, int *y_end)
1608{
1609 *x_start = CLAMP((int)(MIN(corners[TOP_LEFT].x,
1611 + 0.5),
1612 0, image->width);
1613 *x_end = CLAMP((int)(MAX(corners[TOP_LEFT].x,
1615 + 0.5),
1616 0, image->width);
1617 *y_start = CLAMP((int)(MIN(corners[TOP_LEFT].y,
1619 + 0.5),
1620 0, image->height);
1621 *y_end = CLAMP((int)(MAX(corners[TOP_LEFT].y,
1623 + 0.5),
1624 0, image->height);
1625}
1626
1627static void reset_bb(image_t *image)
1628{
1629 image->bb[TOP_LEFT].x = 0.05;
1630 image->bb[TOP_LEFT].y = 0.05;
1631 image->bb[TOP_RIGHT].x = 0.95;
1632 image->bb[TOP_RIGHT].y = 0.05;
1633 image->bb[BOTTOM_RIGHT].x = 0.95;
1634 image->bb[BOTTOM_RIGHT].y = 0.95;
1635 image->bb[BOTTOM_LEFT].x = 0.05;
1636 image->bb[BOTTOM_LEFT].y = 0.95;
1637}
1638
1639static void init_image(dt_lut_t *self, image_t *image, GCallback motion_cb)
1640{
1641 memset(image, 0x00, sizeof(image_t));
1642 image->chart = &self->chart;
1647 g_signal_connect(image->drawing_area, "size-allocate", G_CALLBACK(size_allocate_callback), image);
1649 g_signal_connect(image->drawing_area, "motion-notify-event", G_CALLBACK(motion_cb), self);
1650}
1651
1652static void free_image(image_t *image)
1653{
1654 if(IS_NULL_PTR(image)) return;
1655 reset_bb(image);
1656 if(image->image) cairo_pattern_destroy(image->image);
1657 if(image->surface) cairo_surface_destroy(image->surface);
1658 dt_free(image->xyz);
1659 image->image = NULL;
1660 image->surface = NULL;
1661 image->xyz = NULL;
1662}
1663
1664static void image_lab_to_xyz(float *image, const int width, const int height)
1665{
1667 for(int y = 0; y < height; y++)
1668 for(int x = 0; x < width; x++)
1669 {
1670 float *pixel = &image[(x + y * width) * 3];
1672 }
1673}
1674
1675static int main_gui(dt_lut_t *self, int argc, char *argv[])
1676{
1677 gtk_init(&argc, &argv);
1678
1679 char *source_filename = argc >= 2 ? argv[1] : NULL;
1680 char *cht_filename = argc >= 3 ? argv[2] : NULL;
1681 char *it8_filename = NULL;
1682 char *reference_filename = NULL;
1683 if(argc >= 4)
1684 {
1685 char *upper_string = g_ascii_strup(argv[3], -1);
1686 if(g_str_has_suffix(upper_string, ".PFM"))
1687 reference_filename = argv[3];
1688 else
1689 it8_filename = argv[3];
1691 }
1692
1693 // build the GUI
1695 self->window = window;
1696 gtk_window_set_title(GTK_WINDOW(window), "darktable LUT tool");
1700
1701 // resizable container
1704
1705 // upper half
1707
1708 // lower half
1710
1717
1719
1720 // only load data now so it can fill widgets
1722 {
1724 if(cht_filename && open_cht(self, cht_filename))
1725 {
1727 if(it8_filename && open_it8(self, it8_filename))
1729 if(reference_filename && open_reference_image(self, reference_filename))
1730 {
1733 }
1734 }
1735 }
1736
1737#ifdef GDK_WINDOWING_QUARTZ
1739#endif
1740 gtk_main();
1741
1742 return 0;
1743}
1744
1745static int parse_csv(dt_lut_t *self, const char *filename, double **target_L_ptr, double **target_a_ptr,
1746 double **target_b_ptr, double **source_Lab_ptr, int *num_gray, char **name, char **description)
1747{
1748 *target_L_ptr = NULL;
1749 *target_a_ptr = NULL;
1750 *target_b_ptr = NULL;
1752 *name = NULL;
1753 *description = NULL;
1754
1755 FILE *f = g_fopen(filename, "rb");
1756 if(IS_NULL_PTR(f)) return 0;
1757 int N = 0;
1758 while(fscanf(f, "%*[^\n]\n") != EOF) N++;
1759 fseek(f, 0, SEEK_SET);
1760
1761 if(N <= 1)
1762 {
1763 fclose(f);
1764 return 0;
1765 }
1766
1767 // header lines
1768 char key[16] = {0}, value[256] = {0};
1769 int read_res = fscanf(f, "%15[^;];%255[^\n]\n", key, value);
1770 if(g_strcmp0(key, "name") || read_res == EOF)
1771 {
1772 fprintf(stderr, "error: expected `name' in the first line\n");
1773 fclose(f);
1774 return 0;
1775 }
1776 *name = g_strdup(value);
1777 N--;
1778
1779 read_res = fscanf(f, "%15[^;];%255[^\n]\n", key, value);
1780 if(g_strcmp0(key, "description") || read_res == EOF)
1781 {
1782 fprintf(stderr, "error: expected `description' in the second line\n");
1783 fclose(f);
1784 return 0;
1785 }
1787 N--;
1788
1789 read_res = fscanf(f, "%15[^;];%d\n", key, num_gray);
1790 if(g_strcmp0(key, "num_gray") || read_res == EOF)
1791 {
1792 fprintf(stderr, "error: missing num_gray in csv\n");
1793 fclose(f);
1794 return 0;
1795 }
1796 N--;
1797
1798 // skip the column title line
1799 read_res = fscanf(f, "%*[^\n]\n");
1800 N--;
1801
1802 double *target_L = (double *)calloc(sizeof(double), (N + 4));
1803 double *target_a = (double *)calloc(sizeof(double), (N + 4));
1804 double *target_b = (double *)calloc(sizeof(double), (N + 4));
1805 double *source_Lab = (double *)calloc(sizeof(double) * 3, N);
1806 *target_L_ptr = target_L;
1807 *target_a_ptr = target_a;
1808 *target_b_ptr = target_b;
1809 *source_Lab_ptr = source_Lab;
1810
1811 char line[512];
1812 for(int i = 0; i < N; i++)
1813 {
1814 char *patchname = line, *iter = line, *endptr;
1815 if(fgets(line, sizeof(line) / sizeof(*line), f) == 0) break;
1816 while(*iter != ';') iter++;
1817 *iter++ = '\0';
1818
1819 source_Lab[3 * i] = g_ascii_strtod(iter, &endptr);
1820 if(iter == endptr || *endptr != ';') break;
1821 iter = endptr + 1;
1822 source_Lab[3 * i + 1] = g_ascii_strtod(iter, &endptr);
1823 if(iter == endptr || *endptr != ';') break;
1824 iter = endptr + 1;
1825 source_Lab[3 * i + 2] = g_ascii_strtod(iter, &endptr);
1826 if(iter == endptr || *endptr != ';') break;
1827 iter = endptr + 1;
1828 target_L[i] = g_ascii_strtod(iter, &endptr);
1829 if(iter == endptr || *endptr != ';') break;
1830 iter = endptr + 1;
1831 target_a[i] = g_ascii_strtod(iter, &endptr);
1832 if(iter == endptr || *endptr != ';') break;
1833 iter = endptr + 1;
1834 target_b[i] = g_ascii_strtod(iter, &endptr);
1835 if(iter == endptr || *endptr != '\n') break;
1836
1837 const double d[3] = { target_L[i], target_a[i], target_b[i] };
1838 if(sqrt(d[0] * d[0] + d[1] * d[1] + d[2] * d[2]) > thrs)
1839 {
1840 fprintf(stderr, "warning: ignoring patch %s with large difference deltaE %g!\n", patchname,
1841 sqrt(d[0] * d[0] + d[1] * d[1] + d[2] * d[2]));
1842 fprintf(stderr, " %g %g %g -- %g %g %g\n", source_Lab[3 * i + 0], source_Lab[3 * i + 1],
1843 source_Lab[3 * i + 2], target_L[i], target_a[i], target_b[i]);
1844 N--; // ignore this patch.
1845 i--;
1846 }
1847 }
1848
1849 fclose(f);
1850 return N;
1851}
1852
1853static int main_csv(dt_lut_t *self, int argc, char *argv[])
1854{
1855 const char *filename_csv = argv[2];
1856 const int num_patches = atoi(argv[3]);
1857 const char *filename_style = argv[4];
1858
1859 const int sparsity = num_patches + 4;
1860
1861 // parse the csv
1862 double *target_L, *target_a, *target_b, *colorchecker_Lab;
1863 int num_tonecurve;
1864 char *name, *description;
1865 int N = parse_csv(self, filename_csv, &target_L, &target_a, &target_b, &colorchecker_Lab, &num_tonecurve, &name,
1866 &description);
1867
1868 if(N == 0)
1869 {
1870 fprintf(stderr, "error parsing `%s', giving up\n", filename_csv);
1871
1872 dt_free(target_L);
1873 dt_free(target_a);
1874 dt_free(target_b);
1876
1877 return 1;
1878 }
1879
1880 add_hdr_patches(&N, &target_L, &target_a, &target_b, &colorchecker_Lab);
1881
1882 process_data(self, target_L, target_a, target_b, colorchecker_Lab, N, sparsity);
1883
1884 // TODO: add command line options to control what modules to include
1886
1887 dt_free(target_L);
1888 dt_free(target_a);
1889 dt_free(target_b);
1891 dt_free(name);
1893
1894 return 0;
1895}
1896
1897static void show_usage(const char *exe)
1898{
1899 fprintf(stderr, "Usage: %s [<input Lab pfm file>] [<cht file>] [<reference cgats/it8 or Lab pfm file>]\n"
1900 " %s --csv <csv file> <number patches> <output dtstyle file>\n",
1901 exe, exe);
1902}
1903
1904int main(int argc, char *argv[])
1905{
1906#ifdef __APPLE__
1908#endif
1909#ifdef _WIN32
1911#endif
1912
1913#ifdef _OPENMP
1915#endif
1916
1917 int res = 1;
1918 dt_lut_t *self = (dt_lut_t *)calloc(1, sizeof(dt_lut_t));
1920
1921 if(argc >= 2 && !strcmp(argv[1], "--help"))
1922 show_usage(argv[0]);
1923 else if(argc >= 2 && !g_strcmp0(argv[1], "--csv"))
1924 {
1925 if(argc != 5)
1926 show_usage(argv[0]);
1927 else
1928 res = main_csv(self, argc, argv);
1929 }
1930 else if(argc <= 4)
1931 res = main_gui(self, argc, argv);
1932 else
1933 show_usage(argv[0]);
1934
1935 if(self->model) g_object_unref(self->model);
1937 free_image(&self->source);
1938 free_image(&self->reference);
1939 free_chart(self->chart);
1942 dt_free(self);
1943
1944 return res;
1945}
1946
1947// clang-format off
1948// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1949// vim: shiftwidth=2 expandtab tabstop=2 cindent
1950// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1951// clang-format on
1952
static char * get_filename_base(const char *filename)
static void add_patches_to_array(dt_lut_t *self, GList *patch_names, int *N, int *i, double *target_L, double *target_a, double *target_b, double *colorchecker_Lab)
static gboolean handle_motion(GtkWidget *widget, GdkEventMotion *event, dt_lut_t *self, image_t *image)
static int main_gui(dt_lut_t *self, int argc, char *argv[])
static box_t * find_patch(GHashTable *table, gpointer key)
static void cht_state_callback(GtkWidget *widget, GtkStateFlags flags, gpointer user_data)
static void init_image(dt_lut_t *self, image_t *image, GCallback motion_cb)
static void get_Lab_from_box(box_t *box, float *Lab)
static int find_closest_corner(point_t *bb, float x, float y)
static void process_button_clicked_callback(GtkButton *button, gpointer user_data)
static box_t get_sample_box(chart_t *chart, box_t *outer_box, float shrink)
static point_t map_point_to_view(image_t *image, point_t p)
static void export_raw(dt_lut_t *self, char *filename, char *name, char *description)
static void reference_mode_changed_callback(GtkComboBox *widget, gpointer user_data)
static void reset_bb(image_t *image)
static int parse_csv(dt_lut_t *self, const char *filename, double **target_L_ptr, double **target_a_ptr, double **target_b_ptr, double **source_Lab_ptr, int *num_gray, char **name, char **description)
static void print_patches(dt_lut_t *self, FILE *fd, GList *patch_names)
static void get_pixel_region(const image_t *const image, const point_t *const corners, int *x_start, int *y_start, int *x_end, int *y_end)
static void collect_reference_patches(dt_lut_t *self)
static const point_t bb_ref[]
static int main_csv(dt_lut_t *self, int argc, char *argv[])
static char * encode_colorchecker(int num, const double *point, const double **target, int *permutation)
static void map_mouse_to_0_1(GtkWidget *widget, GdkEventMotion *event, image_t *image, float *x, float *y)
static void free_image(image_t *image)
static gboolean open_cht(dt_lut_t *self, const char *filename)
static void get_corners(const float *homography, box_t *box, point_t *corners)
static void export_style(dt_lut_t *self, const char *filename, const char *name, const char *description, gboolean include_basecurve, gboolean include_colorchecker, gboolean include_colorin, gboolean include_tonecurve)
static void collect_reference_patches_foreach(gpointer key, gpointer value, gpointer user_data)
static void get_xyz_sample_from_image(const image_t *const image, float shrink, box_t *box, float *xyz)
static gboolean draw_image_callback(GtkWidget *widget, cairo_t *cr, gpointer user_data)
static void export_button_clicked_callback(GtkButton *button, gpointer user_data)
static void show_usage(const char *exe)
static void export_raw_button_clicked_callback(GtkButton *button, gpointer user_data)
static gboolean open_it8(dt_lut_t *self, const char *filename)
static char * get_export_filename(dt_lut_t *self, const char *extension, char **name, char **description, gboolean *basecurve, gboolean *colorchecker, gboolean *colorin, gboolean *tonecurve)
static gboolean open_image(image_t *image, const char *filename)
static void init_table(dt_lut_t *self)
static void source_image_changed_callback(GtkFileChooserButton *widget, gpointer user_data)
static void size_allocate_callback(GtkWidget *widget, GdkRectangle *allocation, gpointer user_data)
static void print_xml_plugin(FILE *fd, int num, int op_version, const char *operation, const char *op_params, gboolean enabled)
static void add_hdr_patches(int *N, double **target_L, double **target_a, double **target_b, double **colorchecker_Lab)
static void process_data(dt_lut_t *self, double *target_L, double *target_a, double *target_b, double *colorchecker_Lab, int N, int sparsity)
static GtkWidget * create_notebook_page_reference(dt_lut_t *self)
static gboolean motion_notify_callback_reference(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
static gboolean open_reference_image(dt_lut_t *self, const char *filename)
const double thrs
static void add_column(GtkTreeView *treeview, const char *title, int column_id, int sort_column)
static void collect_source_patches(dt_lut_t *self)
static void update_table(dt_lut_t *self)
static GtkWidget * create_notebook_page_process(dt_lut_t *self)
#define SWAP(a, b)
static void collect_source_patches_foreach(gpointer key, gpointer value, gpointer user_data)
static int compare_L_source(const void *x_, const void *y_)
static void ref_image_changed_callback(GtkFileChooserButton *widget, gpointer user_data)
static void cht_changed_callback(GtkFileChooserButton *widget, gpointer user_data)
static void image_lab_to_xyz(float *image, const int width, const int height)
static char * encode_tonecurve(const tonecurve_t *c)
static void update_corner(image_t *image, int which, float *x, float *y)
#define MAX_PATCHES
static GtkWidget * create_table(dt_lut_t *self)
static GtkWidget * create_notebook(dt_lut_t *self)
static void it8_changed_callback(GtkFileChooserButton *widget, gpointer user_data)
static GtkWidget * create_notebook_page_source(dt_lut_t *self)
static void map_boundingbox_to_view(image_t *image, point_t *bb)
@ COLUMN_DE_1976_FLOAT
@ COLUMN_NAME
@ COLUMN_LAB_IN
@ COLUMN_RGB_IN
@ COLUMN_DE_2000_FLOAT
@ COLUMN_DE_1976
@ NUM_COLUMNS
@ COLUMN_DE_2000
@ COLUMN_LAB_REF
static gboolean open_source_image(dt_lut_t *self, const char *filename)
static void shrink_changed_callback(GtkRange *range, gpointer user_data)
static void get_boundingbox(const image_t *const image, point_t *bb)
static gboolean motion_notify_callback_source(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
float * read_pfm(const char *filename, int *wd, int *ht)
double tonecurve_apply(const tonecurve_t *c, const double L)
double tonecurve_unapply(const tonecurve_t *c, const double L)
void tonecurve_delete(tonecurve_t *c)
void tonecurve_create(tonecurve_t *c, double *Lin, double *Lout, const int32_t num)
const char ** description(struct dt_iop_module_t *self)
Definition ashift.c:166
static __DT_CLONE_TARGETS__ void homography(float *homograph, const float angle, const float shift_v, const float shift_h, const float shear, const float f_length_kb, const float orthocorr, const float aspect, const int width, const int height, dt_iop_ashift_homodir_t dir)
Definition ashift.c:761
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
const char * extension(dt_imageio_module_data_t *data)
Definition avif.c:651
chart_t * parse_cht(const char *filename)
Definition colorchart.c:158
void free_chart(chart_t *chart)
Definition colorchart.c:53
int parse_it8(const char *filename, chart_t *chart)
Definition colorchart.c:528
void checker_set_color(box_t *box, dt_colorspaces_color_profile_type_t color_space, float c0, float c1, float c2)
Definition colorchart.c:116
static const float x
const float l2
const float f
const float *const const float coeff[3]
const float l1
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
static dt_aligned_pixel_t rgb
dt_Lab_to_XYZ(Lab, XYZ)
static dt_aligned_pixel_t XYZ
static dt_aligned_pixel_t Lab
dt_XYZ_to_Lab(XYZ, Lab)
float dt_colorspaces_deltaE_1976(dt_aligned_pixel_t Lab0, dt_aligned_pixel_t Lab1)
Definition deltaE.c:36
float dt_colorspaces_deltaE_2000(dt_aligned_pixel_t Lab0, dt_aligned_pixel_t Lab1)
Definition deltaE.c:48
void draw_image(cairo_t *cr, image_t *image)
Definition dtcairo.c:87
void draw_color_boxes_outline(cairo_t *cr, const float *homography, chart_t *chart)
Definition dtcairo.c:129
void draw_no_image(cairo_t *cr, GtkWidget *widget)
Definition dtcairo.c:28
void center_image(cairo_t *cr, image_t *image)
Definition dtcairo.c:82
void stroke_boxes(cairo_t *cr, float line_width)
Definition dtcairo.c:162
void clear_background(cairo_t *cr)
Definition dtcairo.c:76
void draw_color_boxes_inside(cairo_t *cr, const float *homography, chart_t *chart, float shrink, float line_width, gboolean colored)
Definition dtcairo.c:134
cairo_surface_t * cairo_surface_create_from_xyz_data(const float *const image, const int width, const int height)
Definition dtcairo.c:190
void draw_f_boxes(cairo_t *cr, const float *homography, chart_t *chart)
Definition dtcairo.c:98
void draw_boundingbox(cairo_t *cr, point_t *bb)
Definition dtcairo.c:93
void draw_d_boxes(cairo_t *cr, const float *homography, chart_t *chart)
Definition dtcairo.c:124
void set_offset_and_scale(image_t *image, float width, float height)
Definition dtcairo.c:173
char * dt_exif_xmp_encode_internal(const unsigned char *input, const int len, int *output_len, gboolean do_compress)
Definition exif.cc:1907
What a photograph says about itself: the EXIF, IPTC and XMP tags a camera and a cataloguer write,...
static int perm[512]
Definition grain.c:174
static int permutation[]
Definition grain.c:160
point_t apply_homography(point_t p, const float *h)
Definition homography.c:69
int get_homography(const point_t *source, const point_t *target, float *h)
Definition homography.c:28
static const float colorchecker_Lab[]
#define MAX_PATCHES
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:65
static void dt_free_gpointer(gpointer ptr)
Definition mem_alloc.h:104
#define dt_free(ptr)
Definition mem_alloc.h:97
char * key
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
dt_mipmap_buffer_dsc_flags flags
Definition mipmap_cache.c:4
#define N
#define __OMP_PARALLEL_FOR__(...)
Definition openmp.h:60
void dt_osx_focus_window()
Definition osx.mm:302
void dt_osx_prepare_environment()
Definition osx.mm:213
const char * name
Definition pdf.h:90
@ DT_COLORSPACE_LAB
@ DT_COLORSPACE_XYZ
int main()
Definition prova.c:47
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
float h
Definition colorchart.h:41
float w
Definition colorchart.h:41
dt_colorspaces_color_profile_type_t color_space
Definition colorchart.h:43
dt_aligned_pixel_t color
Definition colorchart.h:44
dt_aligned_pixel_t rgb
Definition colorchart.h:45
point_t p
Definition colorchart.h:40
float bb_w
Definition colorchart.h:59
GHashTable * patch_sets
Definition colorchart.h:57
float bb_h
Definition colorchart.h:59
float box_shrink
Definition colorchart.h:61
GHashTable * box_table
Definition colorchart.h:53
dt_iop_tonecurve_node_t tonecurve[3][20]
Definition lightroom.c:172
char * colorchecker_encoded
char * tonecurve_encoded
GtkWidget * it8_button
GtkWidget * reference_image_box
char * reference_filename
GtkWidget * result_label
GtkWidget * image_button
GtkWidget * cht_button
GtkTreeModel * model
GtkWidget * source_shrink
GtkWidget * reference_mode
GtkWidget * treeview
GtkWidget * reference_shrink
GtkWidget * window
GtkWidget * process_button
GtkWidget * reference_image_button
GtkWidget * reference_it8_box
GtkWidget * number_patches
image_t reference
GtkWidget * export_raw_button
chart_t * chart
GtkWidget * export_button
image_t source
GHashTable * picked_source_patches
cairo_pattern_t * image
cairo_surface_t * surface
size_t y
Definition censorize.c:78
size_t x
Definition censorize.c:78
GtkWidget * window
GtkWidget * notebook
#define MIN(a, b)
Definition thinplate.c:32
float thinplate_color_pos(float L, float a, float b)
Definition thinplate.c:448
int thinplate_match(const tonecurve_t *curve, int dim, int N, const double *point, const double **target, int S, int *permutation, double **coeff, double *avgerr, double *maxerr)
Definition thinplate.c:157
#define MAX(a, b)
Definition thinplate.c:29
#define DT_GUI_BOX_SPACING