Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
lut_viewer.c
Go to the documentation of this file.
1/*
2 This file is part of Ansel,
3 Copyright (C) 2026 Aurélien PIERRE.
4
5 Ansel is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 Ansel is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with darktable. If not, see <http://www.gnu.org/licenses/>.
17*/
18
19#include "common/lut_viewer.h"
20
21#include "bauhaus/bauhaus.h"
22#include "common/colorspaces.h"
23#include "common/darktable.h"
24#include "common/matrices.h"
25#include "control/conf.h"
26#include "control/control.h"
27#include "dtgtk/drawingarea.h"
28#include "gui/draw.h"
29#include "gui/gtk.h"
30
31#include <math.h>
32#include <stdlib.h>
33#include <string.h>
34
35#define DT_LUT_VIEWER_MARGIN DT_PIXEL_APPLY_DPI(12)
36#define DT_LUT_VIEWER_TARGET_SAMPLES 4096
37#define DT_LUT_VIEWER_AXIS_LENGTH DT_PIXEL_APPLY_DPI(20.f)
38
46
48{
49 dt_aligned_pixel_simd_t screen_x;
50 dt_aligned_pixel_simd_t screen_y;
51 dt_aligned_pixel_simd_t screen_z;
52 float min_x;
53 float max_x;
54 float min_y;
55 float max_y;
56 float min_depth;
57 float max_depth;
58 float scale;
59 float offset_x;
60 float offset_y;
64
71
73{
75 GtkDrawingArea *area;
85
86 const float *clut;
87 uint16_t clut_level;
93 float zoom;
94 float pan_x;
95 float pan_y;
103
115 const float *cached_clut;
123 cairo_surface_t *surface;
124
125 dt_aligned_pixel_simd_t *sample_input_work;
126 dt_aligned_pixel_simd_t *sample_output_work;
127 dt_aligned_pixel_simd_t *sample_input_display;
128 dt_aligned_pixel_simd_t *sample_output_display;
140 const float *sample_cache_clut;
147};
148
149static inline dt_aligned_pixel_simd_t _set_vector(const float x, const float y, const float z)
150{
151 return (dt_aligned_pixel_simd_t){ x, y, z, 0.f };
152}
153
154static inline float _dot3(const dt_aligned_pixel_simd_t a, const dt_aligned_pixel_simd_t b)
155{
156 return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
157}
158
159static inline dt_aligned_pixel_simd_t _cross3(const dt_aligned_pixel_simd_t a, const dt_aligned_pixel_simd_t b)
160{
161 return _set_vector(a[1] * b[2] - a[2] * b[1],
162 a[2] * b[0] - a[0] * b[2],
163 a[0] * b[1] - a[1] * b[0]);
164}
165
166static inline dt_aligned_pixel_simd_t _normalize3(const dt_aligned_pixel_simd_t vector)
167{
168 const float norm = sqrtf(_dot3(vector, vector));
169 if(norm < 1e-6f) return vector;
170
171 const float inv_norm = 1.f / norm;
172 return (dt_aligned_pixel_simd_t){ vector[0] * inv_norm, vector[1] * inv_norm, vector[2] * inv_norm, 0.f };
173}
174
175static inline float _wrap_degrees_pm180(float angle)
176{
177 while(angle <= -180.f) angle += 360.f;
178 while(angle > 180.f) angle -= 360.f;
179 return angle;
180}
181
182static inline float _shift_distance_percent(const dt_aligned_pixel_simd_t input_rgb,
183 const dt_aligned_pixel_simd_t output_rgb)
184{
185 const float dr = output_rgb[0] - input_rgb[0];
186 const float dg = output_rgb[1] - input_rgb[1];
187 const float db = output_rgb[2] - input_rgb[2];
188 return 100.f * sqrtf((dr * dr + dg * dg + db * db) / 3.f);
189}
190
191static inline dt_aligned_pixel_simd_t _clamp01_simd(const dt_aligned_pixel_simd_t value)
192{
193 dt_aligned_pixel_simd_t out = dt_simd_max_zero(value);
194
195 for(int c = 0; c < 3; c++)
196 out[c] = fminf(out[c], 1.f);
197
198 out[3] = 0.f;
199 return out;
200}
201
207static inline void _clamp_display_rgb_array_simd(const dt_aligned_pixel_simd_t *source_rgb,
208 dt_aligned_pixel_simd_t *display_rgb, const size_t count)
209{
210 if(IS_NULL_PTR(source_rgb) || !display_rgb || count == 0) return;
211 __OMP_SIMD__(aligned(source_rgb, display_rgb:16))
212 for(size_t k = 0; k < count; k++)
213 display_rgb[k] = _clamp01_simd(source_rgb[k]);
214}
215
222static inline dt_aligned_pixel_simd_t _transform_single_rgb_matrix(
223 const dt_aligned_pixel_simd_t input,
224 const dt_iop_order_iccprofile_info_t *const profile_info_from,
225 const dt_iop_order_iccprofile_info_t *const profile_info_to)
226{
227 dt_aligned_pixel_t xyz = { 0.f };
228 dt_aligned_pixel_t output = { 0.f };
229
230 dt_ioppr_rgb_matrix_to_xyz((const float *)&input, xyz, profile_info_from->matrix_in_transposed,
231 profile_info_from->lut_in, profile_info_from->unbounded_coeffs_in,
232 profile_info_from->lutsize, profile_info_from->nonlinearlut);
233
234 if(profile_info_to->nonlinearlut)
235 {
236 dt_aligned_pixel_t linear_rgb = { 0.f };
237 dt_apply_transposed_color_matrix(xyz, profile_info_to->matrix_out_transposed, linear_rgb);
238 _apply_trc(linear_rgb, output, profile_info_to->lut_out, profile_info_to->unbounded_coeffs_out,
239 profile_info_to->lutsize);
240 }
241 else
242 dt_apply_transposed_color_matrix(xyz, profile_info_to->matrix_out_transposed, output);
243
244 return _clamp01_simd(dt_load_simd_aligned(output));
245}
246
247static inline dt_aligned_pixel_simd_t _to_display_rgb(const dt_lut_viewer_t *viewer,
248 const dt_aligned_pixel_simd_t work_rgb)
249{
250 dt_aligned_pixel_simd_t display_rgb = _clamp01_simd(work_rgb);
251
252 if(IS_NULL_PTR(viewer->lut_profile) || !viewer->display_profile) return display_rgb;
253
254 if(!isnan(viewer->lut_profile->matrix_in[0][0]) && !isnan(viewer->lut_profile->matrix_out[0][0])
255 && !isnan(viewer->display_profile->matrix_in[0][0]) && !isnan(viewer->display_profile->matrix_out[0][0]))
256 return _transform_single_rgb_matrix(work_rgb, viewer->lut_profile, viewer->display_profile);
257
258 dt_aligned_pixel_simd_t in = work_rgb;
259 dt_aligned_pixel_simd_t out = work_rgb;
260 dt_ioppr_transform_image_colorspace_rgb((float *)&in, (float *)&out, 1, 1, viewer->lut_profile, viewer->display_profile,
261 "lut viewer swatch");
262
263 return _clamp01_simd(out);
264}
265
266static inline void _to_display_rgb_array(const dt_lut_viewer_t *viewer, const dt_aligned_pixel_simd_t *work_rgb,
267 dt_aligned_pixel_simd_t *display_rgb, const size_t count, const char *message)
268{
269 if(IS_NULL_PTR(work_rgb) || !display_rgb || count == 0) return;
270
271 _clamp_display_rgb_array_simd(work_rgb, display_rgb, count);
272 if(IS_NULL_PTR(viewer->lut_profile) || !viewer->display_profile) return;
273
274 dt_ioppr_transform_image_colorspace_rgb((float *)work_rgb, (float *)display_rgb, (int)count, 1,
275 viewer->lut_profile, viewer->display_profile, message);
276 _clamp_display_rgb_array_simd(display_rgb, display_rgb, count);
277}
278
280{
281 if(viewer->surface)
282 {
283 cairo_surface_destroy(viewer->surface);
284 viewer->surface = NULL;
285 }
286}
287
289{
290 viewer->sample_cache_valid = FALSE;
291 viewer->sample_count = 0;
292 viewer->sample_white_index = 0;
294}
295
296static inline gboolean _show_control_nodes(const dt_lut_viewer_t *viewer)
297{
298 return viewer && viewer->show_control_nodes
299 && gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(viewer->show_control_nodes));
300}
301
302static int _ensure_sample_cache_capacity(dt_lut_viewer_t *viewer, const size_t capacity)
303{
304 if(viewer->sample_capacity >= capacity) return 0;
305
310 viewer->sample_input_work = dt_calloc_align(capacity * sizeof(dt_aligned_pixel_simd_t));
311 viewer->sample_output_work = dt_calloc_align(capacity * sizeof(dt_aligned_pixel_simd_t));
312 viewer->sample_input_display = dt_calloc_align(capacity * sizeof(dt_aligned_pixel_simd_t));
313 viewer->sample_output_display = dt_calloc_align(capacity * sizeof(dt_aligned_pixel_simd_t));
314
316 || !viewer->sample_output_display)
317 {
322 viewer->sample_input_work = NULL;
323 viewer->sample_output_work = NULL;
324 viewer->sample_input_display = NULL;
325 viewer->sample_output_display = NULL;
326 viewer->sample_capacity = 0;
328 return 1;
329 }
330
331 viewer->sample_capacity = capacity;
332 return 0;
333}
334
350
351static inline gboolean _gamut_matches_lut_profile(const dt_lut_viewer_t *viewer,
352 const dt_lut_viewer_gamut_t gamut)
353{
354 if(IS_NULL_PTR(viewer->lut_profile)) return FALSE;
355
356 switch(gamut)
357 {
359 return viewer->lut_profile->type == DT_COLORSPACE_SRGB
363 return viewer->lut_profile->type == DT_COLORSPACE_ADOBERGB;
365 return viewer->lut_profile->type == DT_COLORSPACE_DISPLAY_P3
367 || viewer->lut_profile->type == DT_COLORSPACE_PQ_P3;
372 default:
373 return FALSE;
374 }
375}
376
385{
386 const dt_colorspaces_color_profile_t *profile
388 if(IS_NULL_PTR(profile)|| IS_NULL_PTR(profile->profile)) return 1;
389
390 const cmsCIEXYZ *red = cmsReadTag(profile->profile, cmsSigRedColorantTag);
391 const cmsCIEXYZ *green = cmsReadTag(profile->profile, cmsSigGreenColorantTag);
392 const cmsCIEXYZ *blue = cmsReadTag(profile->profile, cmsSigBlueColorantTag);
393 if(IS_NULL_PTR(red) || IS_NULL_PTR(green) || IS_NULL_PTR(blue)) return 1;
394
395 dt_colormatrix_t rgb_to_xyz = { { 0.f } };
396 rgb_to_xyz[0][0] = red->X;
397 rgb_to_xyz[0][1] = green->X;
398 rgb_to_xyz[0][2] = blue->X;
399 rgb_to_xyz[1][0] = red->Y;
400 rgb_to_xyz[1][1] = green->Y;
401 rgb_to_xyz[1][2] = blue->Y;
402 rgb_to_xyz[2][0] = red->Z;
403 rgb_to_xyz[2][1] = green->Z;
404 rgb_to_xyz[2][2] = blue->Z;
405
406 return mat3SSEinv(xyz_to_rgb, rgb_to_xyz);
407}
408
409static inline gboolean _sample_fits_gamut(const dt_iop_order_iccprofile_info_t *lut_profile,
410 const dt_colormatrix_t xyz_to_rgb,
411 const dt_aligned_pixel_simd_t rgb)
412{
413 const float epsilon = 1e-3f;
414 dt_aligned_pixel_simd_t xyz = { 0.f };
415 dt_aligned_pixel_simd_t gamut_rgb = { 0.f };
416 dt_apply_transposed_color_matrix((float *)&rgb, lut_profile->matrix_in_transposed, (float *)&xyz);
417 dot_product((float *)&xyz, xyz_to_rgb, (float *)&gamut_rgb);
418
425 return gamut_rgb[0] >= -epsilon && gamut_rgb[0] <= 1.f + epsilon
426 && gamut_rgb[1] >= -epsilon && gamut_rgb[1] <= 1.f + epsilon
427 && gamut_rgb[2] >= -epsilon && gamut_rgb[2] <= 1.f + epsilon;
428}
429
430static void _build_projection(dt_lut_viewer_projection_t *projection, const float rotation_around_axis,
431 const float rotation_of_axis, const float slice_depth, const float slice_thickness,
432 const float zoom,
433 const float pan_x, const float pan_y, const int width, const int height)
434{
435 static const dt_aligned_pixel_simd_t axis = { 0.5773502691896258f, 0.5773502691896258f, 0.5773502691896258f, 0.f };
436 static const dt_aligned_pixel_simd_t chroma_x = { 0.7071067811865475f, -0.7071067811865475f, 0.f, 0.f };
437 static const dt_aligned_pixel_simd_t chroma_y = { 0.4082482904638630f, 0.4082482904638630f, -0.8164965809277260f, 0.f };
438
439 const float azimuth = rotation_around_axis * M_PI_F / 180.f;
440 const float tilt = rotation_of_axis * M_PI_F / 180.f;
441 dt_aligned_pixel_simd_t rotated_x = { 0.f };
442 dt_aligned_pixel_simd_t rotated_y = { 0.f };
443
444 for(int c = 0; c < 3; c++)
445 {
446 rotated_x[c] = cosf(azimuth) * chroma_x[c] + sinf(azimuth) * chroma_y[c];
447 rotated_y[c] = -sinf(azimuth) * chroma_x[c] + cosf(azimuth) * chroma_y[c];
448 projection->screen_z[c] = cosf(tilt) * rotated_y[c] + sinf(tilt) * axis[c];
449 projection->screen_x[c] = rotated_x[c];
450 }
451
452 projection->screen_y = _normalize3(_cross3(projection->screen_x, projection->screen_z));
453 projection->screen_z = _normalize3(projection->screen_z);
454
455 projection->min_x = INFINITY;
456 projection->max_x = -INFINITY;
457 projection->min_y = INFINITY;
458 projection->max_y = -INFINITY;
459 projection->min_depth = INFINITY;
460 projection->max_depth = -INFINITY;
461
467 for(int corner = 0; corner < 8; corner++)
468 {
469 const dt_aligned_pixel_simd_t centered = {
470 ((corner & 1) ? 1.f : 0.f) - 0.5f,
471 ((corner & 2) ? 1.f : 0.f) - 0.5f,
472 ((corner & 4) ? 1.f : 0.f) - 0.5f,
473 0.f
474 };
475
476 const float px = _dot3(centered, projection->screen_x);
477 const float py = _dot3(centered, projection->screen_y);
478 const float pz = _dot3(centered, projection->screen_z);
479
480 projection->min_x = fminf(projection->min_x, px);
481 projection->max_x = fmaxf(projection->max_x, px);
482 projection->min_y = fminf(projection->min_y, py);
483 projection->max_y = fmaxf(projection->max_y, py);
484 projection->min_depth = fminf(projection->min_depth, pz);
485 projection->max_depth = fmaxf(projection->max_depth, pz);
486 }
487
488 const float span_x = fmaxf(projection->max_x - projection->min_x, 1e-3f);
489 const float span_y = fmaxf(projection->max_y - projection->min_y, 1e-3f);
490 const float available_width = fmaxf((float)width - 2.f * DT_LUT_VIEWER_MARGIN, 1.f);
491 const float available_height = fmaxf((float)height - 2.f * DT_LUT_VIEWER_MARGIN, 1.f);
492
493 projection->scale = 0.9f * CLAMP(zoom, 0.25f, 8.f) * fminf(available_width / span_x, available_height / span_y);
494 projection->offset_x = 0.5f * ((float)width - projection->scale * (projection->min_x + projection->max_x)) + pan_x;
495 projection->offset_y = 0.5f * ((float)height + projection->scale * (projection->min_y + projection->max_y)) + pan_y;
496 projection->slice_depth = projection->min_depth
497 + CLAMP(slice_depth, 0.f, 100.f) * 0.01f
498 * (projection->max_depth - projection->min_depth);
499 projection->slice_half_thickness = CLAMP(slice_thickness, 0.f, 100.f) * 0.01f
500 * (projection->max_depth - projection->min_depth);
501}
502
503static inline void _project_point(const dt_lut_viewer_projection_t *projection, const dt_aligned_pixel_simd_t rgb,
504 float *const x, float *const y, float *const depth)
505{
506 const dt_aligned_pixel_simd_t centered = { rgb[0] - 0.5f, rgb[1] - 0.5f, rgb[2] - 0.5f, 0.f };
507 const float px = _dot3(centered, projection->screen_x);
508 const float py = _dot3(centered, projection->screen_y);
509
510 *x = projection->offset_x + projection->scale * px;
511 *y = projection->offset_y - projection->scale * py;
512 *depth = _dot3(centered, projection->screen_z);
513}
514
515static inline void _draw_arrow(cairo_t *cr, const float x0, const float y0, const float x1, const float y1,
516 const float radius0, const float radius1, const dt_aligned_pixel_simd_t color)
517{
518 const float dx = x1 - x0;
519 const float dy = y1 - y0;
520 const float length = sqrtf(dx * dx + dy * dy);
521 if(length < 1e-3f) return;
522
523 const float ux = dx / length;
524 const float uy = dy / length;
525 const float start_x = x0 + radius0 * ux;
526 const float start_y = y0 + radius0 * uy;
527 const float end_x = x1 - radius1 * ux;
528 const float end_y = y1 - radius1 * uy;
529 const float visible_dx = end_x - start_x;
530 const float visible_dy = end_y - start_y;
531 const float visible_length = sqrtf(visible_dx * visible_dx + visible_dy * visible_dy);
532 if(visible_length < 1e-3f) return;
533
534 cairo_set_source_rgba(cr, color[0], color[1], color[2], 0.65);
535 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.8f));
536 cairo_move_to(cr, start_x, start_y);
537 cairo_line_to(cr, end_x, end_y);
538 cairo_stroke(cr);
539
540 const float head = DT_PIXEL_APPLY_DPI(8.f);
541 const float nx = -uy;
542 const float ny = ux;
543
544 cairo_move_to(cr, end_x, end_y);
545 cairo_line_to(cr, end_x - head * ux + 0.5f * head * nx, end_y - head * uy + 0.5f * head * ny);
546 cairo_line_to(cr, end_x - head * ux - 0.5f * head * nx, end_y - head * uy - 0.5f * head * ny);
547 cairo_close_path(cr);
548 cairo_set_source_rgba(cr, color[0], color[1], color[2], 0.75);
549 cairo_fill(cr);
550}
551
552static void _draw_cube(cairo_t *cr, const dt_lut_viewer_t *viewer, const dt_lut_viewer_projection_t *projection)
553{
554 float x[8] = { 0.f };
555 float y[8] = { 0.f };
556 float depth = 0.f;
557
558 for(int corner = 0; corner < 8; corner++)
559 {
560 const dt_aligned_pixel_simd_t rgb = {
561 (float)((corner & 1) != 0),
562 (float)((corner & 2) != 0),
563 (float)((corner & 4) != 0),
564 0.f
565 };
566 _project_point(projection, rgb, &x[corner], &y[corner], &depth);
567 }
568
570 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.f));
571
572 for(int corner = 0; corner < 8; corner++)
573 {
574 for(int axis = 0; axis < 3; axis++)
575 {
576 const int other = corner ^ (1 << axis);
577 if(other < corner) continue;
578
579 cairo_move_to(cr, x[corner], y[corner]);
580 cairo_line_to(cr, x[other], y[other]);
581 cairo_stroke(cr);
582 }
583 }
584
585 const dt_aligned_pixel_simd_t black = { 0.f, 0.f, 0.f, 0.f };
586 const dt_aligned_pixel_simd_t white = { 1.f, 1.f, 1.f, 0.f };
587 float x0 = 0.f, y0 = 0.f;
588 float x1 = 0.f, y1 = 0.f;
589 dt_aligned_pixel_simd_t display_rgb = { 0.f };
590 cairo_pattern_t *gradient = NULL;
591 _project_point(projection, black, &x0, &y0, &depth);
592 _project_point(projection, white, &x1, &y1, &depth);
593
599 gradient = cairo_pattern_create_linear(x0, y0, x1, y1);
600 for(int k = 0; k <= 16; k++)
601 {
602 const float grey = (float)k / 16.f;
603 display_rgb = _to_display_rgb(viewer, (dt_aligned_pixel_simd_t){ grey, grey, grey, 0.f });
604 cairo_pattern_add_color_stop_rgba(gradient, grey, display_rgb[0], display_rgb[1], display_rgb[2], 0.85);
605 }
606
607 cairo_set_source(cr, gradient);
608 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.f));
609 cairo_move_to(cr, x0, y0);
610 cairo_line_to(cr, x1, y1);
611 cairo_stroke(cr);
612 cairo_pattern_destroy(gradient);
613}
614
615static void _draw_axes(cairo_t *cr, const dt_lut_viewer_t *viewer, const dt_lut_viewer_projection_t *projection)
616{
617 static const dt_aligned_pixel_simd_t center = { 0.5f, 0.5f, 0.5f, 0.f };
618 const dt_aligned_pixel_simd_t red = { 0.5f + DT_LUT_VIEWER_AXIS_LENGTH / projection->scale, 0.5f, 0.5f, 0.f };
619 const dt_aligned_pixel_simd_t green = { 0.5f, 0.5f + DT_LUT_VIEWER_AXIS_LENGTH / projection->scale, 0.5f, 0.f };
620 const dt_aligned_pixel_simd_t blue = { 0.5f, 0.5f, 0.5f + DT_LUT_VIEWER_AXIS_LENGTH / projection->scale, 0.f };
621 const dt_aligned_pixel_simd_t axis_work[3] = {
622 { 1.f, 0.f, 0.f, 0.f },
623 { 0.f, 1.f, 0.f, 0.f },
624 { 0.f, 0.f, 1.f, 0.f }
625 };
626 dt_aligned_pixel_simd_t axis_display[3] = { { 0.f }, { 0.f }, { 0.f } };
627 float cx = 0.f, cy = 0.f, depth = 0.f;
628 float px = 0.f, py = 0.f;
629
630 _project_point(projection, center, &cx, &cy, &depth);
631 for(int k = 0; k < 3; k++) axis_display[k] = _to_display_rgb(viewer, axis_work[k]);
632
633 _project_point(projection, red, &px, &py, &depth);
634 cairo_set_source_rgba(cr, axis_display[0][0], axis_display[0][1], axis_display[0][2], 0.9);
635 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.f));
636 cairo_move_to(cr, cx, cy);
637 cairo_line_to(cr, px, py);
638 cairo_stroke(cr);
639
640 _project_point(projection, green, &px, &py, &depth);
641 cairo_set_source_rgba(cr, axis_display[1][0], axis_display[1][1], axis_display[1][2], 0.9);
642 cairo_move_to(cr, cx, cy);
643 cairo_line_to(cr, px, py);
644 cairo_stroke(cr);
645
646 _project_point(projection, blue, &px, &py, &depth);
647 cairo_set_source_rgba(cr, axis_display[2][0], axis_display[2][1], axis_display[2][2], 0.9);
648 cairo_move_to(cr, cx, cy);
649 cairo_line_to(cr, px, py);
650 cairo_stroke(cr);
651}
652
653static int _sample_stride(const int level)
654{
655 int stride = 1;
656
657 while(((level + stride - 1) / stride) * ((level + stride - 1) / stride) * ((level + stride - 1) / stride)
659 stride++;
660
661 return stride;
662}
663
664static inline int _sample_count(const int level, const int stride)
665{
666 return MAX((level + stride - 1) / stride, 2);
667}
668
669static inline int _sample_index(const int sample, const int samples, const int level)
670{
671 if(samples <= 1) return 0;
672 return MIN((int)lroundf((float)sample * (float)(level - 1) / (float)(samples - 1)), level - 1);
673}
674
675static void _draw_samples(cairo_t *cr, const dt_lut_viewer_t *viewer,
676 const dt_lut_viewer_projection_t *projection, const dt_lut_viewer_gamut_t gamut)
677{
678 const gboolean show_control_nodes = _show_control_nodes(viewer);
679 if(IS_NULL_PTR(viewer->lut_profile)) return;
680 if(show_control_nodes)
681 {
682 if(!viewer->control_nodes || viewer->control_node_count == 0) return;
683 }
684 else if(!viewer->clut || viewer->clut_level < 2)
685 return;
686
687 dt_lut_viewer_t *mutable_viewer = (dt_lut_viewer_t *)viewer;
688 const gboolean log_perf = (darktable.unmuted & DT_DEBUG_PERF) != 0;
689 const double total_start = log_perf ? dt_get_wtime() : 0.0;
690 double collect_done = 0.0;
691 double convert_done = 0.0;
692 dt_colormatrix_t xyz_to_rgb = { { 0.f } };
693 const gboolean same_gamut_as_lut_profile = _gamut_matches_lut_profile(viewer, gamut);
694 if(!same_gamut_as_lut_profile && _get_xyz_to_rgb_matrix(gamut, xyz_to_rgb)) return;
695
696 const int stride = show_control_nodes ? 1 : _sample_stride(viewer->clut_level);
697 const int samples = show_control_nodes ? 0 : _sample_count(viewer->clut_level, stride);
698 const int level2 = viewer->clut_level * viewer->clut_level;
699 const size_t max_samples = show_control_nodes ? viewer->control_node_count
700 : (size_t)samples * (size_t)samples * (size_t)samples;
701 const float rotation_around_axis = dt_bauhaus_slider_get(viewer->rotation_around_axis);
702 const float rotation_of_axis = dt_bauhaus_slider_get(viewer->rotation_of_axis);
703 const float slice_depth = dt_bauhaus_slider_get(viewer->slice_depth);
704 const float slice_thickness = dt_bauhaus_slider_get(viewer->slice_thickness);
705 const float shift_threshold = dt_bauhaus_slider_get(viewer->shift_threshold);
706 const float zoom_scale = sqrtf(fmaxf(viewer->zoom, 0.25f));
707 const float target_radius = DT_PIXEL_APPLY_DPI(2.8f) * zoom_scale;
708 const float destination_radius = DT_PIXEL_APPLY_DPI(3.4f) * zoom_scale;
709 const gboolean rebuild_sample_cache
710 = !viewer->sample_cache_valid
711 || fabsf(viewer->sample_cache_rotation_around_axis - rotation_around_axis) > 1e-6f
712 || fabsf(viewer->sample_cache_rotation_of_axis - rotation_of_axis) > 1e-6f
713 || fabsf(viewer->sample_cache_slice_depth - slice_depth) > 1e-6f
714 || fabsf(viewer->sample_cache_slice_thickness - slice_thickness) > 1e-6f
715 || fabsf(viewer->sample_cache_shift_threshold - shift_threshold) > 1e-6f
716 || viewer->sample_cache_gamut != gamut
717 || viewer->sample_cache_clut != viewer->clut
718 || viewer->sample_cache_clut_level != viewer->clut_level
719 || viewer->sample_cache_lut_profile != viewer->lut_profile
721 || viewer->sample_cache_control_nodes != viewer->control_nodes
723 || viewer->sample_cache_show_control_nodes != show_control_nodes;
724
725 if(rebuild_sample_cache)
726 {
727 if(_ensure_sample_cache_capacity(mutable_viewer, max_samples)) return;
728
729 mutable_viewer->sample_count = 0;
730 mutable_viewer->sample_white_index = 0;
731 mutable_viewer->sample_draw_white_last = FALSE;
732
733 if(show_control_nodes)
734 {
735 for(size_t k = 0; k < viewer->control_node_count; k++)
736 {
737 const dt_aligned_pixel_simd_t input_rgb = {
738 CLAMP(viewer->control_nodes[k].input_rgb[0], 0.f, 1.f),
739 CLAMP(viewer->control_nodes[k].input_rgb[1], 0.f, 1.f),
740 CLAMP(viewer->control_nodes[k].input_rgb[2], 0.f, 1.f),
741 0.f
742 };
743 const dt_aligned_pixel_simd_t output_rgb = {
744 CLAMP(viewer->control_nodes[k].output_rgb[0], 0.f, 1.f),
745 CLAMP(viewer->control_nodes[k].output_rgb[1], 0.f, 1.f),
746 CLAMP(viewer->control_nodes[k].output_rgb[2], 0.f, 1.f),
747 0.f
748 };
749 if(!same_gamut_as_lut_profile && !_sample_fits_gamut(viewer->lut_profile, xyz_to_rgb, input_rgb)) continue;
750
751 float x0 = 0.f, y0 = 0.f, depth0 = 0.f;
752 float x1 = 0.f, y1 = 0.f, depth1 = 0.f;
753 _project_point(projection, input_rgb, &x0, &y0, &depth0);
754 _project_point(projection, output_rgb, &x1, &y1, &depth1);
755
756 const gboolean target_in_slice = fabsf(depth0 - projection->slice_depth) <= projection->slice_half_thickness;
757 const gboolean destination_in_slice
758 = fabsf(depth1 - projection->slice_depth) <= projection->slice_half_thickness;
759 if(!target_in_slice && !destination_in_slice) continue;
760
761 mutable_viewer->sample_input_work[mutable_viewer->sample_count] = input_rgb;
762 mutable_viewer->sample_output_work[mutable_viewer->sample_count] = output_rgb;
763
764 if(input_rgb[0] >= 1.f - 1e-6f && input_rgb[1] >= 1.f - 1e-6f && input_rgb[2] >= 1.f - 1e-6f)
765 {
766 mutable_viewer->sample_white_index = mutable_viewer->sample_count;
767 mutable_viewer->sample_draw_white_last = TRUE;
768 }
769
770 mutable_viewer->sample_count++;
771 }
772 }
773 else
774 {
787 for(int sample_b = 0; sample_b < samples; sample_b++)
788 for(int sample_g = 0; sample_g < samples; sample_g++)
789 for(int sample_r = 0; sample_r < samples; sample_r++)
790 {
791 const int b = _sample_index(sample_b, samples, viewer->clut_level);
792 const int g = _sample_index(sample_g, samples, viewer->clut_level);
793 const int r = _sample_index(sample_r, samples, viewer->clut_level);
794 const dt_aligned_pixel_simd_t input_rgb = {
795 (float)r / (float)(viewer->clut_level - 1),
796 (float)g / (float)(viewer->clut_level - 1),
797 (float)b / (float)(viewer->clut_level - 1),
798 0.f
799 };
800
801 if(!same_gamut_as_lut_profile && !_sample_fits_gamut(viewer->lut_profile, xyz_to_rgb, input_rgb))
802 continue;
803
804 const size_t index = (size_t)(r + g * viewer->clut_level + b * level2) * 3;
805 const dt_aligned_pixel_simd_t output_rgb = {
806 CLAMP(viewer->clut[index + 0], 0.f, 1.f),
807 CLAMP(viewer->clut[index + 1], 0.f, 1.f),
808 CLAMP(viewer->clut[index + 2], 0.f, 1.f),
809 0.f
810 };
811 if(_shift_distance_percent(input_rgb, output_rgb) < shift_threshold) continue;
812
813 float x0 = 0.f, y0 = 0.f, depth0 = 0.f;
814 float x1 = 0.f, y1 = 0.f, depth1 = 0.f;
815 _project_point(projection, input_rgb, &x0, &y0, &depth0);
816 _project_point(projection, output_rgb, &x1, &y1, &depth1);
817
818 const gboolean target_in_slice
819 = fabsf(depth0 - projection->slice_depth) <= projection->slice_half_thickness;
820 const gboolean destination_in_slice
821 = fabsf(depth1 - projection->slice_depth) <= projection->slice_half_thickness;
822 if(!target_in_slice && !destination_in_slice) continue;
823
824 mutable_viewer->sample_input_work[mutable_viewer->sample_count] = input_rgb;
825 mutable_viewer->sample_output_work[mutable_viewer->sample_count] = output_rgb;
826
827 if(r == viewer->clut_level - 1 && g == viewer->clut_level - 1 && b == viewer->clut_level - 1)
828 {
829 mutable_viewer->sample_white_index = mutable_viewer->sample_count;
830 mutable_viewer->sample_draw_white_last = TRUE;
831 }
832 mutable_viewer->sample_count++;
833 }
834 }
835
836 if(log_perf) collect_done = dt_get_wtime();
837
839 "lut viewer swatch inputs");
841 "lut viewer swatch outputs");
842 if(log_perf) convert_done = dt_get_wtime();
843
844 mutable_viewer->sample_cache_rotation_around_axis = rotation_around_axis;
845 mutable_viewer->sample_cache_rotation_of_axis = rotation_of_axis;
846 mutable_viewer->sample_cache_slice_depth = slice_depth;
847 mutable_viewer->sample_cache_slice_thickness = slice_thickness;
848 mutable_viewer->sample_cache_shift_threshold = shift_threshold;
849 mutable_viewer->sample_cache_gamut = gamut;
850 mutable_viewer->sample_cache_clut = viewer->clut;
851 mutable_viewer->sample_cache_clut_level = viewer->clut_level;
852 mutable_viewer->sample_cache_lut_profile = viewer->lut_profile;
853 mutable_viewer->sample_cache_display_profile = viewer->display_profile;
854 mutable_viewer->sample_cache_control_nodes = viewer->control_nodes;
855 mutable_viewer->sample_cache_control_node_count = viewer->control_node_count;
856 mutable_viewer->sample_cache_show_control_nodes = show_control_nodes;
857 mutable_viewer->sample_cache_valid = TRUE;
858 }
859 else if(log_perf)
860 {
861 collect_done = total_start;
862 convert_done = total_start;
863 }
864
865 for(size_t k = 0; k < viewer->sample_count; k++)
866 {
867 if(viewer->sample_draw_white_last && k == viewer->sample_white_index) continue;
868
869 float x0 = 0.f, y0 = 0.f, depth0 = 0.f;
870 float x1 = 0.f, y1 = 0.f, depth1 = 0.f;
871 _project_point(projection, viewer->sample_input_work[k], &x0, &y0, &depth0);
872 _project_point(projection, viewer->sample_output_work[k], &x1, &y1, &depth1);
873
874 _draw_arrow(cr, x0, y0, x1, y1, target_radius, destination_radius, viewer->sample_output_display[k]);
875
876 cairo_arc(cr, x0, y0, target_radius, 0.f, 2.f * M_PI_F);
877 cairo_set_source_rgba(cr, viewer->sample_input_display[k][0], viewer->sample_input_display[k][1],
878 viewer->sample_input_display[k][2], 0.8);
879 cairo_fill_preserve(cr);
880 cairo_set_source_rgba(cr, 0.1, 0.1, 0.1, 0.6);
881 cairo_stroke(cr);
882
883 cairo_arc(cr, x1, y1, destination_radius, 0.f, 2.f * M_PI_F);
884 cairo_set_source_rgba(cr, viewer->sample_output_display[k][0], viewer->sample_output_display[k][1],
885 viewer->sample_output_display[k][2], 0.9);
886 cairo_fill_preserve(cr);
887 cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 0.5);
888 cairo_stroke(cr);
889 }
890
891 if(viewer->sample_draw_white_last)
892 {
893 const size_t k = viewer->sample_white_index;
894 float x0 = 0.f, y0 = 0.f, depth0 = 0.f;
895 float x1 = 0.f, y1 = 0.f, depth1 = 0.f;
896 _project_point(projection, viewer->sample_input_work[k], &x0, &y0, &depth0);
897 _project_point(projection, viewer->sample_output_work[k], &x1, &y1, &depth1);
898
899 _draw_arrow(cr, x0, y0, x1, y1, target_radius, destination_radius, viewer->sample_output_display[k]);
900
901 cairo_arc(cr, x0, y0, target_radius, 0.f, 2.f * M_PI_F);
902 cairo_set_source_rgba(cr, viewer->sample_input_display[k][0], viewer->sample_input_display[k][1],
903 viewer->sample_input_display[k][2], 0.95);
904 cairo_fill_preserve(cr);
905 cairo_set_source_rgba(cr, 0.2, 0.2, 0.2, 0.8);
906 cairo_stroke(cr);
907
908 cairo_arc(cr, x1, y1, destination_radius, 0.f, 2.f * M_PI_F);
909 cairo_set_source_rgba(cr, viewer->sample_output_display[k][0], viewer->sample_output_display[k][1],
910 viewer->sample_output_display[k][2], 0.95);
911 cairo_fill_preserve(cr);
912 cairo_set_source_rgba(cr, 0.2, 0.2, 0.2, 0.8);
913 cairo_stroke(cr);
914 }
915
916 if(log_perf)
917 {
918 const double total_done = dt_get_wtime();
920 "[lut_viewer] draw_samples mode=%s level=%u sparse=%d^3 max=%" G_GSIZE_FORMAT " drawn=%" G_GSIZE_FORMAT " gamut=%d cache=%s collect=%.3fms convert=%.3fms paint=%.3fms total=%.3fms\n",
921 show_control_nodes ? "controls" : "lut", viewer->clut_level, samples, max_samples, viewer->sample_count, gamut,
922 rebuild_sample_cache ? "rebuild" : "reuse",
923 1000.0 * (collect_done - total_start),
924 1000.0 * (convert_done - collect_done),
925 1000.0 * (total_done - convert_done),
926 1000.0 * (total_done - total_start));
927 }
928}
929
930static void _draw_placeholder(cairo_t *cr, const int width, const int height, const char *message)
931{
932 cairo_text_extents_t extents;
933 cairo_select_font_face(cr, "sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
934 cairo_set_font_size(cr, DT_PIXEL_APPLY_DPI(12.f));
935 cairo_text_extents(cr, message, &extents);
936 cairo_set_source_rgba(cr, 0.9, 0.9, 0.9, 0.7);
937 cairo_move_to(cr, 0.5f * ((float)width - extents.width), 0.5f * ((float)height - extents.height));
938 cairo_show_text(cr, message);
939}
940
941static void _render_surface(dt_lut_viewer_t *viewer, const int width, const int height)
942{
943 const gboolean log_perf = (darktable.unmuted & DT_DEBUG_PERF) != 0;
944 const double start = log_perf ? dt_get_wtime() : 0.0;
945 const double ppd = (darktable.gui && darktable.gui->ppd > 0.0) ? darktable.gui->ppd : 1.0;
946 if(viewer->clut_lock) dt_pthread_rwlock_rdlock(viewer->clut_lock);
947 _invalidate_surface(viewer);
948 viewer->surface = dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
949 MAX((int)ceil((double)width * ppd), 1),
950 MAX((int)ceil((double)height * ppd), 1));
951 cairo_surface_set_device_scale(viewer->surface, ppd, ppd);
952 cairo_t *cr = cairo_create(viewer->surface);
953 GtkStyleContext *context = gtk_widget_get_style_context(GTK_WIDGET(viewer->area));
954
955 gtk_render_background(context, cr, 0, 0, width, height);
956 gtk_render_frame(context, cr, 0, 0, width, height);
957
958 if(IS_NULL_PTR(viewer->lut_profile))
959 {
960 _draw_placeholder(cr, width, height, _("no LUT to display"));
961 cairo_destroy(cr);
962 if(viewer->clut_lock) dt_pthread_rwlock_unlock(viewer->clut_lock);
963 if(log_perf)
965 "[lut_viewer] render_surface %dx%d ppd=%.2f placeholder=1 total=%.3fms\n",
966 width, height, ppd, 1000.0 * (dt_get_wtime() - start));
967 return;
968 }
969
970 if(_show_control_nodes(viewer))
971 {
972 if(!viewer->control_nodes || viewer->control_node_count == 0)
973 {
974 _draw_placeholder(cr, width, height, _("no control nodes to display"));
975 cairo_destroy(cr);
976 if(viewer->clut_lock) dt_pthread_rwlock_unlock(viewer->clut_lock);
977 return;
978 }
979 }
980 else if(!viewer->clut || viewer->clut_level < 2)
981 {
982 _draw_placeholder(cr, width, height, _("no LUT to display"));
983 cairo_destroy(cr);
984 if(viewer->clut_lock) dt_pthread_rwlock_unlock(viewer->clut_lock);
985 if(log_perf)
987 "[lut_viewer] render_surface %dx%d ppd=%.2f placeholder=1 total=%.3fms\n",
988 width, height, ppd, 1000.0 * (dt_get_wtime() - start));
989 return;
990 }
991
992 const dt_lut_viewer_gamut_t gamut
995 _build_projection(&projection,
1000 viewer->zoom,
1001 viewer->pan_x,
1002 viewer->pan_y,
1003 width, height);
1004
1005 _draw_cube(cr, viewer, &projection);
1006 _draw_axes(cr, viewer, &projection);
1007 _draw_samples(cr, viewer, &projection, gamut);
1008 cairo_destroy(cr);
1009 if(viewer->clut_lock) dt_pthread_rwlock_unlock(viewer->clut_lock);
1010
1011 if(log_perf)
1013 "[lut_viewer] render_surface %dx%d ppd=%.2f level=%u gamut=%d total=%.3fms\n",
1014 width, height, ppd, viewer->clut_level, gamut, 1000.0 * (dt_get_wtime() - start));
1015}
1016
1017static gboolean _draw_callback(GtkWidget *widget, cairo_t *cr, gpointer user_data)
1018{
1019 dt_lut_viewer_t *viewer = (dt_lut_viewer_t *)user_data;
1020 GtkAllocation allocation;
1021 gtk_widget_get_allocation(widget, &allocation);
1022
1023 const float rotation_around_axis = dt_bauhaus_slider_get(viewer->rotation_around_axis);
1024 const float rotation_of_axis = dt_bauhaus_slider_get(viewer->rotation_of_axis);
1025 const float slice_depth = dt_bauhaus_slider_get(viewer->slice_depth);
1026 const float slice_thickness = dt_bauhaus_slider_get(viewer->slice_thickness);
1027 const float shift_threshold = dt_bauhaus_slider_get(viewer->shift_threshold);
1028 const float zoom = viewer->zoom;
1029 const int gamut = dt_bauhaus_combobox_get(viewer->gamut);
1030 const gboolean show_control_nodes = _show_control_nodes(viewer);
1031 const double ppd = (darktable.gui && darktable.gui->ppd > 0.0) ? darktable.gui->ppd : 1.0;
1032
1033 if(!viewer->surface
1034 || viewer->cached_width != allocation.width
1035 || viewer->cached_height != allocation.height
1036 || fabsf(viewer->cached_rotation_around_axis - rotation_around_axis) > 1e-6f
1037 || fabsf(viewer->cached_rotation_of_axis - rotation_of_axis) > 1e-6f
1038 || fabsf(viewer->cached_slice_depth - slice_depth) > 1e-6f
1039 || fabsf(viewer->cached_slice_thickness - slice_thickness) > 1e-6f
1040 || fabsf(viewer->cached_shift_threshold - shift_threshold) > 1e-6f
1041 || fabsf(viewer->cached_zoom - zoom) > 1e-6f
1042 || fabsf(viewer->cached_pan_x - viewer->pan_x) > 1e-6f
1043 || fabsf(viewer->cached_pan_y - viewer->pan_y) > 1e-6f
1044 || viewer->cached_gamut != gamut
1045 || viewer->cached_clut != viewer->clut
1046 || viewer->cached_clut_level != viewer->clut_level
1047 || viewer->cached_lut_profile != viewer->lut_profile
1048 || viewer->cached_display_profile != viewer->display_profile
1049 || viewer->cached_control_nodes != viewer->control_nodes
1050 || viewer->cached_control_node_count != viewer->control_node_count
1051 || viewer->cached_show_control_nodes != show_control_nodes
1052 || fabs(viewer->cached_ppd - ppd) > 1e-9)
1053 {
1054 _render_surface(viewer, allocation.width, allocation.height);
1055 viewer->cached_width = allocation.width;
1056 viewer->cached_height = allocation.height;
1057 viewer->cached_rotation_around_axis = rotation_around_axis;
1058 viewer->cached_rotation_of_axis = rotation_of_axis;
1059 viewer->cached_slice_depth = slice_depth;
1060 viewer->cached_slice_thickness = slice_thickness;
1061 viewer->cached_shift_threshold = shift_threshold;
1062 viewer->cached_zoom = zoom;
1063 viewer->cached_pan_x = viewer->pan_x;
1064 viewer->cached_pan_y = viewer->pan_y;
1065 viewer->cached_gamut = gamut;
1066 viewer->cached_clut = viewer->clut;
1067 viewer->cached_clut_level = viewer->clut_level;
1068 viewer->cached_lut_profile = viewer->lut_profile;
1069 viewer->cached_display_profile = viewer->display_profile;
1070 viewer->cached_control_nodes = viewer->control_nodes;
1072 viewer->cached_show_control_nodes = show_control_nodes;
1073 viewer->cached_ppd = ppd;
1074 }
1075
1076 if(viewer->surface)
1077 {
1078 cairo_set_source_surface(cr, viewer->surface, 0, 0);
1079 cairo_paint(cr);
1080 }
1081
1082 return TRUE;
1083}
1084
1085static void _control_changed(GtkWidget *widget, gpointer user_data)
1086{
1087 dt_lut_viewer_t *viewer = (dt_lut_viewer_t *)user_data;
1088 _invalidate_surface(viewer);
1089 gtk_widget_queue_draw(GTK_WIDGET(viewer->area));
1090}
1091
1092static gboolean _scroll_callback(GtkWidget *widget, GdkEventScroll *event, gpointer user_data)
1093{
1094 dt_lut_viewer_t *viewer = (dt_lut_viewer_t *)user_data;
1095 GtkAllocation allocation;
1096 gtk_widget_get_allocation(widget, &allocation);
1097 float zoom_step = 1.f;
1098
1099 switch(event->direction)
1100 {
1101 case GDK_SCROLL_UP:
1102 zoom_step = 1.1f;
1103 break;
1104 case GDK_SCROLL_DOWN:
1105 zoom_step = 1.f / 1.1f;
1106 break;
1107 case GDK_SCROLL_SMOOTH:
1108 zoom_step = exp2f((float)(-event->delta_y) * 0.2f);
1109 break;
1110 default:
1111 return FALSE;
1112 }
1113
1114 const float old_zoom = viewer->zoom;
1115 const float new_zoom = CLAMP(old_zoom * zoom_step, 0.25f, 8.f);
1116 const float zoom_ratio = new_zoom / old_zoom;
1117 const float center_x = 0.5f * allocation.width;
1118 const float center_y = 0.5f * allocation.height;
1119
1120 viewer->pan_x = event->x - center_x - zoom_ratio * (event->x - center_x - viewer->pan_x);
1121 viewer->pan_y = event->y - center_y - zoom_ratio * (event->y - center_y - viewer->pan_y);
1122 viewer->zoom = new_zoom;
1123 _invalidate_surface(viewer);
1124 gtk_widget_queue_draw(widget);
1125 return TRUE;
1126}
1127
1128static gboolean _button_press_callback(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
1129{
1130 dt_lut_viewer_t *viewer = (dt_lut_viewer_t *)user_data;
1131 if(event->type == GDK_2BUTTON_PRESS)
1132 {
1137 viewer->zoom = 1.f;
1138 viewer->pan_x = 0.f;
1139 viewer->pan_y = 0.f;
1141 _invalidate_surface(viewer);
1142 gtk_widget_queue_draw(widget);
1143 return TRUE;
1144 }
1145
1146 if(event->button == 1)
1148 else if(event->button == 2)
1150 else
1151 return FALSE;
1152
1153 viewer->drag_anchor_x = event->x;
1154 viewer->drag_anchor_y = event->y;
1155 viewer->drag_origin_pan_x = viewer->pan_x;
1156 viewer->drag_origin_pan_y = viewer->pan_y;
1159 return TRUE;
1160}
1161
1162static gboolean _motion_notify_callback(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
1163{
1164 dt_lut_viewer_t *viewer = (dt_lut_viewer_t *)user_data;
1165 if(viewer->drag_mode == DT_LUT_VIEWER_DRAG_NONE) return FALSE;
1166
1167 if(viewer->drag_mode == DT_LUT_VIEWER_DRAG_PAN)
1168 {
1169 viewer->pan_x = viewer->drag_origin_pan_x + (float)(event->x - viewer->drag_anchor_x);
1170 viewer->pan_y = viewer->drag_origin_pan_y + (float)(event->y - viewer->drag_anchor_y);
1171 _invalidate_surface(viewer);
1172 gtk_widget_queue_draw(widget);
1173 }
1174 else if(viewer->drag_mode == DT_LUT_VIEWER_DRAG_ORBIT)
1175 {
1176 const float azimuth = _wrap_degrees_pm180(viewer->drag_origin_azimuth
1177 + 0.35f * (float)(event->x - viewer->drag_anchor_x));
1178 const float tilt = CLAMP(viewer->drag_origin_tilt
1179 - 0.25f * (float)(event->y - viewer->drag_anchor_y), 0.f, 90.f);
1182 }
1183
1184 return TRUE;
1185}
1186
1187static gboolean _button_release_callback(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
1188{
1189 dt_lut_viewer_t *viewer = (dt_lut_viewer_t *)user_data;
1190 if((event->button == 1 && viewer->drag_mode == DT_LUT_VIEWER_DRAG_PAN)
1191 || (event->button == 2 && viewer->drag_mode == DT_LUT_VIEWER_DRAG_ORBIT))
1193 return TRUE;
1194}
1195
1196static void _save_clut_callback(GtkWidget *widget, gpointer user_data)
1197{
1198 dt_lut_viewer_t *viewer = (dt_lut_viewer_t *)user_data;
1199 if(IS_NULL_PTR(viewer) || IS_NULL_PTR(viewer->clut) || viewer->clut_level < 2)
1200 {
1201 dt_control_log(_("no LUT to save"));
1202 return;
1203 }
1204
1206 GtkFileChooserNative *filechooser = gtk_file_chooser_native_new(
1207 _("save 3D LUT"), GTK_WINDOW(win), GTK_FILE_CHOOSER_ACTION_SAVE,
1208 _("_save"), _("_cancel"));
1209 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(filechooser), FALSE);
1210 gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(filechooser), TRUE);
1211 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(filechooser), "lut-viewer-export.cube");
1212
1213 gchar *lutfolder = dt_conf_get_string("plugins/darkroom/lut3d/def_path");
1214 if(lutfolder[0])
1215 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(filechooser), lutfolder);
1216 else
1217 dt_conf_get_folder_to_file_chooser("ui_last/export_path", GTK_FILE_CHOOSER(filechooser));
1218
1219 GtkFileFilter *filter = GTK_FILE_FILTER(gtk_file_filter_new());
1220 gtk_file_filter_add_pattern(filter, "*.cube");
1221 gtk_file_filter_add_pattern(filter, "*.CUBE");
1222 gtk_file_filter_set_name(filter, _("3D lut (cube)"));
1223 gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(filechooser), filter);
1224 gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(filechooser), filter);
1225
1226 if(gtk_native_dialog_run(GTK_NATIVE_DIALOG(filechooser)) == GTK_RESPONSE_ACCEPT)
1227 {
1228 gchar *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(filechooser));
1229 char *path = filename;
1230 FILE *cube_file = NULL;
1231
1232 if(!g_str_has_suffix(filename, ".cube") && !g_str_has_suffix(filename, ".CUBE"))
1233 path = g_strconcat(filename, ".cube", NULL);
1234
1235 cube_file = g_fopen(path, "wb");
1236 if(IS_NULL_PTR(cube_file))
1237 {
1238 dt_control_log(_("failed to save LUT file"));
1239 }
1240 else
1241 {
1242 const char *const lut_profile_name
1244 : _("unknown");
1245 fprintf(cube_file, "# generated by ansel LUT viewer\n");
1246 fprintf(cube_file, "# LUT_COLORSPACE %s\n", lut_profile_name);
1247 fprintf(cube_file, "TITLE \"lut viewer export\"\n");
1248 fprintf(cube_file, "LUT_3D_SIZE %u\n", viewer->clut_level);
1249 fprintf(cube_file, "DOMAIN_MIN 0.0 0.0 0.0\n");
1250 fprintf(cube_file, "DOMAIN_MAX 1.0 1.0 1.0\n");
1251
1252 for(uint16_t b = 0; b < viewer->clut_level; b++)
1253 for(uint16_t g = 0; g < viewer->clut_level; g++)
1254 for(uint16_t r = 0; r < viewer->clut_level; r++)
1255 {
1256 const size_t index
1257 = ((size_t)b * viewer->clut_level * viewer->clut_level + (size_t)g * viewer->clut_level + r) * 3;
1258 fprintf(cube_file, "%.7f %.7f %.7f\n",
1259 viewer->clut[index + 0], viewer->clut[index + 1], viewer->clut[index + 2]);
1260 }
1261
1262 fclose(cube_file);
1263 dt_control_log(_("saved LUT to %s"), path);
1264 dt_conf_set_folder_from_file_chooser("ui_last/export_path", GTK_FILE_CHOOSER(filechooser));
1265 }
1266
1267 if(path != filename) g_free(path);
1268 g_free(filename);
1269 }
1270
1271 dt_free(lutfolder);
1272 g_object_unref(filechooser);
1273}
1274
1276{
1277 dt_lut_viewer_t *viewer = calloc(1, sizeof(*viewer));
1278 if(IS_NULL_PTR(viewer)) return NULL;
1279
1280 viewer->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1281 viewer->controls = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1282 viewer->area = GTK_DRAWING_AREA(dtgtk_drawing_area_new_with_aspect_ratio(1.f));
1283 gtk_widget_set_size_request(GTK_WIDGET(viewer->area), -1, DT_PIXEL_APPLY_DPI(180));
1284 gtk_widget_add_events(GTK_WIDGET(viewer->area),
1285 GDK_SCROLL_MASK | GDK_SMOOTH_SCROLL_MASK | GDK_BUTTON_PRESS_MASK
1286 | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK);
1287 g_signal_connect(G_OBJECT(viewer->area), "draw", G_CALLBACK(_draw_callback), viewer);
1288 g_signal_connect(G_OBJECT(viewer->area), "scroll-event", G_CALLBACK(_scroll_callback), viewer);
1289 g_signal_connect(G_OBJECT(viewer->area), "button-press-event", G_CALLBACK(_button_press_callback), viewer);
1290 g_signal_connect(G_OBJECT(viewer->area), "motion-notify-event", G_CALLBACK(_motion_notify_callback), viewer);
1291 g_signal_connect(G_OBJECT(viewer->area), "button-release-event", G_CALLBACK(_button_release_callback), viewer);
1292 gtk_box_pack_start(GTK_BOX(viewer->widget), GTK_WIDGET(viewer->area), TRUE, TRUE, 0);
1293
1294 viewer->rotation_around_axis
1295 = dt_bauhaus_slider_new_with_range(darktable.bauhaus, module, -180.f, 180.f, 1.f, 35.f, 0);
1298 gtk_box_pack_start(GTK_BOX(viewer->controls), viewer->rotation_around_axis, FALSE, FALSE, 0);
1299 g_signal_connect(G_OBJECT(viewer->rotation_around_axis), "value-changed", G_CALLBACK(_control_changed), viewer);
1300
1301 viewer->rotation_of_axis
1302 = dt_bauhaus_slider_new_with_range(darktable.bauhaus, module, 0.f, 90.f, 1.f, 0.f, 0);
1303 dt_bauhaus_widget_set_label(viewer->rotation_of_axis, _("axis tilt"));
1305 gtk_box_pack_start(GTK_BOX(viewer->controls), viewer->rotation_of_axis, FALSE, FALSE, 0);
1306 g_signal_connect(G_OBJECT(viewer->rotation_of_axis), "value-changed", G_CALLBACK(_control_changed), viewer);
1307
1308 viewer->slice_depth
1309 = dt_bauhaus_slider_new_with_range(darktable.bauhaus, module, 0.f, 100.f, 1.f, 50.f, 0);
1310 dt_bauhaus_widget_set_label(viewer->slice_depth, _("slice depth"));
1311 dt_bauhaus_slider_set_format(viewer->slice_depth, _(" %"));
1312 gtk_box_pack_start(GTK_BOX(viewer->controls), viewer->slice_depth, FALSE, FALSE, 0);
1313 g_signal_connect(G_OBJECT(viewer->slice_depth), "value-changed", G_CALLBACK(_control_changed), viewer);
1314
1315 viewer->slice_thickness
1316 = dt_bauhaus_slider_new_with_range(darktable.bauhaus, module, 0.f, 100.f, 1.f, 100.f, 0);
1317 dt_bauhaus_widget_set_label(viewer->slice_thickness, _("slice thickness"));
1319 gtk_box_pack_start(GTK_BOX(viewer->controls), viewer->slice_thickness, FALSE, FALSE, 0);
1320 g_signal_connect(G_OBJECT(viewer->slice_thickness), "value-changed", G_CALLBACK(_control_changed), viewer);
1321
1322 viewer->shift_threshold
1323 = dt_bauhaus_slider_new_with_range(darktable.bauhaus, module, 0.f, 100.f, 0.1f, 0.1f, 1);
1324 dt_bauhaus_widget_set_label(viewer->shift_threshold, _("color shift threshold"));
1326 gtk_box_pack_start(GTK_BOX(viewer->controls), viewer->shift_threshold, FALSE, FALSE, 0);
1327 g_signal_connect(G_OBJECT(viewer->shift_threshold), "value-changed", G_CALLBACK(_control_changed), viewer);
1328
1329 viewer->show_control_nodes = gtk_check_button_new_with_label(_("show control nodes"));
1330 gtk_widget_set_sensitive(viewer->show_control_nodes, FALSE);
1331 gtk_box_pack_start(GTK_BOX(viewer->controls), viewer->show_control_nodes, FALSE, FALSE, 0);
1332 g_signal_connect(G_OBJECT(viewer->show_control_nodes), "toggled", G_CALLBACK(_control_changed), viewer);
1333
1335 dt_bauhaus_widget_set_label(viewer->gamut, _("target gamut"));
1336 dt_bauhaus_combobox_add(viewer->gamut, _("sRGB/Rec709"));
1337 dt_bauhaus_combobox_add(viewer->gamut, _("Adobe RGB"));
1338 dt_bauhaus_combobox_add(viewer->gamut, _("Display P3"));
1339 dt_bauhaus_combobox_add(viewer->gamut, _("Rec2020"));
1341 gtk_box_pack_start(GTK_BOX(viewer->controls), viewer->gamut, FALSE, FALSE, 0);
1342 g_signal_connect(G_OBJECT(viewer->gamut), "value-changed", G_CALLBACK(_control_changed), viewer);
1343
1344 viewer->save_button = gtk_button_new_with_label(_("save to cLUT"));
1345 gtk_box_pack_start(GTK_BOX(viewer->controls), viewer->save_button, FALSE, FALSE, 0);
1346 g_signal_connect(G_OBJECT(viewer->save_button), "clicked", G_CALLBACK(_save_clut_callback), viewer);
1347
1348 GtkWidget *expander = gtk_expander_new(_("viewer controls"));
1349 gtk_expander_set_expanded(GTK_EXPANDER(expander), FALSE);
1350 gtk_container_add(GTK_CONTAINER(expander), viewer->controls);
1351 gtk_box_pack_start(GTK_BOX(viewer->widget), expander, FALSE, FALSE, 0);
1352
1353 viewer->zoom = 1.f;
1354 viewer->pan_x = 0.f;
1355 viewer->pan_y = 0.f;
1357 viewer->cached_gamut = -1;
1358 viewer->cached_rotation_around_axis = NAN;
1359 viewer->cached_rotation_of_axis = NAN;
1360 viewer->cached_slice_depth = NAN;
1361 viewer->cached_slice_thickness = NAN;
1362 viewer->cached_zoom = NAN;
1363 viewer->cached_pan_x = NAN;
1364 viewer->cached_pan_y = NAN;
1365 viewer->cached_shift_threshold = NAN;
1366 viewer->cached_ppd = NAN;
1368 viewer->sample_cache_rotation_of_axis = NAN;
1369 viewer->sample_cache_slice_depth = NAN;
1370 viewer->sample_cache_slice_thickness = NAN;
1371 viewer->sample_cache_shift_threshold = NAN;
1372 viewer->sample_cache_gamut = -1;
1373 viewer->sample_cache_control_nodes = NULL;
1376 return viewer;
1377}
1378
1380{
1381 if(IS_NULL_PTR(viewer) || !*viewer) return;
1382
1383 _invalidate_surface(*viewer);
1384 dt_free_align((*viewer)->sample_input_work);
1385 dt_free_align((*viewer)->sample_output_work);
1386 dt_free_align((*viewer)->sample_input_display);
1387 dt_free_align((*viewer)->sample_output_display);
1388 free(*viewer);
1389 *viewer = NULL;
1390}
1391
1393{
1394 return viewer ? viewer->widget : NULL;
1395}
1396
1397void dt_lut_viewer_set_lut(dt_lut_viewer_t *viewer, const float *clut, uint16_t level,
1398 dt_pthread_rwlock_t *clut_lock,
1399 const dt_iop_order_iccprofile_info_t *lut_profile,
1400 const dt_iop_order_iccprofile_info_t *display_profile)
1401{
1402 if(IS_NULL_PTR(viewer)) return;
1403
1404 viewer->clut = clut;
1405 viewer->clut_level = level;
1406 viewer->clut_lock = clut_lock;
1407 viewer->lut_profile = lut_profile;
1408 viewer->display_profile = display_profile;
1410 _invalidate_surface(viewer);
1411}
1412
1414 const dt_lut_viewer_control_node_t *control_nodes,
1415 size_t control_node_count)
1416{
1417 if(IS_NULL_PTR(viewer)) return;
1418
1419 viewer->control_nodes = control_nodes;
1420 viewer->control_node_count = control_node_count;
1421
1422 if(viewer->show_control_nodes)
1423 {
1424 const gboolean enabled = control_nodes && control_node_count > 0;
1425 gtk_widget_set_sensitive(viewer->show_control_nodes, enabled);
1426 if(!enabled) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(viewer->show_control_nodes), FALSE);
1427 }
1428
1430 _invalidate_surface(viewer);
1431}
1432
1434{
1435 if(viewer) gtk_widget_queue_draw(GTK_WIDGET(viewer->area));
1436}
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
void dt_bauhaus_slider_reset(GtkWidget *widget)
Definition bauhaus.c:3588
float dt_bauhaus_slider_get(GtkWidget *widget)
Definition bauhaus.c:3483
int dt_bauhaus_combobox_get(GtkWidget *widget)
Definition bauhaus.c:2347
void dt_bauhaus_slider_set(GtkWidget *widget, float pos)
Definition bauhaus.c:3506
void dt_bauhaus_combobox_set(GtkWidget *widget, const int pos)
Definition bauhaus.c:2301
void dt_bauhaus_widget_set_label(GtkWidget *widget, const char *label)
Definition bauhaus.c:1653
GtkWidget * dt_bauhaus_slider_new_with_range(dt_bauhaus_t *bh, dt_gui_module_t *self, float min, float max, float step, float defval, int digits)
Definition bauhaus.c:1780
GtkWidget * dt_bauhaus_combobox_new(dt_bauhaus_t *bh, dt_gui_module_t *self)
Definition bauhaus.c:1842
void dt_bauhaus_slider_set_format(GtkWidget *widget, const char *format)
Definition bauhaus.c:3598
void dt_bauhaus_combobox_add(GtkWidget *widget, const char *text)
Definition bauhaus.c:2016
static void set_color(cairo_t *cr, GdkRGBA color)
Definition bauhaus.h:446
int width
Definition bilateral.h:1
int height
Definition bilateral.h:1
const dt_colorspaces_color_profile_t * dt_colorspaces_get_profile(dt_colorspaces_color_profile_type_t type, const char *filename, dt_colorspaces_profile_direction_t direction)
const char * dt_colorspaces_get_name(dt_colorspaces_color_profile_type_t type, const char *filename)
dt_colorspaces_color_profile_type_t
Definition colorspaces.h:81
@ DT_COLORSPACE_ADOBERGB
Definition colorspaces.h:85
@ DT_COLORSPACE_SRGB
Definition colorspaces.h:84
@ DT_COLORSPACE_REC709
@ DT_COLORSPACE_HLG_P3
@ DT_COLORSPACE_PQ_P3
@ DT_COLORSPACE_PQ_REC2020
@ DT_COLORSPACE_HLG_REC2020
@ DT_COLORSPACE_LIN_REC2020
Definition colorspaces.h:87
@ DT_COLORSPACE_DISPLAY_P3
@ DT_COLORSPACE_LIN_REC709
Definition colorspaces.h:86
@ DT_PROFILE_DIRECTION_ANY
static dt_aligned_pixel_t rgb
dt_apply_transposed_color_matrix(XYZ, xyz_to_srgb_matrix_transposed, sRGB)
const dt_colormatrix_t dt_aligned_pixel_t out
gchar * dt_conf_get_string(const char *name)
void dt_conf_set_folder_from_file_chooser(const char *name, GtkFileChooser *chooser)
gboolean dt_conf_get_folder_to_file_chooser(const char *name, GtkFileChooser *chooser)
void dt_control_log(const char *msg,...)
Definition control.c:761
darktable_t darktable
Definition darktable.c:181
void dt_print(dt_debug_thread_t thread, const char *msg,...)
Definition darktable.c:1542
#define dt_free_align(ptr)
Definition darktable.h:481
static void * dt_calloc_align(size_t size)
Definition darktable.h:488
#define __OMP_SIMD__(...)
Definition darktable.h:262
@ DT_DEBUG_PERF
Definition darktable.h:719
#define dt_free(ptr)
Definition darktable.h:456
static const dt_aligned_pixel_simd_t value
Definition darktable.h:577
static double dt_get_wtime(void)
Definition darktable.h:914
#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
#define M_PI_F
GtkWidget * dtgtk_drawing_area_new_with_aspect_ratio(double aspect)
Definition drawingarea.c:54
#define dt_pthread_rwlock_t
Definition dtpthread.h:389
#define dt_pthread_rwlock_unlock
Definition dtpthread.h:392
#define dt_pthread_rwlock_rdlock
Definition dtpthread.h:393
static cairo_surface_t * dt_cairo_image_surface_create(cairo_format_t format, int width, int height)
Definition gtk.h:316
GtkWidget * dt_ui_main_window(dt_ui_t *ui)
get the main window widget
#define DT_GUI_BOX_SPACING
Definition gtk.h:109
#define DT_PIXEL_APPLY_DPI(value)
Definition gtk.h:90
void dt_ioppr_transform_image_colorspace_rgb(const float *const restrict image_in, float *const restrict image_out, const int width, const int height, const dt_iop_order_iccprofile_info_t *const profile_info_from, const dt_iop_order_iccprofile_info_t *const profile_info_to, const char *message)
static const float x
float *const restrict const size_t k
static void _control_changed(GtkWidget *widget, gpointer user_data)
static gboolean _show_control_nodes(const dt_lut_viewer_t *viewer)
Definition lut_viewer.c:296
static float _wrap_degrees_pm180(float angle)
Definition lut_viewer.c:175
static gboolean _button_press_callback(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
static dt_aligned_pixel_simd_t _normalize3(const dt_aligned_pixel_simd_t vector)
Definition lut_viewer.c:166
static dt_aligned_pixel_simd_t _set_vector(const float x, const float y, const float z)
Definition lut_viewer.c:149
static void _save_clut_callback(GtkWidget *widget, gpointer user_data)
static int _get_xyz_to_rgb_matrix(const dt_lut_viewer_gamut_t gamut, dt_colormatrix_t xyz_to_rgb)
Definition lut_viewer.c:384
dt_lut_viewer_gamut_t
Definition lut_viewer.c:40
@ DT_LUT_VIEWER_GAMUT_REC2020
Definition lut_viewer.c:44
@ DT_LUT_VIEWER_GAMUT_ADOBE_RGB
Definition lut_viewer.c:42
@ DT_LUT_VIEWER_GAMUT_SRGB
Definition lut_viewer.c:41
@ DT_LUT_VIEWER_GAMUT_DISPLAY_P3
Definition lut_viewer.c:43
static void _project_point(const dt_lut_viewer_projection_t *projection, const dt_aligned_pixel_simd_t rgb, float *const x, float *const y, float *const depth)
Definition lut_viewer.c:503
void dt_lut_viewer_destroy(dt_lut_viewer_t **viewer)
static gboolean _button_release_callback(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
static int _sample_stride(const int level)
Definition lut_viewer.c:653
static float _shift_distance_percent(const dt_aligned_pixel_simd_t input_rgb, const dt_aligned_pixel_simd_t output_rgb)
Definition lut_viewer.c:182
static gboolean _scroll_callback(GtkWidget *widget, GdkEventScroll *event, gpointer user_data)
static void _render_surface(dt_lut_viewer_t *viewer, const int width, const int height)
Definition lut_viewer.c:941
static void _clamp_display_rgb_array_simd(const dt_aligned_pixel_simd_t *source_rgb, dt_aligned_pixel_simd_t *display_rgb, const size_t count)
Definition lut_viewer.c:207
void dt_lut_viewer_queue_draw(dt_lut_viewer_t *viewer)
static dt_aligned_pixel_simd_t _to_display_rgb(const dt_lut_viewer_t *viewer, const dt_aligned_pixel_simd_t work_rgb)
Definition lut_viewer.c:247
static dt_aligned_pixel_simd_t _clamp01_simd(const dt_aligned_pixel_simd_t value)
Definition lut_viewer.c:191
static int _sample_count(const int level, const int stride)
Definition lut_viewer.c:664
static gboolean _sample_fits_gamut(const dt_iop_order_iccprofile_info_t *lut_profile, const dt_colormatrix_t xyz_to_rgb, const dt_aligned_pixel_simd_t rgb)
Definition lut_viewer.c:409
void dt_lut_viewer_set_control_nodes(dt_lut_viewer_t *viewer, const dt_lut_viewer_control_node_t *control_nodes, size_t control_node_count)
static gboolean _draw_callback(GtkWidget *widget, cairo_t *cr, gpointer user_data)
static dt_colorspaces_color_profile_type_t _gamut_to_profile_type(const dt_lut_viewer_gamut_t gamut)
Definition lut_viewer.c:335
GtkWidget * dt_lut_viewer_get_widget(dt_lut_viewer_t *viewer)
dt_lut_viewer_t * dt_lut_viewer_new(dt_gui_module_t *module)
static void _draw_samples(cairo_t *cr, const dt_lut_viewer_t *viewer, const dt_lut_viewer_projection_t *projection, const dt_lut_viewer_gamut_t gamut)
Definition lut_viewer.c:675
static void _to_display_rgb_array(const dt_lut_viewer_t *viewer, const dt_aligned_pixel_simd_t *work_rgb, dt_aligned_pixel_simd_t *display_rgb, const size_t count, const char *message)
Definition lut_viewer.c:266
#define DT_LUT_VIEWER_TARGET_SAMPLES
Definition lut_viewer.c:36
dt_lut_viewer_drag_mode_t
Definition lut_viewer.c:66
@ DT_LUT_VIEWER_DRAG_ORBIT
Definition lut_viewer.c:69
@ DT_LUT_VIEWER_DRAG_NONE
Definition lut_viewer.c:67
@ DT_LUT_VIEWER_DRAG_PAN
Definition lut_viewer.c:68
static dt_aligned_pixel_simd_t _transform_single_rgb_matrix(const dt_aligned_pixel_simd_t input, const dt_iop_order_iccprofile_info_t *const profile_info_from, const dt_iop_order_iccprofile_info_t *const profile_info_to)
Definition lut_viewer.c:222
static int _sample_index(const int sample, const int samples, const int level)
Definition lut_viewer.c:669
static void _draw_axes(cairo_t *cr, const dt_lut_viewer_t *viewer, const dt_lut_viewer_projection_t *projection)
Definition lut_viewer.c:615
static gboolean _motion_notify_callback(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
static void _draw_placeholder(cairo_t *cr, const int width, const int height, const char *message)
Definition lut_viewer.c:930
static void _invalidate_sample_cache(dt_lut_viewer_t *viewer)
Definition lut_viewer.c:288
static gboolean _gamut_matches_lut_profile(const dt_lut_viewer_t *viewer, const dt_lut_viewer_gamut_t gamut)
Definition lut_viewer.c:351
#define DT_LUT_VIEWER_MARGIN
Definition lut_viewer.c:35
static dt_aligned_pixel_simd_t _cross3(const dt_aligned_pixel_simd_t a, const dt_aligned_pixel_simd_t b)
Definition lut_viewer.c:159
static void _build_projection(dt_lut_viewer_projection_t *projection, const float rotation_around_axis, const float rotation_of_axis, const float slice_depth, const float slice_thickness, const float zoom, const float pan_x, const float pan_y, const int width, const int height)
Definition lut_viewer.c:430
static void _draw_arrow(cairo_t *cr, const float x0, const float y0, const float x1, const float y1, const float radius0, const float radius1, const dt_aligned_pixel_simd_t color)
Definition lut_viewer.c:515
static int _ensure_sample_cache_capacity(dt_lut_viewer_t *viewer, const size_t capacity)
Definition lut_viewer.c:302
#define DT_LUT_VIEWER_AXIS_LENGTH
Definition lut_viewer.c:37
static float _dot3(const dt_aligned_pixel_simd_t a, const dt_aligned_pixel_simd_t b)
Definition lut_viewer.c:154
void dt_lut_viewer_set_lut(dt_lut_viewer_t *viewer, const float *clut, uint16_t level, dt_pthread_rwlock_t *clut_lock, const dt_iop_order_iccprofile_info_t *lut_profile, const dt_iop_order_iccprofile_info_t *display_profile)
static void _invalidate_surface(dt_lut_viewer_t *viewer)
Definition lut_viewer.c:279
static void _draw_cube(cairo_t *cr, const dt_lut_viewer_t *viewer, const dt_lut_viewer_projection_t *projection)
Definition lut_viewer.c:552
float DT_ALIGNED_ARRAY dt_colormatrix_t[4][4]
Definition matrices.h:33
static int mat3SSEinv(dt_colormatrix_t dst, const dt_colormatrix_t src)
Definition matrices.h:36
float dt_aligned_pixel_t[4]
struct _GtkWidget GtkWidget
Definition splash.h:29
const float r
struct dt_gui_gtk_t * gui
Definition darktable.h:775
struct dt_bauhaus_t * bauhaus
Definition darktable.h:778
int32_t unmuted
Definition darktable.h:760
GdkRGBA graph_border
Definition bauhaus.h:281
double ppd
Definition gtk.h:200
dt_ui_t * ui
Definition gtk.h:164
The dt_gui_module_t type is the intersection between a dt_lib_module_t and a dt_iop_module_t structur...
dt_colormatrix_t matrix_out_transposed
Definition iop_profile.h:66
dt_colorspaces_color_profile_type_t type
Definition iop_profile.h:53
char filename[DT_IOP_COLOR_ICC_LEN]
Definition iop_profile.h:54
dt_colormatrix_t matrix_in_transposed
Definition iop_profile.h:65
dt_aligned_pixel_simd_t screen_x
Definition lut_viewer.c:49
dt_aligned_pixel_simd_t screen_z
Definition lut_viewer.c:51
dt_aligned_pixel_simd_t screen_y
Definition lut_viewer.c:50
dt_pthread_rwlock_t * clut_lock
Definition lut_viewer.c:88
float sample_cache_rotation_of_axis
Definition lut_viewer.c:135
float cached_rotation_around_axis
Definition lut_viewer.c:106
const dt_iop_order_iccprofile_info_t * display_profile
Definition lut_viewer.c:90
GtkWidget * widget
Definition lut_viewer.c:74
float cached_slice_thickness
Definition lut_viewer.c:109
const dt_iop_order_iccprofile_info_t * cached_display_profile
Definition lut_viewer.c:118
GtkDrawingArea * area
Definition lut_viewer.c:75
float sample_cache_slice_thickness
Definition lut_viewer.c:137
float cached_rotation_of_axis
Definition lut_viewer.c:107
double drag_anchor_y
Definition lut_viewer.c:98
float drag_origin_azimuth
Definition lut_viewer.c:101
float sample_cache_slice_depth
Definition lut_viewer.c:136
gboolean cached_show_control_nodes
Definition lut_viewer.c:121
gboolean sample_cache_show_control_nodes
Definition lut_viewer.c:146
dt_aligned_pixel_simd_t * sample_input_display
Definition lut_viewer.c:127
dt_aligned_pixel_simd_t * sample_output_display
Definition lut_viewer.c:128
const dt_iop_order_iccprofile_info_t * lut_profile
Definition lut_viewer.c:89
float cached_slice_depth
Definition lut_viewer.c:108
const float * cached_clut
Definition lut_viewer.c:115
dt_aligned_pixel_simd_t * sample_input_work
Definition lut_viewer.c:125
const float * sample_cache_clut
Definition lut_viewer.c:140
uint16_t sample_cache_clut_level
Definition lut_viewer.c:141
const float * clut
Definition lut_viewer.c:86
float cached_shift_threshold
Definition lut_viewer.c:113
GtkWidget * save_button
Definition lut_viewer.c:77
uint16_t cached_clut_level
Definition lut_viewer.c:116
cairo_surface_t * surface
Definition lut_viewer.c:123
const dt_lut_viewer_control_node_t * control_nodes
Definition lut_viewer.c:91
size_t cached_control_node_count
Definition lut_viewer.c:120
size_t sample_white_index
Definition lut_viewer.c:131
float sample_cache_rotation_around_axis
Definition lut_viewer.c:134
float drag_origin_tilt
Definition lut_viewer.c:102
const dt_iop_order_iccprofile_info_t * sample_cache_display_profile
Definition lut_viewer.c:143
GtkWidget * slice_thickness
Definition lut_viewer.c:81
gboolean sample_cache_valid
Definition lut_viewer.c:133
const dt_iop_order_iccprofile_info_t * sample_cache_lut_profile
Definition lut_viewer.c:142
float drag_origin_pan_x
Definition lut_viewer.c:99
GtkWidget * rotation_of_axis
Definition lut_viewer.c:79
double drag_anchor_x
Definition lut_viewer.c:97
float sample_cache_shift_threshold
Definition lut_viewer.c:138
GtkWidget * slice_depth
Definition lut_viewer.c:80
const dt_lut_viewer_control_node_t * cached_control_nodes
Definition lut_viewer.c:119
gboolean sample_draw_white_last
Definition lut_viewer.c:132
dt_lut_viewer_drag_mode_t drag_mode
Definition lut_viewer.c:96
const dt_iop_order_iccprofile_info_t * cached_lut_profile
Definition lut_viewer.c:117
float drag_origin_pan_y
Definition lut_viewer.c:100
dt_aligned_pixel_simd_t * sample_output_work
Definition lut_viewer.c:126
size_t sample_capacity
Definition lut_viewer.c:129
size_t sample_cache_control_node_count
Definition lut_viewer.c:145
GtkWidget * rotation_around_axis
Definition lut_viewer.c:78
uint16_t clut_level
Definition lut_viewer.c:87
GtkWidget * controls
Definition lut_viewer.c:76
const dt_lut_viewer_control_node_t * sample_cache_control_nodes
Definition lut_viewer.c:144
size_t sample_count
Definition lut_viewer.c:130
GtkWidget * show_control_nodes
Definition lut_viewer.c:83
GtkWidget * gamut
Definition lut_viewer.c:84
GtkWidget * shift_threshold
Definition lut_viewer.c:82
size_t control_node_count
Definition lut_viewer.c:92
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29