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