focus.h 12 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
/*
    This file is part of darktable,
    copyright (c) 2013 johannes hanika.
    darktable is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    darktable is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with darktable.  If not, see <http://www.gnu.org/licenses/>.
*/

18
#pragma once
19

Roman Lebedev's avatar
Roman Lebedev committed
20 21
#include "common/image_cache.h"

22 23 24 25 26
typedef struct dt_focus_cluster_t
{
  int64_t n;
  float x, y, x2, y2;
  float thrs;
27
} dt_focus_cluster_t;
28

29
#define gbuf(BUF, A, B) ((BUF)[4 * (width * ((B)) + ((A))) + ch])
30 31 32
#define FOCUS_THRS 10
#define CHANNEL 1

33 34 35 36 37 38 39 40 41 42 43 44
static inline uint8_t _to_uint8(int i)
{
  return (uint8_t)CLAMP(i + 127, 0, 255);
}
static inline int _from_uint8(uint8_t i)
{
  return i - 127;
}
static inline void _dt_focus_cdf22_wtf(uint8_t *buf, const int l, const int width, const int height)
{
  const int ch = CHANNEL;

45 46
  const int step = 1 << l;
  const int st = step / 2;
47 48

#ifdef _OPENMP
49
#pragma omp parallel for default(none) shared(buf) schedule(static)
50
#endif
51
  for(int j = 0; j < height; j++)
52 53 54 55
  {
    // rows
    // predict, get detail
    int i = st;
56 57 58 59 60
    for(; i < width - st; i += step) /*for(ch=0; ch<3; ch++)*/
      gbuf(buf, i, j)
          = _to_uint8((int)gbuf(buf, i, j) - ((int)gbuf(buf, i - st, j) + (int)gbuf(buf, i + st, j)) / 2);
    if(i < width) /*for(ch=0; ch<3; ch++)*/
      gbuf(buf, i, j) = _to_uint8(gbuf(buf, i, j) - gbuf(buf, i - st, j));
61
    // update coarse
62 63 64 65 66
    /*for(ch=0; ch<3; ch++)*/ gbuf(buf, 0, j) += _from_uint8(gbuf(buf, st, j)) / 2;
    for(i = step; i < width - st; i += step) /*for(ch=0; ch<3; ch++)*/
      gbuf(buf, i, j) += (_from_uint8(gbuf(buf, i - st, j)) + _from_uint8(gbuf(buf, i + st, j))) / 4;
    if(i < width) /*for(ch=0; ch<3; ch++)*/
      gbuf(buf, i, j) += _from_uint8(gbuf(buf, i - st, j)) / 2;
67 68
  }
#ifdef _OPENMP
69
#pragma omp parallel for default(none) shared(buf) schedule(static)
70
#endif
71
  for(int i = 0; i < width; i++)
72 73 74 75
  {
    // cols
    int j = st;
    // predict, get detail
76 77 78 79 80
    for(; j < height - st; j += step) /*for(ch=0; ch<3; ch++)*/
      gbuf(buf, i, j)
          = _to_uint8((int)gbuf(buf, i, j) - ((int)gbuf(buf, i, j - st) + (int)gbuf(buf, i, j + st)) / 2);
    if(j < height) /*for(int ch=0; ch<3; ch++)*/
      gbuf(buf, i, j) = _to_uint8((int)gbuf(buf, i, j) - (int)gbuf(buf, i, j - st));
81
    // update
82 83 84 85 86
    /*for(ch=0; ch<3; ch++)*/ gbuf(buf, i, 0) += _from_uint8(gbuf(buf, i, st)) / 2;
    for(j = step; j < height - st; j += step) /*for(ch=0; ch<3; ch++)*/
      gbuf(buf, i, j) += (_from_uint8(gbuf(buf, i, j - st)) + _from_uint8(gbuf(buf, i, j + st))) / 4;
    if(j < height) /*for(int ch=0; ch<3; ch++)*/
      gbuf(buf, i, j) += _from_uint8(gbuf(buf, i, j - st)) / 2;
87 88 89
  }
}

90 91
static void _dt_focus_update(dt_focus_cluster_t *f, int frows, int fcols, int i, int j, int wd, int ht,
                             int diff)
92 93 94 95
{
  const int32_t thrs = FOCUS_THRS;
  if(diff > thrs)
  {
96 97 98
    int fx = i / (float)wd * fcols;
    int fy = j / (float)ht * frows;
    int fi = fcols * fy + fx;
99 100 101 102 103 104 105 106 107 108 109
#ifdef _OPENMP
#pragma omp atomic
#endif
    f[fi].x += i;
#ifdef _OPENMP
#pragma omp atomic
#endif
    f[fi].y += j;
#ifdef _OPENMP
#pragma omp atomic
#endif
110
    f[fi].x2 += (float)i * i;
111 112 113
#ifdef _OPENMP
#pragma omp atomic
#endif
114
    f[fi].y2 += (float)j * j;
115 116 117
#ifdef _OPENMP
#pragma omp atomic
#endif
118
    f[fi].n++;
119 120 121 122 123 124 125 126 127
#ifdef _OPENMP
#pragma omp atomic
#endif
    f[fi].thrs += diff;
  }
}


// read 8-bit buffer and create focus clusters from it
128 129
static void dt_focus_create_clusters(dt_focus_cluster_t *focus, int frows, int fcols, uint8_t *buffer,
                                     int buffer_width, int buffer_height)
130
{
131 132 133
  // mark in-focus pixels:
  const int wd = buffer_width;
  const int ht = buffer_height;
134
  const int fs = frows * fcols;
135
  // two-stage cdf 2/2 wavelet transform, use HH1 and HH2 to detect very sharp and sharp spots:
136 137
  // pretend we already did the first step (coarse will stay in place, maybe even where the pre-demosaic
  // sample was at)
138 139
  _dt_focus_cdf22_wtf(buffer, 2, wd, ht);
  // go through HH1 and detect sharp clusters:
140
  memset(focus, 0, sizeof(dt_focus_cluster_t) * fcols * frows);
141 142 143
#ifdef _OPENMP
#pragma omp parallel for schedule(static) default(shared)
#endif
144 145
  for(int j = 0; j < ht - 1; j += 4)
    for(int i = 0; i < wd - 1; i += 4)
146
    {
147 148 149 150
      _dt_focus_update(focus, frows, fcols, i, j, wd, ht,
                       abs(_from_uint8(buffer[4 * ((j + 2) * wd + i) + CHANNEL])));
      _dt_focus_update(focus, frows, fcols, i, j, wd, ht,
                       abs(_from_uint8(buffer[4 * (j * wd + i + 2) + CHANNEL])));
151
    }
152 153

#if 1 // second pass, HH2
154
  int num_clusters = 0;
155 156
  for(int k = 0; k < fs; k++)
    if(focus[k].n * 4 > wd * ht / (float)fs * 0.01f) num_clusters++;
157 158
  if(num_clusters < 1)
  {
159
    memset(focus, 0, sizeof(dt_focus_cluster_t) * fs);
160
    _dt_focus_cdf22_wtf(buffer, 3, wd, ht);
161 162 163
#ifdef _OPENMP
#pragma omp parallel for schedule(static) default(shared)
#endif
164
    for(int j = 0; j < ht - 1; j += 8)
165
    {
166
      for(int i = 0; i < wd - 1; i += 8)
167
      {
168 169 170 171
        _dt_focus_update(focus, frows, fcols, i, j, wd, ht,
                         1.5 * abs(_from_uint8(buffer[4 * ((j + 4) * wd + i) + CHANNEL])));
        _dt_focus_update(focus, frows, fcols, i, j, wd, ht,
                         1.5 * abs(_from_uint8(buffer[4 * (j * wd + i + 4) + CHANNEL])));
172
      }
173
    }
174
    num_clusters = 0;
175
    for(int k = 0; k < fs; k++)
176
    {
177
      if(focus[k].n * 6.0f > wd * ht / (float)fs * 0.01f)
178 179
      {
        focus[k].n *= -1;
180
        num_clusters++;
181
      }
182
    }
183
  }
184 185 186
#endif
#undef CHANNEL

187
#if 0 // simple high pass filter, doesn't work on slighty unsharp/high iso images
188
  memset(focus, 0, sizeof(dt_focus_cluster_t)*fs);
189 190 191
#ifdef _OPENMP
#pragma omp parallel for schedule(static) default(shared)
#endif
192 193 194 195 196 197 198 199 200 201 202 203 204 205
  for(int j=1;j<ht-1;j++)
  {
    int index = 4*j*wd+4;
    for(int i=1;i<wd-1;i++)
    {
      int32_t diff = 4*buffer[index+1]
        - buffer[index-4+1]
        - buffer[index+4+1]
        - buffer[index-4*wd+1]
        - buffer[index+4*wd+1];
      _dt_focus_update(focus, frows, fcols, i, j, wd, ht, abs(diff));
      index += 4;
    }
  }
206
#endif
207
  // normalize data in clusters:
208
  for(int k = 0; k < fs; k++)
209 210
  {
    focus[k].thrs /= fabsf((float)focus[k].n);
211
    focus[k].x /= fabsf((float)focus[k].n);
212
    focus[k].x2 /= fabsf((float)focus[k].n);
213
    focus[k].y /= fabsf((float)focus[k].n);
214 215
    focus[k].y2 /= fabsf((float)focus[k].n);
  }
216 217
}

218 219
static void dt_focus_draw_clusters(cairo_t *cr, int width, int height, int imgid, int buffer_width,
                                   int buffer_height, dt_focus_cluster_t *focus, int frows, int fcols)
220
{
221
  const int fs = frows * fcols;
222
  cairo_save(cr);
223
  cairo_translate(cr, width / 2.0, height / 2.0f);
224

Roman Lebedev's avatar
Roman Lebedev committed
225 226 227 228 229 230
  const dt_image_t *img = dt_image_cache_get(darktable.image_cache, imgid, 'r');
  dt_image_t image = *img;
  dt_image_cache_read_release(darktable.image_cache, img);

  // FIXME: get those from rawprepare IOP somehow !!!
  int wd = buffer_width + image.crop_x, ht = buffer_height + image.crop_y;
231

232
  // array with cluster positions
233 234
  float *pos = malloc(fs * 6 * sizeof(float));
  float *offx = pos + fs * 2, *offy = pos + fs * 4;
235
  for(int k = 0; k < fs; k++)
236
  {
237 238
    const float stddevx = sqrtf(focus[k].x2 - focus[k].x * focus[k].x);
    const float stddevy = sqrtf(focus[k].y2 - focus[k].y * focus[k].y);
Roman Lebedev's avatar
Roman Lebedev committed
239 240 241

    // FIXME: get those from rawprepare IOP somehow !!!
    float x = focus[k].x + image.crop_x, y = focus[k].y + image.crop_y;
242

243 244 245 246 247 248
    pos[2 * k + 0] = x;
    pos[2 * k + 1] = y;
    offx[2 * k + 0] = x + stddevx;
    offx[2 * k + 1] = y;
    offy[2 * k + 0] = x;
    offy[2 * k + 1] = y + stddevy;
249
  }
250

Roman Lebedev's avatar
Roman Lebedev committed
251
  // could use dt_image_altered() here, but it ignores flip module
252 253 254 255 256 257 258 259 260
  {
    dt_develop_t dev;
    dt_dev_init(&dev, 0);
    dt_dev_load_image(&dev, imgid);
    dt_dev_pixelpipe_t pipe;
    int res = dt_dev_pixelpipe_init_dummy(&pipe, wd, ht);
    if(res)
    {
      // set mem pointer to 0, won't be used.
261
      dt_dev_pixelpipe_set_input(&pipe, &dev, NULL, wd, ht, 1.0f);
262 263
      dt_dev_pixelpipe_create_nodes(&pipe, &dev);
      dt_dev_pixelpipe_synch_all(&pipe, &dev);
264 265 266
      dt_dev_pixelpipe_get_dimensions(&pipe, &dev, pipe.iwidth, pipe.iheight, &pipe.processed_width,
                                      &pipe.processed_height);
      dt_dev_distort_transform_plus(&dev, &pipe, 0, 99999, pos, fs * 3);
267 268 269
      dt_dev_pixelpipe_cleanup(&pipe);
      wd = pipe.processed_width;
      ht = pipe.processed_height;
270
    }
271 272
    dt_dev_cleanup(&dev);
  }
273

274 275
  const int32_t tb = DT_PIXEL_APPLY_DPI(dt_conf_get_int("plugins/darkroom/ui/border_size"));
  const float scale = fminf((width-2*tb) / (float)wd, (height-2*tb) / (float)ht);
276
  cairo_scale(cr, scale, scale);
277

278
  cairo_translate(cr, -wd / 2.0f, -ht / 2.0f);
279

280
  cairo_rectangle(cr, 0, 0, wd, ht);
281 282
  cairo_clip(cr);

283 284
  double dashes[] = { 3 };
  int ndash = sizeof(dashes) / sizeof(dashes[0]);
285
  double offset = 0.0f;
286
  cairo_set_dash(cr, dashes, ndash, offset);
287

288
  // draw clustered focus regions
289
  for(int k = 0; k < fs; k++)
290
  {
291
    const float intens = (focus[k].thrs - FOCUS_THRS) / FOCUS_THRS;
292
    const float col = fminf(1.0f, intens);
293
    int draw = 0;
294
    if(focus[k].n * 4.0f > buffer_width * buffer_height / (float)fs * 0.01f)
295
      draw = 1;
296
    else if(-focus[k].n * 6.0f > buffer_width * buffer_height / (float)fs * 0.01f)
297
      draw = 2;
298 299
    if(draw)
    {
300
      for(int i = 0; i < 2; i++)
301
      {
302 303
        if(i)
        {
304 305 306 307 308
          if(draw == 2)
            cairo_set_source_rgb(cr, .1f, .1f, col);
          else
            cairo_set_source_rgb(cr, col, .1f, .1f);
          cairo_set_dash(cr, dashes, ndash, dashes[0]);
309 310
        }
        else
311
        {
312
          cairo_set_source_rgb(cr, .1f, .1f, .1f);
313
          cairo_set_dash(cr, dashes, ndash, 0);
314
        }
315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333
        cairo_move_to(cr, offx[2 * k + 0], offx[2 * k + 1]);
        cairo_curve_to(cr, -pos[2 * k + 0] + offx[2 * k + 0] + offy[2 * k + 0],
                       -pos[2 * k + 1] + offx[2 * k + 1] + offy[2 * k + 1],
                       -pos[2 * k + 0] + offx[2 * k + 0] + offy[2 * k + 0],
                       -pos[2 * k + 1] + offx[2 * k + 1] + offy[2 * k + 1], offy[2 * k + 0], offy[2 * k + 1]);
        cairo_curve_to(cr, pos[2 * k + 0] - offx[2 * k + 0] + offy[2 * k + 0],
                       pos[2 * k + 1] - offx[2 * k + 1] + offy[2 * k + 1],
                       pos[2 * k + 0] - offx[2 * k + 0] + offy[2 * k + 0],
                       pos[2 * k + 1] - offx[2 * k + 1] + offy[2 * k + 1],
                       2 * pos[2 * k + 0] - offx[2 * k + 0], 2 * pos[2 * k + 1] - offx[2 * k + 1]);
        cairo_curve_to(cr, 3 * pos[2 * k + 0] - offx[2 * k + 0] - offy[2 * k + 0],
                       3 * pos[2 * k + 1] - offx[2 * k + 1] - offy[2 * k + 1],
                       3 * pos[2 * k + 0] - offx[2 * k + 0] - offy[2 * k + 0],
                       3 * pos[2 * k + 1] - offx[2 * k + 1] - offy[2 * k + 1],
                       2 * pos[2 * k + 0] - offy[2 * k + 0], 2 * pos[2 * k + 1] - offy[2 * k + 1]);
        cairo_curve_to(cr, pos[2 * k + 0] + offx[2 * k + 0] - offy[2 * k + 0],
                       pos[2 * k + 1] + offx[2 * k + 1] - offy[2 * k + 1],
                       pos[2 * k + 0] + offx[2 * k + 0] - offy[2 * k + 0],
                       pos[2 * k + 1] + offx[2 * k + 1] - offy[2 * k + 1], offx[2 * k + 0], offx[2 * k + 1]);
334 335

        cairo_save(cr);
336
        cairo_scale(cr, 1. / scale, 1. / scale);
337
        cairo_set_line_width(cr, 2.0f);
338
        cairo_stroke(cr);
339
        cairo_restore(cr);
340 341
      }
    }
342 343
  }
  cairo_restore(cr);
344
  free(pos);
345
}
346 347 348
#undef CHANNEL
#undef gbuf
#undef FOCUS_THRS
349

Tobias Ellinghaus's avatar
Tobias Ellinghaus committed
350 351 352
// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
// vim: shiftwidth=2 expandtab tabstop=2 cindent
// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;