Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
focus.h
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2013-2014 johannes hanika.
4 Copyright (C) 2013-2016 Tobias Ellinghaus.
5 Copyright (C) 2014 Pedro Côrte-Real.
6 Copyright (C) 2014-2016 Roman Lebedev.
7 Copyright (C) 2018 Edgardo Hoszowski.
8 Copyright (C) 2019 Aldric Renaudin.
9 Copyright (C) 2019 Andreas Schneider.
10 Copyright (C) 2019, 2023-2026 Aurélien PIERRE.
11 Copyright (C) 2019 luzpaz.
12 Copyright (C) 2020 Hanno Schwalm.
13 Copyright (C) 2020 Pascal Obry.
14 Copyright (C) 2022 Martin Bařinka.
15 Copyright (C) 2026 Guillaume Stutin.
16
17 darktable is free software: you can redistribute it and/or modify
18 it under the terms of the GNU General Public License as published by
19 the Free Software Foundation, either version 3 of the License, or
20 (at your option) any later version.
21
22 darktable is distributed in the hope that it will be useful,
23 but WITHOUT ANY WARRANTY; without even the implied warranty of
24 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 GNU General Public License for more details.
26
27 You should have received a copy of the GNU General Public License
28 along with darktable. If not, see <http://www.gnu.org/licenses/>.
29*/
30
31#ifndef DT_GUI_DTGTK_FOCUS_H
32#define DT_GUI_DTGTK_FOCUS_H
33
34#include "gui/application.h"
35#include "caches/image_cache.h"
36#include "develop/develop.h"
37
38typedef struct dt_focus_cluster_t
39{
40 int64_t n;
41 float x, y, x2, y2;
42 float thrs;
44
45#define gbuf(BUF, A, B) ((BUF)[4 * (width * ((B)) + ((A))) + ch])
46#define FOCUS_THRS 10
47#define CHANNEL 1
48
49static inline uint8_t _to_uint8(int i)
50{
51 return (uint8_t)CLAMP(i + 127, 0, 255);
52}
53static inline int _from_uint8(uint8_t i)
54{
55 return i - 127;
56}
57static inline void _dt_focus_cdf22_wtf(uint8_t *buf, const int l, const int width, const int height)
58{
59 const int ch = CHANNEL;
60
61 const int step = 1 << l;
62 const int st = step / 2;
64 for(int j = 0; j < height; j++)
65 {
66 // rows
67 // predict, get detail
68 int i = st;
69 for(; i < width - st; i += step) /*for(ch=0; ch<3; ch++)*/
70 gbuf(buf, i, j)
71 = _to_uint8((int)gbuf(buf, i, j) - ((int)gbuf(buf, i - st, j) + (int)gbuf(buf, i + st, j)) / 2);
72 if(i < width) /*for(ch=0; ch<3; ch++)*/
73 gbuf(buf, i, j) = _to_uint8(gbuf(buf, i, j) - gbuf(buf, i - st, j));
74 // update coarse
75 /*for(ch=0; ch<3; ch++)*/ gbuf(buf, 0, j) += _from_uint8(gbuf(buf, st, j)) / 2;
76 for(i = step; i < width - st; i += step) /*for(ch=0; ch<3; ch++)*/
77 gbuf(buf, i, j) += (_from_uint8(gbuf(buf, i - st, j)) + _from_uint8(gbuf(buf, i + st, j))) / 4;
78 if(i < width) /*for(ch=0; ch<3; ch++)*/
79 gbuf(buf, i, j) += _from_uint8(gbuf(buf, i - st, j)) / 2;
80 }
82 for(int i = 0; i < width; i++)
83 {
84 // cols
85 int j = st;
86 // predict, get detail
87 for(; j < height - st; j += step) /*for(ch=0; ch<3; ch++)*/
88 gbuf(buf, i, j)
89 = _to_uint8((int)gbuf(buf, i, j) - ((int)gbuf(buf, i, j - st) + (int)gbuf(buf, i, j + st)) / 2);
90 if(j < height) /*for(int ch=0; ch<3; ch++)*/
91 gbuf(buf, i, j) = _to_uint8((int)gbuf(buf, i, j) - (int)gbuf(buf, i, j - st));
92 // update
93 /*for(ch=0; ch<3; ch++)*/ gbuf(buf, i, 0) += _from_uint8(gbuf(buf, i, st)) / 2;
94 for(j = step; j < height - st; j += step) /*for(ch=0; ch<3; ch++)*/
95 gbuf(buf, i, j) += (_from_uint8(gbuf(buf, i, j - st)) + _from_uint8(gbuf(buf, i, j + st))) / 4;
96 if(j < height) /*for(int ch=0; ch<3; ch++)*/
97 gbuf(buf, i, j) += _from_uint8(gbuf(buf, i, j - st)) / 2;
98 }
99}
100
101static void _dt_focus_update(dt_focus_cluster_t *f, int frows, int fcols, int i, int j, int wd, int ht,
102 int diff)
103{
104 const int32_t thrs = FOCUS_THRS;
105 if(diff > thrs)
106 {
107 int fx = i / (float)wd * fcols;
108 int fy = j / (float)ht * frows;
109 int fi = fcols * fy + fx;
110#ifdef _OPENMP
111#pragma omp atomic
112#endif
113 f[fi].x += i;
114#ifdef _OPENMP
115#pragma omp atomic
116#endif
117 f[fi].y += j;
118#ifdef _OPENMP
119#pragma omp atomic
120#endif
121 f[fi].x2 += (float)i * i;
122#ifdef _OPENMP
123#pragma omp atomic
124#endif
125 f[fi].y2 += (float)j * j;
126#ifdef _OPENMP
127#pragma omp atomic
128#endif
129 f[fi].n++;
130#ifdef _OPENMP
131#pragma omp atomic
132#endif
133 f[fi].thrs += diff;
134 }
135}
136
137
138// read 8-bit buffer and create focus clusters from it
139static void dt_focus_create_clusters(dt_focus_cluster_t *focus, int frows, int fcols, uint8_t *buffer,
140 int buffer_width, int buffer_height)
141{
142 // mark in-focus pixels:
143 const int wd = buffer_width;
144 const int ht = buffer_height;
145 const int fs = frows * fcols;
146 // two-stage cdf 2/2 wavelet transform, use HH1 and HH2 to detect very sharp and sharp spots:
147 // pretend we already did the first step (coarse will stay in place, maybe even where the pre-demosaic
148 // sample was at)
149 _dt_focus_cdf22_wtf(buffer, 2, wd, ht);
150 // go through HH1 and detect sharp clusters:
151 memset(focus, 0, sizeof(dt_focus_cluster_t) * fcols * frows);
152#ifdef _OPENMP
153#pragma omp parallel for default(shared)
154#endif
155 for(int j = 0; j < ht - 1; j += 4)
156 for(int i = 0; i < wd - 1; i += 4)
157 {
158 _dt_focus_update(focus, frows, fcols, i, j, wd, ht,
159 abs(_from_uint8(buffer[4 * ((j + 2) * wd + i) + CHANNEL])));
160 _dt_focus_update(focus, frows, fcols, i, j, wd, ht,
161 abs(_from_uint8(buffer[4 * (j * wd + i + 2) + CHANNEL])));
162 }
163
164#if 1 // second pass, HH2
165 int num_clusters = 0;
166 for(int k = 0; k < fs; k++)
167 if(focus[k].n * 4 > wd * ht / (float)fs * 0.01f) num_clusters++;
168 if(num_clusters < 1)
169 {
170 memset(focus, 0, sizeof(dt_focus_cluster_t) * fs);
171 _dt_focus_cdf22_wtf(buffer, 3, wd, ht);
172#ifdef _OPENMP
173#pragma omp parallel for default(shared)
174#endif
175 for(int j = 0; j < ht - 1; j += 8)
176 {
177 for(int i = 0; i < wd - 1; i += 8)
178 {
179 _dt_focus_update(focus, frows, fcols, i, j, wd, ht,
180 1.5 * abs(_from_uint8(buffer[4 * ((j + 4) * wd + i) + CHANNEL])));
181 _dt_focus_update(focus, frows, fcols, i, j, wd, ht,
182 1.5 * abs(_from_uint8(buffer[4 * (j * wd + i + 4) + CHANNEL])));
183 }
184 }
185 num_clusters = 0;
186 for(int k = 0; k < fs; k++)
187 {
188 if(focus[k].n * 6.0f > wd * ht / (float)fs * 0.01f)
189 {
190 focus[k].n *= -1;
191 num_clusters++;
192 }
193 }
194 }
195#endif
196#undef CHANNEL
197
198#if 0 // simple high pass filter, doesn't work on slightly unsharp/high iso images
199 memset(focus, 0, sizeof(dt_focus_cluster_t)*fs);
200#ifdef _OPENMP
201#pragma omp parallel for default(shared)
202#endif
203 for(int j=1;j<ht-1;j++)
204 {
205 int index = 4*j*wd+4;
206 for(int i=1;i<wd-1;i++)
207 {
208 int32_t diff = 4*buffer[index+1]
209 - buffer[index-4+1]
210 - buffer[index+4+1]
211 - buffer[index-4*wd+1]
212 - buffer[index+4*wd+1];
213 _dt_focus_update(focus, frows, fcols, i, j, wd, ht, abs(diff));
214 index += 4;
215 }
216 }
217#endif
218 // normalize data in clusters:
219 for(int k = 0; k < fs; k++)
220 {
221 focus[k].thrs /= fabsf((float)focus[k].n);
222 focus[k].x /= fabsf((float)focus[k].n);
223 focus[k].x2 /= fabsf((float)focus[k].n);
224 focus[k].y /= fabsf((float)focus[k].n);
225 focus[k].y2 /= fabsf((float)focus[k].n);
226 }
227}
228
229static void dt_focus_draw_clusters(cairo_t *cr, int width, int height, int32_t imgid, int buffer_width,
230 int buffer_height, dt_focus_cluster_t *focus, int frows, int fcols,
231 float full_zoom, float full_x, float full_y)
232{
233 const dt_image_t *img = dt_image_cache_get(imgid, 'r');
234 if(IS_NULL_PTR(img)) return;
235 dt_image_t image = *img;
237
238 const int fs = frows * fcols;
239 cairo_save(cr);
240 cairo_translate(cr, width / 2.0, height / 2.0f);
241
242 // FIXME: get those from rawprepare IOP somehow !!!
243 int wd = buffer_width + image.crop_x;
244 int ht = buffer_height + image.crop_y;
245
246 // array with cluster positions
247 float *pos = malloc(fs * 6 * sizeof(float));
248 float *offx = pos + fs * 2, *offy = pos + fs * 4;
249
250 for(int k = 0; k < fs; k++)
251 {
252 const float stddevx = sqrtf(focus[k].x2 - focus[k].x * focus[k].x);
253 const float stddevy = sqrtf(focus[k].y2 - focus[k].y * focus[k].y);
254
255 // FIXME: get those from rawprepare IOP somehow !!!
256 const float x = focus[k].x + image.crop_x;
257 const float y = focus[k].y + image.crop_y;
258
259 pos[2 * k + 0] = x;
260 pos[2 * k + 1] = y;
261 offx[2 * k + 0] = x + stddevx;
262 offx[2 * k + 1] = y;
263 offy[2 * k + 0] = x;
264 offy[2 * k + 1] = y + stddevy;
265 }
266
267 // could use dt_image_altered() here, but it ignores flip module
268 {
269 dt_develop_t dev;
270 dt_dev_init(&dev, 0);
271 dt_dev_load_image(&dev, imgid);
273 const int res = dt_dev_pixelpipe_init_dummy(&pipe, &dev);
274 if(res)
275 {
276 // set mem pointer to 0, won't be used.
286 }
287 dt_dev_cleanup(&dev);
288 }
289
290 // Reads the DARKROOM's border size from a lighttable thumbnail render job, on a control-job
291 // thread, through the global dev. Wrong on two axes -- a view that is not darkroom, a thread
292 // that is not the GUI's -- and flagged as such in doc/develop-split.md; kept behaviour-
293 // identical here because changing which border a thumbnail overlay uses is a visual change,
294 // not a relocation.
296 const float scale = fminf((width - 2 * tb) / (float)wd, (height - 2 * tb) / (float)ht) * full_zoom;
297 cairo_scale(cr, scale, scale);
298 float fx = 0.0f;
299 float fy = 0.0f;
300 if(full_zoom > 1.0f)
301 {
302 // we want to be sure the image stay in the window
303 fx = fminf((wd * scale - width) / 2, fabsf(full_x));
304 if(full_x < 0) fx = -fx;
305 if(wd * scale <= width) fx = 0;
306 fy = fminf((ht * scale - height) / 2, fabsf(full_y));
307 if(full_y < 0) fy = -fy;
308 if(ht * scale <= height) fy = 0;
309 }
310
311 cairo_translate(cr, -wd / 2.0f + fx / scale * dt_gui_get_global()->ppd, -ht / 2.0f + fy / scale * dt_gui_get_global()->ppd);
312
313 cairo_rectangle(cr, 0, 0, wd, ht);
314 cairo_clip(cr);
315
316 double dashes[] = { DT_PIXEL_APPLY_DPI(5.), DT_PIXEL_APPLY_DPI(5.) };
317 const int ndash = sizeof(dashes) / sizeof(dashes[0]);
318 double offset = 0.0f;
319 cairo_set_dash(cr, dashes, ndash, offset);
320
321 // draw clustered focus regions
322 for(int k = 0; k < fs; k++)
323 {
324 const float intens = (focus[k].thrs - FOCUS_THRS) / FOCUS_THRS;
325 const float col = fminf(1.0f, intens);
326 int draw = 0;
327 if(focus[k].n * 4.0f > buffer_width * buffer_height / (float)fs * 0.01f)
328 draw = 1;
329 else if(-focus[k].n * 6.0f > buffer_width * buffer_height / (float)fs * 0.01f)
330 draw = 2;
331 if(draw)
332 {
333 for(int i = 0; i < 2; i++)
334 {
335 if(i)
336 {
337 if(draw == 2)
338 cairo_set_source_rgb(cr, .1f, .1f, col);
339 else
340 cairo_set_source_rgb(cr, col, .1f, .1f);
341 cairo_set_dash(cr, dashes, ndash, dashes[0]);
342 }
343 else
344 {
345 cairo_set_source_rgb(cr, .1f, .1f, .1f);
346 cairo_set_dash(cr, dashes, ndash, 0);
347 }
348 cairo_move_to(cr, offx[2 * k + 0], offx[2 * k + 1]);
349 cairo_curve_to(cr, -pos[2 * k + 0] + offx[2 * k + 0] + offy[2 * k + 0],
350 -pos[2 * k + 1] + offx[2 * k + 1] + offy[2 * k + 1],
351 -pos[2 * k + 0] + offx[2 * k + 0] + offy[2 * k + 0],
352 -pos[2 * k + 1] + offx[2 * k + 1] + offy[2 * k + 1], offy[2 * k + 0], offy[2 * k + 1]);
353 cairo_curve_to(cr, pos[2 * k + 0] - offx[2 * k + 0] + offy[2 * k + 0],
354 pos[2 * k + 1] - offx[2 * k + 1] + offy[2 * k + 1],
355 pos[2 * k + 0] - offx[2 * k + 0] + offy[2 * k + 0],
356 pos[2 * k + 1] - offx[2 * k + 1] + offy[2 * k + 1],
357 2 * pos[2 * k + 0] - offx[2 * k + 0], 2 * pos[2 * k + 1] - offx[2 * k + 1]);
358 cairo_curve_to(cr, 3 * pos[2 * k + 0] - offx[2 * k + 0] - offy[2 * k + 0],
359 3 * pos[2 * k + 1] - offx[2 * k + 1] - offy[2 * k + 1],
360 3 * pos[2 * k + 0] - offx[2 * k + 0] - offy[2 * k + 0],
361 3 * pos[2 * k + 1] - offx[2 * k + 1] - offy[2 * k + 1],
362 2 * pos[2 * k + 0] - offy[2 * k + 0], 2 * pos[2 * k + 1] - offy[2 * k + 1]);
363 cairo_curve_to(cr, pos[2 * k + 0] + offx[2 * k + 0] - offy[2 * k + 0],
364 pos[2 * k + 1] + offx[2 * k + 1] - offy[2 * k + 1],
365 pos[2 * k + 0] + offx[2 * k + 0] - offy[2 * k + 0],
366 pos[2 * k + 1] + offx[2 * k + 1] - offy[2 * k + 1], offx[2 * k + 0], offx[2 * k + 1]);
367
368 cairo_save(cr);
369 cairo_scale(cr, 1. / scale, 1. / scale);
370 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2));
371 cairo_stroke(cr);
372 cairo_restore(cr);
373 }
374 }
375 }
376 cairo_restore(cr);
377 dt_free(pos);
378}
379#undef CHANNEL
380#undef gbuf
381#undef FOCUS_THRS
382
383#endif // DT_GUI_DTGTK_FOCUS_H
384
385// clang-format off
386// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
387// vim: shiftwidth=2 expandtab tabstop=2 cindent
388// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
389// clang-format on
const double thrs
static const float x
const float f
struct dt_develop_t * dt_dev_get_global(void)
Definition darktable.c:531
struct dt_gui_gtk_t * dt_gui_get_global(void)
Definition darktable.c:526
void dt_dev_pixelpipe_get_roi_out(dt_dev_pixelpipe_t *pipe, const int width_in, const int height_in, int *width, int *height)
int32_t dt_dev_viewport_border_size(const dt_develop_t *dev)
void dt_dev_cleanup(dt_develop_t *dev)
Definition develop.c:240
int dt_dev_distort_transform_plus(const dt_dev_pixelpipe_t *pipe, const double iop_order, const int transf_direction, float *points, size_t points_count)
Definition develop.c:1864
dt_dev_image_storage_t dt_dev_load_image(dt_develop_t *dev, const int32_t imgid)
Definition develop.c:1054
void dt_dev_init(dt_develop_t *dev, int32_t gui_attached)
Definition develop.c:146
@ DT_DEV_TRANSFORM_DIR_ALL
Definition develop.h:107
const int res
Definition dtpthread.h:351
static void dt_focus_draw_clusters(cairo_t *cr, int width, int height, int32_t imgid, int buffer_width, int buffer_height, dt_focus_cluster_t *focus, int frows, int fcols, float full_zoom, float full_x, float full_y)
Definition focus.h:229
static void dt_focus_create_clusters(dt_focus_cluster_t *focus, int frows, int fcols, uint8_t *buffer, int buffer_width, int buffer_height)
Definition focus.h:139
#define gbuf(BUF, A, B)
Definition focus.h:45
static void _dt_focus_cdf22_wtf(uint8_t *buf, const int l, const int width, const int height)
Definition focus.h:57
static void _dt_focus_update(dt_focus_cluster_t *f, int frows, int fcols, int i, int j, int wd, int ht, int diff)
Definition focus.h:101
#define CHANNEL
Definition focus.h:47
#define FOCUS_THRS
Definition focus.h:46
static int _from_uint8(uint8_t i)
Definition focus.h:53
static uint8_t _to_uint8(int i)
Definition focus.h:49
#define UNKNOWN_IMAGE
Definition image.h:78
dt_image_t * dt_image_cache_get(const int32_t imgid, char mode)
void dt_image_cache_read_release(const dt_image_t *img)
static const dt_dev_pixelpipe_t * pipe
Definition layers.h:49
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(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
@ DT_MIPMAP_NONE
#define __OMP_PARALLEL_FOR__(...)
Definition openmp.h:95
void dt_dev_pixelpipe_set_input(dt_dev_pixelpipe_t *pipe, int32_t imgid, int width, int height, float iscale, dt_mipmap_size_t size)
int dt_dev_pixelpipe_init_dummy(dt_dev_pixelpipe_t *pipe, dt_develop_t *dev)
void dt_dev_pixelpipe_create_nodes(dt_dev_pixelpipe_t *pipe)
void dt_dev_pixelpipe_cleanup(dt_dev_pixelpipe_t *pipe)
#define dt_dev_pixelpipe_synch_all(pipe)
int32_t crop_y
Definition image.h:407
int32_t crop_x
Definition image.h:407
#define DT_PIXEL_APPLY_DPI(value)