Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
widgets.c
Go to the documentation of this file.
1/*
2 This file is part of the Ansel project.
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 Ansel. If not, see <http://www.gnu.org/licenses/>.
17*/
18
19#ifdef HAVE_CONFIG_H
20#include "common/darktable.h"
21#include "config.h"
22#endif
23
25
26#include "iop/drawlayer/paint.h"
27#include "gui/gtk.h"
28
29#include <float.h>
30#include <math.h>
31#include <string.h>
32
37#define DT_DRAWLAYER_PICKER_U_MAX 0.70710678f
38#define DT_DRAWLAYER_PICKER_V_MAX 0.81649658f
39#define DT_DRAWLAYER_PICKER_C_MAX 0.81649658f
40
48
80
82static inline float _clamp01(const float value)
83{
84 return fminf(fmaxf(value, 0.0f), 1.0f);
85}
86
88static gboolean _display_rgb_equal(const float a[3], const float b[3])
89{
90 return fabsf(a[0] - b[0]) <= 1e-6f && fabsf(a[1] - b[1]) <= 1e-6f && fabsf(a[2] - b[2]) <= 1e-6f;
91}
92
95{
96 if(IS_NULL_PTR(widgets)) return;
97 widgets->picker_hue = atan2f(widgets->picker_v, widgets->picker_u);
98 widgets->picker_chroma = hypotf(widgets->picker_u, widgets->picker_v);
99}
100
103{
104 if(IS_NULL_PTR(widgets)) return;
105 widgets->picker_u = widgets->picker_chroma * cosf(widgets->picker_hue);
106 widgets->picker_v = widgets->picker_chroma * sinf(widgets->picker_hue);
107}
108
110static int _picker_project_opponent_to_display_rgb(const float m, const float u, const float v, float display_rgb[3])
111{
112 const float r = m + u * 0.70710678f + v * 0.40824829f;
113 const float g = m - u * 0.70710678f + v * 0.40824829f;
114 const float b = m - v * 0.81649658f;
115
116 if(!isfinite(r) || !isfinite(g) || !isfinite(b)) return 1;
117 if(r < 0.0f || r > 1.0f || g < 0.0f || g > 1.0f || b < 0.0f || b > 1.0f) return 1;
118
119 display_rgb[0] = r;
120 display_rgb[1] = g;
121 display_rgb[2] = b;
122 return 0;
123}
124
126static float _picker_max_chroma_for_m_hue(const float m, const float hue)
127{
128 const float ku = cosf(hue);
129 const float kv = sinf(hue);
130 const float k[3] = {
131 ku * 0.70710678f + kv * 0.40824829f,
132 -ku * 0.70710678f + kv * 0.40824829f,
133 -kv * 0.81649658f
134 };
135
136 if(m <= 0.0f || m >= 1.0f) return 0.0f;
137
138 float limit = FLT_MAX;
139 for(int c = 0; c < 3; c++)
140 {
141 if(k[c] > 1e-6f)
142 limit = fminf(limit, (1.0f - m) / k[c]);
143 else if(k[c] < -1e-6f)
144 limit = fminf(limit, m / -k[c]);
145 }
146
147 if(!isfinite(limit) || limit < 0.0f) return 0.0f;
148 return limit;
149}
150
153{
154 if(IS_NULL_PTR(widgets)) return;
155
156 widgets->picker_m = CLAMP(widgets->picker_m, 0.0f, 1.0f);
158 const float max_chroma = _picker_max_chroma_for_m_hue(widgets->picker_m, widgets->picker_hue);
159 if(widgets->picker_chroma > max_chroma)
160 {
161 widgets->picker_chroma = max_chroma;
163 }
164}
165
167static void _sync_picker_from_display_rgb(dt_drawlayer_widgets_t *widgets, const float display_rgb[3])
168{
169 if(IS_NULL_PTR(widgets) || !display_rgb) return;
170
171 widgets->picker_m = (display_rgb[0] + display_rgb[1] + display_rgb[2]) / 3.0f;
172 widgets->picker_u = (display_rgb[0] - display_rgb[1]) * 0.70710678f;
173 widgets->picker_v = (display_rgb[0] + display_rgb[1] - 2.0f * display_rgb[2]) * 0.40824829f;
175}
176
179{
180 if(IS_NULL_PTR(widgets)) return;
181 if(widgets->color_surface)
182 {
183 cairo_surface_destroy(widgets->color_surface);
184 widgets->color_surface = NULL;
185 }
186 widgets->color_surface_width = 0;
187 widgets->color_surface_height = 0;
188 widgets->color_surface_ppd = 0.0;
189 widgets->color_surface_dirty = TRUE;
190}
191
194{
195 if(IS_NULL_PTR(widgets)) return;
196 if(widgets->profile_surface)
197 {
198 cairo_surface_destroy(widgets->profile_surface);
199 widgets->profile_surface = NULL;
200 }
201 widgets->profile_surface_width = 0;
202 widgets->profile_surface_height = 0;
203 widgets->profile_surface_ppd = 0.0;
204 widgets->profile_surface_dirty = TRUE;
205}
206
208static void _brush_profile_geometry(const GtkWidget *widget, float *x, float *y, float *width, float *height,
209 float *gap, int *cell_count)
210{
211 const float margin = 0.0f;
212 const float local_gap = DT_PIXEL_APPLY_DPI(6.0f);
213 const int count = 4;
214 const float widget_w = gtk_widget_get_allocated_width((GtkWidget *)widget);
215 const float widget_h = gtk_widget_get_allocated_height((GtkWidget *)widget);
216 if(!IS_NULL_PTR(x)) *x = margin;
217 if(!IS_NULL_PTR(y)) *y = margin;
218 if(!IS_NULL_PTR(width)) *width = fmaxf(24.0f, widget_w - 2.0f * margin);
219 if(!IS_NULL_PTR(height)) *height = fmaxf(24.0f, widget_h - 2.0f * margin);
220 if(!IS_NULL_PTR(gap)) *gap = local_gap;
221 if(!IS_NULL_PTR(cell_count)) *cell_count = count;
222}
223
225static gboolean _brush_profile_cell_rect(const GtkWidget *widget, const int index,
226 float *x, float *y, float *width, float *height)
227{
228 float row_x = 0.0f, row_y = 0.0f, row_w = 0.0f, row_h = 0.0f, gap = 0.0f;
229 int count = 0;
230 _brush_profile_geometry(widget, &row_x, &row_y, &row_w, &row_h, &gap, &count);
231 if(index < 0 || index >= count) return FALSE;
232
233 const float cell_w = fmaxf(8.0f, (row_w - gap * (count - 1)) / (float)count);
234 if(!IS_NULL_PTR(x)) *x = row_x + index * (cell_w + gap);
235 if(!IS_NULL_PTR(y)) *y = row_y;
236 if(!IS_NULL_PTR(width)) *width = cell_w;
237 if(!IS_NULL_PTR(height)) *height = row_h;
238 return TRUE;
239}
240
242static inline uint8_t _linear_channel_to_u8(const float x)
243{
244 const float v = _clamp01(x);
245 const float srgb = (v <= 0.0031308f) ? (12.92f * v) : (1.055f * powf(v, 1.0f / 2.4f) - 0.055f);
246 return (uint8_t)CLAMP((int)lrintf(255.0f * _clamp01(srgb)), 0, 255);
247}
248
250static void _render_brush_profile_cell(unsigned char *dst, const int stride, const int width, const int height,
251 float *rgba_scratch,
252 const dt_drawlayer_widgets_t *widgets, const int shape)
253{
254 if(IS_NULL_PTR(dst) || IS_NULL_PTR(rgba_scratch) || width <= 0 || height <= 0 || IS_NULL_PTR(widgets)) return;
255
256 memset(dst, 0, (size_t)stride * height);
257 for(int py = 0; py < height; py++)
258 {
259 unsigned char *row = dst + (size_t)py * stride;
260 for(int px = 0; px < width; px++)
261 {
262 row[4 * px + 1] = 0xff;
263 row[4 * px + 2] = 0xff;
264 row[4 * px + 3] = 0xff;
265 row[4 * px + 0] = 0xff;
266 }
267 }
268
269 const float radius = 0.36f * fminf((float)width, (float)height);
271 .x = 0.0f,
272 .y = 0.0f,
273 .wx = 0.0f,
274 .wy = 0.0f,
275 .radius = fmaxf(radius, 1.0f),
276 .dir_x = 0.0f,
277 .dir_y = 1.0f,
278 .opacity = _clamp01(widgets->profile_opacity),
279 .flow = 0.0f,
280 .sprinkles = _clamp01(widgets->profile_sprinkles),
281 .sprinkle_size = fmaxf(1.0f, widgets->profile_sprinkle_size),
282 .sprinkle_coarseness = _clamp01(widgets->profile_sprinkle_coarseness),
283 .hardness = _clamp01(widgets->profile_hardness),
284 .color = { 0.0f, 0.0f, 0.0f, 1.0f },
285 .display_color = { 0.0f, 0.0f, 0.0f },
286 .shape = shape,
288 .stroke_batch = 0u,
289 .stroke_pos = 0u,
290 };
291
292 const float bg[3] = { 1.0f, 1.0f, 1.0f };
294 0.5f * (float)width, 0.5f * (float)height, 1.0f, bg);
295
296 for(int y = 0; y < height; y++)
297 {
298 unsigned char *row = dst + (size_t)y * stride;
299 for(int x = 0; x < width; x++)
300 {
301 const float *pixel = rgba_scratch + 4 * ((size_t)y * width + x);
302 row[4 * x + 0] = _linear_channel_to_u8(pixel[2]);
303 row[4 * x + 1] = _linear_channel_to_u8(pixel[1]);
304 row[4 * x + 2] = _linear_channel_to_u8(pixel[0]);
305 row[4 * x + 3] = 255;
306 }
307 }
308}
309
311static void _color_picker_geometry(const GtkWidget *widget, float *uv_x, float *uv_y, float *uv_size,
312 float *plane_x, float *plane_y, float *plane_w, float *plane_h)
313{
314 const float width = gtk_widget_get_allocated_width((GtkWidget *)widget);
315 const float height = gtk_widget_get_allocated_height((GtkWidget *)widget);
316 const float margin = DT_PIXEL_APPLY_DPI(6.0f);
317 const float gap = DT_PIXEL_APPLY_DPI(16.0f);
318 const float usable_w = fmaxf(40.0f, width - 2.0f * margin - gap);
319 const float usable_h = fmaxf(40.0f, height - 2.0f * margin);
320 const float size = fmaxf(40.0f, fminf(usable_h, 0.5f * usable_w));
321 if(!IS_NULL_PTR(uv_x)) *uv_x = margin;
322 if(!IS_NULL_PTR(uv_y)) *uv_y = margin + 0.5f * (usable_h - size);
323 if(!IS_NULL_PTR(uv_size)) *uv_size = size;
324 if(!IS_NULL_PTR(plane_x)) *plane_x = margin + size + gap;
325 if(!IS_NULL_PTR(plane_y)) *plane_y = margin + 0.5f * (usable_h - size);
326 if(!IS_NULL_PTR(plane_w)) *plane_w = size;
327 if(!IS_NULL_PTR(plane_h)) *plane_h = size;
328}
329
331static float _color_picker_hit_margin(void)
332{
333 const float ppd = (darktable.gui && darktable.gui->ppd > 0.0) ? darktable.gui->ppd : 1.0f;
334 return DT_PIXEL_APPLY_DPI(12.0f) * ppd;
335}
336
338static gboolean _rect_contains_with_margin(const float x, const float y, const float rx, const float ry,
339 const float rw, const float rh, const float margin)
340{
341 return x >= rx - margin && x <= rx + rw + margin && y >= ry - margin && y <= ry + rh + margin;
342}
343
346{
347 dt_drawlayer_widgets_t *widgets = g_malloc0(sizeof(*widgets));
349 widgets->color_surface_dirty = TRUE;
350 widgets->profile_surface_dirty = TRUE;
351 widgets->profile_opacity = 1.0f;
352 widgets->profile_hardness = 0.5f;
353 widgets->profile_sprinkle_size = 3.0f;
354 widgets->profile_sprinkle_coarseness = 0.5f;
356 return widgets;
357}
358
361{
362 if(IS_NULL_PTR(widgets) || !*widgets) return;
364 _clear_profile_surface(*widgets);
365 dt_free(*widgets);
366}
367
369void dt_drawlayer_widgets_set_display_color(dt_drawlayer_widgets_t *widgets, const float display_rgb[3])
370{
371 if(IS_NULL_PTR(widgets) || !display_rgb) return;
372 _sync_picker_from_display_rgb(widgets, display_rgb);
373 widgets->color_surface_dirty = TRUE;
374}
375
377gboolean dt_drawlayer_widgets_get_display_color(const dt_drawlayer_widgets_t *widgets, float display_rgb[3])
378{
379 if(!widgets || !display_rgb) return FALSE;
380 return _picker_project_opponent_to_display_rgb(widgets->picker_m, widgets->picker_u, widgets->picker_v, display_rgb) == 0;
381}
382
385{
386 if(IS_NULL_PTR(widgets)) return;
387 widgets->color_surface_dirty = TRUE;
388}
389
392 const float history[DT_DRAWLAYER_COLOR_HISTORY_COUNT][3],
393 const gboolean valid[DT_DRAWLAYER_COLOR_HISTORY_COUNT])
394{
395 if(IS_NULL_PTR(widgets) || !history || !valid) return;
396 for(int i = 0; i < DT_DRAWLAYER_COLOR_HISTORY_COUNT; i++)
397 {
398 widgets->color_history_valid[i] = valid[i];
399 widgets->color_history[i][0] = _clamp01(history[i][0]);
400 widgets->color_history[i][1] = _clamp01(history[i][1]);
401 widgets->color_history[i][2] = _clamp01(history[i][2]);
402 }
403}
404
407 float history[DT_DRAWLAYER_COLOR_HISTORY_COUNT][3],
408 gboolean valid[DT_DRAWLAYER_COLOR_HISTORY_COUNT])
409{
410 if(IS_NULL_PTR(widgets) || !history || !valid) return;
411 for(int i = 0; i < DT_DRAWLAYER_COLOR_HISTORY_COUNT; i++)
412 {
413 valid[i] = widgets->color_history_valid[i];
414 history[i][0] = widgets->color_history[i][0];
415 history[i][1] = widgets->color_history[i][1];
416 history[i][2] = widgets->color_history[i][2];
417 }
418}
419
421gboolean dt_drawlayer_widgets_push_color_history(dt_drawlayer_widgets_t *widgets, const float display_rgb[3])
422{
423 if(!widgets || !display_rgb) return FALSE;
424
425 if(widgets->color_history_valid[0] && _display_rgb_equal(widgets->color_history[0], display_rgb)) return FALSE;
426
427 for(int i = DT_DRAWLAYER_COLOR_HISTORY_COUNT - 1; i > 0; i--)
428 {
429 widgets->color_history_valid[i] = widgets->color_history_valid[i - 1];
430 widgets->color_history[i][0] = widgets->color_history[i - 1][0];
431 widgets->color_history[i][1] = widgets->color_history[i - 1][1];
432 widgets->color_history[i][2] = widgets->color_history[i - 1][2];
433 }
434
435 widgets->color_history_valid[0] = TRUE;
436 widgets->color_history[0][0] = _clamp01(display_rgb[0]);
437 widgets->color_history[0][1] = _clamp01(display_rgb[1]);
438 widgets->color_history[0][2] = _clamp01(display_rgb[2]);
439 return TRUE;
440}
441
444 float x, float y, float display_rgb[3])
445{
446 if(!widgets || !widget || !display_rgb) return FALSE;
447
448 float uv_x = 0.0f, uv_y = 0.0f, uv_size = 0.0f;
449 float plane_x = 0.0f, plane_y = 0.0f, plane_w = 0.0f, plane_h = 0.0f;
450 _color_picker_geometry(widget, &uv_x, &uv_y, &uv_size, &plane_x, &plane_y, &plane_w, &plane_h);
451 const float hit_margin
453 const gboolean in_uv = _rect_contains_with_margin(x, y, uv_x, uv_y, uv_size, uv_size,
455 ? hit_margin
456 : 0.0f);
457 const gboolean in_plane = _rect_contains_with_margin(x, y, plane_x, plane_y, plane_w, plane_h,
459 ? hit_margin
460 : 0.0f);
461
462 if(in_uv)
463 {
464 const float tx = 2.0f * _clamp01((x - uv_x) / fmaxf(uv_size, 1.0f)) - 1.0f;
465 const float ty = 1.0f - 2.0f * _clamp01((y - uv_y) / fmaxf(uv_size, 1.0f));
466 widgets->picker_u = tx * DT_DRAWLAYER_PICKER_U_MAX;
467 widgets->picker_v = ty * DT_DRAWLAYER_PICKER_V_MAX;
470 }
471 else if(in_plane)
472 {
473 const float tx = _clamp01((x - plane_x) / fmaxf(plane_w, 1.0f));
474 const float ty = _clamp01((y - plane_y) / fmaxf(plane_h, 1.0f));
475 const float new_m = 1.0f - ty;
476 const float new_chroma = tx * DT_DRAWLAYER_PICKER_C_MAX;
477 const float new_u = new_chroma * cosf(widgets->picker_hue);
478 const float new_v = new_chroma * sinf(widgets->picker_hue);
479 if(_picker_project_opponent_to_display_rgb(new_m, new_u, new_v, display_rgb)) return FALSE;
480
481 widgets->picker_m = new_m;
482 widgets->picker_chroma = new_chroma;
483 widgets->picker_u = new_u;
484 widgets->picker_v = new_v;
486 }
487 else
488 {
489 return FALSE;
490 }
491
493 if(!_picker_project_opponent_to_display_rgb(widgets->picker_m, widgets->picker_u, widgets->picker_v, display_rgb))
494 {
495 widgets->color_surface_dirty = TRUE;
496 return TRUE;
497 }
498 return FALSE;
499}
500
503{
504 if(IS_NULL_PTR(widgets)) return FALSE;
506 if(IS_NULL_PTR(display_rgb)) return FALSE;
507 return !_picker_project_opponent_to_display_rgb(widgets->picker_m, widgets->picker_u, widgets->picker_v, display_rgb);
508}
509
512{
513 return widgets && widgets->picker_drag_mode != DT_DRAWLAYER_COLOR_DRAG_NONE;
514}
515
518 float x, float y, float display_rgb[3])
519{
520 if(!widgets || !widget || !display_rgb) return FALSE;
521
522 const int width = gtk_widget_get_allocated_width(widget);
523 const int height = gtk_widget_get_allocated_height(widget);
524 if(width <= 0 || height <= 0) return FALSE;
525
526 const int col = CLAMP((int)floorf(x / ((float)width / DT_DRAWLAYER_COLOR_HISTORY_COLS)), 0,
528 const int row = CLAMP((int)floorf(y / ((float)height / DT_DRAWLAYER_COLOR_HISTORY_ROWS)), 0,
530 const int index = row * DT_DRAWLAYER_COLOR_HISTORY_COLS + col;
531 if(index < 0 || index >= DT_DRAWLAYER_COLOR_HISTORY_COUNT || !widgets->color_history_valid[index]) return FALSE;
532
533 display_rgb[0] = widgets->color_history[index][0];
534 display_rgb[1] = widgets->color_history[index][1];
535 display_rgb[2] = widgets->color_history[index][2];
536 return TRUE;
537}
538
541 double pixels_per_dip)
542{
543 if(!widgets || !widget || IS_NULL_PTR(cr)) return FALSE;
544
545 const int width = gtk_widget_get_allocated_width(widget);
546 const int height = gtk_widget_get_allocated_height(widget);
547 if(width <= 0 || height <= 0) return FALSE;
548
549 const double ppd = (pixels_per_dip > 0.0) ? pixels_per_dip : 1.0;
550 const int width_px = MAX(1, (int)ceil(width * ppd));
551 const int height_px = MAX(1, (int)ceil(height * ppd));
552 const gboolean size_changed = !widgets->color_surface || widgets->color_surface_width != width_px
553 || widgets->color_surface_height != height_px
554 || fabs(widgets->color_surface_ppd - ppd) > 1e-9;
555
556 if(size_changed)
557 {
559 widgets->color_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width_px, height_px);
560 if(cairo_surface_status(widgets->color_surface) != CAIRO_STATUS_SUCCESS)
561 {
563 return FALSE;
564 }
565 cairo_surface_set_device_scale(widgets->color_surface, ppd, ppd);
566 widgets->color_surface_width = width_px;
567 widgets->color_surface_height = height_px;
568 widgets->color_surface_ppd = ppd;
569 widgets->color_surface_dirty = TRUE;
570 }
571
572 float uv_x = 0.0f, uv_y = 0.0f, uv_size = 0.0f;
573 float plane_x = 0.0f, plane_y = 0.0f, plane_w = 0.0f, plane_h = 0.0f;
574 _color_picker_geometry(widget, &uv_x, &uv_y, &uv_size, &plane_x, &plane_y, &plane_w, &plane_h);
575
576 if(widgets->color_surface_dirty)
577 {
578 unsigned char *data = cairo_image_surface_get_data(widgets->color_surface);
579 const int stride = cairo_image_surface_get_stride(widgets->color_surface);
580 memset(data, 0, (size_t)stride * height_px);
581
582 for(int py = 0; py < height_px; py++)
583 {
584 for(int px = 0; px < width_px; px++)
585 {
586 float rgb[3] = { 0.12f, 0.12f, 0.12f };
587 gboolean paint = TRUE;
588 const float fx = ((float)px + 0.5f) / ppd;
589 const float fy = ((float)py + 0.5f) / ppd;
590
591 if(fx >= uv_x && fx <= uv_x + uv_size && fy >= uv_y && fy <= uv_y + uv_size)
592 {
593 const float tx = 2.0f * _clamp01((fx - uv_x) / fmaxf(uv_size, 1.0f)) - 1.0f;
594 const float ty = 1.0f - 2.0f * _clamp01((fy - uv_y) / fmaxf(uv_size, 1.0f));
595 const float u = tx * DT_DRAWLAYER_PICKER_U_MAX;
596 const float v = ty * DT_DRAWLAYER_PICKER_V_MAX;
598 }
599 else if(fx >= plane_x && fx <= plane_x + plane_w && fy >= plane_y && fy <= plane_y + plane_h)
600 {
601 const float tx = _clamp01((fx - plane_x) / fmaxf(plane_w, 1.0f));
602 const float ty = _clamp01((fy - plane_y) / fmaxf(plane_h, 1.0f));
603 const float m = 1.0f - ty;
604 const float chroma = tx * DT_DRAWLAYER_PICKER_C_MAX;
605 const float u = chroma * cosf(widgets->picker_hue);
606 const float v = chroma * sinf(widgets->picker_hue);
608 }
609 else
610 {
611 paint = FALSE;
612 }
613
614 unsigned char *pixel = data + (size_t)py * stride + 4 * px;
615 if(paint)
616 {
617 pixel[0] = (unsigned char)CLAMP(roundf(255.0f * _clamp01(rgb[2])), 0, 255);
618 pixel[1] = (unsigned char)CLAMP(roundf(255.0f * _clamp01(rgb[1])), 0, 255);
619 pixel[2] = (unsigned char)CLAMP(roundf(255.0f * _clamp01(rgb[0])), 0, 255);
620 pixel[3] = 255;
621 }
622 else
623 {
624 pixel[0] = pixel[1] = pixel[2] = 0;
625 pixel[3] = 0;
626 }
627 }
628 }
629 cairo_surface_mark_dirty(widgets->color_surface);
630 widgets->color_surface_dirty = FALSE;
631 }
632
633 cairo_set_source_surface(cr, widgets->color_surface, 0.0, 0.0);
634 cairo_paint(cr);
635
636 const float uv_mark_x
637 = uv_x + 0.5f * ((widgets->picker_u / fmaxf(DT_DRAWLAYER_PICKER_U_MAX, 1e-6f)) + 1.0f) * uv_size;
638 const float uv_mark_y
639 = uv_y + 0.5f * (1.0f - (widgets->picker_v / fmaxf(DT_DRAWLAYER_PICKER_V_MAX, 1e-6f))) * uv_size;
640 const float plane_mark_x = plane_x + _clamp01(widgets->picker_chroma / DT_DRAWLAYER_PICKER_C_MAX) * plane_w;
641 const float plane_mark_y = plane_y + (1.0f - _clamp01(widgets->picker_m)) * plane_h;
642 const float mark_r = 5.0f;
643
644 cairo_set_line_width(cr, 2.0);
645 cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);
646 cairo_arc(cr, uv_mark_x, uv_mark_y, mark_r + 1.5f, 0.0, 2.0 * G_PI);
647 cairo_stroke(cr);
648 cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
649 cairo_arc(cr, uv_mark_x, uv_mark_y, mark_r, 0.0, 2.0 * G_PI);
650 cairo_stroke(cr);
651
652 cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);
653 cairo_arc(cr, plane_mark_x, plane_mark_y, mark_r + 1.5f, 0.0, 2.0 * G_PI);
654 cairo_stroke(cr);
655 cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
656 cairo_arc(cr, plane_mark_x, plane_mark_y, mark_r, 0.0, 2.0 * G_PI);
657 cairo_stroke(cr);
658
659 return FALSE;
660}
661
663gboolean dt_drawlayer_widgets_draw_swatch(const dt_drawlayer_widgets_t *widgets, GtkWidget *widget, cairo_t *cr)
664{
665 if(!widgets || !widget || IS_NULL_PTR(cr)) return FALSE;
666
667 const int width = gtk_widget_get_allocated_width(widget);
668 const int height = gtk_widget_get_allocated_height(widget);
669 if(width <= 0 || height <= 0) return FALSE;
670
671 const float cell_w = (float)width / DT_DRAWLAYER_COLOR_HISTORY_COLS;
672 const float cell_h = (float)height / DT_DRAWLAYER_COLOR_HISTORY_ROWS;
673 const float gap = DT_PIXEL_APPLY_DPI(3.0f);
674
675 cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 0.12);
676 cairo_rectangle(cr, 0.0, 0.0, width, height);
677 cairo_fill(cr);
678
679 for(int i = 0; i < DT_DRAWLAYER_COLOR_HISTORY_COUNT; i++)
680 {
682 const int col = i % DT_DRAWLAYER_COLOR_HISTORY_COLS;
683 const float x = col * cell_w;
684 const float y = row * cell_h;
685
686 if(widgets->color_history_valid[i])
687 cairo_set_source_rgb(cr, widgets->color_history[i][0], widgets->color_history[i][1], widgets->color_history[i][2]);
688 else
689 cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 0.0);
690
691 cairo_rectangle(cr, x + gap * 0.5f, y + gap * 0.5f, MAX(1.0f, cell_w - gap), MAX(1.0f, cell_h - gap));
692 cairo_fill(cr);
693 }
694 return FALSE;
695}
696
698 const float opacity,
699 const float hardness,
700 const float sprinkles,
701 const float sprinkle_size,
702 const float sprinkle_coarseness,
703 const int selected_shape)
704{
705 if(IS_NULL_PTR(widgets)) return;
706
707 const float new_opacity = _clamp01(opacity);
708 const float new_hardness = _clamp01(hardness);
709 const float new_sprinkles = _clamp01(sprinkles);
710 const float new_size = fmaxf(1.0f, sprinkle_size);
711 const float new_coarseness = _clamp01(sprinkle_coarseness);
712 const int new_shape = CLAMP(selected_shape, DT_DRAWLAYER_BRUSH_SHAPE_LINEAR, DT_DRAWLAYER_BRUSH_SHAPE_SIGMOIDAL);
713
714 if(fabsf(widgets->profile_opacity - new_opacity) > 1e-6f
715 || fabsf(widgets->profile_hardness - new_hardness) > 1e-6f
716 || fabsf(widgets->profile_sprinkles - new_sprinkles) > 1e-6f
717 || fabsf(widgets->profile_sprinkle_size - new_size) > 1e-6f
718 || fabsf(widgets->profile_sprinkle_coarseness - new_coarseness) > 1e-6f
719 || widgets->profile_selected_shape != new_shape)
720 widgets->profile_surface_dirty = TRUE;
721
722 widgets->profile_opacity = new_opacity;
723 widgets->profile_hardness = new_hardness;
724 widgets->profile_sprinkles = new_sprinkles;
725 widgets->profile_sprinkle_size = new_size;
726 widgets->profile_sprinkle_coarseness = new_coarseness;
727 widgets->profile_selected_shape = new_shape;
728}
729
734
736 cairo_t *cr, const double pixels_per_dip)
737{
738 if(!widgets || !widget || IS_NULL_PTR(cr)) return FALSE;
739
740 const int width = gtk_widget_get_allocated_width(widget);
741 const int height = gtk_widget_get_allocated_height(widget);
742 if(width <= 0 || height <= 0) return FALSE;
743
744 const double ppd = (pixels_per_dip > 0.0) ? pixels_per_dip : 1.0;
745 const int width_px = MAX(1, (int)ceil(width * ppd));
746 const int height_px = MAX(1, (int)ceil(height * ppd));
747 const gboolean size_changed = !widgets->profile_surface || widgets->profile_surface_width != width_px
748 || widgets->profile_surface_height != height_px
749 || fabs(widgets->profile_surface_ppd - ppd) > 1e-9;
750 if(size_changed)
751 {
752 _clear_profile_surface(widgets);
753 widgets->profile_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width_px, height_px);
754 if(cairo_surface_status(widgets->profile_surface) != CAIRO_STATUS_SUCCESS)
755 {
756 _clear_profile_surface(widgets);
757 return FALSE;
758 }
759 cairo_surface_set_device_scale(widgets->profile_surface, ppd, ppd);
760 widgets->profile_surface_width = width_px;
761 widgets->profile_surface_height = height_px;
762 widgets->profile_surface_ppd = ppd;
763 widgets->profile_surface_dirty = TRUE;
764 }
765
766 float gap = 0.0f;
767 int count = 0;
768 _brush_profile_geometry(widget, NULL, NULL, NULL, NULL, &gap, &count);
769
770 if(widgets->profile_surface_dirty)
771 {
772 unsigned char *data = cairo_image_surface_get_data(widgets->profile_surface);
773 const int stride = cairo_image_surface_get_stride(widgets->profile_surface);
774 memset(data, 0, (size_t)stride * height_px);
775 int max_cell_w_px = 0;
776 int max_cell_h_px = 0;
777
778 for(int i = 0; i < count; i++)
779 {
780 float cell_x = 0.0f, cell_y = 0.0f, cell_w = 0.0f, cell_h = 0.0f;
781 if(!_brush_profile_cell_rect(widget, i, &cell_x, &cell_y, &cell_w, &cell_h)) continue;
782 const int x0 = MAX(0, (int)floor(cell_x * ppd));
783 const int y0 = MAX(0, (int)floor(cell_y * ppd));
784 const int x1 = MIN(width_px, (int)ceil((cell_x + cell_w) * ppd));
785 const int y1 = MIN(height_px, (int)ceil((cell_y + cell_h) * ppd));
786 max_cell_w_px = MAX(max_cell_w_px, MAX(1, x1 - x0));
787 max_cell_h_px = MAX(max_cell_h_px, MAX(1, y1 - y0));
788 }
789
790 unsigned char *cell = g_malloc0((size_t)max_cell_w_px * max_cell_h_px * 4);
791 float *rgba_scratch = g_malloc((size_t)max_cell_w_px * max_cell_h_px * 4 * sizeof(float));
792
793 for(int i = 0; i < count; i++)
794 {
795 float cell_x = 0.0f, cell_y = 0.0f, cell_w = 0.0f, cell_h = 0.0f;
796 if(!_brush_profile_cell_rect(widget, i, &cell_x, &cell_y, &cell_w, &cell_h)) continue;
797
798 const int x0 = MAX(0, (int)floor(cell_x * ppd));
799 const int y0 = MAX(0, (int)floor(cell_y * ppd));
800 const int x1 = MIN(width_px, (int)ceil((cell_x + cell_w) * ppd));
801 const int y1 = MIN(height_px, (int)ceil((cell_y + cell_h) * ppd));
802 const int cell_w_px = MAX(1, x1 - x0);
803 const int cell_h_px = MAX(1, y1 - y0);
804
805 memset(cell, 0, (size_t)max_cell_w_px * max_cell_h_px * 4);
806 _render_brush_profile_cell(cell, cell_w_px * 4, cell_w_px, cell_h_px, rgba_scratch, widgets, i);
807
808 for(int py = 0; py < cell_h_px; py++)
809 {
810 memcpy(data + (size_t)(y0 + py) * stride + 4 * x0,
811 cell + (size_t)py * cell_w_px * 4,
812 (size_t)cell_w_px * 4);
813 }
814 }
815 dt_free(rgba_scratch);
816 dt_free(cell);
817
818 cairo_surface_mark_dirty(widgets->profile_surface);
819 widgets->profile_surface_dirty = FALSE;
820 }
821
822 cairo_set_source_surface(cr, widgets->profile_surface, 0.0, 0.0);
823 cairo_paint(cr);
824
825 for(int i = 0; i < count; i++)
826 {
827 float cell_x = 0.0f, cell_y = 0.0f, cell_w = 0.0f, cell_h = 0.0f;
828 if(!_brush_profile_cell_rect(widget, i, &cell_x, &cell_y, &cell_w, &cell_h)) continue;
829
830 const gboolean selected = (i == widgets->profile_selected_shape);
831 cairo_rectangle(cr, cell_x, cell_y, cell_w, cell_h);
832 cairo_set_line_width(cr, selected ? 2.5 : 1.0);
833 if(selected) cairo_set_source_rgb(cr, 0.12, 0.45, 0.85);
834 else cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 0.18);
835 cairo_stroke(cr);
836 }
837 return FALSE;
838}
839
841 const float x, const float y, int *shape)
842{
843 if(!widgets || !widget) return FALSE;
844
846 {
847 float cell_x = 0.0f, cell_y = 0.0f, cell_w = 0.0f, cell_h = 0.0f;
848 if(!_brush_profile_cell_rect(widget, i, &cell_x, &cell_y, &cell_w, &cell_h)) continue;
849 if(x >= cell_x && x <= cell_x + cell_w && y >= cell_y && y <= cell_y + cell_h)
850 {
851 widgets->profile_selected_shape = i;
852 widgets->profile_surface_dirty = TRUE;
853 if(shape) *shape = i;
854 return TRUE;
855 }
856 }
857 return FALSE;
858}
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
#define m
Definition basecurve.c:278
int width
Definition bilateral.h:1
int height
Definition bilateral.h:1
@ DT_DRAWLAYER_BRUSH_MODE_PAINT
Definition brush.h:48
@ DT_DRAWLAYER_BRUSH_SHAPE_SIGMOIDAL
Definition brush.h:42
@ DT_DRAWLAYER_BRUSH_SHAPE_LINEAR
Definition brush.h:39
static dt_aligned_pixel_t rgb
static const int row
darktable_t darktable
Definition darktable.c:181
#define dt_free(ptr)
Definition darktable.h:456
static const dt_aligned_pixel_simd_t value
Definition darktable.h:577
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition darktable.h:281
#define DT_PIXEL_APPLY_DPI(value)
Definition gtk.h:90
gboolean dt_drawlayer_brush_rasterize_dab_rgbaf(const dt_drawlayer_brush_dab_t *dab, float *rgba, const int width, const int height, const float center_x, const float center_y, const float opacity_multiplier, const float background_rgb[3])
Rasterize a single dab preview in linear float RGBA over an opaque background.
Stroke-level path sampling and runtime-state API for drawlayer.
static const float x
const float v
float *const restrict const size_t k
size_t size
Definition mipmap_cache.c:3
struct _GtkWidget GtkWidget
Definition splash.h:29
const float r
struct dt_gui_gtk_t * gui
Definition darktable.h:775
Fully resolved input dab descriptor.
Definition brush.h:64
Runtime state for drawlayer custom color widgets.
Definition widgets.c:51
cairo_surface_t * profile_surface
Definition widgets.c:68
double profile_surface_ppd
Definition widgets.c:71
gboolean color_history_valid[DT_DRAWLAYER_COLOR_HISTORY_COUNT]
Definition widgets.c:60
float color_history[DT_DRAWLAYER_COLOR_HISTORY_COUNT][3]
Definition widgets.c:59
cairo_surface_t * color_surface
Definition widgets.c:62
float profile_sprinkle_coarseness
Definition widgets.c:77
gboolean profile_surface_dirty
Definition widgets.c:72
float profile_sprinkle_size
Definition widgets.c:76
gboolean color_surface_dirty
Definition widgets.c:66
double ppd
Definition gtk.h:200
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29
static void _picker_update_uv_from_polar(dt_drawlayer_widgets_t *widgets)
Update U/V from current hue/chroma components.
Definition widgets.c:102
#define DT_DRAWLAYER_PICKER_U_MAX
Definition widgets.c:37
gboolean dt_drawlayer_widgets_finish_picker_drag(dt_drawlayer_widgets_t *widgets, float display_rgb[3])
End picker drag mode and optionally output current color.
Definition widgets.c:502
gboolean dt_drawlayer_widgets_pick_history_color(const dt_drawlayer_widgets_t *widgets, GtkWidget *widget, float x, float y, float display_rgb[3])
Pick one history swatch color from widget coordinates.
Definition widgets.c:517
static void _clear_color_picker_surface(dt_drawlayer_widgets_t *widgets)
Destroy cached picker surface and reset cache metadata.
Definition widgets.c:178
static gboolean _brush_profile_cell_rect(const GtkWidget *widget, const int index, float *x, float *y, float *width, float *height)
Compute one profile-cell rectangle.
Definition widgets.c:225
static void _color_picker_geometry(const GtkWidget *widget, float *uv_x, float *uv_y, float *uv_size, float *plane_x, float *plane_y, float *plane_w, float *plane_h)
Compute picker-disc and value/chroma-plane geometry inside widget.
Definition widgets.c:311
#define DT_DRAWLAYER_PICKER_C_MAX
Definition widgets.c:39
gboolean dt_drawlayer_widgets_update_from_picker_position(dt_drawlayer_widgets_t *widgets, GtkWidget *widget, float x, float y, float display_rgb[3])
Update picker state from mouse position and return selected color.
Definition widgets.c:443
gboolean dt_drawlayer_widgets_push_color_history(dt_drawlayer_widgets_t *widgets, const float display_rgb[3])
Push one color to history head if different from current head.
Definition widgets.c:421
static gboolean _rect_contains_with_margin(const float x, const float y, const float rx, const float ry, const float rw, const float rh, const float margin)
Rectangle hit-test with configurable margin expansion.
Definition widgets.c:338
void dt_drawlayer_widgets_set_color_history(dt_drawlayer_widgets_t *widgets, const float history[DT_DRAWLAYER_COLOR_HISTORY_COUNT][3], const gboolean valid[DT_DRAWLAYER_COLOR_HISTORY_COUNT])
Replace full color-history content and validity flags.
Definition widgets.c:391
static float _picker_max_chroma_for_m_hue(const float m, const float hue)
Compute max reachable chroma at given lightness/hue within display gamut.
Definition widgets.c:126
static float _clamp01(const float value)
Clamp scalar to [0,1].
Definition widgets.c:82
static gboolean _display_rgb_equal(const float a[3], const float b[3])
Compare display-RGB triplets with tiny epsilon tolerance.
Definition widgets.c:88
static void _picker_clamp_state_to_gamut(dt_drawlayer_widgets_t *widgets)
Clamp picker state so projected display RGB always remains valid.
Definition widgets.c:152
dt_drawlayer_widgets_t * dt_drawlayer_widgets_init(void)
Allocate and initialize widget runtime state.
Definition widgets.c:345
static void _render_brush_profile_cell(unsigned char *dst, const int stride, const int width, const int height, float *rgba_scratch, const dt_drawlayer_widgets_t *widgets, const int shape)
Render one brush preview cell into ARGB8 memory.
Definition widgets.c:250
gboolean dt_drawlayer_widgets_get_display_color(const dt_drawlayer_widgets_t *widgets, float display_rgb[3])
Get current color projected to display RGB.
Definition widgets.c:377
void dt_drawlayer_widgets_set_brush_profile_preview(dt_drawlayer_widgets_t *widgets, const float opacity, const float hardness, const float sprinkles, const float sprinkle_size, const float sprinkle_coarseness, const int selected_shape)
Update cached brush-profile preview parameters and selected profile.
Definition widgets.c:697
gboolean dt_drawlayer_widgets_is_picker_dragging(const dt_drawlayer_widgets_t *widgets)
Tell whether picker drag interaction is currently active.
Definition widgets.c:511
static void _clear_profile_surface(dt_drawlayer_widgets_t *widgets)
Destroy cached brush-profile preview surface and reset cache metadata.
Definition widgets.c:193
int dt_drawlayer_widgets_get_brush_profile_selection(const dt_drawlayer_widgets_t *widgets)
Read currently selected brush profile.
Definition widgets.c:730
void dt_drawlayer_widgets_get_color_history(const dt_drawlayer_widgets_t *widgets, float history[DT_DRAWLAYER_COLOR_HISTORY_COUNT][3], gboolean valid[DT_DRAWLAYER_COLOR_HISTORY_COUNT])
Copy full color-history content and validity flags out.
Definition widgets.c:406
static void _picker_update_polar_from_uv(dt_drawlayer_widgets_t *widgets)
Update hue/chroma from current U/V components.
Definition widgets.c:94
dt_drawlayer_color_drag_mode_t
Active drag target in picker UI.
Definition widgets.c:43
@ DT_DRAWLAYER_COLOR_DRAG_DISC
Definition widgets.c:45
@ DT_DRAWLAYER_COLOR_DRAG_PLANE
Definition widgets.c:46
@ DT_DRAWLAYER_COLOR_DRAG_NONE
Definition widgets.c:44
static float _color_picker_hit_margin(void)
Hit-margin in device-independent pixels for drag continuity.
Definition widgets.c:331
#define DT_DRAWLAYER_PICKER_V_MAX
Definition widgets.c:38
gboolean dt_drawlayer_widgets_pick_brush_profile(dt_drawlayer_widgets_t *widgets, GtkWidget *widget, const float x, const float y, int *shape)
Hit-test and select one brush profile from the preview row.
Definition widgets.c:840
void dt_drawlayer_widgets_cleanup(dt_drawlayer_widgets_t **widgets)
Free widget runtime state and owned cairo resources.
Definition widgets.c:360
static void _sync_picker_from_display_rgb(dt_drawlayer_widgets_t *widgets, const float display_rgb[3])
Initialize picker opponent coordinates from display RGB color.
Definition widgets.c:167
void dt_drawlayer_widgets_set_display_color(dt_drawlayer_widgets_t *widgets, const float display_rgb[3])
Set current color and synchronize picker internals.
Definition widgets.c:369
gboolean dt_drawlayer_widgets_draw_picker(dt_drawlayer_widgets_t *widgets, GtkWidget *widget, cairo_t *cr, double pixels_per_dip)
Draw color picker map, controls and selection markers.
Definition widgets.c:540
gboolean dt_drawlayer_widgets_draw_swatch(const dt_drawlayer_widgets_t *widgets, GtkWidget *widget, cairo_t *cr)
Draw compact color-history swatch grid.
Definition widgets.c:663
void dt_drawlayer_widgets_mark_picker_dirty(dt_drawlayer_widgets_t *widgets)
Mark picker surface dirty for regeneration on next draw.
Definition widgets.c:384
static uint8_t _linear_channel_to_u8(const float x)
Convert linear float channel to display-encoded 8-bit channel.
Definition widgets.c:242
gboolean dt_drawlayer_widgets_draw_brush_profiles(dt_drawlayer_widgets_t *widgets, GtkWidget *widget, cairo_t *cr, const double pixels_per_dip)
Draw selectable row of brush-profile previews.
Definition widgets.c:735
static void _brush_profile_geometry(const GtkWidget *widget, float *x, float *y, float *width, float *height, float *gap, int *cell_count)
Compute profile-row geometry inside widget.
Definition widgets.c:208
static int _picker_project_opponent_to_display_rgb(const float m, const float u, const float v, float display_rgb[3])
Project opponent-space color to display RGB; fail when out of gamut.
Definition widgets.c:110
Public color-picker/history widget API for drawlayer GUI.
#define DT_DRAWLAYER_COLOR_HISTORY_ROWS
Definition widgets.h:32
#define DT_DRAWLAYER_COLOR_HISTORY_COUNT
Definition widgets.h:30
#define DT_DRAWLAYER_COLOR_HISTORY_COLS
Definition widgets.h:31