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/brush.h" // dt_drawlayer_brush_dab_t, _rasterize_dab_rgbaf
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 .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 = (dt_gui_get_global() && dt_gui_get_global()->ppd > 0.0) ? dt_gui_get_global()->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 const float history[DT_DRAWLAYER_COLOR_HISTORY_COUNT][3],
386 const gboolean valid[DT_DRAWLAYER_COLOR_HISTORY_COUNT])
387{
388 if(IS_NULL_PTR(widgets) || !history || !valid) return;
389 for(int i = 0; i < DT_DRAWLAYER_COLOR_HISTORY_COUNT; i++)
390 {
391 widgets->color_history_valid[i] = valid[i];
392 widgets->color_history[i][0] = _clamp01(history[i][0]);
393 widgets->color_history[i][1] = _clamp01(history[i][1]);
394 widgets->color_history[i][2] = _clamp01(history[i][2]);
395 }
396}
397
400 float history[DT_DRAWLAYER_COLOR_HISTORY_COUNT][3],
401 gboolean valid[DT_DRAWLAYER_COLOR_HISTORY_COUNT])
402{
403 if(IS_NULL_PTR(widgets) || !history || !valid) return;
404 for(int i = 0; i < DT_DRAWLAYER_COLOR_HISTORY_COUNT; i++)
405 {
406 valid[i] = widgets->color_history_valid[i];
407 history[i][0] = widgets->color_history[i][0];
408 history[i][1] = widgets->color_history[i][1];
409 history[i][2] = widgets->color_history[i][2];
410 }
411}
412
414gboolean dt_drawlayer_widgets_push_color_history(dt_drawlayer_widgets_t *widgets, const float display_rgb[3])
415{
416 if(!widgets || !display_rgb) return FALSE;
417
418 if(widgets->color_history_valid[0] && _display_rgb_equal(widgets->color_history[0], display_rgb)) return FALSE;
419
420 for(int i = DT_DRAWLAYER_COLOR_HISTORY_COUNT - 1; i > 0; i--)
421 {
422 widgets->color_history_valid[i] = widgets->color_history_valid[i - 1];
423 widgets->color_history[i][0] = widgets->color_history[i - 1][0];
424 widgets->color_history[i][1] = widgets->color_history[i - 1][1];
425 widgets->color_history[i][2] = widgets->color_history[i - 1][2];
426 }
427
428 widgets->color_history_valid[0] = TRUE;
429 widgets->color_history[0][0] = _clamp01(display_rgb[0]);
430 widgets->color_history[0][1] = _clamp01(display_rgb[1]);
431 widgets->color_history[0][2] = _clamp01(display_rgb[2]);
432 return TRUE;
433}
434
437 float x, float y, float display_rgb[3])
438{
439 if(!widgets || !widget || !display_rgb) return FALSE;
440
441 float uv_x = 0.0f, uv_y = 0.0f, uv_size = 0.0f;
442 float plane_x = 0.0f, plane_y = 0.0f, plane_w = 0.0f, plane_h = 0.0f;
443 _color_picker_geometry(widget, &uv_x, &uv_y, &uv_size, &plane_x, &plane_y, &plane_w, &plane_h);
444 const float hit_margin
446 const gboolean in_uv = _rect_contains_with_margin(x, y, uv_x, uv_y, uv_size, uv_size,
448 ? hit_margin
449 : 0.0f);
450 const gboolean in_plane = _rect_contains_with_margin(x, y, plane_x, plane_y, plane_w, plane_h,
452 ? hit_margin
453 : 0.0f);
454
455 if(in_uv)
456 {
457 const float tx = 2.0f * _clamp01((x - uv_x) / fmaxf(uv_size, 1.0f)) - 1.0f;
458 const float ty = 1.0f - 2.0f * _clamp01((y - uv_y) / fmaxf(uv_size, 1.0f));
459 widgets->picker_u = tx * DT_DRAWLAYER_PICKER_U_MAX;
460 widgets->picker_v = ty * DT_DRAWLAYER_PICKER_V_MAX;
463 }
464 else if(in_plane)
465 {
466 const float tx = _clamp01((x - plane_x) / fmaxf(plane_w, 1.0f));
467 const float ty = _clamp01((y - plane_y) / fmaxf(plane_h, 1.0f));
468 const float new_m = 1.0f - ty;
469 const float new_chroma = tx * DT_DRAWLAYER_PICKER_C_MAX;
470 const float new_u = new_chroma * cosf(widgets->picker_hue);
471 const float new_v = new_chroma * sinf(widgets->picker_hue);
472 if(_picker_project_opponent_to_display_rgb(new_m, new_u, new_v, display_rgb)) return FALSE;
473
474 widgets->picker_m = new_m;
475 widgets->picker_chroma = new_chroma;
476 widgets->picker_u = new_u;
477 widgets->picker_v = new_v;
479 }
480 else
481 {
482 return FALSE;
483 }
484
486 if(!_picker_project_opponent_to_display_rgb(widgets->picker_m, widgets->picker_u, widgets->picker_v, display_rgb))
487 {
488 widgets->color_surface_dirty = TRUE;
489 return TRUE;
490 }
491 return FALSE;
492}
493
496{
497 if(IS_NULL_PTR(widgets)) return FALSE;
499 if(IS_NULL_PTR(display_rgb)) return FALSE;
500 return !_picker_project_opponent_to_display_rgb(widgets->picker_m, widgets->picker_u, widgets->picker_v, display_rgb);
501}
502
505{
506 return widgets && widgets->picker_drag_mode != DT_DRAWLAYER_COLOR_DRAG_NONE;
507}
508
511 float x, float y, float display_rgb[3])
512{
513 if(!widgets || !widget || !display_rgb) return FALSE;
514
515 const int width = gtk_widget_get_allocated_width(widget);
516 const int height = gtk_widget_get_allocated_height(widget);
517 if(width <= 0 || height <= 0) return FALSE;
518
519 const int col = CLAMP((int)floorf(x / ((float)width / DT_DRAWLAYER_COLOR_HISTORY_COLS)), 0,
521 const int row = CLAMP((int)floorf(y / ((float)height / DT_DRAWLAYER_COLOR_HISTORY_ROWS)), 0,
523 const int index = row * DT_DRAWLAYER_COLOR_HISTORY_COLS + col;
524 if(index < 0 || index >= DT_DRAWLAYER_COLOR_HISTORY_COUNT || !widgets->color_history_valid[index]) return FALSE;
525
526 display_rgb[0] = widgets->color_history[index][0];
527 display_rgb[1] = widgets->color_history[index][1];
528 display_rgb[2] = widgets->color_history[index][2];
529 return TRUE;
530}
531
534 double pixels_per_dip)
535{
536 if(!widgets || !widget || IS_NULL_PTR(cr)) return FALSE;
537
538 const int width = gtk_widget_get_allocated_width(widget);
539 const int height = gtk_widget_get_allocated_height(widget);
540 if(width <= 0 || height <= 0) return FALSE;
541
542 const double ppd = (pixels_per_dip > 0.0) ? pixels_per_dip : 1.0;
543 const int width_px = MAX(1, (int)ceil(width * ppd));
544 const int height_px = MAX(1, (int)ceil(height * ppd));
545 const gboolean size_changed = !widgets->color_surface || widgets->color_surface_width != width_px
546 || widgets->color_surface_height != height_px
547 || fabs(widgets->color_surface_ppd - ppd) > 1e-9;
548
549 if(size_changed)
550 {
552 widgets->color_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width_px, height_px);
553 if(cairo_surface_status(widgets->color_surface) != CAIRO_STATUS_SUCCESS)
554 {
556 return FALSE;
557 }
558 cairo_surface_set_device_scale(widgets->color_surface, ppd, ppd);
559 widgets->color_surface_width = width_px;
560 widgets->color_surface_height = height_px;
561 widgets->color_surface_ppd = ppd;
562 widgets->color_surface_dirty = TRUE;
563 }
564
565 float uv_x = 0.0f, uv_y = 0.0f, uv_size = 0.0f;
566 float plane_x = 0.0f, plane_y = 0.0f, plane_w = 0.0f, plane_h = 0.0f;
567 _color_picker_geometry(widget, &uv_x, &uv_y, &uv_size, &plane_x, &plane_y, &plane_w, &plane_h);
568
569 if(widgets->color_surface_dirty)
570 {
571 unsigned char *data = cairo_image_surface_get_data(widgets->color_surface);
572 const int stride = cairo_image_surface_get_stride(widgets->color_surface);
573 memset(data, 0, (size_t)stride * height_px);
574
575 for(int py = 0; py < height_px; py++)
576 {
577 for(int px = 0; px < width_px; px++)
578 {
579 float rgb[3] = { 0.12f, 0.12f, 0.12f };
580 gboolean paint = TRUE;
581 const float fx = ((float)px + 0.5f) / ppd;
582 const float fy = ((float)py + 0.5f) / ppd;
583
584 if(fx >= uv_x && fx <= uv_x + uv_size && fy >= uv_y && fy <= uv_y + uv_size)
585 {
586 const float tx = 2.0f * _clamp01((fx - uv_x) / fmaxf(uv_size, 1.0f)) - 1.0f;
587 const float ty = 1.0f - 2.0f * _clamp01((fy - uv_y) / fmaxf(uv_size, 1.0f));
588 const float u = tx * DT_DRAWLAYER_PICKER_U_MAX;
589 const float v = ty * DT_DRAWLAYER_PICKER_V_MAX;
591 }
592 else if(fx >= plane_x && fx <= plane_x + plane_w && fy >= plane_y && fy <= plane_y + plane_h)
593 {
594 const float tx = _clamp01((fx - plane_x) / fmaxf(plane_w, 1.0f));
595 const float ty = _clamp01((fy - plane_y) / fmaxf(plane_h, 1.0f));
596 const float m = 1.0f - ty;
597 const float chroma = tx * DT_DRAWLAYER_PICKER_C_MAX;
598 const float u = chroma * cosf(widgets->picker_hue);
599 const float v = chroma * sinf(widgets->picker_hue);
601 }
602 else
603 {
604 paint = FALSE;
605 }
606
607 unsigned char *pixel = data + (size_t)py * stride + 4 * px;
608 if(paint)
609 {
610 pixel[0] = (unsigned char)CLAMP(roundf(255.0f * _clamp01(rgb[2])), 0, 255);
611 pixel[1] = (unsigned char)CLAMP(roundf(255.0f * _clamp01(rgb[1])), 0, 255);
612 pixel[2] = (unsigned char)CLAMP(roundf(255.0f * _clamp01(rgb[0])), 0, 255);
613 pixel[3] = 255;
614 }
615 else
616 {
617 pixel[0] = pixel[1] = pixel[2] = 0;
618 pixel[3] = 0;
619 }
620 }
621 }
622 cairo_surface_mark_dirty(widgets->color_surface);
623 widgets->color_surface_dirty = FALSE;
624 }
625
626 cairo_set_source_surface(cr, widgets->color_surface, 0.0, 0.0);
627 cairo_paint(cr);
628
629 const float uv_mark_x
630 = uv_x + 0.5f * ((widgets->picker_u / fmaxf(DT_DRAWLAYER_PICKER_U_MAX, 1e-6f)) + 1.0f) * uv_size;
631 const float uv_mark_y
632 = uv_y + 0.5f * (1.0f - (widgets->picker_v / fmaxf(DT_DRAWLAYER_PICKER_V_MAX, 1e-6f))) * uv_size;
633 const float plane_mark_x = plane_x + _clamp01(widgets->picker_chroma / DT_DRAWLAYER_PICKER_C_MAX) * plane_w;
634 const float plane_mark_y = plane_y + (1.0f - _clamp01(widgets->picker_m)) * plane_h;
635 const float mark_r = 5.0f;
636
637 cairo_set_line_width(cr, 2.0);
638 cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);
639 cairo_arc(cr, uv_mark_x, uv_mark_y, mark_r + 1.5f, 0.0, 2.0 * G_PI);
640 cairo_stroke(cr);
641 cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
642 cairo_arc(cr, uv_mark_x, uv_mark_y, mark_r, 0.0, 2.0 * G_PI);
643 cairo_stroke(cr);
644
645 cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);
646 cairo_arc(cr, plane_mark_x, plane_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, plane_mark_x, plane_mark_y, mark_r, 0.0, 2.0 * G_PI);
650 cairo_stroke(cr);
651
652 return FALSE;
653}
654
656gboolean dt_drawlayer_widgets_draw_swatch(const dt_drawlayer_widgets_t *widgets, GtkWidget *widget, cairo_t *cr)
657{
658 if(!widgets || !widget || IS_NULL_PTR(cr)) return FALSE;
659
660 const int width = gtk_widget_get_allocated_width(widget);
661 const int height = gtk_widget_get_allocated_height(widget);
662 if(width <= 0 || height <= 0) return FALSE;
663
664 const float cell_w = (float)width / DT_DRAWLAYER_COLOR_HISTORY_COLS;
665 const float cell_h = (float)height / DT_DRAWLAYER_COLOR_HISTORY_ROWS;
666 const float gap = DT_PIXEL_APPLY_DPI(3.0f);
667
668 cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 0.12);
669 cairo_rectangle(cr, 0.0, 0.0, width, height);
670 cairo_fill(cr);
671
672 for(int i = 0; i < DT_DRAWLAYER_COLOR_HISTORY_COUNT; i++)
673 {
675 const int col = i % DT_DRAWLAYER_COLOR_HISTORY_COLS;
676 const float x = col * cell_w;
677 const float y = row * cell_h;
678
679 if(widgets->color_history_valid[i])
680 cairo_set_source_rgb(cr, widgets->color_history[i][0], widgets->color_history[i][1], widgets->color_history[i][2]);
681 else
682 cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 0.0);
683
684 cairo_rectangle(cr, x + gap * 0.5f, y + gap * 0.5f, MAX(1.0f, cell_w - gap), MAX(1.0f, cell_h - gap));
685 cairo_fill(cr);
686 }
687 return FALSE;
688}
689
691 const float opacity,
692 const float hardness,
693 const float sprinkles,
694 const float sprinkle_size,
695 const float sprinkle_coarseness,
696 const int selected_shape)
697{
698 if(IS_NULL_PTR(widgets)) return;
699
700 const float new_opacity = _clamp01(opacity);
701 const float new_hardness = _clamp01(hardness);
702 const float new_sprinkles = _clamp01(sprinkles);
703 const float new_size = fmaxf(1.0f, sprinkle_size);
704 const float new_coarseness = _clamp01(sprinkle_coarseness);
705 const int new_shape = CLAMP(selected_shape, DT_DRAWLAYER_BRUSH_SHAPE_LINEAR, DT_DRAWLAYER_BRUSH_SHAPE_SIGMOIDAL);
706
707 if(fabsf(widgets->profile_opacity - new_opacity) > 1e-6f
708 || fabsf(widgets->profile_hardness - new_hardness) > 1e-6f
709 || fabsf(widgets->profile_sprinkles - new_sprinkles) > 1e-6f
710 || fabsf(widgets->profile_sprinkle_size - new_size) > 1e-6f
711 || fabsf(widgets->profile_sprinkle_coarseness - new_coarseness) > 1e-6f
712 || widgets->profile_selected_shape != new_shape)
713 widgets->profile_surface_dirty = TRUE;
714
715 widgets->profile_opacity = new_opacity;
716 widgets->profile_hardness = new_hardness;
717 widgets->profile_sprinkles = new_sprinkles;
718 widgets->profile_sprinkle_size = new_size;
719 widgets->profile_sprinkle_coarseness = new_coarseness;
720 widgets->profile_selected_shape = new_shape;
721}
722
727
729 cairo_t *cr, const double pixels_per_dip)
730{
731 if(!widgets || !widget || IS_NULL_PTR(cr)) return FALSE;
732
733 const int width = gtk_widget_get_allocated_width(widget);
734 const int height = gtk_widget_get_allocated_height(widget);
735 if(width <= 0 || height <= 0) return FALSE;
736
737 const double ppd = (pixels_per_dip > 0.0) ? pixels_per_dip : 1.0;
738 const int width_px = MAX(1, (int)ceil(width * ppd));
739 const int height_px = MAX(1, (int)ceil(height * ppd));
740 const gboolean size_changed = !widgets->profile_surface || widgets->profile_surface_width != width_px
741 || widgets->profile_surface_height != height_px
742 || fabs(widgets->profile_surface_ppd - ppd) > 1e-9;
743 if(size_changed)
744 {
745 _clear_profile_surface(widgets);
746 widgets->profile_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width_px, height_px);
747 if(cairo_surface_status(widgets->profile_surface) != CAIRO_STATUS_SUCCESS)
748 {
749 _clear_profile_surface(widgets);
750 return FALSE;
751 }
752 cairo_surface_set_device_scale(widgets->profile_surface, ppd, ppd);
753 widgets->profile_surface_width = width_px;
754 widgets->profile_surface_height = height_px;
755 widgets->profile_surface_ppd = ppd;
756 widgets->profile_surface_dirty = TRUE;
757 }
758
759 float gap = 0.0f;
760 int count = 0;
761 _brush_profile_geometry(widget, NULL, NULL, NULL, NULL, &gap, &count);
762
763 if(widgets->profile_surface_dirty)
764 {
765 unsigned char *data = cairo_image_surface_get_data(widgets->profile_surface);
766 const int stride = cairo_image_surface_get_stride(widgets->profile_surface);
767 memset(data, 0, (size_t)stride * height_px);
768 int max_cell_w_px = 0;
769 int max_cell_h_px = 0;
770
771 for(int i = 0; i < count; i++)
772 {
773 float cell_x = 0.0f, cell_y = 0.0f, cell_w = 0.0f, cell_h = 0.0f;
774 if(!_brush_profile_cell_rect(widget, i, &cell_x, &cell_y, &cell_w, &cell_h)) continue;
775 const int x0 = MAX(0, (int)floor(cell_x * ppd));
776 const int y0 = MAX(0, (int)floor(cell_y * ppd));
777 const int x1 = MIN(width_px, (int)ceil((cell_x + cell_w) * ppd));
778 const int y1 = MIN(height_px, (int)ceil((cell_y + cell_h) * ppd));
779 max_cell_w_px = MAX(max_cell_w_px, MAX(1, x1 - x0));
780 max_cell_h_px = MAX(max_cell_h_px, MAX(1, y1 - y0));
781 }
782
783 unsigned char *cell = g_malloc0((size_t)max_cell_w_px * max_cell_h_px * 4);
784 float *rgba_scratch = g_malloc((size_t)max_cell_w_px * max_cell_h_px * 4 * sizeof(float));
785
786 for(int i = 0; i < count; i++)
787 {
788 float cell_x = 0.0f, cell_y = 0.0f, cell_w = 0.0f, cell_h = 0.0f;
789 if(!_brush_profile_cell_rect(widget, i, &cell_x, &cell_y, &cell_w, &cell_h)) continue;
790
791 const int x0 = MAX(0, (int)floor(cell_x * ppd));
792 const int y0 = MAX(0, (int)floor(cell_y * ppd));
793 const int x1 = MIN(width_px, (int)ceil((cell_x + cell_w) * ppd));
794 const int y1 = MIN(height_px, (int)ceil((cell_y + cell_h) * ppd));
795 const int cell_w_px = MAX(1, x1 - x0);
796 const int cell_h_px = MAX(1, y1 - y0);
797
798 memset(cell, 0, (size_t)max_cell_w_px * max_cell_h_px * 4);
799 _render_brush_profile_cell(cell, cell_w_px * 4, cell_w_px, cell_h_px, rgba_scratch, widgets, i);
800
801 for(int py = 0; py < cell_h_px; py++)
802 {
803 memcpy(data + (size_t)(y0 + py) * stride + 4 * x0,
804 cell + (size_t)py * cell_w_px * 4,
805 (size_t)cell_w_px * 4);
806 }
807 }
808 dt_free(rgba_scratch);
809 dt_free(cell);
810
811 cairo_surface_mark_dirty(widgets->profile_surface);
812 widgets->profile_surface_dirty = FALSE;
813 }
814
815 cairo_set_source_surface(cr, widgets->profile_surface, 0.0, 0.0);
816 cairo_paint(cr);
817
818 for(int i = 0; i < count; i++)
819 {
820 float cell_x = 0.0f, cell_y = 0.0f, cell_w = 0.0f, cell_h = 0.0f;
821 if(!_brush_profile_cell_rect(widget, i, &cell_x, &cell_y, &cell_w, &cell_h)) continue;
822
823 const gboolean selected = (i == widgets->profile_selected_shape);
824 cairo_rectangle(cr, cell_x, cell_y, cell_w, cell_h);
825 cairo_set_line_width(cr, selected ? 2.5 : 1.0);
826 if(selected) cairo_set_source_rgb(cr, 0.12, 0.45, 0.85);
827 else cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 0.18);
828 cairo_stroke(cr);
829 }
830 return FALSE;
831}
832
834 const float x, const float y, int *shape)
835{
836 if(!widgets || !widget) return FALSE;
837
839 {
840 float cell_x = 0.0f, cell_y = 0.0f, cell_w = 0.0f, cell_h = 0.0f;
841 if(!_brush_profile_cell_rect(widget, i, &cell_x, &cell_y, &cell_w, &cell_h)) continue;
842 if(x >= cell_x && x <= cell_x + cell_w && y >= cell_y && y <= cell_y + cell_h)
843 {
844 widgets->profile_selected_shape = i;
845 widgets->profile_surface_dirty = TRUE;
846 if(shape) *shape = i;
847 return TRUE;
848 }
849 }
850 return FALSE;
851}
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
#define m
Definition basecurve.c:283
Dab-level brush rasterization API for drawlayer.
@ DT_DRAWLAYER_BRUSH_MODE_PAINT
Definition brush.h:50
@ DT_DRAWLAYER_BRUSH_SHAPE_SIGMOIDAL
Definition brush.h:44
@ DT_DRAWLAYER_BRUSH_SHAPE_LINEAR
Definition brush.h:41
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:526
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.
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:66
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:495
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:510
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:311
#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:436
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:414
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:384
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: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: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: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:690
gboolean dt_drawlayer_widgets_is_picker_dragging(const dt_drawlayer_widgets_t *widgets)
Tell whether picker drag interaction is currently active.
Definition widgets.c:504
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:723
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:399
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:331
#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:833
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: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: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:533
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:656
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:728
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:31
#define DT_DRAWLAYER_COLOR_HISTORY_COUNT
Definition widgets.h:29
#define DT_DRAWLAYER_COLOR_HISTORY_COLS
Definition widgets.h:30