Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
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 "common/darktable.h"
32#include "chart/dtcairo.h"
33#include "chart/colorchart.h"
34#include "chart/common.h"
35#include "chart/deltaE.h"
36#include "chart/pfm.h"
37#include "chart/thinplate.h"
38#include "chart/tonecurve.h"
39#include "common/exif.h"
40
41#include <gtk/gtk.h>
42#include <stdio.h>
43#include <stdlib.h>
44#include <string.h>
45
46#ifdef __APPLE__
47#include "osx/osx.h"
48#endif
49
50#ifdef _WIN32
51#include "win/main_wrapper.h"
52#endif
53
54const double thrs = 200.0;
55
56static const point_t bb_ref[] = {{.x=.0, .y=.0}, {.x=1., .y=0.}, {.x=1., .y=1.}, {.x=0., .y=1.}};
57
58enum
59{
69};
70
90
91// boring helper functions
92static void init_image(dt_lut_t *self, image_t *image, GCallback motion_cb);
93static void image_lab_to_xyz(float *image, const int width, const int height);
94static void map_boundingbox_to_view(image_t *image, point_t *bb);
96static void get_xyz_sample_from_image(const image_t *const image, float shrink, box_t *box, float *xyz);
97static void add_column(GtkTreeView *treeview, const char *title, int column_id, int sort_column);
98static void update_table(dt_lut_t *self);
99static void init_table(dt_lut_t *self);
100static void get_Lab_from_box(box_t *box, float *Lab);
101static void collect_source_patches(dt_lut_t *self);
102static void collect_source_patches_foreach(gpointer key, gpointer value, gpointer user_data);
103static void collect_reference_patches(dt_lut_t *self);
104static void collect_reference_patches_foreach(gpointer key, gpointer value, gpointer user_data);
105static box_t *find_patch(GHashTable *table, gpointer key);
106static void get_boundingbox(const image_t *const image, point_t *bb);
107static box_t get_sample_box(chart_t *chart, box_t *outer_box, float shrink);
108static void get_corners(const float *homography, box_t *box, point_t *corners);
109static void get_pixel_region(const image_t *const image, const point_t *const corners, int *x_start, int *y_start,
110 int *x_end, int *y_end);
111static void reset_bb(image_t *image);
112static void free_image(image_t *image);
113static gboolean handle_motion(GtkWidget *widget, GdkEventMotion *event, dt_lut_t *self, image_t *image);
114static int find_closest_corner(point_t *bb, float x, float y);
115static void map_mouse_to_0_1(GtkWidget *widget, GdkEventMotion *event, image_t *image, float *x, float *y);
116static void update_corner(image_t *image, int which, float *x, float *y);
117static gboolean open_source_image(dt_lut_t *self, const char *filename);
118static gboolean open_reference_image(dt_lut_t *self, const char *filename);
119static gboolean open_image(image_t *image, const char *filename);
120static gboolean open_cht(dt_lut_t *self, const char *filename);
121static gboolean open_it8(dt_lut_t *self, const char *filename);
122
123static void size_allocate_callback(GtkWidget *widget, GdkRectangle *allocation, gpointer user_data)
124{
125 image_t *image = (image_t *)user_data;
126 set_offset_and_scale(image, allocation->width, allocation->height);
127}
128
129static gboolean draw_image_callback(GtkWidget *widget, cairo_t *cr, gpointer user_data)
130{
131 image_t *image = (image_t *)user_data;
132 chart_t *chart = *(image->chart);
133
135
136 // done when no image is loaded
137 if(IS_NULL_PTR(image->image))
138 {
139 draw_no_image(cr, widget);
140 return FALSE;
141 }
142
143 center_image(cr, image);
144
145 draw_image(cr, image);
146
147 // done when no chart was loaded
148 if(IS_NULL_PTR(chart)) return FALSE;
149
150 // draw overlay
151 point_t bb[4];
152 float homography[9];
153 map_boundingbox_to_view(image, bb);
154 // calculating the homography takes hardly any time, so we do it here instead of the move handler.
155 // the benefits are that the window size is taken into account and image->bb can't disagree with the cached homography
157
158 draw_boundingbox(cr, bb);
159 draw_f_boxes(cr, homography, chart);
160 draw_d_boxes(cr, homography, chart);
162
163 stroke_boxes(cr, 1.0);
164
165 draw_color_boxes_inside(cr, homography, chart, image->shrink, 2.0, image->draw_colored);
166
167 return FALSE;
168}
169
171{
172 for(int i = 0; i < 4; i++) bb[i] = map_point_to_view(image, image->bb[i]);
173}
174
176{
177 point_t result;
178
179 result.x = p.x * image->width / image->scale;
180 result.y = p.y * image->height / image->scale;
181
182 return result;
183}
184
185static gboolean motion_notify_callback_source(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
186{
187 dt_lut_t *self = (dt_lut_t *)user_data;
188 const gboolean res = handle_motion(widget, event, self, &self->source);
189 if(res)
190 {
192 update_table(self);
193 }
194 return res;
195}
196
197static gboolean motion_notify_callback_reference(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
198{
199 dt_lut_t *self = (dt_lut_t *)user_data;
200 const gboolean res = handle_motion(widget, event, self, &self->reference);
201 if(res)
202 {
204 update_table(self);
205 }
206 return res;
207}
208
209static gboolean handle_motion(GtkWidget *widget, GdkEventMotion *event, dt_lut_t *self, image_t *image)
210{
211 if(!(event->state & GDK_BUTTON1_MASK) || IS_NULL_PTR(image->image)) return FALSE;
212
213 // mouse -> 0..1
214 float x, y;
215 map_mouse_to_0_1(widget, event, image, &x, &y);
216
217 // dragging the crosses is hard when they are not in a cornerof the bb but sprinkled over the chart
218 int closest_corner = find_closest_corner(image->bb, x, y);
219
220 update_corner(image, closest_corner, &x, &y);
221
222 // check if the shape would turn concave by testing if the new location
223 // is inside the triangle formed by the other three. google barycentric coordinates to see how it's done.
224 const int prev_corner = (closest_corner + 3) % 4;
225 const int opposite_corner = (closest_corner + 2) % 4;
226 const int next_corner = (closest_corner + 1) % 4;
227
228 const float x1 = image->bb[prev_corner].x;
229 const float y1 = image->bb[prev_corner].y;
230 const float x2 = image->bb[next_corner].x;
231 const float y2 = image->bb[next_corner].y;
232 const float x3 = image->bb[opposite_corner].x;
233 const float y3 = image->bb[opposite_corner].y;
234
235 const float denom = (y2 - y3) * (x1 - x3) + (x3 - x2) * (y1 - y3);
236 const float l1 = ((y2 - y3) * (x - x3) + (x3 - x2) * (y - y3)) / denom;
237 const float l2 = ((y3 - y1) * (x - x3) + (x1 - x3) * (y - y3)) / denom;
238 const float l3 = 1.0 - l1 - l2;
239
240 if(l1 < 0.0 || l2 < 0.0 || l3 < 0.0)
241 {
242 image->bb[closest_corner].x = x;
243 image->bb[closest_corner].y = y;
244 }
245
246 gtk_widget_queue_draw(widget);
247
248 return TRUE;
249}
250
251static int find_closest_corner(point_t *bb, float x, float y)
252{
253 int closest_corner = 0;
254 float distance = G_MAXFLOAT;
255 for(int i = 0; i < 4; i++)
256 {
257 const float d_x = (x - bb[i].x);
258 const float d_y = (y - bb[i].y);
259 float d = d_x * d_x + d_y * d_y;
260 if(d < distance)
261 {
262 distance = d;
263 closest_corner = i;
264 }
265 }
266 // TODO: only react when the distance < some threshold?
267 return closest_corner;
268}
269
270static void map_mouse_to_0_1(GtkWidget *widget, GdkEventMotion *event, image_t *image, float *x, float *y)
271{
272 guint width = gtk_widget_get_allocated_width(widget);
273 guint height = gtk_widget_get_allocated_height(widget);
274
275 *x = (event->x - image->offset_x) / (width - 2.0 * image->offset_x);
276 *y = (event->y - image->offset_y) / (height - 2.0 * image->offset_y);
277}
278
279static void update_corner(image_t *image, int which, float *x, float *y)
280{
281 // keep the corners in clockwise order
282 if(which == TOP_LEFT)
283 {
284 *x = CLAMP(*x, 0.0, image->bb[TOP_RIGHT].x);
285 *y = CLAMP(*y, 0.0, image->bb[BOTTOM_LEFT].y);
286 }
287 else if(which == TOP_RIGHT)
288 {
289 *x = CLAMP(*x, image->bb[TOP_LEFT].x, 1.0);
290 *y = CLAMP(*y, 0.0, image->bb[BOTTOM_RIGHT].y);
291 }
292 else if(which == BOTTOM_RIGHT)
293 {
294 *x = CLAMP(*x, image->bb[BOTTOM_LEFT].x, 1.0);
295 *y = CLAMP(*y, image->bb[TOP_RIGHT].y, 1.0);
296 }
297 else if(which == BOTTOM_LEFT)
298 {
299 *x = CLAMP(*x, 0.0, image->bb[BOTTOM_RIGHT].x);
300 *y = CLAMP(*y, image->bb[TOP_LEFT].y, 1.0);
301 }
302}
303
304static void source_image_changed_callback(GtkFileChooserButton *widget, gpointer user_data)
305{
306 dt_lut_t *self = (dt_lut_t *)user_data;
307 char *new_filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(widget));
308 open_source_image(self, new_filename);
309 dt_free(new_filename);
310}
311
312static gboolean open_source_image(dt_lut_t *self, const char *filename)
313{
314 gboolean res = open_image(&self->source, filename);
315 gtk_widget_set_sensitive(self->cht_button, res);
316 if(IS_NULL_PTR(res)) gtk_file_chooser_unselect_all(GTK_FILE_CHOOSER(self->image_button));
317 gtk_widget_queue_draw(self->source.drawing_area);
318
319 return res;
320}
321
322static void ref_image_changed_callback(GtkFileChooserButton *widget, gpointer user_data)
323{
324 dt_lut_t *self = (dt_lut_t *)user_data;
325 char *new_filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(widget));
326 open_reference_image(self, new_filename);
327 dt_free(new_filename);
328}
329
330static char *get_filename_base(const char *filename)
331{
332 char *last_slash = g_strrstr(filename, "/");
333 if(last_slash)
334 return g_strdup(last_slash + 1);
335 else
336 return g_strdup(filename);
337}
338
339static gboolean open_reference_image(dt_lut_t *self, const char *filename)
340{
341 const gboolean initial_loading = (IS_NULL_PTR(self->reference.xyz));
342 const gboolean res = open_image(&self->reference, filename);
343 gtk_widget_set_sensitive(self->process_button, res);
344 gtk_widget_set_sensitive(self->export_button, FALSE);
345 gtk_widget_set_sensitive(self->export_raw_button, FALSE);
346 if(IS_NULL_PTR(res))
347 gtk_file_chooser_unselect_all(GTK_FILE_CHOOSER(self->reference_image_button));
348 else
349 {
350 if(initial_loading)
351 {
352 // copy over the bounding box from the source image.
353 // when matching raw to jpeg this is in general what the user wants
354 memcpy(self->reference.bb, self->source.bb, sizeof(self->reference.bb));
355 }
357 update_table(self);
359 self->reference_filename = get_filename_base(filename);
360 }
361 gtk_widget_queue_draw(self->reference.drawing_area);
362 return res;
363}
364
365static gboolean open_image(image_t *image, const char *filename)
366{
367 int width, height;
368
369 free_image(image);
370
371 if(IS_NULL_PTR(filename)) return FALSE;
372
373 float *pfm = read_pfm(filename, &width, &height);
374
375 if(IS_NULL_PTR(pfm))
376 {
377 fprintf(stderr, "error reading image `%s'\n", filename);
378 return FALSE;
379 }
380
381 // we want the image in XYZ to average patches
383
384 cairo_surface_t *image_surface = cairo_surface_create_from_xyz_data(pfm, width, height);
385
386 if(cairo_surface_status(image_surface) != CAIRO_STATUS_SUCCESS)
387 {
388 fprintf(stderr, "error creating cairo surface from `%s'\n", filename);
389 cairo_surface_destroy(image_surface);
390 dt_free(pfm);
391 return FALSE;
392 }
393 image->surface = image_surface;
394 image->image = cairo_pattern_create_for_surface(image_surface);
395 image->width = width;
396 image->height = height;
397 image->xyz = pfm;
398
399 // at init time this can fail once
400 if(GTK_IS_WIDGET(image->drawing_area))
401 {
402 guint widget_width = gtk_widget_get_allocated_width(image->drawing_area);
403 guint widget_height = gtk_widget_get_allocated_height(image->drawing_area);
404 set_offset_and_scale(image, widget_width, widget_height);
405 }
406
407 return TRUE;
408}
409
410static void cht_changed_callback(GtkFileChooserButton *widget, gpointer user_data)
411{
412 dt_lut_t *self = (dt_lut_t *)user_data;
413 char *new_filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(widget));
414 open_cht(self, new_filename);
415 dt_free(new_filename);
416}
417
418static gboolean open_cht(dt_lut_t *self, const char *filename)
419{
420 if(self->chart) free_chart(self->chart);
421 const gboolean res = ((self->chart = parse_cht(filename)) != NULL);
422
423 reset_bb(&self->source);
424 reset_bb(&self->reference);
425
426 g_hash_table_remove_all(self->picked_source_patches);
427 if(res) collect_source_patches(self);
428 init_table(self);
429
430 // reset it8/reference entry
431 if(IS_NULL_PTR(res)) gtk_file_chooser_unselect_all(GTK_FILE_CHOOSER(self->cht_button));
432 gtk_file_chooser_unselect_all(GTK_FILE_CHOOSER(self->it8_button));
433 gtk_file_chooser_unselect_all(GTK_FILE_CHOOSER(self->reference_image_button));
434
435 if(res)
436 {
437 self->source.shrink = self->chart->box_shrink;
438 self->reference.shrink = self->chart->box_shrink;
439 gtk_range_set_value(GTK_RANGE(self->source_shrink), 1.0);
440 gtk_range_set_value(GTK_RANGE(self->reference_shrink), 1.0);
441 }
442
443 gtk_widget_set_sensitive(self->it8_button, res);
444 gtk_widget_set_sensitive(self->reference_image_button, res);
445 gtk_widget_set_sensitive(self->process_button, res);
446 gtk_widget_set_sensitive(self->export_button, FALSE);
447 gtk_widget_set_sensitive(self->export_raw_button, FALSE);
448
449 gtk_widget_queue_draw(self->source.drawing_area);
450 gtk_widget_queue_draw(self->reference.drawing_area);
451
452 return res;
453}
454
455static void reference_mode_changed_callback(GtkComboBox *widget, gpointer user_data)
456{
457 dt_lut_t *self = (dt_lut_t *)user_data;
458 const int selected = gtk_combo_box_get_active(widget);
459 if(selected == 0)
460 {
461 // it8
462 gtk_widget_set_no_show_all(self->reference_it8_box, FALSE);
463 gtk_widget_show_all(self->reference_it8_box);
464 gtk_widget_hide(self->reference_image_box);
465 gtk_widget_hide(self->reference.drawing_area);
466 g_signal_emit_by_name(self->it8_button, "file-set", user_data);
467 }
468 else
469 {
470 // image
471 gtk_widget_set_no_show_all(self->reference_image_box, FALSE);
472 gtk_widget_set_no_show_all(self->reference.drawing_area, FALSE);
473 gtk_widget_show_all(self->reference_image_box);
474 gtk_widget_show_all(self->reference.drawing_area);
475 gtk_widget_hide(self->reference_it8_box);
476 g_signal_emit_by_name(self->reference_image_button, "file-set", user_data);
477 }
478}
479
480static void it8_changed_callback(GtkFileChooserButton *widget, gpointer user_data)
481{
482 dt_lut_t *self = (dt_lut_t *)user_data;
483 char *new_filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(widget));
484 open_it8(self, new_filename);
485 dt_free(new_filename);
486}
487
488static gboolean open_it8(dt_lut_t *self, const char *filename)
489{
490 if(IS_NULL_PTR(self->chart) || !filename) return FALSE;
491 const gboolean res = parse_it8(filename, self->chart);
493 update_table(self);
494
495 gtk_widget_set_sensitive(self->process_button, FALSE);
496 gtk_widget_set_sensitive(self->export_button, FALSE);
497 gtk_widget_set_sensitive(self->export_raw_button, FALSE);
498 if(IS_NULL_PTR(res))
499 gtk_file_chooser_unselect_all(GTK_FILE_CHOOSER(self->it8_button));
500 else
501 {
503 self->reference_filename = get_filename_base(filename);
504 gtk_widget_set_sensitive(self->process_button, TRUE);
505 }
506 gtk_widget_queue_draw(self->source.drawing_area);
507
508 return res;
509}
510
511static char *get_export_filename(dt_lut_t *self, const char *extension, char **name, char **description,
512 gboolean *basecurve, gboolean *colorchecker, gboolean *colorin, gboolean *tonecurve)
513{
514 GtkWidget *name_entry = NULL, *description_entry = NULL;
515 GtkWidget *dialog
516 = gtk_file_chooser_dialog_new("save file", GTK_WINDOW(self->window), GTK_FILE_CHOOSER_ACTION_SAVE,
517 _("_cancel"), GTK_RESPONSE_CANCEL, _("_save"), GTK_RESPONSE_ACCEPT, NULL);
518
519 gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);
520
521 char *reference_filename = g_strdup(self->reference_filename);
522 char *last_dot = g_strrstr(reference_filename, ".");
523 if(last_dot)
524 {
525 *last_dot = '\0';
526 char *new_filename = g_strconcat(reference_filename, extension, NULL);
527 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), new_filename);
528 dt_free(new_filename);
529 }
530 dt_free(reference_filename);
531
532 GtkWidget *grid = gtk_grid_new();
533 gtk_grid_set_row_spacing(GTK_GRID(grid), DT_GUI_BOX_SPACING);
534 gtk_grid_set_column_spacing(GTK_GRID(grid), DT_GUI_BOX_SPACING);
535 gtk_grid_set_row_homogeneous(GTK_GRID(grid), TRUE);
536
537 *name = g_strdup(self->reference_filename);
538 *description = g_strdup_printf("fitted LUT style from %s", self->reference_filename);
539 char *name_dot = g_strrstr(*name, ".");
540 if(name_dot) *name_dot = '\0';
541
542 name_entry = gtk_entry_new();
543 description_entry = gtk_entry_new();
544
545 gtk_entry_set_text(GTK_ENTRY(name_entry), *name);
546 gtk_entry_set_text(GTK_ENTRY(description_entry), *description);
547 dt_free(*name);
549 *name = NULL;
550 *description = NULL;
551
552 GtkWidget *label;
553 label = gtk_label_new("style name");
554 gtk_widget_set_halign(label, GTK_ALIGN_START);
555 gtk_grid_attach(GTK_GRID(grid), label, 0, 0, 1, 1);
556 gtk_grid_attach(GTK_GRID(grid), name_entry, 1, 0, 1, 1);
557 label = gtk_label_new("style description");
558 gtk_widget_set_halign(label, GTK_ALIGN_START);
559 gtk_grid_attach(GTK_GRID(grid), label, 0, 1, 1, 1);
560 gtk_grid_attach(GTK_GRID(grid), description_entry, 1, 1, 1, 1);
561
562 // allow the user to decide what modules to include in the style
563 label = gtk_label_new("modules included in the style:");
564 gtk_widget_set_halign(label, GTK_ALIGN_START);
565 g_object_set(label, "margin-left", 50, NULL);
566
567 GtkWidget *cb_basecurve = gtk_check_button_new_with_label("base curve");
568 GtkWidget *cb_colorchecker = gtk_check_button_new_with_label("color look up table");
569 GtkWidget *cb_colorin = gtk_check_button_new_with_label("input color profile");
570 GtkWidget *cb_tonecurve = gtk_check_button_new_with_label("tone curve");
571
572 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cb_basecurve), TRUE);
573 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cb_colorchecker), TRUE);
574 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cb_colorin), TRUE);
575 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cb_tonecurve), TRUE);
576
577 if(basecurve)
578 {
579 gtk_grid_attach(GTK_GRID(grid), label, 2, 0, 1, 1);
580 gtk_grid_attach_next_to(GTK_GRID(grid), cb_basecurve, label, GTK_POS_RIGHT, 1, 1);
581 gtk_grid_attach_next_to(GTK_GRID(grid), cb_colorchecker, cb_basecurve, GTK_POS_BOTTOM, 1, 1);
582 gtk_grid_attach_next_to(GTK_GRID(grid), cb_colorin, cb_colorchecker, GTK_POS_BOTTOM, 1, 1);
583 gtk_grid_attach_next_to(GTK_GRID(grid), cb_tonecurve, cb_colorin, GTK_POS_BOTTOM, 1, 1);
584 }
585
586 gtk_widget_show_all(grid);
587
588 gtk_file_chooser_set_extra_widget(GTK_FILE_CHOOSER(dialog), grid);
589
590 char *filename = NULL;
591 int res = gtk_dialog_run(GTK_DIALOG(dialog));
592 if(res == GTK_RESPONSE_ACCEPT)
593 {
594 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
595 *name = g_strdup(gtk_entry_get_text(GTK_ENTRY(name_entry)));
596 *description = g_strdup(gtk_entry_get_text(GTK_ENTRY(description_entry)));
597 if(basecurve)
598 {
599 // either request all of them or none ...
600 *basecurve = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cb_basecurve));
601 *colorchecker = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cb_colorchecker));
602 *colorin = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cb_colorin));
603 *tonecurve = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cb_tonecurve));
604 }
605 }
606 gtk_widget_destroy(dialog);
607
608 return filename;
609}
610
611static void print_patches(dt_lut_t *self, FILE *fd, GList *patch_names)
612{
613 for(GList *iter = patch_names; iter; iter = g_list_next(iter))
614 {
615 char s[64];
616 char *key = (char *)iter->data;
617 box_t *source_patch = (box_t *)g_hash_table_lookup(self->picked_source_patches, key);
618 box_t *reference_patch = (box_t *)g_hash_table_lookup(self->chart->box_table, key);
619 if(IS_NULL_PTR(source_patch) || IS_NULL_PTR(reference_patch))
620 {
621 fprintf(stderr, "error: missing patch `%s'\n", key);
622 continue;
623 }
624
625 dt_aligned_pixel_t source_Lab = { 0.0 }, reference_Lab = { 0.0 };
626 get_Lab_from_box(source_patch, source_Lab);
627 get_Lab_from_box(reference_patch, reference_Lab);
628
629 fprintf(fd, "%s", key);
630 for(int i = 0; i < 3; i++) fprintf(fd, ";%s", g_ascii_dtostr(s, sizeof(s), source_Lab[i]));
631 for(int i = 0; i < 3; i++) fprintf(fd, ";%s", g_ascii_dtostr(s, sizeof(s), reference_Lab[i]));
632 fprintf(fd, "\n");
633 }
634}
635
636static void print_xml_plugin(FILE *fd, int num, int op_version, const char *operation, const char *op_params,
637 gboolean enabled)
638{
639 fprintf(fd, " <plugin>\n");
640 fprintf(fd, " <num>%d</num>\n", num);
641 fprintf(fd, " <module>%d</module>\n", op_version);
642 fprintf(fd, " <operation>%s</operation>\n", operation);
643 fprintf(fd, " <op_params>%s</op_params>\n", op_params);
644 fprintf(fd, " <enabled>%d</enabled>\n", enabled);
645 fprintf(fd, " <blendop_params>gz12eJxjYGBgkGAAgRNODESDBnsIHll8ANNSGQM=</blendop_params>\n");
646 fprintf(fd, " <blendop_version>7</blendop_version>\n");
647 fprintf(fd, " <multi_priority>0</multi_priority>\n");
648 fprintf(fd, " <multi_name></multi_name>\n");
649 fprintf(fd, " </plugin>\n");
650}
651
652static void export_style(dt_lut_t *self, const char *filename, const char *name, const char *description,
653 gboolean include_basecurve, gboolean include_colorchecker, gboolean include_colorin,
654 gboolean include_tonecurve)
655{
656 int num = 0;
657
658 FILE *fd = g_fopen(filename, "w");
659 if(IS_NULL_PTR(fd)) return;
660
661 fprintf(fd, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
662 fprintf(fd, "<darktable_style version=\"1.0\">\n");
663 fprintf(fd, "<info>\n");
664 fprintf(fd, " <name>%s</name>\n", name);
665 fprintf(fd, " <description>%s</description>\n", description);
666 fprintf(fd, "</info>\n");
667 fprintf(fd, "<style>\n");
668
669 // 0: disable basecurve
670 if(include_basecurve)
671 {
672 print_xml_plugin(fd, num++, 2, "basecurve",
673 "gz09eJxjYIAAM6vnNnqyn22E9n235b6aa3cy6rVdRaK9/Y970fYf95bbMzA0QPEoGEqADYnNhMQGAO0WEJo=", FALSE);
674 }
675 // 1: set colorin to standard matrix
676 if(include_colorin)
677 {
678 // print_xml_plugin(fd, num++, 4, "colorin", "gz10eJzjZqA/AAAFcAAM", TRUE); // no gamut clipping
679 // and enable gamut clipping. the it8 knows nothing about colours outside
680 // rec2020 (only reflectances, no neon lights for instance)
681 print_xml_plugin(fd, num++, 4, "colorin", "gz09eJzjZqAfYIHSAAWQABA=", TRUE); // gamut clipping to rec2020
682 }
683 // 2: add tonecurve
684 if(include_tonecurve)
685 {
686 print_xml_plugin(fd, num++, 4, "tonecurve", self->tonecurve_encoded, TRUE);
687 }
688 // 3: add lut
689 if(include_colorchecker)
690 {
691 print_xml_plugin(fd, num++, 2, "colorchecker", self->colorchecker_encoded, TRUE);
692 }
693
694 fprintf(fd, "</style>\n");
695 fprintf(fd, "</darktable_style>\n");
696
697 fclose(fd);
698}
699
700static void export_raw(dt_lut_t *self, char *filename, char *name, char *description)
701{
702 GHashTableIter table_iter;
703 gpointer key, value;
704
705 FILE *fd = g_fopen(filename, "w");
706 if(IS_NULL_PTR(fd)) return;
707
708 GList *patch_names = NULL;
709
710 fprintf(fd, "name;%s\n", name);
711 fprintf(fd, "description;%s\n", description);
712 fprintf(fd, "num_gray; 0\n");
713
714 fprintf(fd, "patch;L_source;a_source;b_source;L_reference;a_reference;b_reference\n");
715 // iterate over all known patches in the chart
716 g_hash_table_iter_init(&table_iter, self->chart->patch_sets);
717 while(g_hash_table_iter_next(&table_iter, &key, &value))
718 {
719 patch_names = (GList *)value;
720 print_patches(self, fd, patch_names);
721 }
722 fclose(fd);
723}
724
725static void export_raw_button_clicked_callback(GtkButton *button, gpointer user_data)
726{
727 dt_lut_t *self = (dt_lut_t *)user_data;
728 if(IS_NULL_PTR(self->chart)) return;
729
730 char *name = NULL, *description = NULL;
731 char *filename = get_export_filename(self, ".csv", &name, &description, NULL, NULL, NULL, NULL);
732 if(filename) export_raw(self, filename, name, description);
733 dt_free(name);
735 dt_free(filename);
736}
737
738static void export_button_clicked_callback(GtkButton *button, gpointer user_data)
739{
740 dt_lut_t *self = (dt_lut_t *)user_data;
741 if(!self->tonecurve_encoded || IS_NULL_PTR(self->colorchecker_encoded)) return;
742
743 char *name = NULL, *description = NULL;
744 gboolean include_basecurve, include_colorchecker, include_colorin, include_tonecurve;
745 char *filename = get_export_filename(self, ".dtstyle", &name, &description,
746 &include_basecurve, &include_colorchecker, &include_colorin, &include_tonecurve);
747 if(filename) export_style(self, filename, name, description,
748 include_basecurve, include_colorchecker, include_colorin, include_tonecurve);
749 dt_free(name);
751 dt_free(filename);
752}
753
754static void add_patches_to_array(dt_lut_t *self, GList *patch_names, int *N, int *i, double *target_L,
755 double *target_a, double *target_b, double *colorchecker_Lab)
756{
757
758 for(GList *iter = patch_names; iter; iter = g_list_next(iter))
759 {
760 const char *key = (char *)iter->data;
761 box_t *source_patch = (box_t *)g_hash_table_lookup(self->picked_source_patches, key);
762 box_t *reference_patch = (box_t *)g_hash_table_lookup(self->chart->box_table, key);
763 if(IS_NULL_PTR(source_patch) || IS_NULL_PTR(reference_patch))
764 {
765 fprintf(stderr, "error: missing patch `%s'\n", key);
766 continue;
767 }
768
769 dt_aligned_pixel_t source_Lab = { 0.0 }, reference_Lab = { 0.0 };
770 get_Lab_from_box(source_patch, source_Lab);
771 get_Lab_from_box(reference_patch, reference_Lab);
772
773 for(int j = 0; j < 3; j++) colorchecker_Lab[3 * (*i) + j] = source_Lab[j];
774 target_L[*i] = reference_Lab[0];
775 target_a[*i] = reference_Lab[1];
776 target_b[*i] = reference_Lab[2];
777
778 const double deltaE = dt_colorspaces_deltaE_1976(source_Lab, reference_Lab);
779 if(deltaE > thrs)
780 {
781 fprintf(stderr, "warning: ignoring patch %s with large difference deltaE %g!\n", key, deltaE);
782 fprintf(stderr, " %g %g %g -- %g %g %g\n", source_Lab[0], source_Lab[1], source_Lab[2],
783 reference_Lab[0], reference_Lab[1], reference_Lab[2]);
784 (*N)--; // ignore this patch.
785 (*i)--;
786 }
787 (*i)++;
788 }
789}
790
791static void add_hdr_patches(int *N, double **target_L, double **target_a, double **target_b,
792 double **colorchecker_Lab)
793{
794 gboolean need_hdr00 = TRUE, need_hdr01 = TRUE;
795 int n_extra_patches = 0;
796 double extra_target_L[2], extra_target_a[2], extra_target_b[2], extra_colorchecker_Lab[2 * 3];
797
798 for(int j = 0; j < *N; j++)
799 {
800 if((*target_L)[j] == 100.0 && (*target_a)[j] == 0.0 && (*target_b)[j] == 0.0 && (*colorchecker_Lab)[j * 3] == 100.0
801 && (*colorchecker_Lab)[j * 3 + 1] == 0.0 && (*colorchecker_Lab)[j * 3 + 2] == 0.0)
802 {
803 need_hdr00 = FALSE;
804 }
805 else if((*target_L)[j] == 200.0 && (*target_a)[j] == 0.0 && (*target_b)[j] == 0.0 && (*colorchecker_Lab)[j * 3] == 200.0
806 && (*colorchecker_Lab)[j * 3 + 1] == 0.0 && (*colorchecker_Lab)[j * 3 + 2] == 0.0)
807 {
808 need_hdr01 = FALSE;
809 }
810 }
811
812 if(need_hdr00)
813 {
814 extra_target_L[n_extra_patches] = 100.0;
815 extra_target_a[n_extra_patches] = 0.0;
816 extra_target_b[n_extra_patches] = 0.0;
817 extra_colorchecker_Lab[n_extra_patches * 3] = 100.0;
818 extra_colorchecker_Lab[n_extra_patches * 3 + 1] = 0.0;
819 extra_colorchecker_Lab[n_extra_patches * 3 + 2] = 0.0;
820 n_extra_patches++;
821 }
822
823 if(need_hdr01)
824 {
825 extra_target_L[n_extra_patches] = 200.0;
826 extra_target_a[n_extra_patches] = 0.0;
827 extra_target_b[n_extra_patches] = 0.0;
828 extra_colorchecker_Lab[n_extra_patches * 3] = 200.0;
829 extra_colorchecker_Lab[n_extra_patches * 3 + 1] = 0.0;
830 extra_colorchecker_Lab[n_extra_patches * 3 + 2] = 0.0;
831 n_extra_patches++;
832 }
833
834 if(n_extra_patches > 0)
835 {
836 *target_L = realloc(*target_L, sizeof(double) * (*N + n_extra_patches + 4));
837 *target_a = realloc(*target_a, sizeof(double) * (*N + n_extra_patches + 4));
838 *target_b = realloc(*target_b, sizeof(double) * (*N + n_extra_patches + 4));
839 *colorchecker_Lab = realloc(*colorchecker_Lab, sizeof(double) * 3 * (*N + n_extra_patches));
840
841 memmove(&(*target_L)[n_extra_patches], *target_L, sizeof(double) * *N);
842 memmove(&(*target_a)[n_extra_patches], *target_a, sizeof(double) * *N);
843 memmove(&(*target_b)[n_extra_patches], *target_b, sizeof(double) * *N);
844 memmove(&(*colorchecker_Lab)[3 * n_extra_patches], *colorchecker_Lab, sizeof(double) * 3 * *N);
845
846 memcpy(*target_L, extra_target_L, sizeof(double) * n_extra_patches);
847 memcpy(*target_a, extra_target_a, sizeof(double) * n_extra_patches);
848 memcpy(*target_b, extra_target_b, sizeof(double) * n_extra_patches);
849 memcpy(*colorchecker_Lab, extra_colorchecker_Lab, sizeof(double) * 3 * n_extra_patches);
850
851 *N += n_extra_patches;
852 }
853}
854
855static char *encode_tonecurve(const tonecurve_t *c)
856{
857 // hardcoded params v4 from tonecurve:
858 typedef struct dt_iop_tonecurve_node_t
859 {
860 float x;
861 float y;
863 typedef struct dt_iop_tonecurve_params_t
864 {
865 dt_iop_tonecurve_node_t tonecurve[3][20]; // three curves (L, a, b) with max number
866 // of nodes
867 int tonecurve_nodes[3];
868 int tonecurve_type[3];
873
875 memset(&params, 0, sizeof(params));
876 params.tonecurve_autoscale_ab = 3; // prophoto rgb
877
878 params.tonecurve_type[0] = 2; // MONOTONE_HERMITE
879 params.tonecurve_nodes[0] = 20;
880 for(int k = 0; k < 20; k++)
881 {
882 const double x = (k / 19.0) * (k / 19.0);
883 params.tonecurve[0][k].x = x;
884 params.tonecurve[0][k].y = tonecurve_apply(c, 100.0 * x) / 100.0;
885 }
886
887 params.tonecurve_type[1] = 2; // MONOTONE_HERMITE
888 params.tonecurve_nodes[1] = 2;
889 params.tonecurve[1][0].x = 0.0f;
890 params.tonecurve[1][0].y = 0.0f;
891 params.tonecurve[1][1].x = 1.0f;
892 params.tonecurve[1][1].y = 1.0f;
893
894 params.tonecurve_type[2] = 2; // MONOTONE_HERMITE
895 params.tonecurve_nodes[2] = 2;
896 params.tonecurve[2][0].x = 0.0f;
897 params.tonecurve[2][0].y = 0.0f;
898 params.tonecurve[2][1].x = 1.0f;
899 params.tonecurve[2][1].y = 1.0f;
900
901 return dt_exif_xmp_encode_internal((uint8_t *)&params, sizeof(params), NULL, FALSE);
902}
903
904static char *encode_colorchecker(int num, const double *point, const double **target, int *permutation)
905{
906// hardcoded v2 of the module
907#define MAX_PATCHES 49
908 typedef struct dt_iop_colorchecker_params_t
909 {
910 float source_L[MAX_PATCHES];
911 float source_a[MAX_PATCHES];
912 float source_b[MAX_PATCHES];
913 float target_L[MAX_PATCHES];
914 float target_a[MAX_PATCHES];
915 float target_b[MAX_PATCHES];
916 int32_t num_patches;
918
920 memset(&params, 0, sizeof(params));
921 num = MIN(MAX_PATCHES, num);
922 // assert(num <= MAX_PATCHES);
923 params.num_patches = num;
924
925 for(int k = 0; k < num; k++)
926 {
927 params.source_L[k] = point[3 * permutation[k]];
928 params.source_a[k] = point[3 * permutation[k] + 1];
929 params.source_b[k] = point[3 * permutation[k] + 2];
930 params.target_L[k] = target[0][permutation[k]];
931 params.target_a[k] = target[1][permutation[k]];
932 params.target_b[k] = target[2][permutation[k]];
933 }
934
935#define SWAP(a, b) \
936 { \
937 const float tmp = (a); \
938 (a) = (b); \
939 (b) = tmp; \
940 }
941 // bubble sort by octant and brightness:
942 for(int k = 0; k < num - 1; k++)
943 for(int j = 0; j < num - k - 1; j++)
944 {
945 if(thinplate_color_pos(params.source_L[j], params.source_a[j], params.source_b[j])
946 < thinplate_color_pos(params.source_L[j + 1], params.source_a[j + 1], params.source_b[j + 1]))
947 {
948 SWAP(params.source_L[j], params.source_L[j + 1]);
949 SWAP(params.source_a[j], params.source_a[j + 1]);
950 SWAP(params.source_b[j], params.source_b[j + 1]);
951 SWAP(params.target_L[j], params.target_L[j + 1]);
952 SWAP(params.target_a[j], params.target_a[j + 1]);
953 SWAP(params.target_b[j], params.target_b[j + 1]);
954 }
955 }
956#undef SWAP
957#undef MAX_PATCHES
958
959 return dt_exif_xmp_encode_internal((uint8_t *)&params, sizeof(params), NULL, FALSE);
960}
961
962static int compare_L_source(const void *x_, const void *y_)
963{
964 const double x = *(const double *)x_;
965 const double y = *(const double *)y_;
966 return x < y ? -1 : (x > y ? +1 : 0);
967}
968
969static void process_data(dt_lut_t *self, double *target_L, double *target_a, double *target_b,
970 double *colorchecker_Lab, int N, int sparsity)
971{
972 // get all the memory, just in case:
973 double *cx = malloc(sizeof(double)*N);
974 double *cy = malloc(sizeof(double)*N);
975 double *grays = malloc(sizeof(double) * 6 * N);
976 tonecurve_t tonecurve;
977 int num_tonecurve = 0;
978 {
979 int cnt = 0;
980
981 for(int i=0;i<N;i++)
982 {
983 const double sat_in =
986 const double sat_out =
987 target_a[i] * target_a[i] +
988 target_b[i] * target_b[i];
989 // we'll allow some artistic tint or one due to illuminants (note square scale)
990 if(sat_in < 15.0 && sat_out < 15.0)
991 {
992 cnt++; // store as gray patch:
993 grays[6*cnt + 0] = colorchecker_Lab[3*i+0];
994 grays[6*cnt + 1] = colorchecker_Lab[3*i+1];
995 grays[6*cnt + 2] = colorchecker_Lab[3*i+2];
996 grays[6*cnt + 3] = target_L[i];
997 grays[6*cnt + 4] = target_a[i];
998 grays[6*cnt + 5] = target_b[i];
999 }
1000 }
1001 fprintf(stderr, "detected %d/%d as gray patches for tonecurve computation\n", cnt, N);
1002 // sort entries by source L
1003 qsort(grays, cnt, sizeof(double) * 6, compare_L_source);
1004
1005 // put entries with fixed black and white into cx, cy
1006 cx[0] = cy[0] = 0.0; // fix black
1007 cx[cnt+1] = cy[cnt+1] = 100.0; // fix white
1008
1009 // construct a tone curve from the grays plus pure black and pure white
1010 num_tonecurve = cnt + 2;
1011 for(int k = 0; k < cnt; k++) cx[k + 1] = grays[6*k+0];
1012 for(int k = 0; k < cnt; k++) cy[k + 1] = grays[6*k+3];
1013 tonecurve_create(&tonecurve, cx, cy, num_tonecurve);
1014 }
1015
1016#if 0 // quiet.
1017 for(int k = 0; k < num_tonecurve; k++)
1018 fprintf(stderr, "L[%g] = %g\n", 100.0 * k / (num_tonecurve - 1.0),
1019 tonecurve_apply(&tonecurve, 100.0 * k / (num_tonecurve - 1.0)));
1020#endif
1021
1022#if 0 // Lab tonecurve on L only
1023 // unapply from target data, we will apply it later in the pipe and want to match the colours only:
1024 for(int k = 0; k < N; k++) target_L[k] = tonecurve_unapply(&tonecurve, target_L[k]);
1025#else // rgb tonecurve affecting colours, too
1026 tonecurve_t rgbcurve;
1027 // ownership transferred to tonecurve object, so we just alloc without free here:
1028 cx = malloc(sizeof(double)*num_tonecurve);
1029 cy = malloc(sizeof(double)*num_tonecurve);
1030 cx[0] = cy[0] = 0.0; // fix black
1031 cx[num_tonecurve - 1] = cy[num_tonecurve - 1] = 100.0; // fix white
1032 for(int k = 1; k < num_tonecurve-1; k++)
1033 {
1034 dt_aligned_pixel_t rgb, Lab = { 0.0f, 0.0f, 0.0f };
1035 Lab[0] = grays[6*k+0];
1036 dt_Lab_to_prophotorgb(Lab, rgb);
1037 cx[k] = rgb[0];
1038 Lab[0] = tonecurve_apply(&tonecurve, Lab[0]);
1039 dt_Lab_to_prophotorgb(Lab, rgb);
1040 cy[k] = rgb[0];
1041 }
1042 tonecurve_create(&rgbcurve, cx, cy, num_tonecurve);
1043 dt_free(grays);
1044
1045 // now unapply the curve:
1046 for(int k = 0; k < N; k++)
1047 {
1048 dt_aligned_pixel_t rgb, Lab = { 0.0f, 0.0f, 0.0f };
1049 Lab[0] = target_L[k];
1050 Lab[1] = target_a[k];
1051 Lab[2] = target_b[k];
1052 dt_Lab_to_prophotorgb(Lab, rgb);
1053 rgb[0] = tonecurve_unapply(&rgbcurve, rgb[0]);
1054 rgb[1] = tonecurve_unapply(&rgbcurve, rgb[1]);
1055 rgb[2] = tonecurve_unapply(&rgbcurve, rgb[2]);
1056 dt_prophotorgb_to_Lab(rgb, Lab);
1057 target_L[k] = Lab[0];
1058 target_a[k] = Lab[1];
1059 target_b[k] = Lab[2];
1060 }
1061 tonecurve_delete(&rgbcurve);
1062#endif
1063
1064 const double *target[3] = { target_L, target_a, target_b };
1065 double *coeff_L = malloc(sizeof(double) * (N + 4) );
1066 double *coeff_a = malloc(sizeof(double) * (N + 4) );
1067 double *coeff_b = malloc(sizeof(double) * (N + 4) );
1068 double *coeff[] = { coeff_L, coeff_a, coeff_b };
1069 int *perm = malloc(sizeof(int) * (N + 4));
1070 double avgerr, maxerr;
1071 sparsity = thinplate_match(&tonecurve, 3, N, colorchecker_Lab, target, sparsity, perm, coeff, &avgerr, &maxerr);
1072
1073 if (!IS_NULL_PTR(self->result_label))
1074 {
1075 // TODO: is the rank interesting, too?
1076 char *result_string = g_strdup_printf(_("average dE: %.02f\nmax dE: %.02f"), avgerr, maxerr);
1077 gtk_label_set_text(GTK_LABEL(self->result_label), result_string);
1078 dt_free(result_string);
1079 }
1080
1081 dt_free(coeff_b);
1082 dt_free(coeff_a);
1083 dt_free(coeff_L);
1084
1085 int sp = 0;
1086 int cperm[300] = { 0 };
1087 for(int k = 0; k < sparsity; k++)
1088 if(perm[k] < N) // skip polynomial parts
1089 cperm[sp++] = perm[k];
1090
1091#if 0 // quiet.
1092 fprintf(stderr, "found %d basis functions:\n", sp);
1093 for(int k = 0; k < sp; k++)
1094 fprintf(stderr, "perm[%d] = %d source %g %g %g\n", k, cperm[k], colorchecker_Lab[3 * cperm[k]],
1095 colorchecker_Lab[3 * cperm[k] + 1], colorchecker_Lab[3 * cperm[k] + 2]);
1096#endif
1097
1098 dt_free(perm);
1099 self->tonecurve_encoded = encode_tonecurve(&tonecurve);
1100 self->colorchecker_encoded = encode_colorchecker(sp, colorchecker_Lab, target, cperm);
1101
1102 tonecurve_delete(&tonecurve);
1103}
1104
1105static void process_button_clicked_callback(GtkButton *button, gpointer user_data)
1106{
1107 dt_lut_t *self = (dt_lut_t *)user_data;
1108
1109 gtk_widget_set_sensitive(self->export_button, FALSE);
1112 self->tonecurve_encoded = NULL;
1113 self->colorchecker_encoded = NULL;
1114
1115 if(IS_NULL_PTR(self->chart)) return;
1116
1117 int i = 0;
1118 int N = g_hash_table_size(self->chart->box_table);
1119
1120 double *target_L = (double *)calloc(sizeof(double), (N + 4));
1121 double *target_a = (double *)calloc(sizeof(double), (N + 4));
1122 double *target_b = (double *)calloc(sizeof(double), (N + 4));
1123 double *colorchecker_Lab = (double *)calloc(sizeof(double) * 3, N);
1124
1125 GHashTableIter table_iter;
1126 gpointer set_key, value;
1127
1128 g_hash_table_iter_init(&table_iter, self->chart->patch_sets);
1129 while(g_hash_table_iter_next(&table_iter, &set_key, &value))
1130 {
1131 GList *patch_names = (GList *)value;
1132 add_patches_to_array(self, patch_names, &N, &i, target_L, target_a, target_b, colorchecker_Lab);
1133 }
1134
1135 add_hdr_patches(&N, &target_L, &target_a, &target_b, &colorchecker_Lab);
1136
1137 int sparsity = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(self->number_patches)) + 4;
1138
1139 process_data(self, target_L, target_a, target_b, colorchecker_Lab, N, sparsity);
1140
1141 gtk_widget_set_sensitive(self->export_button, TRUE);
1142 gtk_widget_set_sensitive(self->export_raw_button, TRUE);
1143
1144 dt_free(target_L);
1145 dt_free(target_a);
1146 dt_free(target_b);
1148}
1149
1150static void cht_state_callback(GtkWidget *widget, GtkStateFlags flags, gpointer user_data)
1151{
1152 dt_lut_t *self = (dt_lut_t *)user_data;
1153 // cht not sensitive -> no reference or export
1154 if(flags & GTK_STATE_FLAG_INSENSITIVE)
1155 {
1156 gtk_widget_set_sensitive(self->it8_button, FALSE);
1157 gtk_widget_set_sensitive(self->reference_image_button, FALSE);
1158 gtk_widget_set_sensitive(self->process_button, FALSE);
1159 gtk_widget_set_sensitive(self->export_button, FALSE);
1160 gtk_widget_set_sensitive(self->export_raw_button, FALSE);
1161 }
1162}
1163
1164static void shrink_changed_callback(GtkRange *range, gpointer user_data)
1165{
1166 image_t *image = (image_t *)user_data;
1167 image->shrink = gtk_range_get_value(range);
1168
1169 gtk_widget_queue_draw(image->drawing_area);
1170}
1171
1173{
1174 GtkWidget *page = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1175
1176 GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
1177 gtk_box_pack_start(GTK_BOX(page), hbox, FALSE, TRUE, 0);
1178
1179 GtkWidget *image_button = gtk_file_chooser_button_new("image of a color chart", GTK_FILE_CHOOSER_ACTION_OPEN);
1180 g_signal_connect(image_button, "file-set", G_CALLBACK(source_image_changed_callback), self);
1181
1182 GtkWidget *cht_button
1183 = gtk_file_chooser_button_new("description of a color chart", GTK_FILE_CHOOSER_ACTION_OPEN);
1184 g_signal_connect(cht_button, "file-set", G_CALLBACK(cht_changed_callback), self);
1185
1186 GtkWidget *source_shrink = gtk_scale_new_with_range(GTK_ORIENTATION_HORIZONTAL, 0.5, 2.0, 0.01);
1187 gtk_scale_set_value_pos(GTK_SCALE(source_shrink), GTK_POS_RIGHT);
1188 g_signal_connect(source_shrink, "value-changed", G_CALLBACK(shrink_changed_callback), &self->source);
1189
1190 gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new("image:"), FALSE, TRUE, 0);
1191 gtk_box_pack_start(GTK_BOX(hbox), image_button, TRUE, TRUE, 0);
1192 gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new("chart:"), FALSE, TRUE, 0);
1193 gtk_box_pack_start(GTK_BOX(hbox), cht_button, TRUE, TRUE, 0);
1194 gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new("size:"), FALSE, TRUE, 0);
1195 gtk_box_pack_start(GTK_BOX(hbox), source_shrink, TRUE, TRUE, 0);
1196
1197 init_image(self, &self->source, G_CALLBACK(motion_notify_callback_source));
1198 self->source.draw_colored = TRUE;
1199 gtk_box_pack_start(GTK_BOX(page), self->source.drawing_area, TRUE, TRUE, 0);
1200
1201 g_signal_connect(cht_button, "state-flags-changed", G_CALLBACK(cht_state_callback), self);
1202
1203 self->image_button = image_button;
1204 self->cht_button = cht_button;
1205 self->source_shrink = source_shrink;
1206
1207 return page;
1208}
1209
1211{
1212 GtkWidget *page = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1213
1214 GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
1215 gtk_box_pack_start(GTK_BOX(page), hbox, FALSE, TRUE, 0);
1216
1217 GtkWidget *reference_mode = gtk_combo_box_text_new();
1218 gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(reference_mode), NULL, "cie/it8 file");
1219 gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(reference_mode), NULL, "color chart image");
1220 gtk_combo_box_set_active(GTK_COMBO_BOX(reference_mode), 0);
1221 g_signal_connect(reference_mode, "changed", G_CALLBACK(reference_mode_changed_callback), self);
1222
1223 GtkWidget *it8_button
1224 = gtk_file_chooser_button_new("reference data of a color chart", GTK_FILE_CHOOSER_ACTION_OPEN);
1225 g_signal_connect(it8_button, "file-set", G_CALLBACK(it8_changed_callback), self);
1226
1227 GtkWidget *reference_image_button
1228 = gtk_file_chooser_button_new("image of a color chart", GTK_FILE_CHOOSER_ACTION_OPEN);
1229 g_signal_connect(reference_image_button, "file-set", G_CALLBACK(ref_image_changed_callback), self);
1230
1231 GtkWidget *reference_shrink = gtk_scale_new_with_range(GTK_ORIENTATION_HORIZONTAL, 0.5, 2.0, 0.01);
1232 gtk_scale_set_value_pos(GTK_SCALE(reference_shrink), GTK_POS_RIGHT);
1233 g_signal_connect(reference_shrink, "value-changed", G_CALLBACK(shrink_changed_callback), &self->reference);
1234
1235 gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new("mode:"), FALSE, TRUE, 0);
1236 gtk_box_pack_start(GTK_BOX(hbox), reference_mode, TRUE, TRUE, 0);
1237
1238 GtkWidget *reference_it8_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
1239 gtk_box_pack_start(GTK_BOX(reference_it8_box), gtk_label_new("reference it8:"), FALSE, TRUE, 0);
1240 gtk_box_pack_start(GTK_BOX(reference_it8_box), it8_button, TRUE, TRUE, 0);
1241 gtk_box_pack_start(GTK_BOX(hbox), reference_it8_box, TRUE, TRUE, 0);
1242
1243 GtkWidget *reference_image_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
1244 gtk_box_pack_start(GTK_BOX(reference_image_box), gtk_label_new("reference image:"), FALSE, TRUE, 0);
1245 gtk_box_pack_start(GTK_BOX(reference_image_box), reference_image_button, TRUE, TRUE, 0);
1246 gtk_box_pack_start(GTK_BOX(reference_image_box), gtk_label_new("size:"), FALSE, TRUE, 0);
1247 gtk_box_pack_start(GTK_BOX(reference_image_box), reference_shrink, TRUE, TRUE, 0);
1248 gtk_box_pack_start(GTK_BOX(hbox), reference_image_box, TRUE, TRUE, 0);
1249
1250 init_image(self, &self->reference, G_CALLBACK(motion_notify_callback_reference));
1252 gtk_box_pack_start(GTK_BOX(page), self->reference.drawing_area, TRUE, TRUE, 0);
1253
1254 gtk_widget_show_all(reference_it8_box);
1255 gtk_widget_show_all(reference_image_box);
1256 gtk_widget_show_all(self->reference.drawing_area);
1257 gtk_widget_hide(reference_image_box);
1258 gtk_widget_hide(self->reference.drawing_area);
1259 gtk_widget_set_no_show_all(reference_it8_box, TRUE);
1260 gtk_widget_set_no_show_all(reference_image_box, TRUE);
1261 gtk_widget_set_no_show_all(self->reference.drawing_area, TRUE);
1262
1263 self->reference_mode = reference_mode;
1264 self->it8_button = it8_button;
1265 self->reference_image_button = reference_image_button;
1266 self->reference_it8_box = reference_it8_box;
1267 self->reference_image_box = reference_image_box;
1268 self->reference_shrink = reference_shrink;
1269
1270 return page;
1271}
1272
1274{
1275 GtkWidget *page = gtk_grid_new();
1276 gtk_grid_set_row_spacing(GTK_GRID(page), DT_GUI_BOX_SPACING);
1277 gtk_grid_set_column_spacing(GTK_GRID(page), DT_GUI_BOX_SPACING);
1278
1279 int line = 0;
1280
1281 // TODO: it might make sense to limit this to a smaller range and/or use a slider
1282 // 49 is the current max in the lut iop
1283 GtkWidget *number_patches = gtk_spin_button_new_with_range(0, 49, 1);
1284 gtk_spin_button_set_value(GTK_SPIN_BUTTON(number_patches), 24);
1285 gtk_grid_attach(GTK_GRID(page), gtk_label_new("number of final patches"), 0, line, 1, 1);
1286 gtk_grid_attach(GTK_GRID(page), number_patches, 1, line++, 1, 1);
1287
1288 GtkWidget *process_button = gtk_button_new_with_label("process");
1289 GtkWidget *export_button = gtk_button_new_with_label("export");
1290 GtkWidget *export_raw_button = gtk_button_new_with_label("export raw data as csv");
1291 gtk_grid_attach(GTK_GRID(page), process_button, 1, line, 1, 1);
1292 gtk_grid_attach(GTK_GRID(page), export_button, 2, line, 1, 1);
1293 gtk_grid_attach(GTK_GRID(page), export_raw_button, 3, line++, 1, 1);
1294
1295 self->result_label = gtk_label_new(NULL);
1296 gtk_widget_set_halign(self->result_label, GTK_ALIGN_START);
1297 gtk_grid_attach(GTK_GRID(page), self->result_label, 1, line++, 3, 1);
1298
1299 g_signal_connect(process_button, "clicked", G_CALLBACK(process_button_clicked_callback), self);
1300 g_signal_connect(export_button, "clicked", G_CALLBACK(export_button_clicked_callback), self);
1301 g_signal_connect(export_raw_button, "clicked", G_CALLBACK(export_raw_button_clicked_callback), self);
1302
1303 self->number_patches = number_patches;
1304 self->process_button = process_button;
1305 self->export_button = export_button;
1306 self->export_raw_button = export_raw_button;
1307
1308 return page;
1309}
1310
1312{
1313 // the notebook with 2 tabs for input
1314 GtkWidget *notebook = gtk_notebook_new();
1315
1316 // first tab: input image + cht file
1317 gtk_notebook_append_page(GTK_NOTEBOOK(notebook), create_notebook_page_source(self),
1318 gtk_label_new("source image"));
1319
1320 // second tab: mode + either reference image or cie file
1321 gtk_notebook_append_page(GTK_NOTEBOOK(notebook), create_notebook_page_reference(self),
1322 gtk_label_new("reference values"));
1323
1324 // third tab: analyze data and process it
1325 gtk_notebook_append_page(GTK_NOTEBOOK(notebook), create_notebook_page_process(self), gtk_label_new("process"));
1326
1327 return notebook;
1328}
1329
1331{
1332 GtkWidget *scrolled_window = gtk_scrolled_window_new(NULL, NULL);
1333 gtk_widget_set_size_request(scrolled_window, -1, 15);
1334 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1335 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled_window), GTK_SHADOW_ETCHED_IN);
1336 // gtk_paned_pack2(GTK_PANED(vpaned), scrolled_window, TRUE, FALSE);
1337
1338 self->model = GTK_TREE_MODEL(gtk_list_store_new(NUM_COLUMNS,
1339 G_TYPE_STRING, // COLUMN_NAME
1340 G_TYPE_STRING, // COLUMN_RGB_IN
1341 G_TYPE_STRING, // COLUMN_LAB_IN
1342 G_TYPE_STRING, // COLUMN_LAB_REF
1343 G_TYPE_STRING, // COLUMN_DE_1976
1344 G_TYPE_FLOAT, // COLUMN_DE_1976_FLOAT
1345 G_TYPE_STRING, // COLUMN_DE_2000
1346 G_TYPE_FLOAT // COLUMN_DE_2000_FLOAT
1347 ));
1348 self->treeview = gtk_tree_view_new_with_model(self->model);
1349 gtk_tree_view_set_search_column(GTK_TREE_VIEW(self->treeview), COLUMN_NAME);
1350 gtk_container_add(GTK_CONTAINER(scrolled_window), self->treeview);
1351
1352 add_column(GTK_TREE_VIEW(self->treeview), "name", COLUMN_NAME, COLUMN_NAME);
1353 add_column(GTK_TREE_VIEW(self->treeview), "sRGB (image)", COLUMN_RGB_IN, COLUMN_RGB_IN);
1354 add_column(GTK_TREE_VIEW(self->treeview), "Lab (image)", COLUMN_LAB_IN, COLUMN_LAB_IN);
1355 add_column(GTK_TREE_VIEW(self->treeview), "Lab (reference)", COLUMN_LAB_REF, COLUMN_LAB_REF);
1356 add_column(GTK_TREE_VIEW(self->treeview), "deltaE (1976)", COLUMN_DE_1976, COLUMN_DE_1976_FLOAT);
1357 add_column(GTK_TREE_VIEW(self->treeview), "deltaE (2000)", COLUMN_DE_2000, COLUMN_DE_2000_FLOAT);
1358
1359 return scrolled_window;
1360}
1361
1362static void add_column(GtkTreeView *treeview, const char *title, int column_id, int sort_column)
1363{
1364 GtkCellRenderer *renderer;
1365 GtkTreeViewColumn *column;
1366 renderer = gtk_cell_renderer_text_new();
1367 column = gtk_tree_view_column_new_with_attributes(title, renderer, "text", column_id, NULL);
1368 gtk_tree_view_column_set_sort_column_id(column, sort_column);
1369 gtk_tree_view_append_column(treeview, column);
1370}
1371
1372// only change the numbers, don't re-fill the table!
1373static void update_table(dt_lut_t *self)
1374{
1375 GtkTreeIter iter;
1376 gboolean valid = gtk_tree_model_get_iter_first(self->model, &iter);
1377
1378 while(valid)
1379 {
1380 char *name;
1381
1382 gtk_tree_model_get(self->model, &iter, COLUMN_NAME, &name, -1);
1383
1384 box_t *box = (box_t *)g_hash_table_lookup(self->chart->box_table, name);
1385 if(box)
1386 {
1387 dt_aligned_pixel_t Lab = { 0.0 };
1388 char *s_Lab_in, *s_RGB_in, *s_deltaE_1976, *s_deltaE_2000;
1389 float deltaE_1976 = 0.0, deltaE_2000 = 0.0;
1390
1391 get_Lab_from_box(box, Lab);
1392
1393 box_t *patch = (box_t *)g_hash_table_lookup(self->picked_source_patches, name);
1394 if(patch)
1395 {
1396 dt_aligned_pixel_t in_Lab = { 0.0 };
1397 get_Lab_from_box(patch, in_Lab);
1398 s_RGB_in = g_strdup_printf("%d; %d; %d", (int)(patch->rgb[0] * 255 + 0.5),
1399 (int)(patch->rgb[1] * 255 + 0.5), (int)(patch->rgb[2] * 255 + 0.5));
1400 s_Lab_in = g_strdup_printf("%.02f; %.02f; %.02f", in_Lab[0], in_Lab[1], in_Lab[2]);
1401 deltaE_1976 = dt_colorspaces_deltaE_1976(in_Lab, Lab);
1402 deltaE_2000 = dt_colorspaces_deltaE_2000(in_Lab, Lab);
1403 s_deltaE_1976 = g_strdup_printf("%.02f", deltaE_1976);
1404 s_deltaE_2000 = g_strdup_printf("%.02f", deltaE_2000);
1405 }
1406 else
1407 {
1408 s_Lab_in = g_strdup("?");
1409 s_RGB_in = g_strdup("?");
1410 s_deltaE_1976 = g_strdup("-");
1411 s_deltaE_2000 = g_strdup("-");
1412 }
1413 char *s_Lab_ref = g_strdup_printf("%.02f; %.02f; %.02f", Lab[0], Lab[1], Lab[2]);
1414
1415 gtk_list_store_set(GTK_LIST_STORE(self->model), &iter, COLUMN_RGB_IN, s_RGB_in, COLUMN_LAB_IN, s_Lab_in,
1416 COLUMN_LAB_REF, s_Lab_ref, COLUMN_DE_1976, s_deltaE_1976, COLUMN_DE_1976_FLOAT,
1417 deltaE_1976, COLUMN_DE_2000, s_deltaE_2000, COLUMN_DE_2000_FLOAT, deltaE_2000, -1);
1418 dt_free(s_RGB_in);
1419 dt_free(s_Lab_in);
1420 dt_free(s_Lab_ref);
1421 dt_free(s_deltaE_1976);
1422 dt_free(s_deltaE_2000);
1423 } // if(box)
1424
1425 dt_free(name);
1426 valid = gtk_tree_model_iter_next(self->model, &iter);
1427 } // while(valid)
1428}
1429
1430static void get_Lab_from_box(box_t *box, float *Lab)
1431{
1432 switch(box->color_space)
1433 {
1434 case DT_COLORSPACE_XYZ:
1435 {
1437 for(int i = 0; i < 3; i++) XYZ[i] = box->color[i] * 0.01;
1439 break;
1440 }
1441 case DT_COLORSPACE_LAB:
1442 for(int i = 0; i < 3; i++) Lab[i] = box->color[i];
1443 break;
1444 default:
1445 break;
1446 }
1447}
1448
1449static void init_table(dt_lut_t *self)
1450{
1451 GtkTreeIter iter;
1452
1453 gtk_list_store_clear(GTK_LIST_STORE(self->model));
1454
1455 if(IS_NULL_PTR(self->chart)) return;
1456
1457 GList *patch_names = g_hash_table_get_keys(self->chart->box_table);
1458 patch_names = g_list_sort(patch_names, (GCompareFunc)g_strcmp0);
1459 for(GList *name = patch_names; name; name = g_list_next(name))
1460 {
1461 gtk_list_store_append(GTK_LIST_STORE(self->model), &iter);
1462 gtk_list_store_set(GTK_LIST_STORE(self->model), &iter, COLUMN_NAME, (char *)name->data, -1);
1463 }
1464 g_list_free(patch_names);
1465 patch_names = NULL;
1466
1467 update_table(self);
1468}
1469
1471{
1472 if(self->chart) g_hash_table_foreach(self->chart->box_table, collect_source_patches_foreach, self);
1473}
1474
1476{
1477 if(self->chart) g_hash_table_foreach(self->chart->box_table, collect_reference_patches_foreach, self);
1478}
1479
1480static void collect_source_patches_foreach(gpointer key, gpointer value, gpointer user_data)
1481{
1482 dt_lut_t *self = (dt_lut_t *)user_data;
1483 box_t *box = (box_t *)value;
1485
1486 box_t *patch = find_patch(self->picked_source_patches, key);
1487
1488 get_xyz_sample_from_image(&self->source, self->source.shrink, box, xyz);
1489
1490 checker_set_color(patch, DT_COLORSPACE_XYZ, xyz[0] * 100.0, xyz[1] * 100.0, xyz[2] * 100.0);
1491}
1492
1493static void collect_reference_patches_foreach(gpointer key, gpointer value, gpointer user_data)
1494{
1495 dt_lut_t *self = (dt_lut_t *)user_data;
1496 box_t *patch = (box_t *)value;
1498
1499 get_xyz_sample_from_image(&self->reference, self->reference.shrink, patch, xyz);
1500
1501 checker_set_color(patch, DT_COLORSPACE_XYZ, xyz[0] * 100.0, xyz[1] * 100.0, xyz[2] * 100.0);
1502}
1503
1504static box_t *find_patch(GHashTable *table, gpointer key)
1505{
1506 box_t *patch = (box_t *)g_hash_table_lookup(table, key);
1507 if(IS_NULL_PTR(patch))
1508 {
1509 // the patch won't be found in the first pass
1510 patch = (box_t *)calloc(1, sizeof(box_t));
1511 g_hash_table_insert(table, g_strdup(key), patch);
1512 }
1513 return patch;
1514}
1515
1516static void get_xyz_sample_from_image(const image_t *const image, float shrink, box_t *box, float *xyz)
1517{
1518 point_t bb[4];
1519 float homography[9];
1520 point_t corners[4];
1521 box_t inner_box;
1522 int x_start, y_start, x_end, y_end;
1523
1524 xyz[0] = xyz[1] = xyz[2] = 0.0;
1525
1526 if(IS_NULL_PTR(box)) return;
1527
1528 get_boundingbox(image, bb);
1530 inner_box = get_sample_box(*(image->chart), box, shrink);
1531 get_corners(homography, &inner_box, corners);
1532 get_pixel_region(image, corners, &x_start, &y_start, &x_end, &y_end);
1533
1534 const float delta_x_top = corners[TOP_RIGHT].x - corners[TOP_LEFT].x;
1535 const float delta_y_top = corners[TOP_RIGHT].y - corners[TOP_LEFT].y;
1536 const float delta_x_bottom = corners[BOTTOM_RIGHT].x - corners[BOTTOM_LEFT].x;
1537 const float delta_y_bottom = corners[BOTTOM_RIGHT].y - corners[BOTTOM_LEFT].y;
1538 const float delta_x_left = corners[BOTTOM_LEFT].x - corners[TOP_LEFT].x;
1539 const float delta_y_left = corners[BOTTOM_LEFT].y - corners[TOP_LEFT].y;
1540 const float delta_x_right = corners[BOTTOM_RIGHT].x - corners[TOP_RIGHT].x;
1541 const float delta_y_right = corners[BOTTOM_RIGHT].y - corners[TOP_RIGHT].y;
1542
1543 double sample_x = 0.0, sample_y = 0.0, sample_z = 0.0;
1544 size_t n_samples = 0;
1545 __OMP_PARALLEL_FOR__(reduction(+ : n_samples, sample_x, sample_y, sample_z) )
1546 for(int y = y_start; y < y_end; y++)
1547 for(int x = x_start; x < x_end; x++)
1548 {
1549 if((x - corners[TOP_LEFT].x) / delta_x_top * delta_y_top + corners[TOP_LEFT].y < y
1550 && (x - corners[BOTTOM_LEFT].x) / delta_x_bottom * delta_y_bottom + corners[BOTTOM_LEFT].y > y
1551 && (y - corners[TOP_LEFT].y) / delta_y_left * delta_x_left + corners[TOP_LEFT].x < x
1552 && (y - corners[TOP_RIGHT].y) / delta_y_right * delta_x_right + corners[TOP_RIGHT].x > x)
1553 {
1554 float *pixel = &image->xyz[(x + y * image->width) * 3];
1555 sample_x += pixel[0];
1556 sample_y += pixel[1];
1557 sample_z += pixel[2];
1558 n_samples++;
1559 }
1560 }
1561
1562 xyz[0] = sample_x / n_samples;
1563 xyz[1] = sample_y / n_samples;
1564 xyz[2] = sample_z / n_samples;
1565}
1566
1567static void get_boundingbox(const image_t *const image, point_t *bb)
1568{
1569 for(int i = 0; i < 4; i++)
1570 {
1571 bb[i].x = image->bb[i].x * image->width;
1572 bb[i].y = image->bb[i].y * image->height;
1573 }
1574}
1575
1576static box_t get_sample_box(chart_t *chart, box_t *outer_box, float shrink)
1577{
1578 box_t inner_box = *outer_box;
1579 float x_shrink = shrink * chart->box_shrink / chart->bb_w, y_shrink = shrink * chart->box_shrink / chart->bb_h;
1580 inner_box.p.x += x_shrink;
1581 inner_box.p.y += y_shrink;
1582 inner_box.w -= 2.0 * x_shrink;
1583 inner_box.h -= 2.0 * y_shrink;
1584 return inner_box;
1585}
1586
1587static void get_corners(const float *homography, box_t *box, point_t *corners)
1588{
1589 corners[TOP_LEFT] = corners[TOP_RIGHT] = corners[BOTTOM_RIGHT] = corners[BOTTOM_LEFT] = box->p;
1590 corners[TOP_RIGHT].x += box->w;
1591 corners[BOTTOM_RIGHT].x += box->w;
1592 corners[BOTTOM_RIGHT].y += box->h;
1593 corners[BOTTOM_LEFT].y += box->h;
1594
1595 for(int i = 0; i < 4; i++) corners[i] = apply_homography(corners[i], homography);
1596}
1597
1598static void get_pixel_region(const image_t *const image, const point_t *const corners, int *x_start, int *y_start,
1599 int *x_end, int *y_end)
1600{
1601 *x_start = CLAMP((int)(MIN(corners[TOP_LEFT].x,
1602 MIN(corners[TOP_RIGHT].x, MIN(corners[BOTTOM_RIGHT].x, corners[BOTTOM_LEFT].x)))
1603 + 0.5),
1604 0, image->width);
1605 *x_end = CLAMP((int)(MAX(corners[TOP_LEFT].x,
1606 MAX(corners[TOP_RIGHT].x, MAX(corners[BOTTOM_RIGHT].x, corners[BOTTOM_LEFT].x)))
1607 + 0.5),
1608 0, image->width);
1609 *y_start = CLAMP((int)(MIN(corners[TOP_LEFT].y,
1610 MIN(corners[TOP_RIGHT].y, MIN(corners[BOTTOM_RIGHT].y, corners[BOTTOM_LEFT].y)))
1611 + 0.5),
1612 0, image->height);
1613 *y_end = CLAMP((int)(MAX(corners[TOP_LEFT].y,
1614 MAX(corners[TOP_RIGHT].y, MAX(corners[BOTTOM_RIGHT].y, corners[BOTTOM_LEFT].y)))
1615 + 0.5),
1616 0, image->height);
1617}
1618
1619static void reset_bb(image_t *image)
1620{
1621 image->bb[TOP_LEFT].x = 0.05;
1622 image->bb[TOP_LEFT].y = 0.05;
1623 image->bb[TOP_RIGHT].x = 0.95;
1624 image->bb[TOP_RIGHT].y = 0.05;
1625 image->bb[BOTTOM_RIGHT].x = 0.95;
1626 image->bb[BOTTOM_RIGHT].y = 0.95;
1627 image->bb[BOTTOM_LEFT].x = 0.05;
1628 image->bb[BOTTOM_LEFT].y = 0.95;
1629}
1630
1631static void init_image(dt_lut_t *self, image_t *image, GCallback motion_cb)
1632{
1633 memset(image, 0x00, sizeof(image_t));
1634 image->chart = &self->chart;
1635 image->drawing_area = gtk_drawing_area_new();
1636 gtk_widget_set_size_request(image->drawing_area, -1, 50);
1637 gtk_widget_add_events(image->drawing_area,
1638 GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
1639 g_signal_connect(image->drawing_area, "size-allocate", G_CALLBACK(size_allocate_callback), image);
1640 g_signal_connect(image->drawing_area, "draw", G_CALLBACK(draw_image_callback), image);
1641 g_signal_connect(image->drawing_area, "motion-notify-event", G_CALLBACK(motion_cb), self);
1642}
1643
1644static void free_image(image_t *image)
1645{
1646 if(IS_NULL_PTR(image)) return;
1647 reset_bb(image);
1648 if(image->image) cairo_pattern_destroy(image->image);
1649 if(image->surface) cairo_surface_destroy(image->surface);
1650 dt_free(image->xyz);
1651 image->image = NULL;
1652 image->surface = NULL;
1653 image->xyz = NULL;
1654}
1655
1656static void image_lab_to_xyz(float *image, const int width, const int height)
1657{
1658 __OMP_PARALLEL_FOR__(shared(image) )
1659 for(int y = 0; y < height; y++)
1660 for(int x = 0; x < width; x++)
1661 {
1662 float *pixel = &image[(x + y * width) * 3];
1663 dt_Lab_to_XYZ(pixel, pixel);
1664 }
1665}
1666
1667static int main_gui(dt_lut_t *self, int argc, char *argv[])
1668{
1669 gtk_init(&argc, &argv);
1670
1671 char *source_filename = argc >= 2 ? argv[1] : NULL;
1672 char *cht_filename = argc >= 3 ? argv[2] : NULL;
1673 char *it8_filename = NULL;
1674 char *reference_filename = NULL;
1675 if(argc >= 4)
1676 {
1677 char *upper_string = g_ascii_strup(argv[3], -1);
1678 if(g_str_has_suffix(upper_string, ".PFM"))
1679 reference_filename = argv[3];
1680 else
1681 it8_filename = argv[3];
1682 dt_free(upper_string);
1683 }
1684
1685 // build the GUI
1686 GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1687 self->window = window;
1688 gtk_window_set_title(GTK_WINDOW(window), "darktable LUT tool");
1689 gtk_container_set_border_width(GTK_CONTAINER(window), 3);
1690 gtk_window_set_default_size(GTK_WINDOW(window), 800, 600);
1691 g_signal_connect(GTK_WINDOW(window), "destroy", G_CALLBACK(gtk_main_quit), NULL);
1692
1693 // resizable container
1694 GtkWidget *vpaned = gtk_paned_new(GTK_ORIENTATION_VERTICAL);
1695 gtk_container_add(GTK_CONTAINER(window), vpaned);
1696
1697 // upper half
1698 gtk_paned_pack1(GTK_PANED(vpaned), create_notebook(self), TRUE, FALSE);
1699
1700 // lower half
1701 gtk_paned_pack2(GTK_PANED(vpaned), create_table(self), TRUE, FALSE);
1702
1703 gtk_widget_set_sensitive(self->cht_button, FALSE);
1704 gtk_widget_set_sensitive(self->it8_button, FALSE);
1705 gtk_widget_set_sensitive(self->reference_image_button, FALSE);
1706 gtk_widget_set_sensitive(self->process_button, FALSE);
1707 gtk_widget_set_sensitive(self->export_button, FALSE);
1708 gtk_widget_set_sensitive(self->export_raw_button, FALSE);
1709
1710 gtk_widget_show_all(window);
1711
1712 // only load data now so it can fill widgets
1713 if(source_filename && open_source_image(self, source_filename))
1714 {
1715 gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(self->image_button), source_filename);
1716 if(cht_filename && open_cht(self, cht_filename))
1717 {
1718 gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(self->cht_button), cht_filename);
1719 if(it8_filename && open_it8(self, it8_filename))
1720 gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(self->it8_button), it8_filename);
1721 if(reference_filename && open_reference_image(self, reference_filename))
1722 {
1723 gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(self->reference_image_button), reference_filename);
1724 gtk_combo_box_set_active(GTK_COMBO_BOX(self->reference_mode), 1);
1725 }
1726 }
1727 }
1728
1729#ifdef GDK_WINDOWING_QUARTZ
1731#endif
1732 gtk_main();
1733
1734 return 0;
1735}
1736
1737static int parse_csv(dt_lut_t *self, const char *filename, double **target_L_ptr, double **target_a_ptr,
1738 double **target_b_ptr, double **source_Lab_ptr, int *num_gray, char **name, char **description)
1739{
1740 *target_L_ptr = NULL;
1741 *target_a_ptr = NULL;
1742 *target_b_ptr = NULL;
1743 *source_Lab_ptr = NULL;
1744 *name = NULL;
1745 *description = NULL;
1746
1747 FILE *f = g_fopen(filename, "rb");
1748 if(IS_NULL_PTR(f)) return 0;
1749 int N = 0;
1750 while(fscanf(f, "%*[^\n]\n") != EOF) N++;
1751 fseek(f, 0, SEEK_SET);
1752
1753 if(N <= 1)
1754 {
1755 fclose(f);
1756 return 0;
1757 }
1758
1759 // header lines
1760 char key[16] = {0}, value[256] = {0};
1761 int read_res = fscanf(f, "%15[^;];%255[^\n]\n", key, value);
1762 if(g_strcmp0(key, "name") || read_res == EOF)
1763 {
1764 fprintf(stderr, "error: expected `name' in the first line\n");
1765 fclose(f);
1766 return 0;
1767 }
1768 *name = g_strdup(value);
1769 N--;
1770
1771 read_res = fscanf(f, "%15[^;];%255[^\n]\n", key, value);
1772 if(g_strcmp0(key, "description") || read_res == EOF)
1773 {
1774 fprintf(stderr, "error: expected `description' in the second line\n");
1775 fclose(f);
1776 return 0;
1777 }
1778 *description = g_strdup(value);
1779 N--;
1780
1781 read_res = fscanf(f, "%15[^;];%d\n", key, num_gray);
1782 if(g_strcmp0(key, "num_gray") || read_res == EOF)
1783 {
1784 fprintf(stderr, "error: missing num_gray in csv\n");
1785 fclose(f);
1786 return 0;
1787 }
1788 N--;
1789
1790 // skip the column title line
1791 read_res = fscanf(f, "%*[^\n]\n");
1792 N--;
1793
1794 double *target_L = (double *)calloc(sizeof(double), (N + 4));
1795 double *target_a = (double *)calloc(sizeof(double), (N + 4));
1796 double *target_b = (double *)calloc(sizeof(double), (N + 4));
1797 double *source_Lab = (double *)calloc(sizeof(double) * 3, N);
1798 *target_L_ptr = target_L;
1799 *target_a_ptr = target_a;
1800 *target_b_ptr = target_b;
1801 *source_Lab_ptr = source_Lab;
1802
1803 char line[512];
1804 for(int i = 0; i < N; i++)
1805 {
1806 char *patchname = line, *iter = line, *endptr;
1807 if(fgets(line, sizeof(line) / sizeof(*line), f) == 0) break;
1808 while(*iter != ';') iter++;
1809 *iter++ = '\0';
1810
1811 source_Lab[3 * i] = g_ascii_strtod(iter, &endptr);
1812 if(iter == endptr || *endptr != ';') break;
1813 iter = endptr + 1;
1814 source_Lab[3 * i + 1] = g_ascii_strtod(iter, &endptr);
1815 if(iter == endptr || *endptr != ';') break;
1816 iter = endptr + 1;
1817 source_Lab[3 * i + 2] = g_ascii_strtod(iter, &endptr);
1818 if(iter == endptr || *endptr != ';') break;
1819 iter = endptr + 1;
1820 target_L[i] = g_ascii_strtod(iter, &endptr);
1821 if(iter == endptr || *endptr != ';') break;
1822 iter = endptr + 1;
1823 target_a[i] = g_ascii_strtod(iter, &endptr);
1824 if(iter == endptr || *endptr != ';') break;
1825 iter = endptr + 1;
1826 target_b[i] = g_ascii_strtod(iter, &endptr);
1827 if(iter == endptr || *endptr != '\n') break;
1828
1829 const double d[3] = { target_L[i], target_a[i], target_b[i] };
1830 if(sqrt(d[0] * d[0] + d[1] * d[1] + d[2] * d[2]) > thrs)
1831 {
1832 fprintf(stderr, "warning: ignoring patch %s with large difference deltaE %g!\n", patchname,
1833 sqrt(d[0] * d[0] + d[1] * d[1] + d[2] * d[2]));
1834 fprintf(stderr, " %g %g %g -- %g %g %g\n", source_Lab[3 * i + 0], source_Lab[3 * i + 1],
1835 source_Lab[3 * i + 2], target_L[i], target_a[i], target_b[i]);
1836 N--; // ignore this patch.
1837 i--;
1838 }
1839 }
1840
1841 fclose(f);
1842 return N;
1843}
1844
1845static int main_csv(dt_lut_t *self, int argc, char *argv[])
1846{
1847 const char *filename_csv = argv[2];
1848 const int num_patches = atoi(argv[3]);
1849 const char *filename_style = argv[4];
1850
1851 const int sparsity = num_patches + 4;
1852
1853 // parse the csv
1854 double *target_L, *target_a, *target_b, *colorchecker_Lab;
1855 int num_tonecurve;
1856 char *name, *description;
1857 int N = parse_csv(self, filename_csv, &target_L, &target_a, &target_b, &colorchecker_Lab, &num_tonecurve, &name,
1858 &description);
1859
1860 if(N == 0)
1861 {
1862 fprintf(stderr, "error parsing `%s', giving up\n", filename_csv);
1863
1864 dt_free(target_L);
1865 dt_free(target_a);
1866 dt_free(target_b);
1868
1869 return 1;
1870 }
1871
1872 add_hdr_patches(&N, &target_L, &target_a, &target_b, &colorchecker_Lab);
1873
1874 process_data(self, target_L, target_a, target_b, colorchecker_Lab, N, sparsity);
1875
1876 // TODO: add command line options to control what modules to include
1877 export_style(self, filename_style, name, description, TRUE, TRUE, TRUE, TRUE);
1878
1879 dt_free(target_L);
1880 dt_free(target_a);
1881 dt_free(target_b);
1883 dt_free(name);
1885
1886 return 0;
1887}
1888
1889static void show_usage(const char *exe)
1890{
1891 fprintf(stderr, "Usage: %s [<input Lab pfm file>] [<cht file>] [<reference cgats/it8 or Lab pfm file>]\n"
1892 " %s --csv <csv file> <number patches> <output dtstyle file>\n",
1893 exe, exe);
1894}
1895
1896int main(int argc, char *argv[])
1897{
1898#ifdef __APPLE__
1900#endif
1901#ifdef _WIN32
1902 SetErrorMode(SEM_FAILCRITICALERRORS);
1903#endif
1904
1905#ifdef _OPENMP
1906 omp_set_num_threads(omp_get_num_procs());
1907#endif
1908
1909 int res = 1;
1910 dt_lut_t *self = (dt_lut_t *)calloc(1, sizeof(dt_lut_t));
1911 self->picked_source_patches = g_hash_table_new_full(g_str_hash, g_str_equal, dt_free_gpointer, dt_free_gpointer);
1912
1913 if(argc >= 2 && !strcmp(argv[1], "--help"))
1914 show_usage(argv[0]);
1915 else if(argc >= 2 && !g_strcmp0(argv[1], "--csv"))
1916 {
1917 if(argc != 5)
1918 show_usage(argv[0]);
1919 else
1920 res = main_csv(self, argc, argv);
1921 }
1922 else if(argc <= 4)
1923 res = main_gui(self, argc, argv);
1924 else
1925 show_usage(argv[0]);
1926
1927 if(self->model) g_object_unref(self->model);
1928 if(self->picked_source_patches) g_hash_table_unref(self->picked_source_patches);
1929 free_image(&self->source);
1930 free_image(&self->reference);
1931 free_chart(self->chart);
1934 dt_free(self);
1935
1936 return res;
1937}
1938
1939// clang-format off
1940// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1941// vim: shiftwidth=2 expandtab tabstop=2 cindent
1942// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1943// clang-format on
1944
const char ** description(struct dt_iop_module_t *self)
Definition ashift.c:160
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:755
#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:645
int width
Definition bilateral.h:1
int height
Definition bilateral.h:1
static char * get_filename_base(const char *filename)
Definition chart/main.c:330
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)
Definition chart/main.c:754
static gboolean handle_motion(GtkWidget *widget, GdkEventMotion *event, dt_lut_t *self, image_t *image)
Definition chart/main.c:209
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)
Definition chart/main.c:251
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)
Definition chart/main.c:175
static void export_raw(dt_lut_t *self, char *filename, char *name, char *description)
Definition chart/main.c:700
static void reference_mode_changed_callback(GtkComboBox *widget, gpointer user_data)
Definition chart/main.c:455
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)
Definition chart/main.c:611
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[]
Definition chart/main.c:56
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)
Definition chart/main.c:904
static void map_mouse_to_0_1(GtkWidget *widget, GdkEventMotion *event, image_t *image, float *x, float *y)
Definition chart/main.c:270
static void free_image(image_t *image)
static gboolean open_cht(dt_lut_t *self, const char *filename)
Definition chart/main.c:418
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)
Definition chart/main.c:652
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)
Definition chart/main.c:129
static void export_button_clicked_callback(GtkButton *button, gpointer user_data)
Definition chart/main.c:738
static void show_usage(const char *exe)
static void export_raw_button_clicked_callback(GtkButton *button, gpointer user_data)
Definition chart/main.c:725
static gboolean open_it8(dt_lut_t *self, const char *filename)
Definition chart/main.c:488
static char * get_export_filename(dt_lut_t *self, const char *extension, char **name, char **description, gboolean *basecurve, gboolean *colorchecker, gboolean *colorin, gboolean *tonecurve)
Definition chart/main.c:511
static gboolean open_image(image_t *image, const char *filename)
Definition chart/main.c:365
static void init_table(dt_lut_t *self)
static void source_image_changed_callback(GtkFileChooserButton *widget, gpointer user_data)
Definition chart/main.c:304
static void size_allocate_callback(GtkWidget *widget, GdkRectangle *allocation, gpointer user_data)
Definition chart/main.c:123
static void print_xml_plugin(FILE *fd, int num, int op_version, const char *operation, const char *op_params, gboolean enabled)
Definition chart/main.c:636
static void add_hdr_patches(int *N, double **target_L, double **target_a, double **target_b, double **colorchecker_Lab)
Definition chart/main.c:791
static void process_data(dt_lut_t *self, double *target_L, double *target_a, double *target_b, double *colorchecker_Lab, int N, int sparsity)
Definition chart/main.c:969
static GtkWidget * create_notebook_page_reference(dt_lut_t *self)
static gboolean motion_notify_callback_reference(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
Definition chart/main.c:197
static gboolean open_reference_image(dt_lut_t *self, const char *filename)
Definition chart/main.c:339
const double thrs
Definition chart/main.c:54
@ COLUMN_DE_1976_FLOAT
Definition chart/main.c:65
@ COLUMN_NAME
Definition chart/main.c:60
@ COLUMN_LAB_IN
Definition chart/main.c:62
@ COLUMN_RGB_IN
Definition chart/main.c:61
@ COLUMN_DE_2000_FLOAT
Definition chart/main.c:67
@ COLUMN_DE_1976
Definition chart/main.c:64
@ NUM_COLUMNS
Definition chart/main.c:68
@ COLUMN_DE_2000
Definition chart/main.c:66
@ COLUMN_LAB_REF
Definition chart/main.c:63
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_)
Definition chart/main.c:962
static void ref_image_changed_callback(GtkFileChooserButton *widget, gpointer user_data)
Definition chart/main.c:322
static void cht_changed_callback(GtkFileChooserButton *widget, gpointer user_data)
Definition chart/main.c:410
static void image_lab_to_xyz(float *image, const int width, const int height)
static char * encode_tonecurve(const tonecurve_t *c)
Definition chart/main.c:855
static void update_corner(image_t *image, int which, float *x, float *y)
Definition chart/main.c:279
#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)
Definition chart/main.c:480
static GtkWidget * create_notebook_page_source(dt_lut_t *self)
static void map_boundingbox_to_view(image_t *image, point_t *bb)
Definition chart/main.c:170
static gboolean open_source_image(dt_lut_t *self, const char *filename)
Definition chart/main.c:312
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)
Definition chart/main.c:185
float * read_pfm(const char *filename, int *wd, int *ht)
Definition chart/pfm.c:33
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)
static const dt_aligned_pixel_simd_t const dt_adaptation_t const float p
chart_t * parse_cht(const char *filename)
Definition colorchart.c:155
void free_chart(chart_t *chart)
Definition colorchart.c:50
int parse_it8(const char *filename, chart_t *chart)
Definition colorchart.c:525
void checker_set_color(box_t *box, dt_colorspaces_color_profile_type_t color_space, float c0, float c1, float c2)
Definition colorchart.c:113
@ DT_COLORSPACE_LAB
Definition colorspaces.h:89
@ DT_COLORSPACE_XYZ
Definition colorspaces.h:88
static dt_aligned_pixel_t rgb
dt_Lab_to_XYZ(Lab, XYZ)
const dt_aligned_pixel_t f
static dt_aligned_pixel_t XYZ
static dt_aligned_pixel_t Lab
dt_XYZ_to_Lab(XYZ, Lab)
char * key
char * name
point_t apply_homography(point_t p, const float *h)
Definition common.c:68
int get_homography(const point_t *source, const point_t *target, float *h)
Definition common.c:27
static void dt_free_gpointer(gpointer ptr)
Definition darktable.h:463
#define dt_free(ptr)
Definition darktable.h:456
#define __OMP_PARALLEL_FOR__(...)
Definition darktable.h:258
static const dt_aligned_pixel_simd_t value
Definition darktable.h:577
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition darktable.h:281
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:2272
static int perm[512]
Definition grain.c:174
static int permutation[]
Definition grain.c:160
#define DT_GUI_BOX_SPACING
Definition gtk.h:109
static const float colorchecker_Lab[]
#define MAX_PATCHES
static const float x
const float l2
const float *const const float coeff[3]
const float l1
float *const restrict const size_t k
dt_mipmap_buffer_dsc_flags flags
Definition mipmap_cache.c:4
#define N
float dt_aligned_pixel_t[4]
void dt_osx_focus_window()
Definition osx.mm:301
void dt_osx_prepare_environment()
Definition osx.mm:212
int main()
Definition prova.c:47
struct _GtkWidget GtkWidget
Definition splash.h:29
@ BOTTOM_RIGHT
@ TOP_LEFT
@ BOTTOM_LEFT
@ TOP_RIGHT
float h
Definition colorchart.h:45
float w
Definition colorchart.h:45
dt_colorspaces_color_profile_type_t color_space
Definition colorchart.h:47
dt_aligned_pixel_t color
Definition colorchart.h:48
dt_aligned_pixel_t rgb
Definition colorchart.h:49
point_t p
Definition colorchart.h:44
float bb_w
Definition colorchart.h:63
GHashTable * patch_sets
Definition colorchart.h:61
float bb_h
Definition colorchart.h:63
float box_shrink
Definition colorchart.h:65
GHashTable * box_table
Definition colorchart.h:57
dt_iop_tonecurve_node_t tonecurve[3][20]
Definition lightroom.c:171
char * colorchecker_encoded
Definition chart/main.c:88
char * tonecurve_encoded
Definition chart/main.c:88
GtkWidget * it8_button
Definition chart/main.c:74
GtkWidget * reference_image_box
Definition chart/main.c:75
char * reference_filename
Definition chart/main.c:83
GtkWidget * result_label
Definition chart/main.c:76
GtkWidget * image_button
Definition chart/main.c:74
GtkWidget * cht_button
Definition chart/main.c:74
GtkTreeModel * model
Definition chart/main.c:78
GtkWidget * source_shrink
Definition chart/main.c:76
GtkWidget * reference_mode
Definition chart/main.c:75
GtkWidget * treeview
Definition chart/main.c:77
GtkWidget * reference_shrink
Definition chart/main.c:76
GtkWidget * window
Definition chart/main.c:74
GtkWidget * process_button
Definition chart/main.c:75
GtkWidget * reference_image_button
Definition chart/main.c:74
GtkWidget * reference_it8_box
Definition chart/main.c:74
GtkWidget * number_patches
Definition chart/main.c:76
image_t reference
Definition chart/main.c:82
GtkWidget * export_raw_button
Definition chart/main.c:75
chart_t * chart
Definition chart/main.c:86
GtkWidget * export_button
Definition chart/main.c:75
image_t source
Definition chart/main.c:81
GHashTable * picked_source_patches
Definition chart/main.c:87
GtkWidget * drawing_area
chart_t ** chart
gboolean draw_colored
cairo_pattern_t * image
cairo_surface_t * surface
point_t bb[4]
float * xyz
float y
Definition colorchart.h:33
float x
Definition colorchart.h:33
#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