Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
colormapping.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2013-2016 Roman Lebedev.
4 Copyright (C) 2013-2017, 2019 Tobias Ellinghaus.
5 Copyright (C) 2013-2014, 2016-2017 Ulrich Pegelow.
6 Copyright (C) 2014 parafin.
7 Copyright (C) 2015 Pedro Côrte-Real.
8 Copyright (C) 2016 johannes hanika.
9 Copyright (C) 2017, 2019-2020 Heiko Bauke.
10 Copyright (C) 2018, 2020, 2022-2023, 2025-2026 Aurélien PIERRE.
11 Copyright (C) 2018 Edgardo Hoszowski.
12 Copyright (C) 2018 Maurizio Paglia.
13 Copyright (C) 2018-2020, 2022 Pascal Obry.
14 Copyright (C) 2018 rawfiner.
15 Copyright (C) 2019 Andreas Schneider.
16 Copyright (C) 2019-2020, 2022 Diederik Ter Rahe.
17 Copyright (C) 2019 Diederik ter Rahe.
18 Copyright (C) 2019 Jacques Le Clerc.
19 Copyright (C) 2020 Aldric Renaudin.
20 Copyright (C) 2020-2021 Hubert Kowalski.
21 Copyright (C) 2020-2021 Ralf Brown.
22 Copyright (C) 2022 Hanno Schwalm.
23 Copyright (C) 2022 Martin Bařinka.
24 Copyright (C) 2022 Philipp Lutz.
25
26 darktable is free software: you can redistribute it and/or modify
27 it under the terms of the GNU General Public License as published by
28 the Free Software Foundation, either version 3 of the License, or
29 (at your option) any later version.
30
31 darktable is distributed in the hope that it will be useful,
32 but WITHOUT ANY WARRANTY; without even the implied warranty of
33 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
34 GNU General Public License for more details.
35
36 You should have received a copy of the GNU General Public License
37 along with darktable. If not, see <http://www.gnu.org/licenses/>.
38*/
39#ifdef HAVE_CONFIG_H
41#include "config.h"
42#endif
43#include "widgets/bauhaus.h"
44#include "system/macros.h"
45#include "system/openmp.h"
47#include "system/mem_alloc.h"
48#include "system/simd.h"
50#include <glib/gstdio.h>
51#include "pixel/bilateral.h"
53#include "common/imagebuf.h"
54#include "common/points.h"
55#include "control/control.h"
56#include "develop/develop.h"
57#include "develop/imageop.h"
58#include "develop/imageop_gui.h"
59#include "develop/tiling.h"
60#include "widgets/drawingarea.h"
61
62#include "iop/iop_api.h"
63
64#include <gtk/gtk.h>
65#include <inttypes.h>
66#include <math.h>
67#include <stdlib.h>
68#include <string.h>
69#include "widgets/label.h"
70#include "gui/screen_metrics.h"
71#include "control/signal.h"
72
86
87#define HISTN (1 << 11)
88#define MAXN 5
89
90typedef float float2[2];
91
102
104{
105 float hist[HISTN];
106 // n-means (max 5?) with mean/variance
109 float weight[MAXN];
110 // number of gaussians used.
111 int n; // $MIN: 1 $MAX: 5 $DEFAULT: 1 $DESCRIPTION: "number of clusters"
113
115{
116 dt_iop_colormapping_flags_t flag; // $DEFAULT: NEUTRAL
117 // number of gaussians used.
118 int n; // $MIN: 1 $MAX: 5 $DEFAULT: 3 $DESCRIPTION: "number of clusters"
119
120 // relative importance of color dominance vs. color proximity
121 float dominance; // $MIN: 0.0 $MAX: 100.0 $DEFAULT: 100.0 $DESCRIPTION: "color dominance"
122
123 // level of histogram equalization
124 float equalization; // $MIN: 0.0 $MAX: 100.0 $DEFAULT: 50.0 $DESCRIPTION: "histogram equalization"
125
126 // hist matching table for source image
128 // n-means (max 5) with mean/variance for source image
132
133 // hist matching table for destination image
135 // n-means (max 5) with mean/variance for source image
140
143
144
163
164const char *name()
165{
166 return _("color mapping");
167}
168
169const char **description(struct dt_iop_module_t *self)
170{
171 return dt_iop_set_description(self, _("transfer a color palette and tonal repartition from one image to another"),
172 _("creative"),
173 _("linear or non-linear, Lab, display-referred"),
174 _("non-linear, Lab"),
175 _("non-linear, Lab, display-referred"));
176}
177
179{
180 return IOP_GROUP_COLOR;
181}
182
187
189{
190 return IOP_CS_LAB;
191}
192
193static void capture_histogram(const float *col, const int width, const int height, int *hist)
194{
195 // build separate histogram
196 memset(hist, 0, sizeof(int) * HISTN);
197 for(int k = 0; k < height; k++)
198 for(int i = 0; i < width; i++)
199 {
200 const int bin = CLAMP(HISTN * col[4 * (k * width + i) + 0] / 100.0, 0, HISTN - 1);
201 hist[bin]++;
202 }
203
204 // accumulated start distribution of G1 G2
205 for(int k = 1; k < HISTN; k++) hist[k] += hist[k - 1];
206 for(int k = 0; k < HISTN; k++)
207 hist[k] = (int)CLAMP(hist[k] * (HISTN / (float)hist[HISTN - 1]), 0, HISTN - 1);
208 // for(int i=0;i<100;i++) printf("#[%d] %d \n", (int)CLAMP(HISTN*i/100.0, 0, HISTN-1),
209 // hist[(int)CLAMP(HISTN*i/100.0, 0, HISTN-1)]);
210}
211
212static void invert_histogram(const int *hist, float *inv_hist)
213{
214// invert non-normalised accumulated hist
215#if 0
216 int last = 0;
217 for(int i=0; i<HISTN; i++) for(int k=last; k<HISTN; k++)
218 if(hist[k] >= i)
219 {
220 last = k;
221 inv_hist[i] = 100.0*k/(float)HISTN;
222 break;
223 }
224#else
225 int last = 31;
226 for(int i = 0; i <= last; i++) inv_hist[i] = 100.0f * i / (float)HISTN;
227 for(int i = last + 1; i < HISTN; i++)
228 for(int k = last; k < HISTN; k++)
229 if(hist[k] >= i)
230 {
231 last = k;
232 inv_hist[i] = 100.0f * k / (float)HISTN;
233 break;
234 }
235#endif
236
237 // printf("inv histogram debug:\n");
238 // for(int i=0;i<100;i++) printf("%d => %f\n", i, inv_hist[hist[(int)CLAMP(HISTN*i/100.0, 0, HISTN-1)]]);
239 // for(int i=0;i<100;i++) printf("[%d] %f => %f\n", (int)CLAMP(HISTN*i/100.0, 0, HISTN-1),
240 // hist[(int)CLAMP(HISTN*i/100.0, 0, HISTN-1)]/(float)HISTN, inv_hist[(int)CLAMP(HISTN*i/100.0, 0,
241 // HISTN-1)]);
242}
243
245static void get_cluster_mapping(const int n, float2 *mi, const float *wi, float2 *mo, const float *wo,
246 const float dominance, int *mapio)
247{
248 const float weightscale = 10000.0f;
249
250 for(int ki = 0; ki < n; ki++)
251 {
252 // for each input cluster
253 float mdist = FLT_MAX;
254 for(int ko = 0; ko < n; ko++)
255 {
256 // find the best target cluster (the same could be used more than once)
257 const float colordist = (mo[ko][0] - mi[ki][0]) * (mo[ko][0] - mi[ki][0])
258 + (mo[ko][1] - mi[ki][1]) * (mo[ko][1] - mi[ki][1]);
259 const float weightdist = weightscale * (wo[ko] - wi[ki]) * (wo[ko] - wi[ki]);
260 const float dist = colordist * (1.0f - dominance) + weightdist * dominance;
261 if(dist < mdist)
262 {
263 // printf("[%d] => [%d] dominance: %f, colordist: %f, weightdist: %f, dist: %f\n", ki, ko, dominance,
264 // colordist, weightdist, dist);
265 mdist = dist;
266 mapio[ki] = ko;
267 }
268 }
269 }
270
271 // printf("cluster mapping:\n");
272 // for(int i=0;i<n;i++) printf("[%d] => [%d]\n", i, mapio[i]);
273}
274
275
276// inverse distant weighting according to D. Shepard's method; with power parameter 2.0
278static void get_clusters(const float *col, const int n, float2 *mean, float *weight)
279{
280 float mdist = FLT_MAX;
281 for(int k = 0; k < n; k++)
282 {
283 const float dist2 = (col[1] - mean[k][0]) * (col[1] - mean[k][0])
284 + (col[2] - mean[k][1]) * (col[2] - mean[k][1]); // dist^2
285 weight[k] = dist2 > 1.0e-6f ? 1.0f / dist2 : -1.0f; // direct hits marked as -1
286 if(dist2 < mdist) mdist = dist2;
287 }
288 if(mdist < 1.0e-6f)
289 for(int k = 0; k < n; k++)
290 weight[k] = weight[k] < 0.0f ? 1.0f : 0.0f; // correction in case of direct hits
291 float sum = 0.0f;
292 for(int k = 0; k < n; k++) sum += weight[k];
293 if(sum > 0.0f)
294 for(int k = 0; k < n; k++) weight[k] /= sum;
295}
296
297
298static int get_cluster(const float *col, const int n, float2 *mean)
299{
300 float mdist = FLT_MAX;
301 int cluster = 0;
302 for(int k = 0; k < n; k++)
303 {
304 const float dist = (col[1] - mean[k][0]) * (col[1] - mean[k][0])
305 + (col[2] - mean[k][1]) * (col[2] - mean[k][1]);
306 if(dist < mdist)
307 {
308 mdist = dist;
309 cluster = k;
310 }
311 }
312 return cluster;
313}
314
315static void kmeans(const float *col, const int width, const int height, const int n, float2 *mean_out,
316 float2 *var_out, float *weight_out)
317{
318 const int nit = 40; // number of iterations
319 const int samples = width * height * 0.2; // samples: only a fraction of the buffer.
320
321 float2 *const mean = malloc(sizeof(float2) * n);
322 float2 *const var = malloc(sizeof(float2) * n);
323 int *const cnt = malloc(sizeof(int) * n);
324 int count;
325
326 float a_min = FLT_MAX, b_min = FLT_MAX, a_max = FLT_MIN, b_max = FLT_MIN;
327
328 for(int s = 0; s < samples; s++)
329 {
330 const int j = CLAMP(dt_points_get() * height, 0, height - 1);
331 const int i = CLAMP(dt_points_get() * width, 0, width - 1);
332
333 const float a = col[4 * (width * j + i) + 1];
334 const float b = col[4 * (width * j + i) + 2];
335
336 a_min = fminf(a, a_min);
337 a_max = fmaxf(a, a_max);
338 b_min = fminf(b, b_min);
339 b_max = fmaxf(b, b_max);
340 }
341
342 // init n clusters for a, b channels at random
343 for(int k = 0; k < n; k++)
344 {
345 mean_out[k][0] = 0.9f * (a_min + (a_max - a_min) * dt_points_get());
346 mean_out[k][1] = 0.9f * (b_min + (b_max - b_min) * dt_points_get());
347 var_out[k][0] = var_out[k][1] = weight_out[k] = 0.0f;
348 mean[k][0] = mean[k][1] = var[k][0] = var[k][1] = 0.0f;
349 }
350 for(int it = 0; it < nit; it++)
351 {
352 for(int k = 0; k < n; k++) cnt[k] = 0;
353// randomly sample col positions inside roi
355 for(int s = 0; s < samples; s++)
356 {
357 const int j = CLAMP(dt_points_get() * height, 0, height - 1);
358 const int i = CLAMP(dt_points_get() * width, 0, width - 1);
359 // for each sample: determine cluster, update new mean, update var
360 for(int k = 0; k < n; k++)
361 {
362 const float L = col[4 * (width * j + i)];
363 const dt_aligned_pixel_t Lab = { L, col[4 * (width * j + i) + 1], col[4 * (width * j + i) + 2] };
364 // determine dist to mean_out
365 const int c = get_cluster(Lab, n, mean_out);
366#ifdef _OPENMP
367#pragma omp atomic
368#endif
369 cnt[c]++;
370// update mean, var
371#ifdef _OPENMP
372#pragma omp atomic
373#endif
374 var[c][0] += Lab[1] * Lab[1];
375#ifdef _OPENMP
376#pragma omp atomic
377#endif
378 var[c][1] += Lab[2] * Lab[2];
379#ifdef _OPENMP
380#pragma omp atomic
381#endif
382 mean[c][0] += Lab[1];
383#ifdef _OPENMP
384#pragma omp atomic
385#endif
386 mean[c][1] += Lab[2];
387 }
388 }
389 // swap old/new means
390 for(int k = 0; k < n; k++)
391 {
392 if(cnt[k] == 0) continue;
393 mean_out[k][0] = mean[k][0] / cnt[k];
394 mean_out[k][1] = mean[k][1] / cnt[k];
395 var_out[k][0] = var[k][0] / cnt[k] - mean_out[k][0] * mean_out[k][0];
396 var_out[k][1] = var[k][1] / cnt[k] - mean_out[k][1] * mean_out[k][1];
397 mean[k][0] = mean[k][1] = var[k][0] = var[k][1] = 0.0f;
398 }
399
400 // determine weight of clusters
401 count = 0;
402 for(int k = 0; k < n; k++) count += cnt[k];
403 for(int k = 0; k < n; k++) weight_out[k] = (count > 0) ? (float)cnt[k] / count : 0.0f;
404
405 // printf("it %d %d means:\n", it, n);
406 // for(int k=0;k<n;k++) printf("mean %f %f -- var %f %f -- weight %f\n", mean_out[k][0], mean_out[k][1],
407 // var_out[k][0], var_out[k][1], weight_out[k]);
408 }
409
410 dt_free(cnt);
411 dt_free(var);
412 dt_free(mean);
413
414 for(int k = 0; k < n; k++)
415 {
416 // "eliminate" clusters with a variance of zero
417 if(var_out[k][0] == 0.0f || var_out[k][1] == 0.0f)
418 mean_out[k][0] = mean_out[k][1] = var_out[k][0] = var_out[k][1] = weight_out[k] = 0;
419
420 // we actually want the std deviation.
421 var_out[k][0] = sqrtf(var_out[k][0]);
422 var_out[k][1] = sqrtf(var_out[k][1]);
423 }
424
425 // simple bubblesort of clusters in order of ascending weight: just a convenience for the user to keep
426 // cluster display a bit more consistent in GUI
427 for(int i = 0; i < n - 1; i++)
428 {
429 for(int j = 0; j < n - 1 - i; j++)
430 {
431 if(weight_out[j] > weight_out[j + 1])
432 {
433 float2 temp_mean = { mean_out[j + 1][0], mean_out[j + 1][1] };
434 float2 temp_var = { var_out[j + 1][0], var_out[j + 1][1] };
435 float temp_weight = weight_out[j + 1];
436
437 mean_out[j + 1][0] = mean_out[j][0];
438 mean_out[j + 1][1] = mean_out[j][1];
439 var_out[j + 1][0] = var_out[j][0];
440 var_out[j + 1][1] = var_out[j][1];
441 weight_out[j + 1] = weight_out[j];
442
443 mean_out[j][0] = temp_mean[0];
444 mean_out[j][1] = temp_mean[1];
445 var_out[j][0] = temp_var[0];
446 var_out[j][1] = temp_var[1];
447 weight_out[j] = temp_weight;
448 }
449 }
450 }
451}
452
454int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
455 void *const ovoid)
456{
457 const dt_iop_roi_t *const roi_in = &piece->roi_in;
458 const dt_iop_roi_t *const roi_out = &piece->roi_out;
459 dt_iop_colormapping_data_t *const restrict data = (dt_iop_colormapping_data_t *)piece->data;
461 float *const restrict in = (float *)ivoid;
462 float *const restrict out = (float *)ovoid;
463
464 const int width = roi_in->width;
465 const int height = roi_in->height;
466
467 const float scale = dt_dev_get_module_scale(pipe, roi_in);
468 const float sigma_s = 50.0f / scale;
469 const float sigma_r = 8.0f; // does not depend on scale
470
471 // save a copy of preview input buffer so we can get histogram and color statistics out of it
472 if(self->dev->gui_attached && !IS_NULL_PTR(g) && dt_dev_pixelpipe_has_preview_output(self->dev, pipe, roi_out)
473 && (data->flag & ACQUIRE))
474 {
476 dt_free_align(g->buffer);
477 g->buffer = NULL;
478
479 g->buffer = dt_iop_image_alloc(width, height, 4);
480 g->width = width;
481 g->height = height;
482 g->ch = 4;
483
484 if(IS_NULL_PTR(g->buffer))
485 {
487 return 1;
488 }
489
490 dt_iop_image_copy_by_size(g->buffer, in, width, height, 4);
491
493 }
494
495 // process image if all mapping information is present in the parameter set
496 if(data->flag & HAS_TARGET && data->flag & HAS_SOURCE)
497 {
498 // for all pixels: find input cluster, transfer to mapped target cluster and apply histogram
499
500 const float dominance = data->dominance / 100.0f;
501 const float equalization = data->equalization / 100.0f;
502
503 // get mapping from input clusters to target clusters
504 int *const mapio = malloc(sizeof(int) * data->n);
505 if(IS_NULL_PTR(mapio)) return 1;
506
507 get_cluster_mapping(data->n, data->target_mean, data->target_weight, data->source_mean,
508 data->source_weight, dominance, mapio);
509
510 float2 *const var_ratio = malloc(sizeof(float2) * data->n);
511 if(IS_NULL_PTR(var_ratio))
512 {
513 dt_free(mapio);
514 return 1;
515 }
516
517 for(int i = 0; i < data->n; i++)
518 {
519 var_ratio[i][0]
520 = (data->target_var[i][0] > 0.0f) ? data->source_var[mapio[i]][0] / data->target_var[i][0] : 0.0f;
521 var_ratio[i][1]
522 = (data->target_var[i][1] > 0.0f) ? data->source_var[mapio[i]][1] / data->target_var[i][1] : 0.0f;
523 }
524
525 const size_t npixels = (size_t)height * width;
526// first get delta L of equalized L minus original image L, scaled to fit into [0 .. 100]
528 for(size_t k = 0; k < npixels * 4; k += 4)
529 {
530 const float L = in[k];
531 out[k] = 0.5f * ((L * (1.0f - equalization)
532 + data->source_ihist[data->target_hist[(int)CLAMP(
533 HISTN * L / 100.0f, 0.0f, (float)HISTN - 1.0f)]] * equalization) - L) + 50.0f;
534 out[k] = CLAMP(out[k], 0.0f, 100.0f);
535 }
536
537 if(equalization > 0.001f)
538 {
539 // bilateral blur of delta L to avoid artifacts caused by limited histogram resolution
541 if(IS_NULL_PTR(b))
542 {
543 dt_free(var_ratio);
544 dt_free(mapio);
545 return 1;
546 }
549 dt_bilateral_slice(b, out, out, -1.0f);
551 }
552
553 size_t allocsize;
554 float *const weight_buf = dt_pixelpipe_cache_alloc_perthread(data->n, sizeof(float), &allocsize);
555 if(IS_NULL_PTR(weight_buf))
556 {
557 dt_free(var_ratio);
558 dt_free(mapio);
559 return 1;
560 }
562 {
563 // get a thread-private scratch buffer; do this before the actual loop so we don't have to look it up for
564 // every single pixel
565 float *const restrict weight = dt_get_perthread(weight_buf,allocsize);
567 for(size_t j = 0; j < 4*npixels; j += 4)
568 {
569 const float L = in[j];
570 const dt_aligned_pixel_t Lab = { L, in[j + 1], in[j + 2] };
571
572 // transfer back scaled and blurred delta L to output L
573 out[j] = 2.0f * (out[j] - 50.0f) + L;
574 out[j] = CLAMP(out[j], 0.0f, 100.0f);
575
576 get_clusters(in + j, data->n, data->target_mean, weight);
577 // zero the 'a' and 'b' channels
578 out[j + 1] = out[j + 2] = 0.0f;
579 // then accumulate a weighted average for a and b
580 for(int c = 0; c < data->n; c++)
581 {
582 out[j + 1] += weight[c] * ((Lab[1] - data->target_mean[c][0]) * var_ratio[c][0]
583 + data->source_mean[mapio[c]][0]);
584 out[j + 2] += weight[c] * ((Lab[2] - data->target_mean[c][1]) * var_ratio[c][1]
585 + data->source_mean[mapio[c]][1]);
586 }
587 // pass through the alpha channel
588 out[j + 3] = in[j + 3];
589 }
590 }
591
593 dt_free(var_ratio);
594 dt_free(mapio);
595 }
596 // incomplete parameter set -> do nothing
597 else
598 {
600 }
601
602 return 0;
603}
604
605void tiling_callback(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe, const struct dt_dev_pixelpipe_iop_t *piece, struct dt_develop_tiling_t *tiling)
606{
607 const dt_iop_roi_t *const roi_in = &piece->roi_in;
608 const float scale = dt_dev_get_module_scale(pipe, roi_in);
609 const float sigma_s = 50.0f / scale;
610 const float sigma_r = 8.0f; // does not depend on scale
611
612 const int width = roi_in->width;
613 const int height = roi_in->height;
614 const int channels = piece->dsc_in.channels;
615
616 const size_t basebuffer = sizeof(float) * channels * width * height;
617
618 tiling->factor = 3.0f + (float)dt_bilateral_memory_use(width, height, sigma_s, sigma_r) / basebuffer;
619 tiling->maxbuf
620 = fmaxf(1.0f, (float)dt_bilateral_singlebuffer_size(width, height, sigma_s, sigma_r) / basebuffer);
621 tiling->overhead = 0;
622 tiling->overlap = ceilf(4 * sigma_s);
623 tiling->xalign = 1;
624 tiling->yalign = 1;
625}
626
635
636void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
637{
640
641 if(w == g->clusters)
642 {
643 // only reset source/target when changing number of clusters
644 memset(p->source_ihist, 0, sizeof(float) * HISTN);
645 memset(p->source_mean, 0, sizeof(float) * MAXN * 2);
646 memset(p->source_var, 0, sizeof(float) * MAXN * 2);
647 memset(p->source_weight, 0, sizeof(float) * MAXN);
648 memset(p->target_hist, 0, sizeof(int) * HISTN);
649 memset(p->target_mean, 0, sizeof(float) * MAXN * 2);
650 memset(p->target_var, 0, sizeof(float) * MAXN * 2);
651 memset(p->target_weight, 0, sizeof(float) * MAXN);
652 p->flag = NEUTRAL;
653 dt_control_queue_redraw_widget(g->source_area);
654 dt_control_queue_redraw_widget(g->target_area);
655 }
656}
657
658static void acquire_source_button_pressed(GtkButton *button, dt_iop_module_t *self)
659{
660 if(dt_gui_widgets_suppressed()) return;
662 p->flag |= ACQUIRE;
663 p->flag |= GET_SOURCE;
664 p->flag &= ~HAS_SOURCE;
666 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
667}
668
669static void acquire_target_button_pressed(GtkButton *button, dt_iop_module_t *self)
670{
671 if(dt_gui_widgets_suppressed()) return;
673 p->flag |= ACQUIRE;
674 p->flag |= GET_TARGET;
675 p->flag &= ~HAS_TARGET;
677 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
678}
679
685
687{
688 dt_free_align(piece->data);
689 piece->data = NULL;
690}
691
693{
694 dt_iop_colormapping_params_t *d = module->default_params;
695
697 if(module->dev->gui_attached && !IS_NULL_PTR(g) && g->flowback_set)
698 {
699 memcpy(d->source_ihist, g->flowback.hist, sizeof(float) * HISTN);
700 memcpy(d->source_mean, g->flowback.mean, sizeof(float) * MAXN * 2);
701 memcpy(d->source_var, g->flowback.var, sizeof(float) * MAXN * 2);
702 memcpy(d->source_weight, g->flowback.weight, sizeof(float) * MAXN);
703 d->n = g->flowback.n;
704 d->flag = HAS_SOURCE;
705 }
706}
707
708
709static gboolean cluster_preview_draw(GtkWidget *widget, cairo_t *crf, dt_iop_module_t *self)
710{
713
714 float2 *mean;
715 float2 *var;
716
717 if(widget == g->source_area)
718 {
719 mean = p->source_mean;
720 var = p->source_var;
721 }
722 else
723 {
724 mean = p->target_mean;
725 var = p->target_var;
726 }
727
728
729 GtkAllocation allocation;
730 gtk_widget_get_allocation(widget, &allocation);
731 const int inset = 5;
732 int width = allocation.width, height = allocation.height;
733 cairo_surface_t *cst = dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
734 cairo_t *cr = cairo_create(cst);
735 cairo_set_source_rgb(cr, .2, .2, .2);
736 cairo_paint(cr);
737
738 cairo_translate(cr, inset, inset);
739 width -= 2 * inset;
740 height -= 2 * inset;
741
742
743 const float sep = DT_PIXEL_APPLY_DPI(2.0);
744 const float qwd = (width - (p->n - 1) * sep) / (float)p->n;
745 for(int cl = 0; cl < p->n; cl++)
746 {
747 // draw cluster
748 for(int j = -1; j <= 1; j++)
749 for(int i = -1; i <= 1; i++)
750 {
751 // draw 9x9 grid showing mean and variance of this cluster.
752 double rgb[3] = { 0.5, 0.5, 0.5 };
753 cmsCIELab Lab;
754 Lab.L = 53.390011;
755 Lab.a = (mean[cl][0] + i * var[cl][0]);
756 Lab.b = (mean[cl][1] + j * var[cl][1]);
757 cmsDoTransform(g->xform, &Lab, rgb, 1);
758 cairo_set_source_rgb(cr, rgb[0], rgb[1], rgb[2]);
759 cairo_rectangle(cr, qwd * (i + 1) / 3.0, height * (j + 1) / 3.0, qwd / 3.0 - DT_PIXEL_APPLY_DPI(.5),
760 height / 3.0 - DT_PIXEL_APPLY_DPI(.5));
761 cairo_fill(cr);
762 }
763 cairo_translate(cr, qwd + sep, 0);
764 }
765
766 cairo_destroy(cr);
767 cairo_set_source_surface(crf, cst, 0, 0);
768 cairo_paint(crf);
769 cairo_surface_destroy(cst);
770 return TRUE;
771}
772
773
774static void process_clusters(gpointer instance, gpointer user_data)
775{
776 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
779 int new_source_clusters = 0;
780
781 if(IS_NULL_PTR(g) || IS_NULL_PTR(g->buffer)) return;
782 if(!(p->flag & ACQUIRE)) return;
783
784 {
785 // Scope guard rather than a begin/end pair: this span has an early return on buffer
786 // allocation failure, which would leak the freeze depth with a raw bracket. The guard
787 // releases the freeze on every exit path, including that return.
789
791 const int width = g->width;
792 const int height = g->height;
793 const int ch = g->ch;
794 float *const restrict buffer = dt_iop_image_alloc(width, height, ch);
795 if(IS_NULL_PTR(buffer))
796 {
798 return;
799 }
800 dt_iop_image_copy_by_size(buffer, g->buffer, width, height, ch);
802
803 if(p->flag & GET_SOURCE)
804 {
805 int hist[HISTN];
806
807 // get histogram of L
808 capture_histogram(buffer, width, height, hist);
809
810 // invert histogram
811 invert_histogram(hist, p->source_ihist);
812
813 // get n color clusters
814 kmeans(buffer, width, height, p->n, p->source_mean, p->source_var, p->source_weight);
815
816 p->flag |= HAS_SOURCE;
817 new_source_clusters = 1;
818
819 dt_control_queue_redraw_widget(g->source_area);
820 }
821 else if(p->flag & GET_TARGET)
822 {
823 // get histogram of L
824 capture_histogram(buffer, width, height, p->target_hist);
825
826 // get n color clusters
827 kmeans(buffer, width, height, p->n, p->target_mean, p->target_var, p->target_weight);
828
829 p->flag |= HAS_TARGET;
830
831 dt_control_queue_redraw_widget(g->target_area);
832 }
833
834 dt_free_align(buffer);
835
836 if(new_source_clusters)
837 {
838 memcpy(g->flowback.hist, p->source_ihist, sizeof(float) * HISTN);
839 memcpy(g->flowback.mean, p->source_mean, sizeof(float) * MAXN * 2);
840 memcpy(g->flowback.var, p->source_var, sizeof(float) * MAXN * 2);
841 memcpy(g->flowback.weight, p->source_weight, sizeof(float) * MAXN);
842 g->flowback.n = p->n;
843 g->flowback_set = 1;
844 FILE *f = g_fopen("/tmp/dt_colormapping_loaded", "wb");
845 if(f)
846 {
847 if(fwrite(&g->flowback, sizeof(g->flowback), 1, f) < 1)
848 fprintf(stderr, "[colormapping] could not write flowback file /tmp/dt_colormapping_loaded\n");
849 fclose(f);
850 }
851 }
852
853 p->flag &= ~(GET_TARGET | GET_SOURCE | ACQUIRE);
854 }
855
856 if(p->flag & HAS_SOURCE) dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
857
859}
860
861
862void gui_init(struct dt_iop_module_t *self)
863{
865
866 g->flag = NEUTRAL;
867 g->flowback_set = 0;
870 g->xform = cmsCreateTransform(hLab, TYPE_Lab_DBL, hsRGB, TYPE_RGB_DBL, INTENT_PERCEPTUAL, 0);
871 g->buffer = NULL;
872
873 self->gui->widget = GTK_WIDGET(gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING));
874
875 gtk_box_pack_start(GTK_BOX(self->gui->widget), dt_ui_label_new(_("source clusters:")), TRUE, TRUE, 0);
876
877 g->source_area = dtgtk_drawing_area_new_with_aspect_ratio(1.0 / 3.0);
878 gtk_box_pack_start(GTK_BOX(self->gui->widget), g->source_area, TRUE, TRUE, 0);
879 g_signal_connect(G_OBJECT(g->source_area), "draw", G_CALLBACK(cluster_preview_draw), self);
880
881 gtk_box_pack_start(GTK_BOX(self->gui->widget), dt_ui_label_new(_("target clusters:")), TRUE, TRUE, 0);
882
883 g->target_area = dtgtk_drawing_area_new_with_aspect_ratio(1.0 / 3.0);
884 gtk_box_pack_start(GTK_BOX(self->gui->widget), g->target_area, TRUE, TRUE, 0);
885 g_signal_connect(G_OBJECT(g->target_area), "draw", G_CALLBACK(cluster_preview_draw), self);
886
887 GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
888 gtk_box_pack_start(GTK_BOX(self->gui->widget), box, TRUE, TRUE, 0);
889
890 g->acquire_source_button = dt_iop_button_new(self, N_("acquire as source"),
891 G_CALLBACK(acquire_source_button_pressed), FALSE, 0, 0,
892 NULL, 0, box);
893 gtk_label_set_ellipsize(GTK_LABEL(gtk_bin_get_child(GTK_BIN(g->acquire_source_button))), PANGO_ELLIPSIZE_START);
894 gtk_widget_set_tooltip_text(g->acquire_source_button, _("analyze this image as a source image"));
895
896 g->acquire_target_button = dt_iop_button_new(self, N_("acquire as target"),
897 G_CALLBACK(acquire_target_button_pressed), FALSE, 0, 0,
898 NULL, 0, box);
899 gtk_label_set_ellipsize(GTK_LABEL(gtk_bin_get_child(GTK_BIN(g->acquire_target_button))), PANGO_ELLIPSIZE_START);
900 gtk_widget_set_tooltip_text(g->acquire_target_button, _("analyze this image as a target image"));
901
902 g->clusters = dt_bauhaus_slider_from_params(self, "n");
903 gtk_widget_set_tooltip_text(g->clusters, _("number of clusters to find in image. value change resets all clusters"));
904
905 g->dominance = dt_bauhaus_slider_from_params(self, "dominance");
906 gtk_widget_set_tooltip_text(g->dominance, _("how clusters are mapped. low values: based on color "
907 "proximity, high values: based on color dominance"));
908 dt_bauhaus_slider_set_format(g->dominance, "%");
909
910 g->equalization = dt_bauhaus_slider_from_params(self, "equalization");
911 gtk_widget_set_tooltip_text(g->equalization, _("level of histogram equalization"));
912 dt_bauhaus_slider_set_format(g->equalization, "%");
913
914 /* add signal handler for preview pipe finished: process clusters if requested */
916 G_CALLBACK(process_clusters), self);
917
918 FILE *f = g_fopen("/tmp/dt_colormapping_loaded", "rb");
919 if(f)
920 {
921 if(fread(&g->flowback, sizeof(g->flowback), 1, f) > 0) g->flowback_set = 1;
922 fclose(f);
923 }
924}
925
926void gui_cleanup(struct dt_iop_module_t *self)
927{
929
931
932 cmsDeleteTransform(g->xform);
933 dt_free_align(g->buffer);
934 g->buffer = NULL;
935
937}
938
939// clang-format off
940// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
941// vim: shiftwidth=2 expandtab tabstop=2 cindent
942// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
943// clang-format on
static double dist(double x1, double y1, double x2, double y2)
Definition ashift_lsd.c:250
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
void dt_bauhaus_slider_set_format(GtkWidget *widget, const char *format)
Definition bauhaus.c:3407
void dt_bilateral_free(dt_bilateral_t *b)
Definition bilateral.c:432
__DT_CLONE_TARGETS__ void dt_bilateral_splat(const dt_bilateral_t *b, const float *const in)
Definition bilateral.c:183
size_t dt_bilateral_memory_use(const int width, const int height, const float sigma_s, const float sigma_r)
Definition bilateral.c:80
dt_bilateral_t * dt_bilateral_init(const int width, const int height, const float sigma_s, const float sigma_r)
Definition bilateral.c:157
size_t dt_bilateral_singlebuffer_size(const int width, const int height, const float sigma_s, const float sigma_r)
Definition bilateral.c:108
__DT_CLONE_TARGETS__ void dt_bilateral_slice(const dt_bilateral_t *const b, const float *const in, float *out, const float detail)
Definition bilateral.c:356
void dt_bilateral_blur(const dt_bilateral_t *b)
Definition bilateral.c:341
float sigma_s
Definition bilateral.h:3
float sigma_r
Definition bilateral.h:3
@ IOP_CS_LAB
void commit_params(struct dt_iop_module_t *self, dt_iop_params_t *p1, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
static __DT_CLONE_TARGETS__ void get_clusters(const float *col, const int n, float2 *mean, float *weight)
struct dt_iop_colormapping_params_t dt_iop_colormapping_data_t
const char ** description(struct dt_iop_module_t *self)
int default_group()
__DT_CLONE_TARGETS__ int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid, void *const ovoid)
void reload_defaults(dt_iop_module_t *module)
static void invert_histogram(const int *hist, float *inv_hist)
static void process_clusters(gpointer instance, gpointer user_data)
static gboolean cluster_preview_draw(GtkWidget *widget, cairo_t *crf, dt_iop_module_t *self)
static int get_cluster(const float *col, const int n, float2 *mean)
void init_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
float float2[2]
const char * name()
static void acquire_target_button_pressed(GtkButton *button, dt_iop_module_t *self)
void gui_init(struct dt_iop_module_t *self)
void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
void tiling_callback(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe, const struct dt_dev_pixelpipe_iop_t *piece, struct dt_develop_tiling_t *tiling)
int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
int flags()
static void capture_histogram(const float *col, const int width, const int height, int *hist)
void gui_cleanup(struct dt_iop_module_t *self)
static __DT_CLONE_TARGETS__ void get_cluster_mapping(const int n, float2 *mi, const float *wi, float2 *mo, const float *wo, const float dominance, int *mapio)
void cleanup_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
#define MAXN
dt_iop_colormapping_flags_t
@ HAS_TARGET
@ GET_TARGET
@ GET_SOURCE
@ HAS_SOURCE
@ HAS_SOURCE_TARGET
@ ACQUIRE
@ NEUTRAL
static void kmeans(const float *col, const int width, const int height, const int n, float2 *mean_out, float2 *var_out, float *weight_out)
static void acquire_source_button_pressed(GtkButton *button, dt_iop_module_t *self)
#define HISTN
const float f
const dt_colorspaces_color_profile_t * dt_colorspaces_get_profile(dt_colorspaces_color_profile_type_t type, const char *filename, dt_colorspaces_profile_role_t role)
Resolve a profile identity to its registered entry.
The colour-profile module's API: which profiles exist, and how to apply one.
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
static dt_aligned_pixel_t rgb
static dt_aligned_pixel_t Lab
const dt_colormatrix_t dt_aligned_pixel_t out
void dt_control_queue_redraw_widget(GtkWidget *widget)
threadsafe request of redraw of specific widget. Use this function if you need to redraw a specific w...
Definition control.c:969
void dt_control_queue_redraw()
Request a redraw of the whole workspace.
Definition control.c:919
#define dt_dev_add_history_item(dev, module, enable, redraw)
void dt_iop_params_t
Definition dev_history.h:43
gboolean dt_dev_pixelpipe_has_preview_output(const dt_develop_t *dev, const dt_dev_pixelpipe_t *pipe, const dt_iop_roi_t *roi)
Definition develop.c:402
GtkWidget * dtgtk_drawing_area_new_with_aspect_ratio(double aspect)
Definition drawingarea.c:54
static void weight(const float *c1, const float *c2, const float sharpen, dt_aligned_pixel_t weight)
Definition eaw.c:29
static void dt_iop_image_copy_by_size(float *const __restrict__ out, const float *const __restrict__ in, const size_t width, const size_t height, const size_t ch)
Definition imagebuf.h:91
static float *__restrict__ dt_iop_image_alloc(const size_t width, const size_t height, const size_t ch)
Definition imagebuf.h:40
const char ** dt_iop_set_description(dt_iop_module_t *module, const char *main_text, const char *purpose, const char *input, const char *process, const char *output)
Definition imageop.c:1893
float dt_dev_get_module_scale(const dt_dev_pixelpipe_t *const pipe, const dt_iop_roi_t *const roi_in)
Definition imageop.c:134
void dt_iop_gui_leave_critical_section(dt_iop_module_t *const module)
Release what dt_iop_gui_enter_critical_section() took. Also a no-op headless.
void dt_iop_request_focus(dt_iop_module_t *module)
Move darkroom focus to module, or clear it with NULL.
@ IOP_FLAGS_DEPRECATED
Definition imageop.h:187
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:186
@ IOP_FLAGS_ONE_INSTANCE
Definition imageop.h:191
void dt_iop_gui_enter_critical_section(dt_iop_module_t *const module)
Take the module's GUI lock, serialising access to its dt_iop_gui_data_t.
@ IOP_GROUP_COLOR
Definition imageop.h:158
GtkWidget * dt_iop_button_new(dt_iop_module_t *self, const gchar *label, GCallback callback, gboolean local, guint accel_key, GdkModifierType mods, DTGTKCairoPaintIconFunc paint, gint paintflags, GtkWidget *box)
GtkWidget * dt_bauhaus_slider_from_params(dt_iop_module_t *self, const char *param)
#define IOP_GUI_FREE
Definition imageop_gui.h:96
static dt_iop_gui_data_t * dt_iop_gui_data(const struct dt_iop_module_t *m)
The module's GUI data blob, NULL-safe for headless callers: IOP process() implementations read it for...
Definition imageop_gui.h:81
#define IOP_GUI_ALLOC(module)
Definition imageop_gui.h:93
void *const ovoid
GtkWidget * dt_ui_label_new(const gchar *str)
Definition label.c:125
float *const restrict const size_t k
float *const restrict const size_t const size_t ch
#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_align(ptr)
Release memory from dt_alloc_align() and set ptr to NULL.
Definition mem_alloc.h:214
static void * dt_calloc_align(size_t size)
dt_alloc_align() followed by a zero fill.
Definition mem_alloc.h:225
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
#define DT_MODULE_INTROSPECTION(MODVER, PARAMSTYPE)
DT_MODULE() for a module whose params struct is introspected.
#define __OMP_FOR__(...)
Definition openmp.h:98
#define __OMP_PARALLEL__(...)
Definition openmp.h:94
#define __OMP_PARALLEL_FOR__(...)
Definition openmp.h:95
#define dt_pixelpipe_cache_alloc_perthread(n, objsize, padded_size)
#define dt_pixelpipe_cache_free_align(mem)
#define dt_get_perthread(buf, padsize)
static float dt_points_get()
Definition points.h:120
@ DT_COLORSPACE_SRGB
sRGB – registered TWICE, and the two entries are not interchangeable.
@ DT_COLORSPACE_LAB
@ DT_PROFILE_ROLE_INPUT
Listed in the input-profile combo (colorin).
@ DT_PROFILE_ROLE_ANY
All four roles, in registration order.
static cairo_surface_t * dt_cairo_image_surface_create(cairo_format_t format, int width, int height)
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
Definition signal.h:407
struct dt_control_signal_t * dt_control_signal_get_global(void)
Definition darktable.c:616
@ DT_SIGNAL_DEVELOP_PREVIEW_PIPE_FINISHED
This signal is raised when develop preview pipe process is finished no param, no returned value.
Definition signal.h:177
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:396
DT_ALIGNED_PIXEL float dt_aligned_pixel_t[4]
Definition simd.h:53
cmsHPROFILE profile
the actual profile; NULL for the three category entries
dt_iop_buffer_dsc_t dsc_in
struct dt_iop_module_t *void * data
int32_t gui_attached
Definition develop.h:167
unsigned int channels
Definition format.h:83
dt_iop_colormapping_flowback_t flowback
dt_iop_colormapping_flags_t flag
GtkWidget * widget
Definition imageop_gui.h:47
struct dt_iop_module_gui_t * gui
Definition imageop.h:346
struct dt_develop_t * dev
Definition imageop.h:311
dt_iop_params_t * params
Definition imageop.h:333
Region of interest passed through the pixelpipe.
Definition format.h:49
int width
Definition format.h:50
int height
Definition format.h:50
#define __DT_CLONE_TARGETS__
gboolean dt_gui_widgets_suppressed(void)
#define dt_gui_widget_freeze()
#define DT_GUI_BOX_SPACING
#define DT_PIXEL_APPLY_DPI(value)