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 <glib/gstdio.h>
20#include "gui/lut_viewer.h"
21
22#include "widgets/bauhaus.h"
24#include "system/macros.h"
25#include "system/openmp.h"
26#include "system/mem_alloc.h"
27#include "system/simd.h"
28#include "common/logging.h"
29#include "common/times.h"
30#include "math/matrices.h"
31#include "common/conf.h"
33#include "widgets/drawingarea.h"
34#include "gui/application.h"
35
36#include <math.h>
37#include <stdlib.h>
38#include <string.h>
39#include "gui/screen_metrics.h"
40#include "widgets/draw.h"
41
42#define DT_LUT_VIEWER_MARGIN DT_PIXEL_APPLY_DPI(12)
43#define DT_LUT_VIEWER_TARGET_SAMPLES 4096
44#define DT_LUT_VIEWER_AXIS_LENGTH DT_PIXEL_APPLY_DPI(20.f)
45
53
55{
56 dt_aligned_pixel_simd_t screen_x;
57 dt_aligned_pixel_simd_t screen_y;
58 dt_aligned_pixel_simd_t screen_z;
59 float min_x;
60 float max_x;
61 float min_y;
62 float max_y;
63 float min_depth;
64 float max_depth;
65 float scale;
66 float offset_x;
67 float offset_y;
71
78
80{
82 GtkDrawingArea *area;
92
93 const float *clut;
94 uint16_t clut_level;
95 dt_pthread_rwlock_t *clut_lock;
100 float zoom;
101 float pan_x;
102 float pan_y;
110
122 const float *cached_clut;
130 cairo_surface_t *surface;
131
132 dt_aligned_pixel_simd_t *sample_input_work;
133 dt_aligned_pixel_simd_t *sample_output_work;
134 dt_aligned_pixel_simd_t *sample_input_display;
135 dt_aligned_pixel_simd_t *sample_output_display;
147 const float *sample_cache_clut;
154};
155
156static inline dt_aligned_pixel_simd_t _set_vector(const float x, const float y, const float z)
157{
158 return (dt_aligned_pixel_simd_t){ x, y, z, 0.f };
159}
160
161static inline float _dot3(const dt_aligned_pixel_simd_t a, const dt_aligned_pixel_simd_t b)
162{
163 return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
164}
165
166static inline dt_aligned_pixel_simd_t _cross3(const dt_aligned_pixel_simd_t a, const dt_aligned_pixel_simd_t b)
167{
168 return _set_vector(a[1] * b[2] - a[2] * b[1],
169 a[2] * b[0] - a[0] * b[2],
170 a[0] * b[1] - a[1] * b[0]);
171}
172
173static inline dt_aligned_pixel_simd_t _normalize3(const dt_aligned_pixel_simd_t vector)
174{
175 const float norm = sqrtf(_dot3(vector, vector));
176 if(norm < 1e-6f) return vector;
177
178 const float inv_norm = 1.f / norm;
179 return (dt_aligned_pixel_simd_t){ vector[0] * inv_norm, vector[1] * inv_norm, vector[2] * inv_norm, 0.f };
180}
181
182static inline float _wrap_degrees_pm180(float angle)
183{
184 while(angle <= -180.f) angle += 360.f;
185 while(angle > 180.f) angle -= 360.f;
186 return angle;
187}
188
189static inline float _shift_distance_percent(const dt_aligned_pixel_simd_t input_rgb,
190 const dt_aligned_pixel_simd_t output_rgb)
191{
192 const float dr = output_rgb[0] - input_rgb[0];
193 const float dg = output_rgb[1] - input_rgb[1];
194 const float db = output_rgb[2] - input_rgb[2];
195 return 100.f * sqrtf((dr * dr + dg * dg + db * db) / 3.f);
196}
197
198static inline dt_aligned_pixel_simd_t _clamp01_simd(const dt_aligned_pixel_simd_t value)
199{
200 dt_aligned_pixel_simd_t out = dt_simd_max_zero(value);
201
202 for(int c = 0; c < 3; c++)
203 out[c] = fminf(out[c], 1.f);
204
205 out[3] = 0.f;
206 return out;
207}
208
214static inline void _clamp_display_rgb_array_simd(const dt_aligned_pixel_simd_t *source_rgb,
215 dt_aligned_pixel_simd_t *display_rgb, const size_t count)
216{
217 if(IS_NULL_PTR(source_rgb) || !display_rgb || count == 0) return;
218 __OMP_SIMD__(aligned(source_rgb, display_rgb:16))
219 for(size_t k = 0; k < count; k++)
220 display_rgb[k] = _clamp01_simd(source_rgb[k]);
221}
222
229static inline dt_aligned_pixel_simd_t _transform_single_rgb_matrix(
230 const dt_aligned_pixel_simd_t input,
231 const dt_iop_order_iccprofile_info_t *const profile_info_from,
232 const dt_iop_order_iccprofile_info_t *const profile_info_to)
233{
234 dt_aligned_pixel_t xyz = { 0.f };
235 dt_aligned_pixel_t output = { 0.f };
236
237 dt_ioppr_rgb_matrix_to_xyz((const float *)&input, xyz, profile_info_from->matrix_in_transposed,
238 profile_info_from->lut_in, profile_info_from->unbounded_coeffs_in,
239 profile_info_from->lutsize, profile_info_from->nonlinearlut);
240
241 if(profile_info_to->nonlinearlut)
242 {
243 dt_aligned_pixel_t linear_rgb = { 0.f };
244 dt_apply_transposed_color_matrix(xyz, profile_info_to->matrix_out_transposed, linear_rgb);
245 _apply_trc(linear_rgb, output, profile_info_to->lut_out, profile_info_to->unbounded_coeffs_out,
246 profile_info_to->lutsize);
247 }
248 else
249 dt_apply_transposed_color_matrix(xyz, profile_info_to->matrix_out_transposed, output);
250
251 return _clamp01_simd(dt_load_simd_aligned(output));
252}
253
254static inline dt_aligned_pixel_simd_t _to_display_rgb(const dt_lut_viewer_t *viewer,
255 const dt_aligned_pixel_simd_t work_rgb)
256{
257 dt_aligned_pixel_simd_t display_rgb = _clamp01_simd(work_rgb);
258
259 if(IS_NULL_PTR(viewer->lut_profile) || !viewer->display_profile) return display_rgb;
260
261 if(!isnan(viewer->lut_profile->matrix_in[0][0]) && !isnan(viewer->lut_profile->matrix_out[0][0])
262 && !isnan(viewer->display_profile->matrix_in[0][0]) && !isnan(viewer->display_profile->matrix_out[0][0]))
263 return _transform_single_rgb_matrix(work_rgb, viewer->lut_profile, viewer->display_profile);
264
265 dt_aligned_pixel_simd_t in = work_rgb;
266 dt_aligned_pixel_simd_t out = work_rgb;
267 dt_ioppr_transform_image_colorspace_rgb((float *)&in, (float *)&out, 1, 1, viewer->lut_profile, viewer->display_profile,
268 "lut viewer swatch");
269
270 return _clamp01_simd(out);
271}
272
273static inline void _to_display_rgb_array(const dt_lut_viewer_t *viewer, const dt_aligned_pixel_simd_t *work_rgb,
274 dt_aligned_pixel_simd_t *display_rgb, const size_t count, const char *message)
275{
276 if(IS_NULL_PTR(work_rgb) || !display_rgb || count == 0) return;
277
278 _clamp_display_rgb_array_simd(work_rgb, display_rgb, count);
279 if(IS_NULL_PTR(viewer->lut_profile) || !viewer->display_profile) return;
280
281 dt_ioppr_transform_image_colorspace_rgb((float *)work_rgb, (float *)display_rgb, (int)count, 1,
282 viewer->lut_profile, viewer->display_profile, message);
283 _clamp_display_rgb_array_simd(display_rgb, display_rgb, count);
284}
285
287{
288 if(viewer->surface)
289 {
290 cairo_surface_destroy(viewer->surface);
291 viewer->surface = NULL;
292 }
293}
294
296{
297 viewer->sample_cache_valid = FALSE;
298 viewer->sample_count = 0;
299 viewer->sample_white_index = 0;
301}
302
303static inline gboolean _show_control_nodes(const dt_lut_viewer_t *viewer)
304{
305 return viewer && viewer->show_control_nodes
306 && gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(viewer->show_control_nodes));
307}
308
309static int _ensure_sample_cache_capacity(dt_lut_viewer_t *viewer, const size_t capacity)
310{
311 if(viewer->sample_capacity >= capacity) return 0;
312
317 viewer->sample_input_work = dt_calloc_align(capacity * sizeof(dt_aligned_pixel_simd_t));
318 viewer->sample_output_work = dt_calloc_align(capacity * sizeof(dt_aligned_pixel_simd_t));
319 viewer->sample_input_display = dt_calloc_align(capacity * sizeof(dt_aligned_pixel_simd_t));
320 viewer->sample_output_display = dt_calloc_align(capacity * sizeof(dt_aligned_pixel_simd_t));
321
323 || !viewer->sample_output_display)
324 {
329 viewer->sample_input_work = NULL;
330 viewer->sample_output_work = NULL;
331 viewer->sample_input_display = NULL;
332 viewer->sample_output_display = NULL;
333 viewer->sample_capacity = 0;
335 return 1;
336 }
337
338 viewer->sample_capacity = capacity;
339 return 0;
340}
341
357
358static inline gboolean _gamut_matches_lut_profile(const dt_lut_viewer_t *viewer,
359 const dt_lut_viewer_gamut_t gamut)
360{
361 if(IS_NULL_PTR(viewer->lut_profile)) return FALSE;
362
363 switch(gamut)
364 {
366 return viewer->lut_profile->type == DT_COLORSPACE_SRGB
370 return viewer->lut_profile->type == DT_COLORSPACE_ADOBERGB;
372 return viewer->lut_profile->type == DT_COLORSPACE_DISPLAY_P3
374 || viewer->lut_profile->type == DT_COLORSPACE_PQ_P3;
379 default:
380 return FALSE;
381 }
382}
383
392{
393 const dt_colorspaces_color_profile_t *profile
395 if(IS_NULL_PTR(profile)|| IS_NULL_PTR(profile->profile)) return 1;
396
397 const cmsCIEXYZ *red = cmsReadTag(profile->profile, cmsSigRedColorantTag);
398 const cmsCIEXYZ *green = cmsReadTag(profile->profile, cmsSigGreenColorantTag);
399 const cmsCIEXYZ *blue = cmsReadTag(profile->profile, cmsSigBlueColorantTag);
400 if(IS_NULL_PTR(red) || IS_NULL_PTR(green) || IS_NULL_PTR(blue)) return 1;
401
402 dt_colormatrix_t rgb_to_xyz = { { 0.f } };
403 rgb_to_xyz[0][0] = red->X;
404 rgb_to_xyz[0][1] = green->X;
405 rgb_to_xyz[0][2] = blue->X;
406 rgb_to_xyz[1][0] = red->Y;
407 rgb_to_xyz[1][1] = green->Y;
408 rgb_to_xyz[1][2] = blue->Y;
409 rgb_to_xyz[2][0] = red->Z;
410 rgb_to_xyz[2][1] = green->Z;
411 rgb_to_xyz[2][2] = blue->Z;
412
413 return mat3SSEinv(xyz_to_rgb, rgb_to_xyz);
414}
415
416static inline gboolean _sample_fits_gamut(const dt_iop_order_iccprofile_info_t *lut_profile,
417 const dt_colormatrix_t xyz_to_rgb,
418 const dt_aligned_pixel_simd_t rgb)
419{
420 const float epsilon = 1e-3f;
421 dt_aligned_pixel_simd_t xyz = { 0.f };
422 dt_aligned_pixel_simd_t gamut_rgb = { 0.f };
423 dt_apply_transposed_color_matrix((float *)&rgb, lut_profile->matrix_in_transposed, (float *)&xyz);
424 dot_product((float *)&xyz, xyz_to_rgb, (float *)&gamut_rgb);
425
432 return gamut_rgb[0] >= -epsilon && gamut_rgb[0] <= 1.f + epsilon
433 && gamut_rgb[1] >= -epsilon && gamut_rgb[1] <= 1.f + epsilon
434 && gamut_rgb[2] >= -epsilon && gamut_rgb[2] <= 1.f + epsilon;
435}
436
437static void _build_projection(dt_lut_viewer_projection_t *projection, const float rotation_around_axis,
438 const float rotation_of_axis, const float slice_depth, const float slice_thickness,
439 const float zoom,
440 const float pan_x, const float pan_y, const int width, const int height)
441{
442 static const dt_aligned_pixel_simd_t axis = { 0.5773502691896258f, 0.5773502691896258f, 0.5773502691896258f, 0.f };
443 static const dt_aligned_pixel_simd_t chroma_x = { 0.7071067811865475f, -0.7071067811865475f, 0.f, 0.f };
444 static const dt_aligned_pixel_simd_t chroma_y = { 0.4082482904638630f, 0.4082482904638630f, -0.8164965809277260f, 0.f };
445
446 const float azimuth = rotation_around_axis * M_PI_F / 180.f;
447 const float tilt = rotation_of_axis * M_PI_F / 180.f;
448 dt_aligned_pixel_simd_t rotated_x = { 0.f };
449 dt_aligned_pixel_simd_t rotated_y = { 0.f };
450
451 for(int c = 0; c < 3; c++)
452 {
453 rotated_x[c] = cosf(azimuth) * chroma_x[c] + sinf(azimuth) * chroma_y[c];
454 rotated_y[c] = -sinf(azimuth) * chroma_x[c] + cosf(azimuth) * chroma_y[c];
455 projection->screen_z[c] = cosf(tilt) * rotated_y[c] + sinf(tilt) * axis[c];
456 projection->screen_x[c] = rotated_x[c];
457 }
458
459 projection->screen_y = _normalize3(_cross3(projection->screen_x, projection->screen_z));
460 projection->screen_z = _normalize3(projection->screen_z);
461
462 projection->min_x = INFINITY;
463 projection->max_x = -INFINITY;
464 projection->min_y = INFINITY;
465 projection->max_y = -INFINITY;
466 projection->min_depth = INFINITY;
467 projection->max_depth = -INFINITY;
468
474 for(int corner = 0; corner < 8; corner++)
475 {
476 const dt_aligned_pixel_simd_t centered = {
477 ((corner & 1) ? 1.f : 0.f) - 0.5f,
478 ((corner & 2) ? 1.f : 0.f) - 0.5f,
479 ((corner & 4) ? 1.f : 0.f) - 0.5f,
480 0.f
481 };
482
483 const float px = _dot3(centered, projection->screen_x);
484 const float py = _dot3(centered, projection->screen_y);
485 const float pz = _dot3(centered, projection->screen_z);
486
487 projection->min_x = fminf(projection->min_x, px);
488 projection->max_x = fmaxf(projection->max_x, px);
489 projection->min_y = fminf(projection->min_y, py);
490 projection->max_y = fmaxf(projection->max_y, py);
491 projection->min_depth = fminf(projection->min_depth, pz);
492 projection->max_depth = fmaxf(projection->max_depth, pz);
493 }
494
495 const float span_x = fmaxf(projection->max_x - projection->min_x, 1e-3f);
496 const float span_y = fmaxf(projection->max_y - projection->min_y, 1e-3f);
497 const float available_width = fmaxf((float)width - 2.f * DT_LUT_VIEWER_MARGIN, 1.f);
498 const float available_height = fmaxf((float)height - 2.f * DT_LUT_VIEWER_MARGIN, 1.f);
499
500 projection->scale = 0.9f * CLAMP(zoom, 0.25f, 8.f) * fminf(available_width / span_x, available_height / span_y);
501 projection->offset_x = 0.5f * ((float)width - projection->scale * (projection->min_x + projection->max_x)) + pan_x;
502 projection->offset_y = 0.5f * ((float)height + projection->scale * (projection->min_y + projection->max_y)) + pan_y;
503 projection->slice_depth = projection->min_depth
504 + CLAMP(slice_depth, 0.f, 100.f) * 0.01f
505 * (projection->max_depth - projection->min_depth);
506 projection->slice_half_thickness = CLAMP(slice_thickness, 0.f, 100.f) * 0.01f
507 * (projection->max_depth - projection->min_depth);
508}
509
510static inline void _project_point(const dt_lut_viewer_projection_t *projection, const dt_aligned_pixel_simd_t rgb,
511 float *const x, float *const y, float *const depth)
512{
513 const dt_aligned_pixel_simd_t centered = { rgb[0] - 0.5f, rgb[1] - 0.5f, rgb[2] - 0.5f, 0.f };
514 const float px = _dot3(centered, projection->screen_x);
515 const float py = _dot3(centered, projection->screen_y);
516
517 *x = projection->offset_x + projection->scale * px;
518 *y = projection->offset_y - projection->scale * py;
519 *depth = _dot3(centered, projection->screen_z);
520}
521
522static inline void _draw_arrow(cairo_t *cr, const float x0, const float y0, const float x1, const float y1,
523 const float radius0, const float radius1, const dt_aligned_pixel_simd_t color)
524{
525 const float dx = x1 - x0;
526 const float dy = y1 - y0;
527 const float length = sqrtf(dx * dx + dy * dy);
528 if(length < 1e-3f) return;
529
530 const float ux = dx / length;
531 const float uy = dy / length;
532 const float start_x = x0 + radius0 * ux;
533 const float start_y = y0 + radius0 * uy;
534 const float end_x = x1 - radius1 * ux;
535 const float end_y = y1 - radius1 * uy;
536 const float visible_dx = end_x - start_x;
537 const float visible_dy = end_y - start_y;
538 const float visible_length = sqrtf(visible_dx * visible_dx + visible_dy * visible_dy);
539 if(visible_length < 1e-3f) return;
540
541 cairo_set_source_rgba(cr, color[0], color[1], color[2], 0.65);
542 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.8f));
543 cairo_move_to(cr, start_x, start_y);
544 cairo_line_to(cr, end_x, end_y);
545 cairo_stroke(cr);
546
547 const float head = DT_PIXEL_APPLY_DPI(8.f);
548 const float nx = -uy;
549 const float ny = ux;
550
551 cairo_move_to(cr, end_x, end_y);
552 cairo_line_to(cr, end_x - head * ux + 0.5f * head * nx, end_y - head * uy + 0.5f * head * ny);
553 cairo_line_to(cr, end_x - head * ux - 0.5f * head * nx, end_y - head * uy - 0.5f * head * ny);
554 cairo_close_path(cr);
555 cairo_set_source_rgba(cr, color[0], color[1], color[2], 0.75);
556 cairo_fill(cr);
557}
558
559static void _draw_cube(cairo_t *cr, const dt_lut_viewer_t *viewer, const dt_lut_viewer_projection_t *projection)
560{
561 float x[8] = { 0.f };
562 float y[8] = { 0.f };
563 float depth = 0.f;
564
565 for(int corner = 0; corner < 8; corner++)
566 {
567 const dt_aligned_pixel_simd_t rgb = {
568 (float)((corner & 1) != 0),
569 (float)((corner & 2) != 0),
570 (float)((corner & 4) != 0),
571 0.f
572 };
573 _project_point(projection, rgb, &x[corner], &y[corner], &depth);
574 }
575
576 set_color(cr, dt_bauhaus_get_global()->graph_border);
577 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.f));
578
579 for(int corner = 0; corner < 8; corner++)
580 {
581 for(int axis = 0; axis < 3; axis++)
582 {
583 const int other = corner ^ (1 << axis);
584 if(other < corner) continue;
585
586 cairo_move_to(cr, x[corner], y[corner]);
587 cairo_line_to(cr, x[other], y[other]);
588 cairo_stroke(cr);
589 }
590 }
591
592 const dt_aligned_pixel_simd_t black = { 0.f, 0.f, 0.f, 0.f };
593 const dt_aligned_pixel_simd_t white = { 1.f, 1.f, 1.f, 0.f };
594 float x0 = 0.f, y0 = 0.f;
595 float x1 = 0.f, y1 = 0.f;
596 dt_aligned_pixel_simd_t display_rgb = { 0.f };
597 cairo_pattern_t *gradient = NULL;
598 _project_point(projection, black, &x0, &y0, &depth);
599 _project_point(projection, white, &x1, &y1, &depth);
600
606 gradient = cairo_pattern_create_linear(x0, y0, x1, y1);
607 for(int k = 0; k <= 16; k++)
608 {
609 const float grey = (float)k / 16.f;
610 display_rgb = _to_display_rgb(viewer, (dt_aligned_pixel_simd_t){ grey, grey, grey, 0.f });
611 cairo_pattern_add_color_stop_rgba(gradient, grey, display_rgb[0], display_rgb[1], display_rgb[2], 0.85);
612 }
613
614 cairo_set_source(cr, gradient);
615 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.f));
616 cairo_move_to(cr, x0, y0);
617 cairo_line_to(cr, x1, y1);
618 cairo_stroke(cr);
619 cairo_pattern_destroy(gradient);
620}
621
622static void _draw_axes(cairo_t *cr, const dt_lut_viewer_t *viewer, const dt_lut_viewer_projection_t *projection)
623{
624 static const dt_aligned_pixel_simd_t center = { 0.5f, 0.5f, 0.5f, 0.f };
625 const dt_aligned_pixel_simd_t red = { 0.5f + DT_LUT_VIEWER_AXIS_LENGTH / projection->scale, 0.5f, 0.5f, 0.f };
626 const dt_aligned_pixel_simd_t green = { 0.5f, 0.5f + DT_LUT_VIEWER_AXIS_LENGTH / projection->scale, 0.5f, 0.f };
627 const dt_aligned_pixel_simd_t blue = { 0.5f, 0.5f, 0.5f + DT_LUT_VIEWER_AXIS_LENGTH / projection->scale, 0.f };
628 const dt_aligned_pixel_simd_t axis_work[3] = {
629 { 1.f, 0.f, 0.f, 0.f },
630 { 0.f, 1.f, 0.f, 0.f },
631 { 0.f, 0.f, 1.f, 0.f }
632 };
633 dt_aligned_pixel_simd_t axis_display[3] = { { 0.f }, { 0.f }, { 0.f } };
634 float cx = 0.f, cy = 0.f, depth = 0.f;
635 float px = 0.f, py = 0.f;
636
637 _project_point(projection, center, &cx, &cy, &depth);
638 for(int k = 0; k < 3; k++) axis_display[k] = _to_display_rgb(viewer, axis_work[k]);
639
640 _project_point(projection, red, &px, &py, &depth);
641 cairo_set_source_rgba(cr, axis_display[0][0], axis_display[0][1], axis_display[0][2], 0.9);
642 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.f));
643 cairo_move_to(cr, cx, cy);
644 cairo_line_to(cr, px, py);
645 cairo_stroke(cr);
646
647 _project_point(projection, green, &px, &py, &depth);
648 cairo_set_source_rgba(cr, axis_display[1][0], axis_display[1][1], axis_display[1][2], 0.9);
649 cairo_move_to(cr, cx, cy);
650 cairo_line_to(cr, px, py);
651 cairo_stroke(cr);
652
653 _project_point(projection, blue, &px, &py, &depth);
654 cairo_set_source_rgba(cr, axis_display[2][0], axis_display[2][1], axis_display[2][2], 0.9);
655 cairo_move_to(cr, cx, cy);
656 cairo_line_to(cr, px, py);
657 cairo_stroke(cr);
658}
659
660static int _sample_stride(const int level)
661{
662 int stride = 1;
663
664 while(((level + stride - 1) / stride) * ((level + stride - 1) / stride) * ((level + stride - 1) / stride)
666 stride++;
667
668 return stride;
669}
670
671static inline int _sample_count(const int level, const int stride)
672{
673 return MAX((level + stride - 1) / stride, 2);
674}
675
676static inline int _sample_index(const int sample, const int samples, const int level)
677{
678 if(samples <= 1) return 0;
679 return MIN((int)lroundf((float)sample * (float)(level - 1) / (float)(samples - 1)), level - 1);
680}
681
682static void _draw_samples(cairo_t *cr, const dt_lut_viewer_t *viewer,
683 const dt_lut_viewer_projection_t *projection, const dt_lut_viewer_gamut_t gamut)
684{
685 const gboolean show_control_nodes = _show_control_nodes(viewer);
686 if(IS_NULL_PTR(viewer->lut_profile)) return;
687 if(show_control_nodes)
688 {
689 if(!viewer->control_nodes || viewer->control_node_count == 0) return;
690 }
691 else if(!viewer->clut || viewer->clut_level < 2)
692 return;
693
694 dt_lut_viewer_t *mutable_viewer = (dt_lut_viewer_t *)viewer;
695 const gboolean log_perf = (dt_get_debug_flags() & DT_DEBUG_PERF) != 0;
696 const double total_start = log_perf ? dt_get_wtime() : 0.0;
697 double collect_done = 0.0;
698 double convert_done = 0.0;
699 dt_colormatrix_t xyz_to_rgb = { { 0.f } };
700 const gboolean same_gamut_as_lut_profile = _gamut_matches_lut_profile(viewer, gamut);
701 if(!same_gamut_as_lut_profile && _get_xyz_to_rgb_matrix(gamut, xyz_to_rgb)) return;
702
703 const int stride = show_control_nodes ? 1 : _sample_stride(viewer->clut_level);
704 const int samples = show_control_nodes ? 0 : _sample_count(viewer->clut_level, stride);
705 const int level2 = viewer->clut_level * viewer->clut_level;
706 const size_t max_samples = show_control_nodes ? viewer->control_node_count
707 : (size_t)samples * (size_t)samples * (size_t)samples;
708 const float rotation_around_axis = dt_bauhaus_slider_get(viewer->rotation_around_axis);
709 const float rotation_of_axis = dt_bauhaus_slider_get(viewer->rotation_of_axis);
710 const float slice_depth = dt_bauhaus_slider_get(viewer->slice_depth);
711 const float slice_thickness = dt_bauhaus_slider_get(viewer->slice_thickness);
712 const float shift_threshold = dt_bauhaus_slider_get(viewer->shift_threshold);
713 const float zoom_scale = sqrtf(fmaxf(viewer->zoom, 0.25f));
714 const float target_radius = DT_PIXEL_APPLY_DPI(2.8f) * zoom_scale;
715 const float destination_radius = DT_PIXEL_APPLY_DPI(3.4f) * zoom_scale;
716 const gboolean rebuild_sample_cache
717 = !viewer->sample_cache_valid
718 || fabsf(viewer->sample_cache_rotation_around_axis - rotation_around_axis) > 1e-6f
719 || fabsf(viewer->sample_cache_rotation_of_axis - rotation_of_axis) > 1e-6f
720 || fabsf(viewer->sample_cache_slice_depth - slice_depth) > 1e-6f
721 || fabsf(viewer->sample_cache_slice_thickness - slice_thickness) > 1e-6f
722 || fabsf(viewer->sample_cache_shift_threshold - shift_threshold) > 1e-6f
723 || viewer->sample_cache_gamut != gamut
724 || viewer->sample_cache_clut != viewer->clut
725 || viewer->sample_cache_clut_level != viewer->clut_level
726 || viewer->sample_cache_lut_profile != viewer->lut_profile
728 || viewer->sample_cache_control_nodes != viewer->control_nodes
730 || viewer->sample_cache_show_control_nodes != show_control_nodes;
731
732 if(rebuild_sample_cache)
733 {
734 if(_ensure_sample_cache_capacity(mutable_viewer, max_samples)) return;
735
736 mutable_viewer->sample_count = 0;
737 mutable_viewer->sample_white_index = 0;
738 mutable_viewer->sample_draw_white_last = FALSE;
739
740 if(show_control_nodes)
741 {
742 for(size_t k = 0; k < viewer->control_node_count; k++)
743 {
744 const dt_aligned_pixel_simd_t input_rgb = {
745 CLAMP(viewer->control_nodes[k].input_rgb[0], 0.f, 1.f),
746 CLAMP(viewer->control_nodes[k].input_rgb[1], 0.f, 1.f),
747 CLAMP(viewer->control_nodes[k].input_rgb[2], 0.f, 1.f),
748 0.f
749 };
750 const dt_aligned_pixel_simd_t output_rgb = {
751 CLAMP(viewer->control_nodes[k].output_rgb[0], 0.f, 1.f),
752 CLAMP(viewer->control_nodes[k].output_rgb[1], 0.f, 1.f),
753 CLAMP(viewer->control_nodes[k].output_rgb[2], 0.f, 1.f),
754 0.f
755 };
756 if(!same_gamut_as_lut_profile && !_sample_fits_gamut(viewer->lut_profile, xyz_to_rgb, input_rgb)) continue;
757
758 float x0 = 0.f, y0 = 0.f, depth0 = 0.f;
759 float x1 = 0.f, y1 = 0.f, depth1 = 0.f;
760 _project_point(projection, input_rgb, &x0, &y0, &depth0);
761 _project_point(projection, output_rgb, &x1, &y1, &depth1);
762
763 const gboolean target_in_slice = fabsf(depth0 - projection->slice_depth) <= projection->slice_half_thickness;
764 const gboolean destination_in_slice
765 = fabsf(depth1 - projection->slice_depth) <= projection->slice_half_thickness;
766 if(!target_in_slice && !destination_in_slice) continue;
767
768 mutable_viewer->sample_input_work[mutable_viewer->sample_count] = input_rgb;
769 mutable_viewer->sample_output_work[mutable_viewer->sample_count] = output_rgb;
770
771 if(input_rgb[0] >= 1.f - 1e-6f && input_rgb[1] >= 1.f - 1e-6f && input_rgb[2] >= 1.f - 1e-6f)
772 {
773 mutable_viewer->sample_white_index = mutable_viewer->sample_count;
774 mutable_viewer->sample_draw_white_last = TRUE;
775 }
776
777 mutable_viewer->sample_count++;
778 }
779 }
780 else
781 {
794 for(int sample_b = 0; sample_b < samples; sample_b++)
795 for(int sample_g = 0; sample_g < samples; sample_g++)
796 for(int sample_r = 0; sample_r < samples; sample_r++)
797 {
798 const int b = _sample_index(sample_b, samples, viewer->clut_level);
799 const int g = _sample_index(sample_g, samples, viewer->clut_level);
800 const int r = _sample_index(sample_r, samples, viewer->clut_level);
801 const dt_aligned_pixel_simd_t input_rgb = {
802 (float)r / (float)(viewer->clut_level - 1),
803 (float)g / (float)(viewer->clut_level - 1),
804 (float)b / (float)(viewer->clut_level - 1),
805 0.f
806 };
807
808 if(!same_gamut_as_lut_profile && !_sample_fits_gamut(viewer->lut_profile, xyz_to_rgb, input_rgb))
809 continue;
810
811 const size_t index = (size_t)(r + g * viewer->clut_level + b * level2) * 3;
812 const dt_aligned_pixel_simd_t output_rgb = {
813 CLAMP(viewer->clut[index + 0], 0.f, 1.f),
814 CLAMP(viewer->clut[index + 1], 0.f, 1.f),
815 CLAMP(viewer->clut[index + 2], 0.f, 1.f),
816 0.f
817 };
818 if(_shift_distance_percent(input_rgb, output_rgb) < shift_threshold) continue;
819
820 float x0 = 0.f, y0 = 0.f, depth0 = 0.f;
821 float x1 = 0.f, y1 = 0.f, depth1 = 0.f;
822 _project_point(projection, input_rgb, &x0, &y0, &depth0);
823 _project_point(projection, output_rgb, &x1, &y1, &depth1);
824
825 const gboolean target_in_slice
826 = fabsf(depth0 - projection->slice_depth) <= projection->slice_half_thickness;
827 const gboolean destination_in_slice
828 = fabsf(depth1 - projection->slice_depth) <= projection->slice_half_thickness;
829 if(!target_in_slice && !destination_in_slice) continue;
830
831 mutable_viewer->sample_input_work[mutable_viewer->sample_count] = input_rgb;
832 mutable_viewer->sample_output_work[mutable_viewer->sample_count] = output_rgb;
833
834 if(r == viewer->clut_level - 1 && g == viewer->clut_level - 1 && b == viewer->clut_level - 1)
835 {
836 mutable_viewer->sample_white_index = mutable_viewer->sample_count;
837 mutable_viewer->sample_draw_white_last = TRUE;
838 }
839 mutable_viewer->sample_count++;
840 }
841 }
842
843 if(log_perf) collect_done = dt_get_wtime();
844
846 "lut viewer swatch inputs");
848 "lut viewer swatch outputs");
849 if(log_perf) convert_done = dt_get_wtime();
850
851 mutable_viewer->sample_cache_rotation_around_axis = rotation_around_axis;
852 mutable_viewer->sample_cache_rotation_of_axis = rotation_of_axis;
853 mutable_viewer->sample_cache_slice_depth = slice_depth;
854 mutable_viewer->sample_cache_slice_thickness = slice_thickness;
855 mutable_viewer->sample_cache_shift_threshold = shift_threshold;
856 mutable_viewer->sample_cache_gamut = gamut;
857 mutable_viewer->sample_cache_clut = viewer->clut;
858 mutable_viewer->sample_cache_clut_level = viewer->clut_level;
859 mutable_viewer->sample_cache_lut_profile = viewer->lut_profile;
860 mutable_viewer->sample_cache_display_profile = viewer->display_profile;
861 mutable_viewer->sample_cache_control_nodes = viewer->control_nodes;
862 mutable_viewer->sample_cache_control_node_count = viewer->control_node_count;
863 mutable_viewer->sample_cache_show_control_nodes = show_control_nodes;
864 mutable_viewer->sample_cache_valid = TRUE;
865 }
866 else if(log_perf)
867 {
868 collect_done = total_start;
869 convert_done = total_start;
870 }
871
872 for(size_t k = 0; k < viewer->sample_count; k++)
873 {
874 if(viewer->sample_draw_white_last && k == viewer->sample_white_index) continue;
875
876 float x0 = 0.f, y0 = 0.f, depth0 = 0.f;
877 float x1 = 0.f, y1 = 0.f, depth1 = 0.f;
878 _project_point(projection, viewer->sample_input_work[k], &x0, &y0, &depth0);
879 _project_point(projection, viewer->sample_output_work[k], &x1, &y1, &depth1);
880
881 _draw_arrow(cr, x0, y0, x1, y1, target_radius, destination_radius, viewer->sample_output_display[k]);
882
883 cairo_arc(cr, x0, y0, target_radius, 0.f, 2.f * M_PI_F);
884 cairo_set_source_rgba(cr, viewer->sample_input_display[k][0], viewer->sample_input_display[k][1],
885 viewer->sample_input_display[k][2], 0.8);
886 cairo_fill_preserve(cr);
887 cairo_set_source_rgba(cr, 0.1, 0.1, 0.1, 0.6);
888 cairo_stroke(cr);
889
890 cairo_arc(cr, x1, y1, destination_radius, 0.f, 2.f * M_PI_F);
891 cairo_set_source_rgba(cr, viewer->sample_output_display[k][0], viewer->sample_output_display[k][1],
892 viewer->sample_output_display[k][2], 0.9);
893 cairo_fill_preserve(cr);
894 cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 0.5);
895 cairo_stroke(cr);
896 }
897
898 if(viewer->sample_draw_white_last)
899 {
900 const size_t k = viewer->sample_white_index;
901 float x0 = 0.f, y0 = 0.f, depth0 = 0.f;
902 float x1 = 0.f, y1 = 0.f, depth1 = 0.f;
903 _project_point(projection, viewer->sample_input_work[k], &x0, &y0, &depth0);
904 _project_point(projection, viewer->sample_output_work[k], &x1, &y1, &depth1);
905
906 _draw_arrow(cr, x0, y0, x1, y1, target_radius, destination_radius, viewer->sample_output_display[k]);
907
908 cairo_arc(cr, x0, y0, target_radius, 0.f, 2.f * M_PI_F);
909 cairo_set_source_rgba(cr, viewer->sample_input_display[k][0], viewer->sample_input_display[k][1],
910 viewer->sample_input_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 cairo_arc(cr, x1, y1, destination_radius, 0.f, 2.f * M_PI_F);
916 cairo_set_source_rgba(cr, viewer->sample_output_display[k][0], viewer->sample_output_display[k][1],
917 viewer->sample_output_display[k][2], 0.95);
918 cairo_fill_preserve(cr);
919 cairo_set_source_rgba(cr, 0.2, 0.2, 0.2, 0.8);
920 cairo_stroke(cr);
921 }
922
923 if(log_perf)
924 {
925 const double total_done = dt_get_wtime();
927 "[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",
928 show_control_nodes ? "controls" : "lut", viewer->clut_level, samples, max_samples, viewer->sample_count, gamut,
929 rebuild_sample_cache ? "rebuild" : "reuse",
930 1000.0 * (collect_done - total_start),
931 1000.0 * (convert_done - collect_done),
932 1000.0 * (total_done - convert_done),
933 1000.0 * (total_done - total_start));
934 }
935}
936
937static void _draw_placeholder(cairo_t *cr, const int width, const int height, const char *message)
938{
939 cairo_text_extents_t extents;
940 cairo_select_font_face(cr, "sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
941 cairo_set_font_size(cr, DT_PIXEL_APPLY_DPI(12.f));
942 cairo_text_extents(cr, message, &extents);
943 cairo_set_source_rgba(cr, 0.9, 0.9, 0.9, 0.7);
944 cairo_move_to(cr, 0.5f * ((float)width - extents.width), 0.5f * ((float)height - extents.height));
945 cairo_show_text(cr, message);
946}
947
948static void _render_surface(dt_lut_viewer_t *viewer, const int width, const int height)
949{
950 const gboolean log_perf = (dt_get_debug_flags() & DT_DEBUG_PERF) != 0;
951 const double start = log_perf ? dt_get_wtime() : 0.0;
952 const double ppd = (dt_gui_get_global() && dt_gui_get_global()->ppd > 0.0) ? dt_gui_get_global()->ppd : 1.0;
953 if(viewer->clut_lock) dt_pthread_rwlock_rdlock(viewer->clut_lock);
954 _invalidate_surface(viewer);
955 viewer->surface = dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
956 MAX((int)ceil((double)width * ppd), 1),
957 MAX((int)ceil((double)height * ppd), 1));
958 cairo_surface_set_device_scale(viewer->surface, ppd, ppd);
959 cairo_t *cr = cairo_create(viewer->surface);
960 GtkStyleContext *context = gtk_widget_get_style_context(GTK_WIDGET(viewer->area));
961
962 gtk_render_background(context, cr, 0, 0, width, height);
963 gtk_render_frame(context, cr, 0, 0, width, height);
964
965 if(IS_NULL_PTR(viewer->lut_profile))
966 {
967 _draw_placeholder(cr, width, height, _("no LUT to display"));
968 cairo_destroy(cr);
969 if(viewer->clut_lock) dt_pthread_rwlock_unlock(viewer->clut_lock);
970 if(log_perf)
972 "[lut_viewer] render_surface %dx%d ppd=%.2f placeholder=1 total=%.3fms\n",
973 width, height, ppd, 1000.0 * (dt_get_wtime() - start));
974 return;
975 }
976
977 if(_show_control_nodes(viewer))
978 {
979 if(!viewer->control_nodes || viewer->control_node_count == 0)
980 {
981 _draw_placeholder(cr, width, height, _("no control nodes to display"));
982 cairo_destroy(cr);
983 if(viewer->clut_lock) dt_pthread_rwlock_unlock(viewer->clut_lock);
984 return;
985 }
986 }
987 else if(!viewer->clut || viewer->clut_level < 2)
988 {
989 _draw_placeholder(cr, width, height, _("no LUT to display"));
990 cairo_destroy(cr);
991 if(viewer->clut_lock) dt_pthread_rwlock_unlock(viewer->clut_lock);
992 if(log_perf)
994 "[lut_viewer] render_surface %dx%d ppd=%.2f placeholder=1 total=%.3fms\n",
995 width, height, ppd, 1000.0 * (dt_get_wtime() - start));
996 return;
997 }
998
999 const dt_lut_viewer_gamut_t gamut
1001 dt_lut_viewer_projection_t projection;
1002 _build_projection(&projection,
1007 viewer->zoom,
1008 viewer->pan_x,
1009 viewer->pan_y,
1010 width, height);
1011
1012 _draw_cube(cr, viewer, &projection);
1013 _draw_axes(cr, viewer, &projection);
1014 _draw_samples(cr, viewer, &projection, gamut);
1015 cairo_destroy(cr);
1016 if(viewer->clut_lock) dt_pthread_rwlock_unlock(viewer->clut_lock);
1017
1018 if(log_perf)
1020 "[lut_viewer] render_surface %dx%d ppd=%.2f level=%u gamut=%d total=%.3fms\n",
1021 width, height, ppd, viewer->clut_level, gamut, 1000.0 * (dt_get_wtime() - start));
1022}
1023
1024static gboolean _draw_callback(GtkWidget *widget, cairo_t *cr, gpointer user_data)
1025{
1026 dt_lut_viewer_t *viewer = (dt_lut_viewer_t *)user_data;
1027 GtkAllocation allocation;
1028 gtk_widget_get_allocation(widget, &allocation);
1029
1030 const float rotation_around_axis = dt_bauhaus_slider_get(viewer->rotation_around_axis);
1031 const float rotation_of_axis = dt_bauhaus_slider_get(viewer->rotation_of_axis);
1032 const float slice_depth = dt_bauhaus_slider_get(viewer->slice_depth);
1033 const float slice_thickness = dt_bauhaus_slider_get(viewer->slice_thickness);
1034 const float shift_threshold = dt_bauhaus_slider_get(viewer->shift_threshold);
1035 const float zoom = viewer->zoom;
1036 const int gamut = dt_bauhaus_combobox_get(viewer->gamut);
1037 const gboolean show_control_nodes = _show_control_nodes(viewer);
1038 const double ppd = (dt_gui_get_global() && dt_gui_get_global()->ppd > 0.0) ? dt_gui_get_global()->ppd : 1.0;
1039
1040 if(!viewer->surface
1041 || viewer->cached_width != allocation.width
1042 || viewer->cached_height != allocation.height
1043 || fabsf(viewer->cached_rotation_around_axis - rotation_around_axis) > 1e-6f
1044 || fabsf(viewer->cached_rotation_of_axis - rotation_of_axis) > 1e-6f
1045 || fabsf(viewer->cached_slice_depth - slice_depth) > 1e-6f
1046 || fabsf(viewer->cached_slice_thickness - slice_thickness) > 1e-6f
1047 || fabsf(viewer->cached_shift_threshold - shift_threshold) > 1e-6f
1048 || fabsf(viewer->cached_zoom - zoom) > 1e-6f
1049 || fabsf(viewer->cached_pan_x - viewer->pan_x) > 1e-6f
1050 || fabsf(viewer->cached_pan_y - viewer->pan_y) > 1e-6f
1051 || viewer->cached_gamut != gamut
1052 || viewer->cached_clut != viewer->clut
1053 || viewer->cached_clut_level != viewer->clut_level
1054 || viewer->cached_lut_profile != viewer->lut_profile
1055 || viewer->cached_display_profile != viewer->display_profile
1056 || viewer->cached_control_nodes != viewer->control_nodes
1057 || viewer->cached_control_node_count != viewer->control_node_count
1058 || viewer->cached_show_control_nodes != show_control_nodes
1059 || fabs(viewer->cached_ppd - ppd) > 1e-9)
1060 {
1061 _render_surface(viewer, allocation.width, allocation.height);
1062 viewer->cached_width = allocation.width;
1063 viewer->cached_height = allocation.height;
1064 viewer->cached_rotation_around_axis = rotation_around_axis;
1065 viewer->cached_rotation_of_axis = rotation_of_axis;
1066 viewer->cached_slice_depth = slice_depth;
1067 viewer->cached_slice_thickness = slice_thickness;
1068 viewer->cached_shift_threshold = shift_threshold;
1069 viewer->cached_zoom = zoom;
1070 viewer->cached_pan_x = viewer->pan_x;
1071 viewer->cached_pan_y = viewer->pan_y;
1072 viewer->cached_gamut = gamut;
1073 viewer->cached_clut = viewer->clut;
1074 viewer->cached_clut_level = viewer->clut_level;
1075 viewer->cached_lut_profile = viewer->lut_profile;
1076 viewer->cached_display_profile = viewer->display_profile;
1077 viewer->cached_control_nodes = viewer->control_nodes;
1079 viewer->cached_show_control_nodes = show_control_nodes;
1080 viewer->cached_ppd = ppd;
1081 }
1082
1083 if(viewer->surface)
1084 {
1085 cairo_set_source_surface(cr, viewer->surface, 0, 0);
1086 cairo_paint(cr);
1087 }
1088
1089 return TRUE;
1090}
1091
1092static void _control_changed(GtkWidget *widget, gpointer user_data)
1093{
1094 dt_lut_viewer_t *viewer = (dt_lut_viewer_t *)user_data;
1095 _invalidate_surface(viewer);
1096 gtk_widget_queue_draw(GTK_WIDGET(viewer->area));
1097}
1098
1099static gboolean _scroll_callback(GtkWidget *widget, GdkEventScroll *event, gpointer user_data)
1100{
1101 dt_lut_viewer_t *viewer = (dt_lut_viewer_t *)user_data;
1102 GtkAllocation allocation;
1103 gtk_widget_get_allocation(widget, &allocation);
1104 float zoom_step = 1.f;
1105
1106 switch(event->direction)
1107 {
1108 case GDK_SCROLL_UP:
1109 zoom_step = 1.1f;
1110 break;
1111 case GDK_SCROLL_DOWN:
1112 zoom_step = 1.f / 1.1f;
1113 break;
1114 case GDK_SCROLL_SMOOTH:
1115 zoom_step = exp2f((float)(-event->delta_y) * 0.2f);
1116 break;
1117 default:
1118 return FALSE;
1119 }
1120
1121 const float old_zoom = viewer->zoom;
1122 const float new_zoom = CLAMP(old_zoom * zoom_step, 0.25f, 8.f);
1123 const float zoom_ratio = new_zoom / old_zoom;
1124 const float center_x = 0.5f * allocation.width;
1125 const float center_y = 0.5f * allocation.height;
1126
1127 viewer->pan_x = event->x - center_x - zoom_ratio * (event->x - center_x - viewer->pan_x);
1128 viewer->pan_y = event->y - center_y - zoom_ratio * (event->y - center_y - viewer->pan_y);
1129 viewer->zoom = new_zoom;
1130 _invalidate_surface(viewer);
1131 gtk_widget_queue_draw(widget);
1132 return TRUE;
1133}
1134
1135static gboolean _button_press_callback(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
1136{
1137 dt_lut_viewer_t *viewer = (dt_lut_viewer_t *)user_data;
1138 if(event->type == GDK_2BUTTON_PRESS)
1139 {
1144 viewer->zoom = 1.f;
1145 viewer->pan_x = 0.f;
1146 viewer->pan_y = 0.f;
1148 _invalidate_surface(viewer);
1149 gtk_widget_queue_draw(widget);
1150 return TRUE;
1151 }
1152
1153 if(event->button == 1)
1155 else if(event->button == 2)
1157 else
1158 return FALSE;
1159
1160 viewer->drag_anchor_x = event->x;
1161 viewer->drag_anchor_y = event->y;
1162 viewer->drag_origin_pan_x = viewer->pan_x;
1163 viewer->drag_origin_pan_y = viewer->pan_y;
1166 return TRUE;
1167}
1168
1169static gboolean _motion_notify_callback(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
1170{
1171 dt_lut_viewer_t *viewer = (dt_lut_viewer_t *)user_data;
1172 if(viewer->drag_mode == DT_LUT_VIEWER_DRAG_NONE) return FALSE;
1173
1174 if(viewer->drag_mode == DT_LUT_VIEWER_DRAG_PAN)
1175 {
1176 viewer->pan_x = viewer->drag_origin_pan_x + (float)(event->x - viewer->drag_anchor_x);
1177 viewer->pan_y = viewer->drag_origin_pan_y + (float)(event->y - viewer->drag_anchor_y);
1178 _invalidate_surface(viewer);
1179 gtk_widget_queue_draw(widget);
1180 }
1181 else if(viewer->drag_mode == DT_LUT_VIEWER_DRAG_ORBIT)
1182 {
1183 const float azimuth = _wrap_degrees_pm180(viewer->drag_origin_azimuth
1184 + 0.35f * (float)(event->x - viewer->drag_anchor_x));
1185 const float tilt = CLAMP(viewer->drag_origin_tilt
1186 - 0.25f * (float)(event->y - viewer->drag_anchor_y), 0.f, 90.f);
1189 }
1190
1191 return TRUE;
1192}
1193
1194static gboolean _button_release_callback(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
1195{
1196 dt_lut_viewer_t *viewer = (dt_lut_viewer_t *)user_data;
1197 if((event->button == 1 && viewer->drag_mode == DT_LUT_VIEWER_DRAG_PAN)
1198 || (event->button == 2 && viewer->drag_mode == DT_LUT_VIEWER_DRAG_ORBIT))
1200 return TRUE;
1201}
1202
1203static void _save_clut_callback(GtkWidget *widget, gpointer user_data)
1204{
1205 dt_lut_viewer_t *viewer = (dt_lut_viewer_t *)user_data;
1206 if(IS_NULL_PTR(viewer) || IS_NULL_PTR(viewer->clut) || viewer->clut_level < 2)
1207 {
1208 dt_control_log(_("no LUT to save"));
1209 return;
1210 }
1211
1213 GtkFileChooserNative *filechooser = gtk_file_chooser_native_new(
1214 _("save 3D LUT"), GTK_WINDOW(win), GTK_FILE_CHOOSER_ACTION_SAVE,
1215 _("_save"), _("_cancel"));
1216 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(filechooser), FALSE);
1217 gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(filechooser), TRUE);
1218 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(filechooser), "lut-viewer-export.cube");
1219
1220 gchar *lutfolder = dt_conf_get_string("plugins/darkroom/lut3d/def_path");
1221 if(lutfolder[0])
1222 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(filechooser), lutfolder);
1223 else
1224 dt_conf_get_folder_to_file_chooser("ui_last/export_path", GTK_FILE_CHOOSER(filechooser));
1225
1226 GtkFileFilter *filter = GTK_FILE_FILTER(gtk_file_filter_new());
1227 gtk_file_filter_add_pattern(filter, "*.cube");
1228 gtk_file_filter_add_pattern(filter, "*.CUBE");
1229 gtk_file_filter_set_name(filter, _("3D lut (cube)"));
1230 gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(filechooser), filter);
1231 gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(filechooser), filter);
1232
1233 if(gtk_native_dialog_run(GTK_NATIVE_DIALOG(filechooser)) == GTK_RESPONSE_ACCEPT)
1234 {
1235 gchar *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(filechooser));
1236 char *path = filename;
1237 FILE *cube_file = NULL;
1238
1239 if(!g_str_has_suffix(filename, ".cube") && !g_str_has_suffix(filename, ".CUBE"))
1240 path = g_strconcat(filename, ".cube", NULL);
1241
1242 cube_file = g_fopen(path, "wb");
1243 if(IS_NULL_PTR(cube_file))
1244 {
1245 dt_control_log(_("failed to save LUT file"));
1246 }
1247 else
1248 {
1249 const char *const lut_profile_name
1251 : _("unknown");
1252 fprintf(cube_file, "# generated by ansel LUT viewer\n");
1253 fprintf(cube_file, "# LUT_COLORSPACE %s\n", lut_profile_name);
1254 fprintf(cube_file, "TITLE \"lut viewer export\"\n");
1255 fprintf(cube_file, "LUT_3D_SIZE %u\n", viewer->clut_level);
1256 fprintf(cube_file, "DOMAIN_MIN 0.0 0.0 0.0\n");
1257 fprintf(cube_file, "DOMAIN_MAX 1.0 1.0 1.0\n");
1258
1259 for(uint16_t b = 0; b < viewer->clut_level; b++)
1260 for(uint16_t g = 0; g < viewer->clut_level; g++)
1261 for(uint16_t r = 0; r < viewer->clut_level; r++)
1262 {
1263 const size_t index
1264 = ((size_t)b * viewer->clut_level * viewer->clut_level + (size_t)g * viewer->clut_level + r) * 3;
1265 fprintf(cube_file, "%.7f %.7f %.7f\n",
1266 viewer->clut[index + 0], viewer->clut[index + 1], viewer->clut[index + 2]);
1267 }
1268
1269 fclose(cube_file);
1270 dt_control_log(_("saved LUT to %s"), path);
1271 dt_conf_set_folder_from_file_chooser("ui_last/export_path", GTK_FILE_CHOOSER(filechooser));
1272 }
1273
1274 if(path != filename) g_free(path);
1275 g_free(filename);
1276 }
1277
1278 dt_free(lutfolder);
1279 g_object_unref(filechooser);
1280}
1281
1283{
1284 dt_lut_viewer_t *viewer = calloc(1, sizeof(*viewer));
1285 if(IS_NULL_PTR(viewer)) return NULL;
1286
1287 viewer->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1288 viewer->controls = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
1289 viewer->area = GTK_DRAWING_AREA(dtgtk_drawing_area_new_with_aspect_ratio(1.f));
1290 gtk_widget_set_size_request(GTK_WIDGET(viewer->area), -1, DT_PIXEL_APPLY_DPI(180));
1291 gtk_widget_add_events(GTK_WIDGET(viewer->area),
1292 GDK_SCROLL_MASK | GDK_SMOOTH_SCROLL_MASK | GDK_BUTTON_PRESS_MASK
1293 | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK);
1294 g_signal_connect(G_OBJECT(viewer->area), "draw", G_CALLBACK(_draw_callback), viewer);
1295 g_signal_connect(G_OBJECT(viewer->area), "scroll-event", G_CALLBACK(_scroll_callback), viewer);
1296 g_signal_connect(G_OBJECT(viewer->area), "button-press-event", G_CALLBACK(_button_press_callback), viewer);
1297 g_signal_connect(G_OBJECT(viewer->area), "motion-notify-event", G_CALLBACK(_motion_notify_callback), viewer);
1298 g_signal_connect(G_OBJECT(viewer->area), "button-release-event", G_CALLBACK(_button_release_callback), viewer);
1299 gtk_box_pack_start(GTK_BOX(viewer->widget), GTK_WIDGET(viewer->area), TRUE, TRUE, 0);
1300
1301 viewer->rotation_around_axis
1302 = dt_bauhaus_slider_new_with_range(dt_bauhaus_get_global(), module, -180.f, 180.f, 1.f, 35.f, 0);
1305 gtk_box_pack_start(GTK_BOX(viewer->controls), viewer->rotation_around_axis, FALSE, FALSE, 0);
1306 g_signal_connect(G_OBJECT(viewer->rotation_around_axis), "value-changed", G_CALLBACK(_control_changed), viewer);
1307
1308 viewer->rotation_of_axis
1309 = dt_bauhaus_slider_new_with_range(dt_bauhaus_get_global(), module, 0.f, 90.f, 1.f, 0.f, 0);
1310 dt_bauhaus_widget_set_label(viewer->rotation_of_axis, _("axis tilt"));
1312 gtk_box_pack_start(GTK_BOX(viewer->controls), viewer->rotation_of_axis, FALSE, FALSE, 0);
1313 g_signal_connect(G_OBJECT(viewer->rotation_of_axis), "value-changed", G_CALLBACK(_control_changed), viewer);
1314
1315 viewer->slice_depth
1316 = dt_bauhaus_slider_new_with_range(dt_bauhaus_get_global(), module, 0.f, 100.f, 1.f, 50.f, 0);
1317 dt_bauhaus_widget_set_label(viewer->slice_depth, _("slice depth"));
1318 dt_bauhaus_slider_set_format(viewer->slice_depth, _(" %"));
1319 gtk_box_pack_start(GTK_BOX(viewer->controls), viewer->slice_depth, FALSE, FALSE, 0);
1320 g_signal_connect(G_OBJECT(viewer->slice_depth), "value-changed", G_CALLBACK(_control_changed), viewer);
1321
1322 viewer->slice_thickness
1323 = dt_bauhaus_slider_new_with_range(dt_bauhaus_get_global(), module, 0.f, 100.f, 1.f, 100.f, 0);
1324 dt_bauhaus_widget_set_label(viewer->slice_thickness, _("slice thickness"));
1326 gtk_box_pack_start(GTK_BOX(viewer->controls), viewer->slice_thickness, FALSE, FALSE, 0);
1327 g_signal_connect(G_OBJECT(viewer->slice_thickness), "value-changed", G_CALLBACK(_control_changed), viewer);
1328
1329 viewer->shift_threshold
1330 = dt_bauhaus_slider_new_with_range(dt_bauhaus_get_global(), module, 0.f, 100.f, 0.1f, 0.1f, 1);
1331 dt_bauhaus_widget_set_label(viewer->shift_threshold, _("color shift threshold"));
1333 gtk_box_pack_start(GTK_BOX(viewer->controls), viewer->shift_threshold, FALSE, FALSE, 0);
1334 g_signal_connect(G_OBJECT(viewer->shift_threshold), "value-changed", G_CALLBACK(_control_changed), viewer);
1335
1336 viewer->show_control_nodes = gtk_check_button_new_with_label(_("show control nodes"));
1337 gtk_widget_set_sensitive(viewer->show_control_nodes, FALSE);
1338 gtk_box_pack_start(GTK_BOX(viewer->controls), viewer->show_control_nodes, FALSE, FALSE, 0);
1339 g_signal_connect(G_OBJECT(viewer->show_control_nodes), "toggled", G_CALLBACK(_control_changed), viewer);
1340
1342 dt_bauhaus_widget_set_label(viewer->gamut, _("target gamut"));
1343 dt_bauhaus_combobox_add(viewer->gamut, _("sRGB/Rec709"));
1344 dt_bauhaus_combobox_add(viewer->gamut, _("Adobe RGB"));
1345 dt_bauhaus_combobox_add(viewer->gamut, _("Display P3"));
1346 dt_bauhaus_combobox_add(viewer->gamut, _("Rec2020"));
1348 gtk_box_pack_start(GTK_BOX(viewer->controls), viewer->gamut, FALSE, FALSE, 0);
1349 g_signal_connect(G_OBJECT(viewer->gamut), "value-changed", G_CALLBACK(_control_changed), viewer);
1350
1351 viewer->save_button = gtk_button_new_with_label(_("save to cLUT"));
1352 gtk_box_pack_start(GTK_BOX(viewer->controls), viewer->save_button, FALSE, FALSE, 0);
1353 g_signal_connect(G_OBJECT(viewer->save_button), "clicked", G_CALLBACK(_save_clut_callback), viewer);
1354
1355 GtkWidget *expander = gtk_expander_new(_("viewer controls"));
1356 gtk_expander_set_expanded(GTK_EXPANDER(expander), FALSE);
1357 gtk_container_add(GTK_CONTAINER(expander), viewer->controls);
1358 gtk_box_pack_start(GTK_BOX(viewer->widget), expander, FALSE, FALSE, 0);
1359
1360 viewer->zoom = 1.f;
1361 viewer->pan_x = 0.f;
1362 viewer->pan_y = 0.f;
1364 viewer->cached_gamut = -1;
1365 viewer->cached_rotation_around_axis = NAN;
1366 viewer->cached_rotation_of_axis = NAN;
1367 viewer->cached_slice_depth = NAN;
1368 viewer->cached_slice_thickness = NAN;
1369 viewer->cached_zoom = NAN;
1370 viewer->cached_pan_x = NAN;
1371 viewer->cached_pan_y = NAN;
1372 viewer->cached_shift_threshold = NAN;
1373 viewer->cached_ppd = NAN;
1375 viewer->sample_cache_rotation_of_axis = NAN;
1376 viewer->sample_cache_slice_depth = NAN;
1377 viewer->sample_cache_slice_thickness = NAN;
1378 viewer->sample_cache_shift_threshold = NAN;
1379 viewer->sample_cache_gamut = -1;
1380 viewer->sample_cache_control_nodes = NULL;
1383 return viewer;
1384}
1385
1387{
1388 if(IS_NULL_PTR(viewer) || !*viewer) return;
1389
1390 _invalidate_surface(*viewer);
1391 dt_free_align((*viewer)->sample_input_work);
1392 dt_free_align((*viewer)->sample_output_work);
1393 dt_free_align((*viewer)->sample_input_display);
1394 dt_free_align((*viewer)->sample_output_display);
1395 free(*viewer);
1396 *viewer = NULL;
1397}
1398
1400{
1401 return viewer ? viewer->widget : NULL;
1402}
1403
1404void dt_lut_viewer_set_lut(dt_lut_viewer_t *viewer, const float *clut, uint16_t level,
1405 dt_pthread_rwlock_t *clut_lock,
1406 const dt_iop_order_iccprofile_info_t *lut_profile,
1407 const dt_iop_order_iccprofile_info_t *display_profile)
1408{
1409 if(IS_NULL_PTR(viewer)) return;
1410
1411 viewer->clut = clut;
1412 viewer->clut_level = level;
1413 viewer->clut_lock = clut_lock;
1414 viewer->lut_profile = lut_profile;
1415 viewer->display_profile = display_profile;
1417 _invalidate_surface(viewer);
1418}
1419
1421 const dt_lut_viewer_control_node_t *control_nodes,
1422 size_t control_node_count)
1423{
1424 if(IS_NULL_PTR(viewer)) return;
1425
1426 viewer->control_nodes = control_nodes;
1427 viewer->control_node_count = control_node_count;
1428
1429 if(viewer->show_control_nodes)
1430 {
1431 const gboolean enabled = control_nodes && control_node_count > 0;
1432 gtk_widget_set_sensitive(viewer->show_control_nodes, enabled);
1433 if(!enabled) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(viewer->show_control_nodes), FALSE);
1434 }
1435
1437 _invalidate_surface(viewer);
1438}
1439
1441{
1442 if(viewer) gtk_widget_queue_draw(GTK_WIDGET(viewer->area));
1443}
GtkWidget * dt_gui_main_window(void)
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
void dt_bauhaus_slider_reset(GtkWidget *widget)
Definition bauhaus.c:3397
float dt_bauhaus_slider_get(GtkWidget *widget)
Definition bauhaus.c:3280
int dt_bauhaus_combobox_get(GtkWidget *widget)
Definition bauhaus.c:2136
void dt_bauhaus_slider_set(GtkWidget *widget, float pos)
Definition bauhaus.c:3331
void dt_bauhaus_combobox_set(GtkWidget *widget, const int pos)
Definition bauhaus.c:2090
void dt_bauhaus_widget_set_label(GtkWidget *widget, const char *label)
Definition bauhaus.c:1504
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:1632
GtkWidget * dt_bauhaus_combobox_new(dt_bauhaus_t *bh, dt_gui_module_t *self)
Definition bauhaus.c:1698
void dt_bauhaus_slider_set_format(GtkWidget *widget, const char *format)
Definition bauhaus.c:3407
void dt_bauhaus_combobox_add(GtkWidget *widget, const char *text)
Definition bauhaus.c:1824
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
const dt_colorspaces_color_profile_t * dt_colorspaces_get_profile(dt_colorspaces_color_profile_type_t type, const char *filename, dt_colorspaces_profile_role_t role)
Resolve a profile identity to its registered entry.
const char * dt_colorspaces_get_name(dt_colorspaces_color_profile_type_t type, const char *filename)
Printable name for a profile identity, without touching the profile list.
The colour-profile module's API: which profiles exist, and how to apply one.
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
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)
Read the stored string for name as a private copy.
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:824
struct dt_gui_gtk_t * dt_gui_get_global(void)
Definition darktable.c:523
struct dt_bauhaus_t * dt_bauhaus_get_global(void)
Definition darktable.c:646
#define M_PI_F
gboolean enabled
–doc was passed on the command line
static void set_color(cairo_t *cr, GdkRGBA color)
Definition draw.h:125
GtkWidget * dtgtk_drawing_area_new_with_aspect_ratio(double aspect)
Definition drawingarea.c:54
static int dt_pthread_rwlock_unlock(dt_pthread_rwlock_t *rwlock) RELEASE_GENERIC(rwlock) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:217
static int dt_pthread_rwlock_rdlock(dt_pthread_rwlock_t *rwlock) ACQUIRE_SHARED(rwlock) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:267
GdkRGBA color[]
Definition geotagging.c:541
@ DT_DEBUG_PERF
Definition logging.h:55
int32_t dt_get_debug_flags(void)
Definition darktable.c:2085
void dt_print(dt_debug_thread_t thread, const char *msg,...) __attribute__((format(printf
Print to stdout when thread is enabled, prefixed with seconds since startup.
float *const restrict const size_t k
static void _control_changed(GtkWidget *widget, gpointer user_data)
static gboolean _show_control_nodes(const dt_lut_viewer_t *viewer)
Definition lut_viewer.c:303
static float _wrap_degrees_pm180(float angle)
Definition lut_viewer.c:182
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:173
static dt_aligned_pixel_simd_t _set_vector(const float x, const float y, const float z)
Definition lut_viewer.c:156
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:391
dt_lut_viewer_gamut_t
Definition lut_viewer.c:47
@ DT_LUT_VIEWER_GAMUT_REC2020
Definition lut_viewer.c:51
@ DT_LUT_VIEWER_GAMUT_ADOBE_RGB
Definition lut_viewer.c:49
@ DT_LUT_VIEWER_GAMUT_SRGB
Definition lut_viewer.c:48
@ DT_LUT_VIEWER_GAMUT_DISPLAY_P3
Definition lut_viewer.c:50
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:510
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:660
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:189
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:948
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:214
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:254
static dt_aligned_pixel_simd_t _clamp01_simd(const dt_aligned_pixel_simd_t value)
Definition lut_viewer.c:198
static int _sample_count(const int level, const int stride)
Definition lut_viewer.c:671
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:416
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:342
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:682
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:273
#define DT_LUT_VIEWER_TARGET_SAMPLES
Definition lut_viewer.c:43
dt_lut_viewer_drag_mode_t
Definition lut_viewer.c:73
@ DT_LUT_VIEWER_DRAG_ORBIT
Definition lut_viewer.c:76
@ DT_LUT_VIEWER_DRAG_NONE
Definition lut_viewer.c:74
@ DT_LUT_VIEWER_DRAG_PAN
Definition lut_viewer.c:75
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:229
static int _sample_index(const int sample, const int samples, const int level)
Definition lut_viewer.c:676
static void _draw_axes(cairo_t *cr, const dt_lut_viewer_t *viewer, const dt_lut_viewer_projection_t *projection)
Definition lut_viewer.c:622
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:937
static void _invalidate_sample_cache(dt_lut_viewer_t *viewer)
Definition lut_viewer.c:295
static gboolean _gamut_matches_lut_profile(const dt_lut_viewer_t *viewer, const dt_lut_viewer_gamut_t gamut)
Definition lut_viewer.c:358
#define DT_LUT_VIEWER_MARGIN
Definition lut_viewer.c:42
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:166
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:437
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:522
static int _ensure_sample_cache_capacity(dt_lut_viewer_t *viewer, const size_t capacity)
Definition lut_viewer.c:309
#define DT_LUT_VIEWER_AXIS_LENGTH
Definition lut_viewer.c:44
static float _dot3(const dt_aligned_pixel_simd_t a, const dt_aligned_pixel_simd_t b)
Definition lut_viewer.c:161
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:286
static void _draw_cube(cairo_t *cr, const dt_lut_viewer_t *viewer, const dt_lut_viewer_projection_t *projection)
Definition lut_viewer.c:559
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition macros.h:96
float DT_ALIGNED_ARRAY dt_colormatrix_t[4][4]
Definition matrices.h:34
static int mat3SSEinv(dt_colormatrix_t dst, const dt_colormatrix_t src)
Definition matrices.h:37
#define dt_free_align(ptr)
Release memory from dt_alloc_align() and set ptr to NULL.
Definition mem_alloc.h:214
static void * dt_calloc_align(size_t size)
dt_alloc_align() followed by a zero fill.
Definition mem_alloc.h:225
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
#define __OMP_SIMD__(...)
Definition openmp.h:99
dt_colorspaces_color_profile_type_t
@ DT_COLORSPACE_ADOBERGB
@ DT_COLORSPACE_SRGB
sRGB – registered TWICE, and the two entries are not interchangeable.
@ DT_COLORSPACE_REC709
@ DT_COLORSPACE_HLG_P3
@ DT_COLORSPACE_PQ_P3
@ DT_COLORSPACE_PQ_REC2020
@ DT_COLORSPACE_HLG_REC2020
@ DT_COLORSPACE_LIN_REC2020
@ DT_COLORSPACE_DISPLAY_P3
@ DT_COLORSPACE_LIN_REC709
@ DT_PROFILE_ROLE_ANY
All four roles, in registration order.
static cairo_surface_t * dt_cairo_image_surface_create(cairo_format_t format, int width, int height)
DT_ALIGNED_PIXEL float dt_aligned_pixel_t[4]
Definition simd.h:53
static const dt_aligned_pixel_simd_t value
Definition simd.h:144
const float r
One registered profile: its identity, its LCMS handle, and where it sits in each combo box.
cmsHPROFILE profile
the actual profile; NULL for the three category entries
The dt_gui_module_t type is the intersection between a dt_lib_module_t and a dt_iop_module_t structur...
A profile reduced to the arithmetic the pixel loop can run: two matrices and six tone-curve LUTs,...
int nonlinearlut
Non-zero when the profile has tone curves at all; tested as a boolean everywhere, but it is really th...
int lutsize
Entry count of each of the six LUTs. Always 65536 in practice: both callers of dt_ioppr_init_profile_...
dt_colorspaces_color_profile_type_t type
Profile identity, half of the memo key. DT_COLORSPACE_NONE means "unset" and makes every apply functi...
float * lut_out[3]
Per-channel linear -> encoded tone curve, same convention as lut_in.
dt_colormatrix_t matrix_out
XYZ (D50) -> RGB, row-major; the inverse of matrix_in.
float * lut_in[3]
Per-channel encoded -> linear tone curve, lutsize entries each, sampled over [0,1]....
char filename[DT_IOP_COLOR_ICC_LEN]
ICC file name, the other half of the memo key; "" for every built-in. Compared with strcmp,...
dt_colormatrix_t matrix_in
RGB -> XYZ (D50), row-major. matrix_in[1][*] is the luminance row. NaN in [0][0] marks the whole prof...
dt_aligned_pixel_simd_t screen_x
Definition lut_viewer.c:56
dt_aligned_pixel_simd_t screen_z
Definition lut_viewer.c:58
dt_aligned_pixel_simd_t screen_y
Definition lut_viewer.c:57
dt_pthread_rwlock_t * clut_lock
Definition lut_viewer.c:95
float sample_cache_rotation_of_axis
Definition lut_viewer.c:142
float cached_rotation_around_axis
Definition lut_viewer.c:113
const dt_iop_order_iccprofile_info_t * display_profile
Definition lut_viewer.c:97
GtkWidget * widget
Definition lut_viewer.c:81
float cached_slice_thickness
Definition lut_viewer.c:116
const dt_iop_order_iccprofile_info_t * cached_display_profile
Definition lut_viewer.c:125
GtkDrawingArea * area
Definition lut_viewer.c:82
float sample_cache_slice_thickness
Definition lut_viewer.c:144
float cached_rotation_of_axis
Definition lut_viewer.c:114
double drag_anchor_y
Definition lut_viewer.c:105
float drag_origin_azimuth
Definition lut_viewer.c:108
float sample_cache_slice_depth
Definition lut_viewer.c:143
gboolean cached_show_control_nodes
Definition lut_viewer.c:128
gboolean sample_cache_show_control_nodes
Definition lut_viewer.c:153
dt_aligned_pixel_simd_t * sample_input_display
Definition lut_viewer.c:134
dt_aligned_pixel_simd_t * sample_output_display
Definition lut_viewer.c:135
const dt_iop_order_iccprofile_info_t * lut_profile
Definition lut_viewer.c:96
float cached_slice_depth
Definition lut_viewer.c:115
const float * cached_clut
Definition lut_viewer.c:122
dt_aligned_pixel_simd_t * sample_input_work
Definition lut_viewer.c:132
const float * sample_cache_clut
Definition lut_viewer.c:147
uint16_t sample_cache_clut_level
Definition lut_viewer.c:148
const float * clut
Definition lut_viewer.c:93
float cached_shift_threshold
Definition lut_viewer.c:120
GtkWidget * save_button
Definition lut_viewer.c:84
uint16_t cached_clut_level
Definition lut_viewer.c:123
cairo_surface_t * surface
Definition lut_viewer.c:130
const dt_lut_viewer_control_node_t * control_nodes
Definition lut_viewer.c:98
size_t cached_control_node_count
Definition lut_viewer.c:127
size_t sample_white_index
Definition lut_viewer.c:138
float sample_cache_rotation_around_axis
Definition lut_viewer.c:141
float drag_origin_tilt
Definition lut_viewer.c:109
const dt_iop_order_iccprofile_info_t * sample_cache_display_profile
Definition lut_viewer.c:150
GtkWidget * slice_thickness
Definition lut_viewer.c:88
gboolean sample_cache_valid
Definition lut_viewer.c:140
const dt_iop_order_iccprofile_info_t * sample_cache_lut_profile
Definition lut_viewer.c:149
float drag_origin_pan_x
Definition lut_viewer.c:106
GtkWidget * rotation_of_axis
Definition lut_viewer.c:86
double drag_anchor_x
Definition lut_viewer.c:104
float sample_cache_shift_threshold
Definition lut_viewer.c:145
GtkWidget * slice_depth
Definition lut_viewer.c:87
const dt_lut_viewer_control_node_t * cached_control_nodes
Definition lut_viewer.c:126
gboolean sample_draw_white_last
Definition lut_viewer.c:139
dt_lut_viewer_drag_mode_t drag_mode
Definition lut_viewer.c:103
const dt_iop_order_iccprofile_info_t * cached_lut_profile
Definition lut_viewer.c:124
float drag_origin_pan_y
Definition lut_viewer.c:107
dt_aligned_pixel_simd_t * sample_output_work
Definition lut_viewer.c:133
size_t sample_capacity
Definition lut_viewer.c:136
size_t sample_cache_control_node_count
Definition lut_viewer.c:152
GtkWidget * rotation_around_axis
Definition lut_viewer.c:85
uint16_t clut_level
Definition lut_viewer.c:94
GtkWidget * controls
Definition lut_viewer.c:83
const dt_lut_viewer_control_node_t * sample_cache_control_nodes
Definition lut_viewer.c:151
size_t sample_count
Definition lut_viewer.c:137
GtkWidget * show_control_nodes
Definition lut_viewer.c:90
GtkWidget * gamut
Definition lut_viewer.c:91
GtkWidget * shift_threshold
Definition lut_viewer.c:89
size_t control_node_count
Definition lut_viewer.c:99
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29
static double dt_get_wtime(void)
Definition times.h:43
Telling the user something happened.
#define DT_GUI_BOX_SPACING
#define DT_PIXEL_APPLY_DPI(value)