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
40#include "common/darktable.h"
41#include "config.h"
42#endif
43#include "bauhaus/bauhaus.h"
44#include "common/bilateral.h"
45#include "common/bilateralcl.h"
46#include "common/colorspaces.h"
47#include "common/imagebuf.h"
48#include "common/opencl.h"
49#include "common/points.h"
50#include "control/control.h"
51#include "develop/develop.h"
52#include "develop/imageop.h"
54#include "develop/imageop_gui.h"
55#include "develop/tiling.h"
56#include "dtgtk/drawingarea.h"
57#include "dtgtk/resetlabel.h"
58
59#include "gui/gtk.h"
60#include "iop/iop_api.h"
61
62#include <gtk/gtk.h>
63#include <inttypes.h>
64#include <math.h>
65#include <stdlib.h>
66#include <string.h>
67#include <strings.h>
68
82
83#define HISTN (1 << 11)
84#define MAXN 5
85
86typedef float float2[2];
87
98
100{
101 float hist[HISTN];
102 // n-means (max 5?) with mean/variance
105 float weight[MAXN];
106 // number of gaussians used.
107 int n; // $MIN: 1 $MAX: 5 $DEFAULT: 1 $DESCRIPTION: "number of clusters"
109
111{
112 dt_iop_colormapping_flags_t flag; // $DEFAULT: NEUTRAL
113 // number of gaussians used.
114 int n; // $MIN: 1 $MAX: 5 $DEFAULT: 3 $DESCRIPTION: "number of clusters"
115
116 // relative importance of color dominance vs. color proximity
117 float dominance; // $MIN: 0.0 $MAX: 100.0 $DEFAULT: 100.0 $DESCRIPTION: "color dominance"
118
119 // level of histogram equalization
120 float equalization; // $MIN: 0.0 $MAX: 100.0 $DEFAULT: 50.0 $DESCRIPTION: "histogram equalization"
121
122 // hist matching table for source image
124 // n-means (max 5) with mean/variance for source image
128
129 // hist matching table for destination image
131 // n-means (max 5) with mean/variance for source image
136
139
140
159
160const char *name()
161{
162 return _("color mapping");
163}
164
165const char **description(struct dt_iop_module_t *self)
166{
167 return dt_iop_set_description(self, _("transfer a color palette and tonal repartition from one image to another"),
168 _("creative"),
169 _("linear or non-linear, Lab, display-referred"),
170 _("non-linear, Lab"),
171 _("non-linear, Lab, display-referred"));
172}
173
175{
176 return IOP_GROUP_COLOR;
177}
178
183
185{
186 return IOP_CS_LAB;
187}
188
189static void capture_histogram(const float *col, const int width, const int height, int *hist)
190{
191 // build separate histogram
192 memset(hist, 0, sizeof(int) * HISTN);
193 for(int k = 0; k < height; k++)
194 for(int i = 0; i < width; i++)
195 {
196 const int bin = CLAMP(HISTN * col[4 * (k * width + i) + 0] / 100.0, 0, HISTN - 1);
197 hist[bin]++;
198 }
199
200 // accumulated start distribution of G1 G2
201 for(int k = 1; k < HISTN; k++) hist[k] += hist[k - 1];
202 for(int k = 0; k < HISTN; k++)
203 hist[k] = (int)CLAMP(hist[k] * (HISTN / (float)hist[HISTN - 1]), 0, HISTN - 1);
204 // for(int i=0;i<100;i++) printf("#[%d] %d \n", (int)CLAMP(HISTN*i/100.0, 0, HISTN-1),
205 // hist[(int)CLAMP(HISTN*i/100.0, 0, HISTN-1)]);
206}
207
208static void invert_histogram(const int *hist, float *inv_hist)
209{
210// invert non-normalised accumulated hist
211#if 0
212 int last = 0;
213 for(int i=0; i<HISTN; i++) for(int k=last; k<HISTN; k++)
214 if(hist[k] >= i)
215 {
216 last = k;
217 inv_hist[i] = 100.0*k/(float)HISTN;
218 break;
219 }
220#else
221 int last = 31;
222 for(int i = 0; i <= last; i++) inv_hist[i] = 100.0f * i / (float)HISTN;
223 for(int i = last + 1; i < HISTN; i++)
224 for(int k = last; k < HISTN; k++)
225 if(hist[k] >= i)
226 {
227 last = k;
228 inv_hist[i] = 100.0f * k / (float)HISTN;
229 break;
230 }
231#endif
232
233 // printf("inv histogram debug:\n");
234 // for(int i=0;i<100;i++) printf("%d => %f\n", i, inv_hist[hist[(int)CLAMP(HISTN*i/100.0, 0, HISTN-1)]]);
235 // for(int i=0;i<100;i++) printf("[%d] %f => %f\n", (int)CLAMP(HISTN*i/100.0, 0, HISTN-1),
236 // hist[(int)CLAMP(HISTN*i/100.0, 0, HISTN-1)]/(float)HISTN, inv_hist[(int)CLAMP(HISTN*i/100.0, 0,
237 // HISTN-1)]);
238}
239
241static void get_cluster_mapping(const int n, float2 *mi, const float *wi, float2 *mo, const float *wo,
242 const float dominance, int *mapio)
243{
244 const float weightscale = 10000.0f;
245
246 for(int ki = 0; ki < n; ki++)
247 {
248 // for each input cluster
249 float mdist = FLT_MAX;
250 for(int ko = 0; ko < n; ko++)
251 {
252 // find the best target cluster (the same could be used more than once)
253 const float colordist = (mo[ko][0] - mi[ki][0]) * (mo[ko][0] - mi[ki][0])
254 + (mo[ko][1] - mi[ki][1]) * (mo[ko][1] - mi[ki][1]);
255 const float weightdist = weightscale * (wo[ko] - wi[ki]) * (wo[ko] - wi[ki]);
256 const float dist = colordist * (1.0f - dominance) + weightdist * dominance;
257 if(dist < mdist)
258 {
259 // printf("[%d] => [%d] dominance: %f, colordist: %f, weightdist: %f, dist: %f\n", ki, ko, dominance,
260 // colordist, weightdist, dist);
261 mdist = dist;
262 mapio[ki] = ko;
263 }
264 }
265 }
266
267 // printf("cluster mapping:\n");
268 // for(int i=0;i<n;i++) printf("[%d] => [%d]\n", i, mapio[i]);
269}
270
271
272// inverse distant weighting according to D. Shepard's method; with power parameter 2.0
274static void get_clusters(const float *col, const int n, float2 *mean, float *weight)
275{
276 float mdist = FLT_MAX;
277 for(int k = 0; k < n; k++)
278 {
279 const float dist2 = (col[1] - mean[k][0]) * (col[1] - mean[k][0])
280 + (col[2] - mean[k][1]) * (col[2] - mean[k][1]); // dist^2
281 weight[k] = dist2 > 1.0e-6f ? 1.0f / dist2 : -1.0f; // direct hits marked as -1
282 if(dist2 < mdist) mdist = dist2;
283 }
284 if(mdist < 1.0e-6f)
285 for(int k = 0; k < n; k++)
286 weight[k] = weight[k] < 0.0f ? 1.0f : 0.0f; // correction in case of direct hits
287 float sum = 0.0f;
288 for(int k = 0; k < n; k++) sum += weight[k];
289 if(sum > 0.0f)
290 for(int k = 0; k < n; k++) weight[k] /= sum;
291}
292
293
294static int get_cluster(const float *col, const int n, float2 *mean)
295{
296 float mdist = FLT_MAX;
297 int cluster = 0;
298 for(int k = 0; k < n; k++)
299 {
300 const float dist = (col[1] - mean[k][0]) * (col[1] - mean[k][0])
301 + (col[2] - mean[k][1]) * (col[2] - mean[k][1]);
302 if(dist < mdist)
303 {
304 mdist = dist;
305 cluster = k;
306 }
307 }
308 return cluster;
309}
310
311static void kmeans(const float *col, const int width, const int height, const int n, float2 *mean_out,
312 float2 *var_out, float *weight_out)
313{
314 const int nit = 40; // number of iterations
315 const int samples = width * height * 0.2; // samples: only a fraction of the buffer.
316
317 float2 *const mean = malloc(sizeof(float2) * n);
318 float2 *const var = malloc(sizeof(float2) * n);
319 int *const cnt = malloc(sizeof(int) * n);
320 int count;
321
322 float a_min = FLT_MAX, b_min = FLT_MAX, a_max = FLT_MIN, b_max = FLT_MIN;
323
324 for(int s = 0; s < samples; s++)
325 {
326 const int j = CLAMP(dt_points_get() * height, 0, height - 1);
327 const int i = CLAMP(dt_points_get() * width, 0, width - 1);
328
329 const float a = col[4 * (width * j + i) + 1];
330 const float b = col[4 * (width * j + i) + 2];
331
332 a_min = fminf(a, a_min);
333 a_max = fmaxf(a, a_max);
334 b_min = fminf(b, b_min);
335 b_max = fmaxf(b, b_max);
336 }
337
338 // init n clusters for a, b channels at random
339 for(int k = 0; k < n; k++)
340 {
341 mean_out[k][0] = 0.9f * (a_min + (a_max - a_min) * dt_points_get());
342 mean_out[k][1] = 0.9f * (b_min + (b_max - b_min) * dt_points_get());
343 var_out[k][0] = var_out[k][1] = weight_out[k] = 0.0f;
344 mean[k][0] = mean[k][1] = var[k][0] = var[k][1] = 0.0f;
345 }
346 for(int it = 0; it < nit; it++)
347 {
348 for(int k = 0; k < n; k++) cnt[k] = 0;
349// randomly sample col positions inside roi
351 for(int s = 0; s < samples; s++)
352 {
353 const int j = CLAMP(dt_points_get() * height, 0, height - 1);
354 const int i = CLAMP(dt_points_get() * width, 0, width - 1);
355 // for each sample: determine cluster, update new mean, update var
356 for(int k = 0; k < n; k++)
357 {
358 const float L = col[4 * (width * j + i)];
359 const dt_aligned_pixel_t Lab = { L, col[4 * (width * j + i) + 1], col[4 * (width * j + i) + 2] };
360 // determine dist to mean_out
361 const int c = get_cluster(Lab, n, mean_out);
362#ifdef _OPENMP
363#pragma omp atomic
364#endif
365 cnt[c]++;
366// update mean, var
367#ifdef _OPENMP
368#pragma omp atomic
369#endif
370 var[c][0] += Lab[1] * Lab[1];
371#ifdef _OPENMP
372#pragma omp atomic
373#endif
374 var[c][1] += Lab[2] * Lab[2];
375#ifdef _OPENMP
376#pragma omp atomic
377#endif
378 mean[c][0] += Lab[1];
379#ifdef _OPENMP
380#pragma omp atomic
381#endif
382 mean[c][1] += Lab[2];
383 }
384 }
385 // swap old/new means
386 for(int k = 0; k < n; k++)
387 {
388 if(cnt[k] == 0) continue;
389 mean_out[k][0] = mean[k][0] / cnt[k];
390 mean_out[k][1] = mean[k][1] / cnt[k];
391 var_out[k][0] = var[k][0] / cnt[k] - mean_out[k][0] * mean_out[k][0];
392 var_out[k][1] = var[k][1] / cnt[k] - mean_out[k][1] * mean_out[k][1];
393 mean[k][0] = mean[k][1] = var[k][0] = var[k][1] = 0.0f;
394 }
395
396 // determine weight of clusters
397 count = 0;
398 for(int k = 0; k < n; k++) count += cnt[k];
399 for(int k = 0; k < n; k++) weight_out[k] = (count > 0) ? (float)cnt[k] / count : 0.0f;
400
401 // printf("it %d %d means:\n", it, n);
402 // 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],
403 // var_out[k][0], var_out[k][1], weight_out[k]);
404 }
405
406 dt_free(cnt);
407 dt_free(var);
408 dt_free(mean);
409
410 for(int k = 0; k < n; k++)
411 {
412 // "eliminate" clusters with a variance of zero
413 if(var_out[k][0] == 0.0f || var_out[k][1] == 0.0f)
414 mean_out[k][0] = mean_out[k][1] = var_out[k][0] = var_out[k][1] = weight_out[k] = 0;
415
416 // we actually want the std deviation.
417 var_out[k][0] = sqrtf(var_out[k][0]);
418 var_out[k][1] = sqrtf(var_out[k][1]);
419 }
420
421 // simple bubblesort of clusters in order of ascending weight: just a convenience for the user to keep
422 // cluster display a bit more consistent in GUI
423 for(int i = 0; i < n - 1; i++)
424 {
425 for(int j = 0; j < n - 1 - i; j++)
426 {
427 if(weight_out[j] > weight_out[j + 1])
428 {
429 float2 temp_mean = { mean_out[j + 1][0], mean_out[j + 1][1] };
430 float2 temp_var = { var_out[j + 1][0], var_out[j + 1][1] };
431 float temp_weight = weight_out[j + 1];
432
433 mean_out[j + 1][0] = mean_out[j][0];
434 mean_out[j + 1][1] = mean_out[j][1];
435 var_out[j + 1][0] = var_out[j][0];
436 var_out[j + 1][1] = var_out[j][1];
437 weight_out[j + 1] = weight_out[j];
438
439 mean_out[j][0] = temp_mean[0];
440 mean_out[j][1] = temp_mean[1];
441 var_out[j][0] = temp_var[0];
442 var_out[j][1] = temp_var[1];
443 weight_out[j] = temp_weight;
444 }
445 }
446 }
447}
448
450int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
451 void *const ovoid)
452{
453 const dt_iop_roi_t *const roi_in = &piece->roi_in;
454 const dt_iop_roi_t *const roi_out = &piece->roi_out;
455 dt_iop_colormapping_data_t *const restrict data = (dt_iop_colormapping_data_t *)piece->data;
457 float *const restrict in = (float *)ivoid;
458 float *const restrict out = (float *)ovoid;
459
460 const int width = roi_in->width;
461 const int height = roi_in->height;
462
463 const float scale = dt_dev_get_module_scale(pipe, roi_in);
464 const float sigma_s = 50.0f / scale;
465 const float sigma_r = 8.0f; // does not depend on scale
466
467 // save a copy of preview input buffer so we can get histogram and color statistics out of it
468 if(self->dev->gui_attached && !IS_NULL_PTR(g) && dt_dev_pixelpipe_has_preview_output(self->dev, pipe, roi_out)
469 && (data->flag & ACQUIRE))
470 {
472 dt_free_align(g->buffer);
473 g->buffer = NULL;
474
475 g->buffer = dt_iop_image_alloc(width, height, 4);
476 g->width = width;
477 g->height = height;
478 g->ch = 4;
479
480 if(IS_NULL_PTR(g->buffer))
481 {
483 return 1;
484 }
485
486 dt_iop_image_copy_by_size(g->buffer, in, width, height, 4);
487
489 }
490
491 // process image if all mapping information is present in the parameter set
492 if(data->flag & HAS_TARGET && data->flag & HAS_SOURCE)
493 {
494 // for all pixels: find input cluster, transfer to mapped target cluster and apply histogram
495
496 const float dominance = data->dominance / 100.0f;
497 const float equalization = data->equalization / 100.0f;
498
499 // get mapping from input clusters to target clusters
500 int *const mapio = malloc(sizeof(int) * data->n);
501 if(IS_NULL_PTR(mapio)) return 1;
502
503 get_cluster_mapping(data->n, data->target_mean, data->target_weight, data->source_mean,
504 data->source_weight, dominance, mapio);
505
506 float2 *const var_ratio = malloc(sizeof(float2) * data->n);
507 if(IS_NULL_PTR(var_ratio))
508 {
509 dt_free(mapio);
510 return 1;
511 }
512
513 for(int i = 0; i < data->n; i++)
514 {
515 var_ratio[i][0]
516 = (data->target_var[i][0] > 0.0f) ? data->source_var[mapio[i]][0] / data->target_var[i][0] : 0.0f;
517 var_ratio[i][1]
518 = (data->target_var[i][1] > 0.0f) ? data->source_var[mapio[i]][1] / data->target_var[i][1] : 0.0f;
519 }
520
521 const size_t npixels = (size_t)height * width;
522// first get delta L of equalized L minus original image L, scaled to fit into [0 .. 100]
524 for(size_t k = 0; k < npixels * 4; k += 4)
525 {
526 const float L = in[k];
527 out[k] = 0.5f * ((L * (1.0f - equalization)
528 + data->source_ihist[data->target_hist[(int)CLAMP(
529 HISTN * L / 100.0f, 0.0f, (float)HISTN - 1.0f)]] * equalization) - L) + 50.0f;
530 out[k] = CLAMP(out[k], 0.0f, 100.0f);
531 }
532
533 if(equalization > 0.001f)
534 {
535 // bilateral blur of delta L to avoid artifacts caused by limited histogram resolution
537 if(IS_NULL_PTR(b))
538 {
539 dt_free(var_ratio);
540 dt_free(mapio);
541 return 1;
542 }
545 dt_bilateral_slice(b, out, out, -1.0f);
547 }
548
549 size_t allocsize;
550 float *const weight_buf = dt_pixelpipe_cache_alloc_perthread(data->n, sizeof(float), &allocsize);
551 if(IS_NULL_PTR(weight_buf))
552 {
553 dt_free(var_ratio);
554 dt_free(mapio);
555 return 1;
556 }
558 {
559 // get a thread-private scratch buffer; do this before the actual loop so we don't have to look it up for
560 // every single pixel
561 float *const restrict weight = dt_get_perthread(weight_buf,allocsize);
563 for(size_t j = 0; j < 4*npixels; j += 4)
564 {
565 const float L = in[j];
566 const dt_aligned_pixel_t Lab = { L, in[j + 1], in[j + 2] };
567
568 // transfer back scaled and blurred delta L to output L
569 out[j] = 2.0f * (out[j] - 50.0f) + L;
570 out[j] = CLAMP(out[j], 0.0f, 100.0f);
571
572 get_clusters(in + j, data->n, data->target_mean, weight);
573 // zero the 'a' and 'b' channels
574 out[j + 1] = out[j + 2] = 0.0f;
575 // then accumulate a weighted average for a and b
576 for(int c = 0; c < data->n; c++)
577 {
578 out[j + 1] += weight[c] * ((Lab[1] - data->target_mean[c][0]) * var_ratio[c][0]
579 + data->source_mean[mapio[c]][0]);
580 out[j + 2] += weight[c] * ((Lab[2] - data->target_mean[c][1]) * var_ratio[c][1]
581 + data->source_mean[mapio[c]][1]);
582 }
583 // pass through the alpha channel
584 out[j + 3] = in[j + 3];
585 }
586 }
587
589 dt_free(var_ratio);
590 dt_free(mapio);
591 }
592 // incomplete parameter set -> do nothing
593 else
594 {
596 }
597
598 return 0;
599}
600
601void 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)
602{
603 const dt_iop_roi_t *const roi_in = &piece->roi_in;
604 const float scale = dt_dev_get_module_scale(pipe, roi_in);
605 const float sigma_s = 50.0f / scale;
606 const float sigma_r = 8.0f; // does not depend on scale
607
608 const int width = roi_in->width;
609 const int height = roi_in->height;
610 const int channels = piece->dsc_in.channels;
611
612 const size_t basebuffer = sizeof(float) * channels * width * height;
613
614 tiling->factor = 3.0f + (float)dt_bilateral_memory_use(width, height, sigma_s, sigma_r) / basebuffer;
615 tiling->maxbuf
616 = fmaxf(1.0f, (float)dt_bilateral_singlebuffer_size(width, height, sigma_s, sigma_r) / basebuffer);
617 tiling->overhead = 0;
618 tiling->overlap = ceilf(4 * sigma_s);
619 tiling->xalign = 1;
620 tiling->yalign = 1;
621}
622
631
632void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
633{
636
637 if(w == g->clusters)
638 {
639 // only reset source/target when changing number of clusters
640 memset(p->source_ihist, 0, sizeof(float) * HISTN);
641 memset(p->source_mean, 0, sizeof(float) * MAXN * 2);
642 memset(p->source_var, 0, sizeof(float) * MAXN * 2);
643 memset(p->source_weight, 0, sizeof(float) * MAXN);
644 memset(p->target_hist, 0, sizeof(int) * HISTN);
645 memset(p->target_mean, 0, sizeof(float) * MAXN * 2);
646 memset(p->target_var, 0, sizeof(float) * MAXN * 2);
647 memset(p->target_weight, 0, sizeof(float) * MAXN);
648 p->flag = NEUTRAL;
649 dt_control_queue_redraw_widget(g->source_area);
650 dt_control_queue_redraw_widget(g->target_area);
651 }
652}
653
654static void acquire_source_button_pressed(GtkButton *button, dt_iop_module_t *self)
655{
656 if(darktable.gui->reset) return;
658 p->flag |= ACQUIRE;
659 p->flag |= GET_SOURCE;
660 p->flag &= ~HAS_SOURCE;
663}
664
665static void acquire_target_button_pressed(GtkButton *button, dt_iop_module_t *self)
666{
667 if(darktable.gui->reset) return;
669 p->flag |= ACQUIRE;
670 p->flag |= GET_TARGET;
671 p->flag &= ~HAS_TARGET;
674}
675
681
683{
684 dt_free_align(piece->data);
685 piece->data = NULL;
686}
687
689{
690 dt_iop_colormapping_params_t *d = module->default_params;
691
693 if(module->dev->gui_attached && !IS_NULL_PTR(g) && g->flowback_set)
694 {
695 memcpy(d->source_ihist, g->flowback.hist, sizeof(float) * HISTN);
696 memcpy(d->source_mean, g->flowback.mean, sizeof(float) * MAXN * 2);
697 memcpy(d->source_var, g->flowback.var, sizeof(float) * MAXN * 2);
698 memcpy(d->source_weight, g->flowback.weight, sizeof(float) * MAXN);
699 d->n = g->flowback.n;
700 d->flag = HAS_SOURCE;
701 }
702}
703
704
705static gboolean cluster_preview_draw(GtkWidget *widget, cairo_t *crf, dt_iop_module_t *self)
706{
709
710 float2 *mean;
711 float2 *var;
712
713 if(widget == g->source_area)
714 {
715 mean = p->source_mean;
716 var = p->source_var;
717 }
718 else
719 {
720 mean = p->target_mean;
721 var = p->target_var;
722 }
723
724
725 GtkAllocation allocation;
726 gtk_widget_get_allocation(widget, &allocation);
727 const int inset = 5;
728 int width = allocation.width, height = allocation.height;
729 cairo_surface_t *cst = dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
730 cairo_t *cr = cairo_create(cst);
731 cairo_set_source_rgb(cr, .2, .2, .2);
732 cairo_paint(cr);
733
734 cairo_translate(cr, inset, inset);
735 width -= 2 * inset;
736 height -= 2 * inset;
737
738
739 const float sep = DT_PIXEL_APPLY_DPI(2.0);
740 const float qwd = (width - (p->n - 1) * sep) / (float)p->n;
741 for(int cl = 0; cl < p->n; cl++)
742 {
743 // draw cluster
744 for(int j = -1; j <= 1; j++)
745 for(int i = -1; i <= 1; i++)
746 {
747 // draw 9x9 grid showing mean and variance of this cluster.
748 double rgb[3] = { 0.5, 0.5, 0.5 };
749 cmsCIELab Lab;
750 Lab.L = 53.390011;
751 Lab.a = (mean[cl][0] + i * var[cl][0]);
752 Lab.b = (mean[cl][1] + j * var[cl][1]);
753 cmsDoTransform(g->xform, &Lab, rgb, 1);
754 cairo_set_source_rgb(cr, rgb[0], rgb[1], rgb[2]);
755 cairo_rectangle(cr, qwd * (i + 1) / 3.0, height * (j + 1) / 3.0, qwd / 3.0 - DT_PIXEL_APPLY_DPI(.5),
756 height / 3.0 - DT_PIXEL_APPLY_DPI(.5));
757 cairo_fill(cr);
758 }
759 cairo_translate(cr, qwd + sep, 0);
760 }
761
762 cairo_destroy(cr);
763 cairo_set_source_surface(crf, cst, 0, 0);
764 cairo_paint(crf);
765 cairo_surface_destroy(cst);
766 return TRUE;
767}
768
769
770static void process_clusters(gpointer instance, gpointer user_data)
771{
772 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
775 int new_source_clusters = 0;
776
777 if(IS_NULL_PTR(g) || IS_NULL_PTR(g->buffer)) return;
778 if(!(p->flag & ACQUIRE)) return;
779
781
783 const int width = g->width;
784 const int height = g->height;
785 const int ch = g->ch;
786 float *const restrict buffer = dt_iop_image_alloc(width, height, ch);
787 if(IS_NULL_PTR(buffer))
788 {
790 return;
791 }
792 dt_iop_image_copy_by_size(buffer, g->buffer, width, height, ch);
794
795 if(p->flag & GET_SOURCE)
796 {
797 int hist[HISTN];
798
799 // get histogram of L
800 capture_histogram(buffer, width, height, hist);
801
802 // invert histogram
803 invert_histogram(hist, p->source_ihist);
804
805 // get n color clusters
806 kmeans(buffer, width, height, p->n, p->source_mean, p->source_var, p->source_weight);
807
808 p->flag |= HAS_SOURCE;
809 new_source_clusters = 1;
810
811 dt_control_queue_redraw_widget(g->source_area);
812 }
813 else if(p->flag & GET_TARGET)
814 {
815 // get histogram of L
816 capture_histogram(buffer, width, height, p->target_hist);
817
818 // get n color clusters
819 kmeans(buffer, width, height, p->n, p->target_mean, p->target_var, p->target_weight);
820
821 p->flag |= HAS_TARGET;
822
823 dt_control_queue_redraw_widget(g->target_area);
824 }
825
826 dt_free_align(buffer);
827
828 if(new_source_clusters)
829 {
830 memcpy(g->flowback.hist, p->source_ihist, sizeof(float) * HISTN);
831 memcpy(g->flowback.mean, p->source_mean, sizeof(float) * MAXN * 2);
832 memcpy(g->flowback.var, p->source_var, sizeof(float) * MAXN * 2);
833 memcpy(g->flowback.weight, p->source_weight, sizeof(float) * MAXN);
834 g->flowback.n = p->n;
835 g->flowback_set = 1;
836 FILE *f = g_fopen("/tmp/dt_colormapping_loaded", "wb");
837 if(f)
838 {
839 if(fwrite(&g->flowback, sizeof(g->flowback), 1, f) < 1)
840 fprintf(stderr, "[colormapping] could not write flowback file /tmp/dt_colormapping_loaded\n");
841 fclose(f);
842 }
843 }
844
845 p->flag &= ~(GET_TARGET | GET_SOURCE | ACQUIRE);
847
849
851}
852
853
854void gui_init(struct dt_iop_module_t *self)
855{
857
858 g->flag = NEUTRAL;
859 g->flowback_set = 0;
862 g->xform = cmsCreateTransform(hLab, TYPE_Lab_DBL, hsRGB, TYPE_RGB_DBL, INTENT_PERCEPTUAL, 0);
863 g->buffer = NULL;
864
865 self->widget = GTK_WIDGET(gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING));
866
867 gtk_box_pack_start(GTK_BOX(self->widget), dt_ui_label_new(_("source clusters:")), TRUE, TRUE, 0);
868
869 g->source_area = dtgtk_drawing_area_new_with_aspect_ratio(1.0 / 3.0);
870 gtk_box_pack_start(GTK_BOX(self->widget), g->source_area, TRUE, TRUE, 0);
871 g_signal_connect(G_OBJECT(g->source_area), "draw", G_CALLBACK(cluster_preview_draw), self);
872
873 gtk_box_pack_start(GTK_BOX(self->widget), dt_ui_label_new(_("target clusters:")), TRUE, TRUE, 0);
874
875 g->target_area = dtgtk_drawing_area_new_with_aspect_ratio(1.0 / 3.0);
876 gtk_box_pack_start(GTK_BOX(self->widget), g->target_area, TRUE, TRUE, 0);
877 g_signal_connect(G_OBJECT(g->target_area), "draw", G_CALLBACK(cluster_preview_draw), self);
878
879 GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
880 gtk_box_pack_start(GTK_BOX(self->widget), box, TRUE, TRUE, 0);
881
882 g->acquire_source_button = dt_iop_button_new(self, N_("acquire as source"),
883 G_CALLBACK(acquire_source_button_pressed), FALSE, 0, 0,
884 NULL, 0, box);
885 gtk_label_set_ellipsize(GTK_LABEL(gtk_bin_get_child(GTK_BIN(g->acquire_source_button))), PANGO_ELLIPSIZE_START);
886 gtk_widget_set_tooltip_text(g->acquire_source_button, _("analyze this image as a source image"));
887
888 g->acquire_target_button = dt_iop_button_new(self, N_("acquire as target"),
889 G_CALLBACK(acquire_target_button_pressed), FALSE, 0, 0,
890 NULL, 0, box);
891 gtk_label_set_ellipsize(GTK_LABEL(gtk_bin_get_child(GTK_BIN(g->acquire_target_button))), PANGO_ELLIPSIZE_START);
892 gtk_widget_set_tooltip_text(g->acquire_target_button, _("analyze this image as a target image"));
893
894 g->clusters = dt_bauhaus_slider_from_params(self, "n");
895 gtk_widget_set_tooltip_text(g->clusters, _("number of clusters to find in image. value change resets all clusters"));
896
897 g->dominance = dt_bauhaus_slider_from_params(self, "dominance");
898 gtk_widget_set_tooltip_text(g->dominance, _("how clusters are mapped. low values: based on color "
899 "proximity, high values: based on color dominance"));
900 dt_bauhaus_slider_set_format(g->dominance, "%");
901
902 g->equalization = dt_bauhaus_slider_from_params(self, "equalization");
903 gtk_widget_set_tooltip_text(g->equalization, _("level of histogram equalization"));
904 dt_bauhaus_slider_set_format(g->equalization, "%");
905
906 /* add signal handler for preview pipe finished: process clusters if requested */
908 G_CALLBACK(process_clusters), self);
909
910 FILE *f = g_fopen("/tmp/dt_colormapping_loaded", "rb");
911 if(f)
912 {
913 if(fread(&g->flowback, sizeof(g->flowback), 1, f) > 0) g->flowback_set = 1;
914 fclose(f);
915 }
916}
917
918void gui_cleanup(struct dt_iop_module_t *self)
919{
921
923
924 cmsDeleteTransform(g->xform);
925 dt_free_align(g->buffer);
926 g->buffer = NULL;
927
929}
930
931// clang-format off
932// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
933// vim: shiftwidth=2 expandtab tabstop=2 cindent
934// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
935// 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:3598
void dt_bilateral_free(dt_bilateral_t *b)
Definition bilateral.c:426
__DT_CLONE_TARGETS__ void dt_bilateral_splat(const dt_bilateral_t *b, const float *const in)
Definition bilateral.c:177
size_t dt_bilateral_memory_use(const int width, const int height, const float sigma_s, const float sigma_r)
Definition bilateral.c:74
dt_bilateral_t * dt_bilateral_init(const int width, const int height, const float sigma_s, const float sigma_r)
Definition bilateral.c:151
size_t dt_bilateral_singlebuffer_size(const int width, const int height, const float sigma_s, const float sigma_r)
Definition bilateral.c:102
__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:350
void dt_bilateral_blur(const dt_bilateral_t *b)
Definition bilateral.c:335
int width
Definition bilateral.h:1
float sigma_s
Definition bilateral.h:3
int height
Definition bilateral.h:1
float sigma_r
Definition bilateral.h:3
static const dt_aligned_pixel_simd_t const dt_adaptation_t const float p
@ 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 dt_colorspaces_color_profile_t * dt_colorspaces_get_profile(dt_colorspaces_color_profile_type_t type, const char *filename, dt_colorspaces_profile_direction_t direction)
@ DT_COLORSPACE_SRGB
Definition colorspaces.h:84
@ DT_COLORSPACE_LAB
Definition colorspaces.h:89
@ DT_PROFILE_DIRECTION_IN
@ DT_PROFILE_DIRECTION_ANY
static dt_aligned_pixel_t rgb
const dt_aligned_pixel_t f
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:906
void dt_control_queue_redraw()
request redraw of the workspace. This redraws the whole workspace within a gdk critical section to pr...
Definition control.c:856
darktable_t darktable
Definition darktable.c:181
#define dt_pixelpipe_cache_alloc_perthread(n, objsize, padded_size)
Definition darktable.h:1006
#define dt_free_align(ptr)
Definition darktable.h:481
static void * dt_calloc_align(size_t size)
Definition darktable.h:488
#define __OMP_FOR__(...)
Definition darktable.h:261
#define dt_free(ptr)
Definition darktable.h:456
#define DT_MODULE_INTROSPECTION(MODVER, PARAMSTYPE)
Definition darktable.h:151
#define __OMP_PARALLEL__(...)
Definition darktable.h:257
#define dt_pixelpipe_cache_free_align(mem)
Definition darktable.h:453
#define __DT_CLONE_TARGETS__
Definition darktable.h:367
#define dt_get_perthread(buf, padsize)
Definition darktable.h:1035
#define __OMP_PARALLEL_FOR__(...)
Definition darktable.h:258
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition darktable.h:281
#define dt_dev_add_history_item(dev, module, enable, redraw)
void dt_iop_params_t
Definition dev_history.h:41
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:367
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:30
static cairo_surface_t * dt_cairo_image_surface_create(cairo_format_t format, int width, int height)
Definition gtk.h:316
#define DT_GUI_BOX_SPACING
Definition gtk.h:109
#define DT_PIXEL_APPLY_DPI(value)
Definition gtk.h:90
static GtkWidget * dt_ui_label_new(const gchar *str)
Definition gtk.h:461
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:87
static float *__restrict__ dt_iop_image_alloc(const size_t width, const size_t height, const size_t ch)
Definition imagebuf.h:36
void dt_iop_request_focus(dt_iop_module_t *module)
Definition imageop.c:2169
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:3141
float dt_dev_get_module_scale(const dt_dev_pixelpipe_t *const pipe, const dt_iop_roi_t *const roi_in)
Definition imageop.c:131
#define IOP_GUI_FREE
Definition imageop.h:602
static void dt_iop_gui_enter_critical_section(dt_iop_module_t *const module) ACQUIRE(&module -> gui_lock)
Definition imageop.h:413
@ IOP_FLAGS_DEPRECATED
Definition imageop.h:168
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:167
@ IOP_FLAGS_ONE_INSTANCE
Definition imageop.h:172
static void dt_iop_gui_leave_critical_section(dt_iop_module_t *const module) RELEASE(&module -> gui_lock)
Definition imageop.h:419
@ IOP_GROUP_COLOR
Definition imageop.h:139
#define IOP_GUI_ALLOC(module)
Definition imageop.h:599
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)
Definition imageop_gui.c:77
void *const ovoid
float *const restrict const size_t k
float *const restrict const size_t const size_t ch
float dt_aligned_pixel_t[4]
static float dt_points_get()
Definition points.h:90
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
Definition signal.h:368
@ 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:174
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:357
struct _GtkWidget GtkWidget
Definition splash.h:29
struct dt_gui_gtk_t * gui
Definition darktable.h:775
struct dt_control_signal_t * signals
Definition darktable.h:774
struct dt_develop_t * develop
Definition darktable.h:770
dt_iop_buffer_dsc_t dsc_in
struct dt_iop_module_t *void * data
int32_t gui_attached
Definition develop.h:162
int32_t reset
Definition gtk.h:172
unsigned int channels
Definition format.h:54
dt_iop_colormapping_flowback_t flowback
dt_iop_colormapping_flags_t flag
GtkWidget * widget
Definition imageop.h:337
struct dt_develop_t * dev
Definition imageop.h:296
dt_iop_gui_data_t * gui_data
Definition imageop.h:311
dt_iop_params_t * params
Definition imageop.h:307
Region of interest passed through the pixelpipe.
Definition imageop.h:72