Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
highpass.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2011-2012 Henrik Andersson.
4 Copyright (C) 2011-2013, 2016 johannes hanika.
5 Copyright (C) 2011 Robert Bieber.
6 Copyright (C) 2011-2012, 2014, 2016-2017 Ulrich Pegelow.
7 Copyright (C) 2012 Richard Wonka.
8 Copyright (C) 2012-2014, 2016, 2019 Tobias Ellinghaus.
9 Copyright (C) 2013-2016 Roman Lebedev.
10 Copyright (C) 2013 Simon Spannagel.
11 Copyright (C) 2015 Pedro Côrte-Real.
12 Copyright (C) 2017 Heiko Bauke.
13 Copyright (C) 2018, 2020, 2022-2023, 2025-2026 Aurélien PIERRE.
14 Copyright (C) 2018 Edgardo Hoszowski.
15 Copyright (C) 2018 Maurizio Paglia.
16 Copyright (C) 2018-2020, 2022 Pascal Obry.
17 Copyright (C) 2018 rawfiner.
18 Copyright (C) 2019 Andreas Schneider.
19 Copyright (C) 2019 Diederik ter Rahe.
20 Copyright (C) 2020 Aldric Renaudin.
21 Copyright (C) 2020, 2022 Diederik Ter Rahe.
22 Copyright (C) 2020 Hubert Kowalski.
23 Copyright (C) 2020 Ralf Brown.
24 Copyright (C) 2022 Hanno Schwalm.
25 Copyright (C) 2022 Martin Bařinka.
26 Copyright (C) 2022 Philipp Lutz.
27
28 darktable is free software: you can redistribute it and/or modify
29 it under the terms of the GNU General Public License as published by
30 the Free Software Foundation, either version 3 of the License, or
31 (at your option) any later version.
32
33 darktable is distributed in the hope that it will be useful,
34 but WITHOUT ANY WARRANTY; without even the implied warranty of
35 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
36 GNU General Public License for more details.
37
38 You should have received a copy of the GNU General Public License
39 along with darktable. If not, see <http://www.gnu.org/licenses/>.
40*/
41#ifdef HAVE_CONFIG_H
42#include "system/macros.h"
43#include "system/mem_alloc.h"
45#include "common/logging.h"
46#include "system/openmp.h"
48#include "config.h"
49#endif
50#include <assert.h>
51#include <stdlib.h>
52#include <string.h>
53
54#include "widgets/bauhaus.h"
55#include "pixel/box_filters.h"
56#include "math/math.h"
57#include "common/opencl.h"
58#include "develop/develop.h"
59#include "develop/imageop.h"
60#include "develop/imageop_gui.h"
61#include "develop/tiling.h"
62
63#include "iop/iop_api.h"
64#include <gtk/gtk.h>
65#include <inttypes.h>
66
67#define MAX_RADIUS 16
68
70
72{
73 float sharpness; // $MIN: 0.0 $MAX: 100.0 $DEFAULT: 50.0
74 float contrast; // $MIN: 0.0 $MAX: 100.0 $DEFAULT: 50.0 $DESCRIPTION: "contrast boost"
76
81
87
95
96
97const char *name()
98{
99 return _("highpass");
100}
101
102const char **description(struct dt_iop_module_t *self)
103{
104 return dt_iop_set_description(self, _("isolate high frequencies in the image"),
105 _("creative"),
106 _("linear or non-linear, Lab, scene-referred"),
107 _("frequential, Lab"),
108 _("special, Lab, scene-referred"));
109}
110
115
117{
118 return IOP_GROUP_EFFECTS;
119}
120
122{
123 return IOP_CS_LAB;
124}
125
126void 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)
127{
128 (void)self;
129 (void)pipe;
130 const dt_iop_roi_t *const roi_in = &piece->roi_in;
132
133 const int rad = MAX_RADIUS * (fmin(100.0f, d->sharpness + 1) / 100.0f);
134 const int radius = MIN(MAX_RADIUS, ceilf(rad * roi_in->scale));
135
136 const float sigma = sqrtf((radius * (radius + 1) * BOX_ITERATIONS + 2) / 3.0f);
137 const int wdh = ceilf(3.0f * sigma);
138
139 tiling->factor = 2.1f; // in + out + small slice for box_mean
140 tiling->factor_cl = 3.0f; // in + out + tmp
141 tiling->maxbuf = 1.0f;
142 tiling->overhead = 0;
143 tiling->overlap = wdh;
144 tiling->xalign = 1;
145 tiling->yalign = 1;
146 return;
147}
148
149
150#ifdef HAVE_OPENCL
151int process_cl(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, cl_mem dev_in, cl_mem dev_out)
152{
153 const dt_iop_roi_t *const roi_in = &piece->roi_in;
156
157 cl_int err = -999;
158 cl_mem dev_tmp = NULL;
159 cl_mem dev_m = NULL;
160
161 const int devid = pipe->devid;
162 const int width = roi_in->width;
163 const int height = roi_in->height;
164
165 const int rad = MAX_RADIUS * (fmin(100.0f, d->sharpness + 1) / 100.0f);
166 const int radius = MIN(MAX_RADIUS, ceilf(rad * roi_in->scale));
167
168 /* sigma-radius correlation to match opencl vs. non-opencl. identified by numerical experiments but
169 * unproven. ask me if you need details. ulrich */
170 const float sigma = sqrtf((radius * (radius + 1) * BOX_ITERATIONS + 2) / 3.0f);
171 const int wdh = ceilf(3.0f * sigma);
172 const int wd = 2 * wdh + 1;
173 const size_t mat_size = (size_t)wd * sizeof(float);
174 float *mat = malloc(mat_size);
175 float *m = mat + wdh;
176 float weight = 0.0f;
177
178 // init gaussian kernel
179 for(int l = -wdh; l <= wdh; l++) weight += m[l] = expf(-(l * l) / (2.f * sigma * sigma));
180 for(int l = -wdh; l <= wdh; l++) m[l] /= weight;
181
182 // for(int l=-wdh; l<=wdh; l++) printf("%.6f ", (double)m[l]);
183 // printf("\n");
184
185 float contrast_scale = ((d->contrast / 100.0f) * 7.5f);
186
187 int hblocksize;
189 = (dt_opencl_local_buffer_t){ .xoffset = 2 * wdh, .xfactor = 1, .yoffset = 0, .yfactor = 1,
190 .cellsize = sizeof(float), .overhead = 0,
191 .sizex = 1 << 16, .sizey = 1 };
192
193 if(dt_opencl_local_buffer_opt(devid, gd->kernel_highpass_hblur, &hlocopt))
194 hblocksize = hlocopt.sizex;
195 else
196 hblocksize = 1;
197
198 int vblocksize;
200 = (dt_opencl_local_buffer_t){ .xoffset = 1, .xfactor = 1, .yoffset = 2 * wdh, .yfactor = 1,
201 .cellsize = sizeof(float), .overhead = 0,
202 .sizex = 1, .sizey = 1 << 16 };
203
204 if(dt_opencl_local_buffer_opt(devid, gd->kernel_highpass_vblur, &vlocopt))
205 vblocksize = vlocopt.sizey;
206 else
207 vblocksize = 1;
208
209
210 const size_t bwidth = ROUNDUP(width, hblocksize);
211 const size_t bheight = ROUNDUP(height, vblocksize);
212
213 size_t sizes[3];
214 size_t local[3];
215
216 dev_tmp = dt_opencl_alloc_device(devid, width, height, sizeof(float) * 4);
217 if(IS_NULL_PTR(dev_tmp)) goto error;
218
219 dev_m = dt_opencl_copy_host_to_device_constant(devid, mat_size, mat);
220 if(IS_NULL_PTR(dev_m)) goto error;
221
222 /* invert image */
223 sizes[0] = ROUNDUPDWD(width, devid);
224 sizes[1] = ROUNDUPDHT(height, devid);
225 sizes[2] = 1;
226 dt_opencl_set_kernel_arg(devid, gd->kernel_highpass_invert, 0, sizeof(cl_mem), (void *)&dev_in);
227 dt_opencl_set_kernel_arg(devid, gd->kernel_highpass_invert, 1, sizeof(cl_mem), (void *)&dev_tmp);
228 dt_opencl_set_kernel_arg(devid, gd->kernel_highpass_invert, 2, sizeof(int), (void *)&width);
229 dt_opencl_set_kernel_arg(devid, gd->kernel_highpass_invert, 3, sizeof(int), (void *)&height);
231 if(err != CL_SUCCESS) goto error;
232
233 if(rad != 0)
234 {
235 /* horizontal blur */
236 sizes[0] = bwidth;
237 sizes[1] = ROUNDUPDHT(height, devid);
238 sizes[2] = 1;
239 local[0] = hblocksize;
240 local[1] = 1;
241 local[2] = 1;
242 dt_opencl_set_kernel_arg(devid, gd->kernel_highpass_hblur, 0, sizeof(cl_mem), (void *)&dev_tmp);
243 dt_opencl_set_kernel_arg(devid, gd->kernel_highpass_hblur, 1, sizeof(cl_mem), (void *)&dev_out);
244 dt_opencl_set_kernel_arg(devid, gd->kernel_highpass_hblur, 2, sizeof(cl_mem), (void *)&dev_m);
245 dt_opencl_set_kernel_arg(devid, gd->kernel_highpass_hblur, 3, sizeof(int), (void *)&wdh);
246 dt_opencl_set_kernel_arg(devid, gd->kernel_highpass_hblur, 4, sizeof(int), (void *)&width);
247 dt_opencl_set_kernel_arg(devid, gd->kernel_highpass_hblur, 5, sizeof(int), (void *)&height);
248 dt_opencl_set_kernel_arg(devid, gd->kernel_highpass_hblur, 6, sizeof(int), (void *)&hblocksize);
249 dt_opencl_set_kernel_arg(devid, gd->kernel_highpass_hblur, 7, (hblocksize + 2 * wdh) * sizeof(float), NULL);
251 if(err != CL_SUCCESS) goto error;
252
253
254 /* vertical blur */
255 sizes[0] = ROUNDUPDWD(width, devid);
256 sizes[1] = bheight;
257 sizes[2] = 1;
258 local[0] = 1;
259 local[1] = vblocksize;
260 local[2] = 1;
261 dt_opencl_set_kernel_arg(devid, gd->kernel_highpass_vblur, 0, sizeof(cl_mem), (void *)&dev_out);
262 dt_opencl_set_kernel_arg(devid, gd->kernel_highpass_vblur, 1, sizeof(cl_mem), (void *)&dev_tmp);
263 dt_opencl_set_kernel_arg(devid, gd->kernel_highpass_vblur, 2, sizeof(cl_mem), (void *)&dev_m);
264 dt_opencl_set_kernel_arg(devid, gd->kernel_highpass_vblur, 3, sizeof(int), (void *)&wdh);
265 dt_opencl_set_kernel_arg(devid, gd->kernel_highpass_vblur, 4, sizeof(int), (void *)&width);
266 dt_opencl_set_kernel_arg(devid, gd->kernel_highpass_vblur, 5, sizeof(int), (void *)&height);
267 dt_opencl_set_kernel_arg(devid, gd->kernel_highpass_vblur, 6, sizeof(int), (void *)&vblocksize);
268 dt_opencl_set_kernel_arg(devid, gd->kernel_highpass_vblur, 7, (vblocksize + 2 * wdh) * sizeof(float), NULL);
270 if(err != CL_SUCCESS) goto error;
271 }
272
273 /* mixing tmp and in -> out */
274 sizes[0] = ROUNDUPDWD(width, devid);
275 sizes[1] = ROUNDUPDHT(height, devid);
276 sizes[2] = 1;
277 dt_opencl_set_kernel_arg(devid, gd->kernel_highpass_mix, 0, sizeof(cl_mem), (void *)&dev_in);
278 dt_opencl_set_kernel_arg(devid, gd->kernel_highpass_mix, 1, sizeof(cl_mem), (void *)&dev_tmp);
279 dt_opencl_set_kernel_arg(devid, gd->kernel_highpass_mix, 2, sizeof(cl_mem), (void *)&dev_out);
280 dt_opencl_set_kernel_arg(devid, gd->kernel_highpass_mix, 3, sizeof(int), (void *)&width);
281 dt_opencl_set_kernel_arg(devid, gd->kernel_highpass_mix, 4, sizeof(int), (void *)&height);
282 dt_opencl_set_kernel_arg(devid, gd->kernel_highpass_mix, 5, sizeof(float), (void *)&contrast_scale);
283 err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_highpass_mix, sizes);
284 if(err != CL_SUCCESS) goto error;
285
288 dt_free(mat);
289 return TRUE;
290
291error:
294 dt_free(mat);
295 dt_print(DT_DEBUG_OPENCL, "[opencl_highpass] couldn't enqueue kernel! %d\n", err);
296 return FALSE;
297}
298#endif
299
301int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
302 void *const ovoid)
303{
304 const dt_iop_roi_t *const roi_in = &piece->roi_in;
305 const dt_iop_roi_t *const roi_out = &piece->roi_out;
307 const float *const in = (float *)ivoid;
308 float *out = (float *)ovoid;
309 const int ch = 4;
310
311/* create inverted image and then blur */
312/* since we use only the L channel, pack the values together instead of every fourth float */
313/* to reduce cache pressure and memory bandwidth during the blur operation */
314 const size_t npixels = (size_t)roi_out->height * roi_out->width;
316 for(size_t k = 0; k < (size_t)npixels; k++)
317 out[k] = 100.0f - LCLIP(in[4 * k]); // only L in Lab space
318
319 const int rad = MAX_RADIUS * (fmin(100.0, data->sharpness + 1) / 100.0);
320 const int radius = MIN(MAX_RADIUS, ceilf(rad * roi_in->scale));
321
322 /* horizontal blur out into out */
323 const int range = 2 * radius + 1;
324 const int hr = range / 2;
325
326 if(dt_box_mean(out, roi_out->height, roi_out->width, 1, hr, BOX_ITERATIONS) != 0)
327 {
328 return 1;
329 }
330
331 const float contrast_scale = ((data->contrast / 100.0) * 7.5);
332 /* Blend the inverted blurred L channel with the original input. Because we packed the L values */
333 /* and are inserting the result in the same buffer containing the L values, we need to work in */
334 /* reverse order */
335 /* We can only do the final 3/4 in parallel here, because updating the first quarter in one thread */
336 /* would clobber values still needed by other threads. */
338 for(size_t k = npixels - 1; k > npixels/4; k--)
339 {
340 size_t index = ch * k;
341 // Mix out and in
342 const float L = out[k] * 0.5 + in[index] * 0.5;
343 out[index] = LCLIP(50.0f + ((L - 50.0f) * contrast_scale));
344 out[index + 1] = out[index + 2] = 0.0f; // desaturate a and b in Lab space
345 out[index + 3] = in[index + 3]; // copy the alpha channel in case it is in use
346 }
347 /* process the final quarter of the pixels */
348 for(ssize_t k = npixels/4; k >= 0; k--)
349 {
350 size_t index = ch * k;
351 // Mix out and in
352 const float L = out[k] * 0.5 + in[index] * 0.5;
353 out[index] = LCLIP(50.0f + ((L - 50.0f) * contrast_scale));
354 out[index + 1] = out[index + 2] = 0.0f; // desaturate a and b in Lab space
355 out[index + 3] = in[index + 3]; // copy the alpha channel in case it is in use
356 }
357
358 return 0;
359}
360
363{
366
367 d->sharpness = p->sharpness;
368 d->contrast = p->contrast;
369}
370
372{
374 piece->data_size = sizeof(dt_iop_highpass_data_t);
375}
376
378{
379 dt_free_align(piece->data);
380 piece->data = NULL;
381}
382
384{
385 const int program = 4; // highpass.cl, from programs.conf
388 module->data = gd;
389 gd->kernel_highpass_invert = dt_opencl_create_kernel(program, "highpass_invert");
390 gd->kernel_highpass_hblur = dt_opencl_create_kernel(program, "highpass_hblur");
391 gd->kernel_highpass_vblur = dt_opencl_create_kernel(program, "highpass_vblur");
392 gd->kernel_highpass_mix = dt_opencl_create_kernel(program, "highpass_mix");
393}
394
404
405
406void gui_init(struct dt_iop_module_t *self)
407{
409
410 g->sharpness = dt_bauhaus_slider_from_params(self, N_("sharpness"));
411 dt_bauhaus_slider_set_format(g->sharpness, "%");
412 gtk_widget_set_tooltip_text(g->sharpness, _("the sharpness of highpass filter"));
413
414 g->contrast = dt_bauhaus_slider_from_params(self, "contrast");
415 dt_bauhaus_slider_set_format(g->contrast, "%");
416 gtk_widget_set_tooltip_text(g->contrast, _("the contrast of highpass filter"));
417}
418// clang-format off
419// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
420// vim: shiftwidth=2 expandtab tabstop=2 cindent
421// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
422// clang-format on
static void error(char *msg)
Definition ashift_lsd.c:202
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
#define m
Definition basecurve.c:283
void dt_bauhaus_slider_set_format(GtkWidget *widget, const char *format)
Definition bauhaus.c:3407
int dt_box_mean(float *const buf, const size_t height, const size_t width, const int ch, const int radius, const unsigned iterations)
#define BOX_ITERATIONS
Definition box_filters.h:33
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
@ IOP_CS_LAB
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
const dt_colormatrix_t dt_aligned_pixel_t out
void dt_iop_params_t
Definition dev_history.h:43
static void weight(const float *c1, const float *c2, const float sharpen, dt_aligned_pixel_t weight)
Definition eaw.c:29
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)
Definition highpass.c:361
const char ** description(struct dt_iop_module_t *self)
Definition highpass.c:102
int default_group()
Definition highpass.c:116
__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)
Definition highpass.c:301
void init_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition highpass.c:371
const char * name()
Definition highpass.c:97
#define MAX_RADIUS
Definition highpass.c:67
void gui_init(struct dt_iop_module_t *self)
Definition highpass.c:406
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)
Definition highpass.c:126
void cleanup_global(dt_iop_module_so_t *module)
Definition highpass.c:395
int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
Definition highpass.c:121
int flags()
Definition highpass.c:111
void cleanup_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition highpass.c:377
void init_global(dt_iop_module_so_t *module)
Definition highpass.c:383
int process_cl(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, cl_mem dev_in, cl_mem dev_out)
Definition highpass.c:151
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
@ IOP_FLAGS_INCLUDE_IN_STYLES
Definition imageop.h:185
@ IOP_FLAGS_DEPRECATED
Definition imageop.h:187
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:186
@ IOP_FLAGS_ALLOW_TILING
Definition imageop.h:188
@ IOP_GROUP_EFFECTS
Definition imageop.h:161
GtkWidget * dt_bauhaus_slider_from_params(dt_iop_module_t *self, const char *param)
#define IOP_GUI_ALLOC(module)
Definition imageop_gui.h:93
void *const ovoid
@ DT_DEBUG_OPENCL
Definition logging.h:57
void dt_print(dt_debug_thread_t thread, const char *msg,...) __attribute__((format(printf
Print to stdout when thread is enabled, prefixed with seconds since startup.
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 LCLIP(x)
Definition math.h:87
#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.
int dt_opencl_local_buffer_opt(const int devid, const int kernel, dt_opencl_local_buffer_t *factors)
Definition opencl.c:3713
int dt_opencl_enqueue_kernel_2d(const int dev, const int kernel, const size_t *sizes)
Definition opencl.c:2554
void * dt_opencl_alloc_device(const int devid, const int width, const int height, const int bpp)
Definition opencl.c:2894
int dt_opencl_create_kernel(const int prog, const char *name)
Definition opencl.c:2448
void * dt_opencl_copy_host_to_device_constant(const int devid, const size_t size, void *host)
Definition opencl.c:2750
void dt_opencl_free_kernel(const int kernel)
Definition opencl.c:2491
int dt_opencl_set_kernel_arg(const int dev, const int kernel, const int num, const size_t size, const void *arg)
Definition opencl.c:2545
int dt_opencl_enqueue_kernel_2d_with_local(const int dev, const int kernel, const size_t *sizes, const size_t *local)
Definition opencl.c:2560
void dt_opencl_release_mem_object(cl_mem mem)
Definition opencl.c:2805
#define ROUNDUP(a, n)
Definition opencl.h:82
#define ROUNDUPDHT(a, b)
Definition opencl.h:86
#define ROUNDUPDWD(a, b)
Definition opencl.h:85
#define __OMP_PARALLEL_FOR__(...)
Definition openmp.h:95
const float sigma
struct dt_iop_module_t *void * data
dt_iop_global_data_t * data
Definition imageop.h:238
dt_iop_global_data_t * global_data
Definition imageop.h:337
Region of interest passed through the pixelpipe.
Definition format.h:49
double scale
Definition format.h:51
int width
Definition format.h:50
int height
Definition format.h:50
#define __DT_CLONE_TARGETS__
#define MIN(a, b)
Definition thinplate.c:32