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 "config.h"
21#endif
22
24#include "system/macros.h"
25#include "system/mem_alloc.h"
26
27#include "iop/drawlayer/paint.h"
28#include "gui/application.h"
29
30#include <float.h>
31#include <math.h>
32#include <string.h>
34
39#define DT_DRAWLAYER_PICKER_U_MAX 0.70710678f
40#define DT_DRAWLAYER_PICKER_V_MAX 0.81649658f
41#define DT_DRAWLAYER_PICKER_C_MAX 0.81649658f
42
50
82
84static inline float _clamp01(const float value)
85{
86 return fminf(fmaxf(value, 0.0f), 1.0f);
87}
88
90static gboolean _display_rgb_equal(const float a[3], const float b[3])
91{
92 return fabsf(a[0] - b[0]) <= 1e-6f && fabsf(a[1] - b[1]) <= 1e-6f && fabsf(a[2] - b[2]) <= 1e-6f;
93}
94
97{
98 if(IS_NULL_PTR(widgets)) return;
99 widgets->picker_hue = atan2f(widgets->picker_v, widgets->picker_u);
100 widgets->picker_chroma = hypotf(widgets->picker_u, widgets->picker_v);
101}
102
105{
106 if(IS_NULL_PTR(widgets)) return;
107 widgets->picker_u = widgets->picker_chroma * cosf(widgets->picker_hue);
108 widgets->picker_v = widgets->picker_chroma * sinf(widgets->picker_hue);
109}
110
112static int _picker_project_opponent_to_display_rgb(const float m, const float u, const float v, float display_rgb[3])
113{
114 const float r = m + u * 0.70710678f + v * 0.40824829f;
115 const float g = m - u * 0.70710678f + v * 0.40824829f;
116 const float b = m - v * 0.81649658f;
117
118 if(!isfinite(r) || !isfinite(g) || !isfinite(b)) return 1;
119 if(r < 0.0f || r > 1.0f || g < 0.0f || g > 1.0f || b < 0.0f || b > 1.0f) return 1;
120
121 display_rgb[0] = r;
122 display_rgb[1] = g;
123 display_rgb[2] = b;
124 return 0;
125}
126
128static float _picker_max_chroma_for_m_hue(const float m, const float hue)
129{
130 const float ku = cosf(hue);
131 const float kv = sinf(hue);
132 const float k[3] = {
133 ku * 0.70710678f + kv * 0.40824829f,
134 -ku * 0.70710678f + kv * 0.40824829f,
135 -kv * 0.81649658f
136 };
137
138 if(m <= 0.0f || m >= 1.0f) return 0.0f;
139
140 float limit = FLT_MAX;
141 for(int c = 0; c < 3; c++)
142 {
143 if(k[c] > 1e-6f)
144 limit = fminf(limit, (1.0f - m) / k[c]);
145 else if(k[c] < -1e-6f)
146 limit = fminf(limit, m / -k[c]);
147 }
148
149 if(!isfinite(limit) || limit < 0.0f) return 0.0f;
150 return limit;
151}
152
155{
156 if(IS_NULL_PTR(widgets)) return;
157
158 widgets->picker_m = CLAMP(widgets->picker_m, 0.0f, 1.0f);
160 const float max_chroma = _picker_max_chroma_for_m_hue(widgets->picker_m, widgets->picker_hue);
161 if(widgets->picker_chroma > max_chroma)
162 {
163 widgets->picker_chroma = max_chroma;
165 }
166}
167
169static void _sync_picker_from_display_rgb(dt_drawlayer_widgets_t *widgets, const float display_rgb[3])
170{
171 if(IS_NULL_PTR(widgets) || !display_rgb) return;
172
173 widgets->picker_m = (display_rgb[0] + display_rgb[1] + display_rgb[2]) / 3.0f;
174 widgets->picker_u = (display_rgb[0] - display_rgb[1]) * 0.70710678f;
175 widgets->picker_v = (display_rgb[0] + display_rgb[1] - 2.0f * display_rgb[2]) * 0.40824829f;
177}
178
181{
182 if(IS_NULL_PTR(widgets)) return;
183 if(widgets->color_surface)
184 {
185 cairo_surface_destroy(widgets->color_surface);
186 widgets->color_surface = NULL;
187 }
188 widgets->color_surface_width = 0;
189 widgets->color_surface_height = 0;
190 widgets->color_surface_ppd = 0.0;
191 widgets->color_surface_dirty = TRUE;
192}
193
196{
197 if(IS_NULL_PTR(widgets)) return;
198 if(widgets->profile_surface)
199 {
200 cairo_surface_destroy(widgets->profile_surface);
201 widgets->profile_surface = NULL;
202 }
203 widgets->profile_surface_width = 0;
204 widgets->profile_surface_height = 0;
205 widgets->profile_surface_ppd = 0.0;
206 widgets->profile_surface_dirty = TRUE;
207}
208
210static void _brush_profile_geometry(const GtkWidget *widget, float *x, float *y, float *width, float *height,
211 float *gap, int *cell_count)
212{
213 const float margin = 0.0f;
214 const float local_gap = DT_PIXEL_APPLY_DPI(6.0f);
215 const int count = 4;
216 const float widget_w = gtk_widget_get_allocated_width((GtkWidget *)widget);
217 const float widget_h = gtk_widget_get_allocated_height((GtkWidget *)widget);
218 if(!IS_NULL_PTR(x)) *x = margin;
219 if(!IS_NULL_PTR(y)) *y = margin;
220 if(!IS_NULL_PTR(width)) *width = fmaxf(24.0f, widget_w - 2.0f * margin);
221 if(!IS_NULL_PTR(height)) *height = fmaxf(24.0f, widget_h - 2.0f * margin);
222 if(!IS_NULL_PTR(gap)) *gap = local_gap;
223 if(!IS_NULL_PTR(cell_count)) *cell_count = count;
224}
225
227static gboolean _brush_profile_cell_rect(const GtkWidget *widget, const int index,
228 float *x, float *y, float *width, float *height)
229{
230 float row_x = 0.0f, row_y = 0.0f, row_w = 0.0f, row_h = 0.0f, gap = 0.0f;
231 int count = 0;
232 _brush_profile_geometry(widget, &row_x, &row_y, &row_w, &row_h, &gap, &count);
233 if(index < 0 || index >= count) return FALSE;
234
235 const float cell_w = fmaxf(8.0f, (row_w - gap * (count - 1)) / (float)count);
236 if(!IS_NULL_PTR(x)) *x = row_x + index * (cell_w + gap);
237 if(!IS_NULL_PTR(y)) *y = row_y;
238 if(!IS_NULL_PTR(width)) *width = cell_w;
239 if(!IS_NULL_PTR(height)) *height = row_h;
240 return TRUE;
241}
242
244static inline uint8_t _linear_channel_to_u8(const float x)
245{
246 const float v = _clamp01(x);
247 const float srgb = (v <= 0.0031308f) ? (12.92f * v) : (1.055f * powf(v, 1.0f / 2.4f) - 0.055f);
248 return (uint8_t)CLAMP((int)lrintf(255.0f * _clamp01(srgb)), 0, 255);
249}
250
252static void _render_brush_profile_cell(unsigned char *dst, const int stride, const int width, const int height,
253 float *rgba_scratch,
254 const dt_drawlayer_widgets_t *widgets, const int shape)
255{
256 if(IS_NULL_PTR(dst) || IS_NULL_PTR(rgba_scratch) || width <= 0 || height <= 0 || IS_NULL_PTR(widgets)) return;
257
258 memset(dst, 0, (size_t)stride * height);
259 for(int py = 0; py < height; py++)
260 {
261 unsigned char *row = dst + (size_t)py * stride;
262 for(int px = 0; px < width; px++)
263 {
264 row[4 * px + 1] = 0xff;
265 row[4 * px + 2] = 0xff;
266 row[4 * px + 3] = 0xff;
267 row[4 * px + 0] = 0xff;
268 }
269 }
270
271 const float radius = 0.36f * fminf((float)width, (float)height);
273 .x = 0.0f,
274 .y = 0.0f,
275 .wx = 0.0f,
276 .wy = 0.0f,
277 .radius = fmaxf(radius, 1.0f),
278 .dir_x = 0.0f,
279 .dir_y = 1.0f,
280 .opacity = _clamp01(widgets->profile_opacity),
281 .flow = 0.0f,
282 .sprinkles = _clamp01(widgets->profile_sprinkles),
283 .sprinkle_size = fmaxf(1.0f, widgets->profile_sprinkle_size),
284 .sprinkle_coarseness = _clamp01(widgets->profile_sprinkle_coarseness),
285 .hardness = _clamp01(widgets->profile_hardness),
286 .color = { 0.0f, 0.0f, 0.0f, 1.0f },
287 .display_color = { 0.0f, 0.0f, 0.0f },
288 .shape = shape,
290 .stroke_batch = 0u,
291 .stroke_pos = 0u,
292 };
293
294 const float bg[3] = { 1.0f, 1.0f, 1.0f };
296 0.5f * (float)width, 0.5f * (float)height, 1.0f, bg);
297
298 for(int y = 0; y < height; y++)
299 {
300 unsigned char *row = dst + (size_t)y * stride;
301 for(int x = 0; x < width; x++)
302 {
303 const float *pixel = rgba_scratch + 4 * ((size_t)y * width + x);
304 row[4 * x + 0] = _linear_channel_to_u8(pixel[2]);
305 row[4 * x + 1] = _linear_channel_to_u8(pixel[1]);
306 row[4 * x + 2] = _linear_channel_to_u8(pixel[0]);
307 row[4 * x + 3] = 255;
308 }
309 }
310}
311
313static void _color_picker_geometry(const GtkWidget *widget, float *uv_x, float *uv_y, float *uv_size,
314 float *plane_x, float *plane_y, float *plane_w, float *plane_h)
315{
316 const float width = gtk_widget_get_allocated_width((GtkWidget *)widget);
317 const float height = gtk_widget_get_allocated_height((GtkWidget *)widget);
318 const float margin = DT_PIXEL_APPLY_DPI(6.0f);
319 const float gap = DT_PIXEL_APPLY_DPI(16.0f);
320 const float usable_w = fmaxf(40.0f, width - 2.0f * margin - gap);
321 const float usable_h = fmaxf(40.0f, height - 2.0f * margin);
322 const float size = fmaxf(40.0f, fminf(usable_h, 0.5f * usable_w));
323 if(!IS_NULL_PTR(uv_x)) *uv_x = margin;
324 if(!IS_NULL_PTR(uv_y)) *uv_y = margin + 0.5f * (usable_h - size);
325 if(!IS_NULL_PTR(uv_size)) *uv_size = size;
326 if(!IS_NULL_PTR(plane_x)) *plane_x = margin + size + gap;
327 if(!IS_NULL_PTR(plane_y)) *plane_y = margin + 0.5f * (usable_h - size);
328 if(!IS_NULL_PTR(plane_w)) *plane_w = size;
329 if(!IS_NULL_PTR(plane_h)) *plane_h = size;
330}
331
333static float _color_picker_hit_margin(void)
334{
335 const float ppd = (dt_gui_get_global() && dt_gui_get_global()->ppd > 0.0) ? dt_gui_get_global()->ppd : 1.0f;
336 return DT_PIXEL_APPLY_DPI(12.0f) * ppd;
337}
338
340static gboolean _rect_contains_with_margin(const float x, const float y, const float rx, const float ry,
341 const float rw, const float rh, const float margin)
342{
343 return x >= rx - margin && x <= rx + rw + margin && y >= ry - margin && y <= ry + rh + margin;
344}
345
348{
349 dt_drawlayer_widgets_t *widgets = g_malloc0(sizeof(*widgets));
351 widgets->color_surface_dirty = TRUE;
352 widgets->profile_surface_dirty = TRUE;
353 widgets->profile_opacity = 1.0f;
354 widgets->profile_hardness = 0.5f;
355 widgets->profile_sprinkle_size = 3.0f;
356 widgets->profile_sprinkle_coarseness = 0.5f;
358 return widgets;
359}
360
363{
364 if(IS_NULL_PTR(widgets) || !*widgets) return;
366 _clear_profile_surface(*widgets);
367 dt_free(*widgets);
368}
369
371void dt_drawlayer_widgets_set_display_color(dt_drawlayer_widgets_t *widgets, const float display_rgb[3])
372{
373 if(IS_NULL_PTR(widgets) || !display_rgb) return;
374 _sync_picker_from_display_rgb(widgets, display_rgb);
375 widgets->color_surface_dirty = TRUE;
376}
377
379gboolean dt_drawlayer_widgets_get_display_color(const dt_drawlayer_widgets_t *widgets, float display_rgb[3])
380{
381 if(!widgets || !display_rgb) return FALSE;
382 return _picker_project_opponent_to_display_rgb(widgets->picker_m, widgets->picker_u, widgets->picker_v, display_rgb) == 0;
383}
384
387{
388 if(IS_NULL_PTR(widgets)) return;
389 widgets->color_surface_dirty = TRUE;
390}
391
394 const float history[DT_DRAWLAYER_COLOR_HISTORY_COUNT][3],
395 const gboolean valid[DT_DRAWLAYER_COLOR_HISTORY_COUNT])
396{
397 if(IS_NULL_PTR(widgets) || !history || !valid) return;
398 for(int i = 0; i < DT_DRAWLAYER_COLOR_HISTORY_COUNT; i++)
399 {
400 widgets->color_history_valid[i] = valid[i];
401 widgets->color_history[i][0] = _clamp01(history[i][0]);
402 widgets->color_history[i][1] = _clamp01(history[i][1]);
403 widgets->color_history[i][2] = _clamp01(history[i][2]);
404 }
405}
406
409 float history[DT_DRAWLAYER_COLOR_HISTORY_COUNT][3],
410 gboolean valid[DT_DRAWLAYER_COLOR_HISTORY_COUNT])
411{
412 if(IS_NULL_PTR(widgets) || !history || !valid) return;
413 for(int i = 0; i < DT_DRAWLAYER_COLOR_HISTORY_COUNT; i++)
414 {
415 valid[i] = widgets->color_history_valid[i];
416 history[i][0] = widgets->color_history[i][0];
417 history[i][1] = widgets->color_history[i][1];
418 history[i][2] = widgets->color_history[i][2];
419 }
420}
421
423gboolean dt_drawlayer_widgets_push_color_history(dt_drawlayer_widgets_t *widgets, const float display_rgb[3])
424{
425 if(!widgets || !display_rgb) return FALSE;
426
427 if(widgets->color_history_valid[0] && _display_rgb_equal(widgets->color_history[0], display_rgb)) return FALSE;
428
429 for(int i = DT_DRAWLAYER_COLOR_HISTORY_COUNT - 1; i > 0; i--)
430 {
431 widgets->color_history_valid[i] = widgets->color_history_valid[i - 1];
432 widgets->color_history[i][0] = widgets->color_history[i - 1][0];
433 widgets->color_history[i][1] = widgets->color_history[i - 1][1];
434 widgets->color_history[i][2] = widgets->color_history[i - 1][2];
435 }
436
437 widgets->color_history_valid[0] = TRUE;
438 widgets->color_history[0][0] = _clamp01(display_rgb[0]);
439 widgets->color_history[0][1] = _clamp01(display_rgb[1]);
440 widgets->color_history[0][2] = _clamp01(display_rgb[2]);
441 return TRUE;
442}
443
446 float x, float y, float display_rgb[3])
447{
448 if(!widgets || !widget || !display_rgb) return FALSE;
449
450 float uv_x = 0.0f, uv_y = 0.0f, uv_size = 0.0f;
451 float plane_x = 0.0f, plane_y = 0.0f, plane_w = 0.0f, plane_h = 0.0f;
452 _color_picker_geometry(widget, &uv_x, &uv_y, &uv_size, &plane_x, &plane_y, &plane_w, &plane_h);
453 const float hit_margin
455 const gboolean in_uv = _rect_contains_with_margin(x, y, uv_x, uv_y, uv_size, uv_size,
457 ? hit_margin
458 : 0.0f);
459 const gboolean in_plane = _rect_contains_with_margin(x, y, plane_x, plane_y, plane_w, plane_h,
461 ? hit_margin
462 : 0.0f);
463
464 if(in_uv)
465 {
466 const float tx = 2.0f * _clamp01((x - uv_x) / fmaxf(uv_size, 1.0f)) - 1.0f;
467 const float ty = 1.0f - 2.0f * _clamp01((y - uv_y) / fmaxf(uv_size, 1.0f));
468 widgets->picker_u = tx * DT_DRAWLAYER_PICKER_U_MAX;
469 widgets->picker_v = ty * DT_DRAWLAYER_PICKER_V_MAX;
472 }
473 else if(in_plane)
474 {
475 const float tx = _clamp01((x - plane_x) / fmaxf(plane_w, 1.0f));
476 const float ty = _clamp01((y - plane_y) / fmaxf(plane_h, 1.0f));
477 const float new_m = 1.0f - ty;
478 const float new_chroma = tx * DT_DRAWLAYER_PICKER_C_MAX;
479 const float new_u = new_chroma * cosf(widgets->picker_hue);
480 const float new_v = new_chroma * sinf(widgets->picker_hue);
481 if(_picker_project_opponent_to_display_rgb(new_m, new_u, new_v, display_rgb)) return FALSE;
482
483 widgets->picker_m = new_m;
484 widgets->picker_chroma = new_chroma;
485 widgets->picker_u = new_u;
486 widgets->picker_v = new_v;
488 }
489 else
490 {
491 return FALSE;
492 }
493
495 if(!_picker_project_opponent_to_display_rgb(widgets->picker_m, widgets->picker_u, widgets->picker_v, display_rgb))
496 {
497 widgets->color_surface_dirty = TRUE;
498 return TRUE;
499 }
500 return FALSE;
501}
502
505{
506 if(IS_NULL_PTR(widgets)) return FALSE;
508 if(IS_NULL_PTR(display_rgb)) return FALSE;
509 return !_picker_project_opponent_to_display_rgb(widgets->picker_m, widgets->picker_u, widgets->picker_v, display_rgb);
510}
511
514{
515 return widgets && widgets->picker_drag_mode != DT_DRAWLAYER_COLOR_DRAG_NONE;
516}
517
520 float x, float y, float display_rgb[3])
521{
522 if(!widgets || !widget || !display_rgb) return FALSE;
523
524 const int width = gtk_widget_get_allocated_width(widget);
525 const int height = gtk_widget_get_allocated_height(widget);
526 if(width <= 0 || height <= 0) return FALSE;
527
528 const int col = CLAMP((int)floorf(x / ((float)width / DT_DRAWLAYER_COLOR_HISTORY_COLS)), 0,
530 const int row = CLAMP((int)floorf(y / ((float)height / DT_DRAWLAYER_COLOR_HISTORY_ROWS)), 0,
532 const int index = row * DT_DRAWLAYER_COLOR_HISTORY_COLS + col;
533 if(index < 0 || index >= DT_DRAWLAYER_COLOR_HISTORY_COUNT || !widgets->color_history_valid[index]) return FALSE;
534
535 display_rgb[0] = widgets->color_history[index][0];
536 display_rgb[1] = widgets->color_history[index][1];
537 display_rgb[2] = widgets->color_history[index][2];
538 return TRUE;
539}
540
543 double pixels_per_dip)
544{
545 if(!widgets || !widget || IS_NULL_PTR(cr)) return FALSE;
546
547 const int width = gtk_widget_get_allocated_width(widget);
548 const int height = gtk_widget_get_allocated_height(widget);
549 if(width <= 0 || height <= 0) return FALSE;
550
551 const double ppd = (pixels_per_dip > 0.0) ? pixels_per_dip : 1.0;
552 const int width_px = MAX(1, (int)ceil(width * ppd));
553 const int height_px = MAX(1, (int)ceil(height * ppd));
554 const gboolean size_changed = !widgets->color_surface || widgets->color_surface_width != width_px
555 || widgets->color_surface_height != height_px
556 || fabs(widgets->color_surface_ppd - ppd) > 1e-9;
557
558 if(size_changed)
559 {
561 widgets->color_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width_px, height_px);
562 if(cairo_surface_status(widgets->color_surface) != CAIRO_STATUS_SUCCESS)
563 {
565 return FALSE;
566 }
567 cairo_surface_set_device_scale(widgets->color_surface, ppd, ppd);
568 widgets->color_surface_width = width_px;
569 widgets->color_surface_height = height_px;
570 widgets->color_surface_ppd = ppd;
571 widgets->color_surface_dirty = TRUE;
572 }
573
574 float uv_x = 0.0f, uv_y = 0.0f, uv_size = 0.0f;
575 float plane_x = 0.0f, plane_y = 0.0f, plane_w = 0.0f, plane_h = 0.0f;
576 _color_picker_geometry(widget, &uv_x, &uv_y, &uv_size, &plane_x, &plane_y, &plane_w, &plane_h);
577
578 if(widgets->color_surface_dirty)
579 {
580 unsigned char *data = cairo_image_surface_get_data(widgets->color_surface);
581 const int stride = cairo_image_surface_get_stride(widgets->color_surface);
582 memset(data, 0, (size_t)stride * height_px);
583
584 for(int py = 0; py < height_px; py++)
585 {
586 for(int px = 0; px < width_px; px++)
587 {
588 float rgb[3] = { 0.12f, 0.12f, 0.12f };
589 gboolean paint = TRUE;
590 const float fx = ((float)px + 0.5f) / ppd;
591 const float fy = ((float)py + 0.5f) / ppd;
592
593 if(fx >= uv_x && fx <= uv_x + uv_size && fy >= uv_y && fy <= uv_y + uv_size)
594 {
595 const float tx = 2.0f * _clamp01((fx - uv_x) / fmaxf(uv_size, 1.0f)) - 1.0f;
596 const float ty = 1.0f - 2.0f * _clamp01((fy - uv_y) / fmaxf(uv_size, 1.0f));
597 const float u = tx * DT_DRAWLAYER_PICKER_U_MAX;
598 const float v = ty * DT_DRAWLAYER_PICKER_V_MAX;
600 }
601 else if(fx >= plane_x && fx <= plane_x + plane_w && fy >= plane_y && fy <= plane_y + plane_h)
602 {
603 const float tx = _clamp01((fx - plane_x) / fmaxf(plane_w, 1.0f));
604 const float ty = _clamp01((fy - plane_y) / fmaxf(plane_h, 1.0f));
605 const float m = 1.0f - ty;
606 const float chroma = tx * DT_DRAWLAYER_PICKER_C_MAX;
607 const float u = chroma * cosf(widgets->picker_hue);
608 const float v = chroma * sinf(widgets->picker_hue);
610 }
611 else
612 {
613 paint = FALSE;
614 }
615
616 unsigned char *pixel = data + (size_t)py * stride + 4 * px;
617 if(paint)
618 {
619 pixel[0] = (unsigned char)CLAMP(roundf(255.0f * _clamp01(rgb[2])), 0, 255);
620 pixel[1] = (unsigned char)CLAMP(roundf(255.0f * _clamp01(rgb[1])), 0, 255);
621 pixel[2] = (unsigned char)CLAMP(roundf(255.0f * _clamp01(rgb[0])), 0, 255);
622 pixel[3] = 255;
623 }
624 else
625 {
626 pixel[0] = pixel[1] = pixel[2] = 0;
627 pixel[3] = 0;
628 }
629 }
630 }
631 cairo_surface_mark_dirty(widgets->color_surface);
632 widgets->color_surface_dirty = FALSE;
633 }
634
635 cairo_set_source_surface(cr, widgets->color_surface, 0.0, 0.0);
636 cairo_paint(cr);
637
638 const float uv_mark_x
639 = uv_x + 0.5f * ((widgets->picker_u / fmaxf(DT_DRAWLAYER_PICKER_U_MAX, 1e-6f)) + 1.0f) * uv_size;
640 const float uv_mark_y
641 = uv_y + 0.5f * (1.0f - (widgets->picker_v / fmaxf(DT_DRAWLAYER_PICKER_V_MAX, 1e-6f))) * uv_size;
642 const float plane_mark_x = plane_x + _clamp01(widgets->picker_chroma / DT_DRAWLAYER_PICKER_C_MAX) * plane_w;
643 const float plane_mark_y = plane_y + (1.0f - _clamp01(widgets->picker_m)) * plane_h;
644 const float mark_r = 5.0f;
645
646 cairo_set_line_width(cr, 2.0);
647 cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);
648 cairo_arc(cr, uv_mark_x, uv_mark_y, mark_r + 1.5f, 0.0, 2.0 * G_PI);
649 cairo_stroke(cr);
650 cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
651 cairo_arc(cr, uv_mark_x, uv_mark_y, mark_r, 0.0, 2.0 * G_PI);
652 cairo_stroke(cr);
653
654 cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);
655 cairo_arc(cr, plane_mark_x, plane_mark_y, mark_r + 1.5f, 0.0, 2.0 * G_PI);
656 cairo_stroke(cr);
657 cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
658 cairo_arc(cr, plane_mark_x, plane_mark_y, mark_r, 0.0, 2.0 * G_PI);
659 cairo_stroke(cr);
660
661 return FALSE;
662}
663
665gboolean dt_drawlayer_widgets_draw_swatch(const dt_drawlayer_widgets_t *widgets, GtkWidget *widget, cairo_t *cr)
666{
667 if(!widgets || !widget || IS_NULL_PTR(cr)) return FALSE;
668
669 const int width = gtk_widget_get_allocated_width(widget);
670 const int height = gtk_widget_get_allocated_height(widget);
671 if(width <= 0 || height <= 0) return FALSE;
672
673 const float cell_w = (float)width / DT_DRAWLAYER_COLOR_HISTORY_COLS;
674 const float cell_h = (float)height / DT_DRAWLAYER_COLOR_HISTORY_ROWS;
675 const float gap = DT_PIXEL_APPLY_DPI(3.0f);
676
677 cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 0.12);
678 cairo_rectangle(cr, 0.0, 0.0, width, height);
679 cairo_fill(cr);
680
681 for(int i = 0; i < DT_DRAWLAYER_COLOR_HISTORY_COUNT; i++)
682 {
684 const int col = i % DT_DRAWLAYER_COLOR_HISTORY_COLS;
685 const float x = col * cell_w;
686 const float y = row * cell_h;
687
688 if(widgets->color_history_valid[i])
689 cairo_set_source_rgb(cr, widgets->color_history[i][0], widgets->color_history[i][1], widgets->color_history[i][2]);
690 else
691 cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 0.0);
692
693 cairo_rectangle(cr, x + gap * 0.5f, y + gap * 0.5f, MAX(1.0f, cell_w - gap), MAX(1.0f, cell_h - gap));
694 cairo_fill(cr);
695 }
696 return FALSE;
697}
698
700 const float opacity,
701 const float hardness,
702 const float sprinkles,
703 const float sprinkle_size,
704 const float sprinkle_coarseness,
705 const int selected_shape)
706{
707 if(IS_NULL_PTR(widgets)) return;
708
709 const float new_opacity = _clamp01(opacity);
710 const float new_hardness = _clamp01(hardness);
711 const float new_sprinkles = _clamp01(sprinkles);
712 const float new_size = fmaxf(1.0f, sprinkle_size);
713 const float new_coarseness = _clamp01(sprinkle_coarseness);
714 const int new_shape = CLAMP(selected_shape, DT_DRAWLAYER_BRUSH_SHAPE_LINEAR, DT_DRAWLAYER_BRUSH_SHAPE_SIGMOIDAL);
715
716 if(fabsf(widgets->profile_opacity - new_opacity) > 1e-6f
717 || fabsf(widgets->profile_hardness - new_hardness) > 1e-6f
718 || fabsf(widgets->profile_sprinkles - new_sprinkles) > 1e-6f
719 || fabsf(widgets->profile_sprinkle_size - new_size) > 1e-6f
720 || fabsf(widgets->profile_sprinkle_coarseness - new_coarseness) > 1e-6f
721 || widgets->profile_selected_shape != new_shape)
722 widgets->profile_surface_dirty = TRUE;
723
724 widgets->profile_opacity = new_opacity;
725 widgets->profile_hardness = new_hardness;
726 widgets->profile_sprinkles = new_sprinkles;
727 widgets->profile_sprinkle_size = new_size;
728 widgets->profile_sprinkle_coarseness = new_coarseness;
729 widgets->profile_selected_shape = new_shape;
730}
731
736
738 cairo_t *cr, const double pixels_per_dip)
739{
740 if(!widgets || !widget || IS_NULL_PTR(cr)) return FALSE;
741
742 const int width = gtk_widget_get_allocated_width(widget);
743 const int height = gtk_widget_get_allocated_height(widget);
744 if(width <= 0 || height <= 0) return FALSE;
745
746 const double ppd = (pixels_per_dip > 0.0) ? pixels_per_dip : 1.0;
747 const int width_px = MAX(1, (int)ceil(width * ppd));
748 const int height_px = MAX(1, (int)ceil(height * ppd));
749 const gboolean size_changed = !widgets->profile_surface || widgets->profile_surface_width != width_px
750 || widgets->profile_surface_height != height_px
751 || fabs(widgets->profile_surface_ppd - ppd) > 1e-9;
752 if(size_changed)
753 {
754 _clear_profile_surface(widgets);
755 widgets->profile_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width_px, height_px);
756 if(cairo_surface_status(widgets->profile_surface) != CAIRO_STATUS_SUCCESS)
757 {
758 _clear_profile_surface(widgets);
759 return FALSE;
760 }
761 cairo_surface_set_device_scale(widgets->profile_surface, ppd, ppd);
762 widgets->profile_surface_width = width_px;
763 widgets->profile_surface_height = height_px;
764 widgets->profile_surface_ppd = ppd;
765 widgets->profile_surface_dirty = TRUE;
766 }
767
768 float gap = 0.0f;
769 int count = 0;
770 _brush_profile_geometry(widget, NULL, NULL, NULL, NULL, &gap, &count);
771
772 if(widgets->profile_surface_dirty)
773 {
774 unsigned char *data = cairo_image_surface_get_data(widgets->profile_surface);
775 const int stride = cairo_image_surface_get_stride(widgets->profile_surface);
776 memset(data, 0, (size_t)stride * height_px);
777 int max_cell_w_px = 0;
778 int max_cell_h_px = 0;
779
780 for(int i = 0; i < count; i++)
781 {
782 float cell_x = 0.0f, cell_y = 0.0f, cell_w = 0.0f, cell_h = 0.0f;
783 if(!_brush_profile_cell_rect(widget, i, &cell_x, &cell_y, &cell_w, &cell_h)) continue;
784 const int x0 = MAX(0, (int)floor(cell_x * ppd));
785 const int y0 = MAX(0, (int)floor(cell_y * ppd));
786 const int x1 = MIN(width_px, (int)ceil((cell_x + cell_w) * ppd));
787 const int y1 = MIN(height_px, (int)ceil((cell_y + cell_h) * ppd));
788 max_cell_w_px = MAX(max_cell_w_px, MAX(1, x1 - x0));
789 max_cell_h_px = MAX(max_cell_h_px, MAX(1, y1 - y0));
790 }
791
792 unsigned char *cell = g_malloc0((size_t)max_cell_w_px * max_cell_h_px * 4);
793 float *rgba_scratch = g_malloc((size_t)max_cell_w_px * max_cell_h_px * 4 * sizeof(float));
794
795 for(int i = 0; i < count; i++)
796 {
797 float cell_x = 0.0f, cell_y = 0.0f, cell_w = 0.0f, cell_h = 0.0f;
798 if(!_brush_profile_cell_rect(widget, i, &cell_x, &cell_y, &cell_w, &cell_h)) continue;
799
800 const int x0 = MAX(0, (int)floor(cell_x * ppd));
801 const int y0 = MAX(0, (int)floor(cell_y * ppd));
802 const int x1 = MIN(width_px, (int)ceil((cell_x + cell_w) * ppd));
803 const int y1 = MIN(height_px, (int)ceil((cell_y + cell_h) * ppd));
804 const int cell_w_px = MAX(1, x1 - x0);
805 const int cell_h_px = MAX(1, y1 - y0);
806
807 memset(cell, 0, (size_t)max_cell_w_px * max_cell_h_px * 4);
808 _render_brush_profile_cell(cell, cell_w_px * 4, cell_w_px, cell_h_px, rgba_scratch, widgets, i);
809
810 for(int py = 0; py < cell_h_px; py++)
811 {
812 memcpy(data + (size_t)(y0 + py) * stride + 4 * x0,
813 cell + (size_t)py * cell_w_px * 4,
814 (size_t)cell_w_px * 4);
815 }
816 }
817 dt_free(rgba_scratch);
818 dt_free(cell);
819
820 cairo_surface_mark_dirty(widgets->profile_surface);
821 widgets->profile_surface_dirty = FALSE;
822 }
823
824 cairo_set_source_surface(cr, widgets->profile_surface, 0.0, 0.0);
825 cairo_paint(cr);
826
827 for(int i = 0; i < count; i++)
828 {
829 float cell_x = 0.0f, cell_y = 0.0f, cell_w = 0.0f, cell_h = 0.0f;
830 if(!_brush_profile_cell_rect(widget, i, &cell_x, &cell_y, &cell_w, &cell_h)) continue;
831
832 const gboolean selected = (i == widgets->profile_selected_shape);
833 cairo_rectangle(cr, cell_x, cell_y, cell_w, cell_h);
834 cairo_set_line_width(cr, selected ? 2.5 : 1.0);
835 if(selected) cairo_set_source_rgb(cr, 0.12, 0.45, 0.85);
836 else cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 0.18);
837 cairo_stroke(cr);
838 }
839 return FALSE;
840}
841
843 const float x, const float y, int *shape)
844{
845 if(!widgets || !widget) return FALSE;
846
848 {
849 float cell_x = 0.0f, cell_y = 0.0f, cell_w = 0.0f, cell_h = 0.0f;
850 if(!_brush_profile_cell_rect(widget, i, &cell_x, &cell_y, &cell_w, &cell_h)) continue;
851 if(x >= cell_x && x <= cell_x + cell_w && y >= cell_y && y <= cell_y + cell_h)
852 {
853 widgets->profile_selected_shape = i;
854 widgets->profile_surface_dirty = TRUE;
855 if(shape) *shape = i;
856 return TRUE;
857 }
858 }
859 return FALSE;
860}
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
#define m
Definition basecurve.c:283
@ DT_DRAWLAYER_BRUSH_MODE_PAINT
Definition brush.h:49
@ DT_DRAWLAYER_BRUSH_SHAPE_SIGMOIDAL
Definition brush.h:43
@ DT_DRAWLAYER_BRUSH_SHAPE_LINEAR
Definition brush.h:40
static const float x
const float v
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
static dt_aligned_pixel_t rgb
static const int row
struct dt_gui_gtk_t * dt_gui_get_global(void)
Definition darktable.c:523
GHashTable * selected
set of checked row labels, mirrored to conf on every change
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.
float *const restrict const size_t k
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition macros.h:96
#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
size_t size
Definition mipmap_cache.c:3
static const dt_aligned_pixel_simd_t value
Definition simd.h:144
const float r
Fully resolved input dab descriptor.
Definition brush.h:65
Runtime state for drawlayer custom color widgets.
Definition widgets.c:53
cairo_surface_t * profile_surface
Definition widgets.c:70
double profile_surface_ppd
Definition widgets.c:73
gboolean color_history_valid[DT_DRAWLAYER_COLOR_HISTORY_COUNT]
Definition widgets.c:62
float color_history[DT_DRAWLAYER_COLOR_HISTORY_COUNT][3]
Definition widgets.c:61
cairo_surface_t * color_surface
Definition widgets.c:64
float profile_sprinkle_coarseness
Definition widgets.c:79
gboolean profile_surface_dirty
Definition widgets.c:74
float profile_sprinkle_size
Definition widgets.c:78
gboolean color_surface_dirty
Definition widgets.c:68
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29
#define DT_PIXEL_APPLY_DPI(value)
static void _picker_update_uv_from_polar(dt_drawlayer_widgets_t *widgets)
Update U/V from current hue/chroma components.
Definition widgets.c:104
#define DT_DRAWLAYER_PICKER_U_MAX
Definition widgets.c:39
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:504
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:519
static void _clear_color_picker_surface(dt_drawlayer_widgets_t *widgets)
Destroy cached picker surface and reset cache metadata.
Definition widgets.c:180
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:227
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:313
#define DT_DRAWLAYER_PICKER_C_MAX
Definition widgets.c:41
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:445
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:423
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:340
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:393
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:128
static float _clamp01(const float value)
Clamp scalar to [0,1].
Definition widgets.c:84
static gboolean _display_rgb_equal(const float a[3], const float b[3])
Compare display-RGB triplets with tiny epsilon tolerance.
Definition widgets.c:90
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:154
dt_drawlayer_widgets_t * dt_drawlayer_widgets_init(void)
Allocate and initialize widget runtime state.
Definition widgets.c:347
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:252
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:379
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:699
gboolean dt_drawlayer_widgets_is_picker_dragging(const dt_drawlayer_widgets_t *widgets)
Tell whether picker drag interaction is currently active.
Definition widgets.c:513
static void _clear_profile_surface(dt_drawlayer_widgets_t *widgets)
Destroy cached brush-profile preview surface and reset cache metadata.
Definition widgets.c:195
int dt_drawlayer_widgets_get_brush_profile_selection(const dt_drawlayer_widgets_t *widgets)
Read currently selected brush profile.
Definition widgets.c:732
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:408
static void _picker_update_polar_from_uv(dt_drawlayer_widgets_t *widgets)
Update hue/chroma from current U/V components.
Definition widgets.c:96
dt_drawlayer_color_drag_mode_t
Active drag target in picker UI.
Definition widgets.c:45
@ DT_DRAWLAYER_COLOR_DRAG_DISC
Definition widgets.c:47
@ DT_DRAWLAYER_COLOR_DRAG_PLANE
Definition widgets.c:48
@ DT_DRAWLAYER_COLOR_DRAG_NONE
Definition widgets.c:46
static float _color_picker_hit_margin(void)
Hit-margin in device-independent pixels for drag continuity.
Definition widgets.c:333
#define DT_DRAWLAYER_PICKER_V_MAX
Definition widgets.c:40
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:842
void dt_drawlayer_widgets_cleanup(dt_drawlayer_widgets_t **widgets)
Free widget runtime state and owned cairo resources.
Definition widgets.c:362
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:169
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:371
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:542
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:665
void dt_drawlayer_widgets_mark_picker_dirty(dt_drawlayer_widgets_t *widgets)
Mark picker surface dirty for regeneration on next draw.
Definition widgets.c:386
static uint8_t _linear_channel_to_u8(const float x)
Convert linear float channel to display-encoded 8-bit channel.
Definition widgets.c:244
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:737
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:210
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:112
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