clahe.c 10.7 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
/*
    This file is part of darktable,
    copyright (c) 2010 Henrik Andersson.

    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/>.
*/
#ifdef HAVE_CONFIG_H
19
#include "config.h"
20
#endif
21
#include "bauhaus/bauhaus.h"
22
#include "common/colorspaces.h"
23 24
#include "common/darktable.h"
#include "control/control.h"
25 26
#include "develop/develop.h"
#include "develop/imageop.h"
27
#include "dtgtk/resetlabel.h"
28
#include "gui/gtk.h"
29 30
#include "iop/iop_api.h"
#include <assert.h>
31 32
#include <gtk/gtk.h>
#include <inttypes.h>
33
#include <math.h>
34
#include <stdlib.h>
35
#include <string.h>
36

37
#define CLIP(x) ((x < 0) ? 0.0 : (x > 1.0) ? 1.0 : x)
38 39 40 41 42 43 44 45 46

#define ROUND_POSISTIVE(f) ((unsigned int)((f)+0.5))

DT_MODULE(1)

typedef struct dt_iop_rlce_params_t
{
  double radius;
  double slope;
47
} dt_iop_rlce_params_t;
48 49 50

typedef struct dt_iop_rlce_gui_data_t
{
51
  GtkBox *vbox1, *vbox2;
52
  GtkWidget *label1, *label2;
53
  GtkWidget *scale1, *scale2; // radie pixels, slope
54
} dt_iop_rlce_gui_data_t;
55 56 57 58 59

typedef struct dt_iop_rlce_data_t
{
  double radius;
  double slope;
60
} dt_iop_rlce_data_t;
61 62 63

const char *name()
{
64
  return _("local contrast");
65 66
}

67
int groups()
68
{
69
  return IOP_GROUP_EFFECT;
70 71
}

72 73
int flags()
{
74
  return IOP_FLAGS_INCLUDE_IN_STYLES | IOP_FLAGS_DEPRECATED;
75
}
76

77 78
void process(struct dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
             void *const ovoid, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
79 80
{
  dt_iop_rlce_data_t *data = (dt_iop_rlce_data_t *)piece->data;
81
  const int ch = piece->colors;
82

83
  // PASS1: Get a luminance map of image...
84 85
  float *luminance = (float *)malloc(((size_t)roi_out->width * roi_out->height) * sizeof(float));
// double lsmax=0.0,lsmin=1.0;
86
#ifdef _OPENMP
87
#pragma omp parallel for default(none) schedule(static) shared(luminance)
88
#endif
89
  for(int j = 0; j < roi_out->height; j++)
90
  {
91 92 93
    float *in = (float *)ivoid + (size_t)j * roi_out->width * ch;
    float *lm = luminance + (size_t)j * roi_out->width;
    for(int i = 0; i < roi_out->width; i++)
94
    {
95 96 97 98
      double pmax = CLIP(fmax(in[0], fmax(in[1], in[2]))); // Max value in RGB set
      double pmin = CLIP(fmin(in[0], fmin(in[1], in[2]))); // Min value in RGB set
      *lm = (pmax + pmin) / 2.0;                           // Pixel luminocity
      in += ch;
99
      lm++;
100
    }
101
  }
102 103


104
  // Params
105
  const int rad = data->radius * roi_in->scale / piece->iscale;
106

107 108
#define BINS (256)

109
  const float slope = data->slope;
110

111 112 113
  const size_t destbuf_size = roi_out->width;
  float *const dest_buf = malloc(destbuf_size * sizeof(float) * dt_get_num_threads());

114
// CLAHE
115
#ifdef _OPENMP
116
#pragma omp parallel for default(none) schedule(static) shared(luminance)
117
#endif
118
  for(int j = 0; j < roi_out->height; j++)
119
  {
120 121
    int yMin = fmax(0, j - rad);
    int yMax = fmin(roi_in->height, j + rad + 1);
122 123
    int h = yMax - yMin;

124 125
    int xMin0 = fmax(0, 0 - rad);
    int xMax0 = fmin(roi_in->width - 1, rad);
126

127 128
    int hist[BINS + 1];
    int clippedhist[BINS + 1];
129 130

    float *dest = dest_buf + destbuf_size * dt_get_thread_num();
131

132
    /* initially fill histogram */
133
    memset(hist, 0, (BINS + 1) * sizeof(int));
134 135
    for(int yi = yMin; yi < yMax; ++yi)
      for(int xi = xMin0; xi < xMax0; ++xi)
136
        ++hist[ROUND_POSISTIVE(luminance[(size_t)yi * roi_in->width + xi] * (float)BINS)];
137 138

    // Destination row
139 140
    memset(dest, 0, roi_out->width * sizeof(float));
    float *ld = dest;
141

142
    for(int i = 0; i < roi_out->width; i++)
143
    {
144

145
      int v = ROUND_POSISTIVE(luminance[(size_t)j * roi_in->width + i] * (float)BINS);
146

147
      int xMin = fmax(0, i - rad);
148
      int xMax = i + rad + 1;
149
      int w = fmin(roi_in->width, xMax) - xMin;
150 151
      int n = h * w;

152
      int limit = (int)(slope * n / BINS + 0.5f);
153

154
      /* remove left behind values from histogram */
155
      if(xMin > 0)
156 157
      {
        int xMin1 = xMin - 1;
158
        for(int yi = yMin; yi < yMax; ++yi)
159
          --hist[ROUND_POSISTIVE(luminance[(size_t)yi * roi_in->width + xMin1] * (float)BINS)];
160 161 162
      }

      /* add newly included values to histogram */
163
      if(xMax <= roi_in->width)
164 165
      {
        int xMax1 = xMax - 1;
166
        for(int yi = yMin; yi < yMax; ++yi)
167
          ++hist[ROUND_POSISTIVE(luminance[(size_t)yi * roi_in->width + xMax1] * (float)BINS)];
168 169 170
      }

      /* clip histogram and redistribute clipped entries */
171
      memcpy(clippedhist, hist, (BINS + 1) * sizeof(int));
172
      int ce = 0, ceb = 0;
173 174 175 176
      do
      {
        ceb = ce;
        ce = 0;
177
        for(int b = 0; b <= BINS; b++)
178
        {
179 180
          int d = clippedhist[b] - limit;
          if(d > 0)
181 182
          {
            ce += d;
183
            clippedhist[b] = limit;
184 185
          }
        }
186

187 188 189
        int d = (ce / (float)(BINS + 1));
        int m = ce % (BINS + 1);
        for(int b = 0; b <= BINS; b++) clippedhist[b] += d;
190

191
        if(m != 0)
192
        {
193 194
          int s = BINS / (float)m;
          for(int b = 0; b <= BINS; b += s) ++clippedhist[b];
195
        }
196
      } while(ce != ceb);
197

198
      /* build cdf of clipped histogram */
199
      unsigned int hMin = BINS;
200 201
      for(int b = 0; b < hMin; b++)
        if(clippedhist[b] != 0) hMin = b;
202 203

      int cdf = 0;
204
      for(int b = hMin; b <= v; b++) cdf += clippedhist[b];
205 206

      int cdfMax = cdf;
207
      for(int b = v + 1; b <= BINS; b++) cdfMax += clippedhist[b];
208

209
      int cdfMin = clippedhist[hMin];
210

211
      *ld = (cdf - cdfMin) / (float)(cdfMax - cdfMin);
212

213
      ld++;
214
    }
215

216
    // Apply row
217 218 219
    float *in = ((float *)ivoid) + (size_t)j * roi_out->width * ch;
    float *out = ((float *)ovoid) + (size_t)j * roi_out->width * ch;
    for(int r = 0; r < roi_out->width; r++)
220
    {
221
      float H, S, L;
222 223 224
      rgb2hsl(in, &H, &S, &L);
      // hsl2rgb(out,H,S,( L / dest[r] ) * (L-lsmin) + lsmin );
      hsl2rgb(out, H, S, dest[r]);
225 226 227
      out += ch;
      in += ch;
      ld++;
228 229
    }
  }
230

231 232
  free(dest_buf);

233 234
  // Cleanup
  free(luminance);
235 236

#undef BINS
237 238
}

239
static void radius_callback(GtkWidget *slider, gpointer user_data)
240 241 242 243
{
  dt_iop_module_t *self = (dt_iop_module_t *)user_data;
  if(self->dt->gui->reset) return;
  dt_iop_rlce_params_t *p = (dt_iop_rlce_params_t *)self->params;
244
  p->radius = dt_bauhaus_slider_get(slider);
245
  dt_dev_add_history_item(darktable.develop, self, TRUE);
246 247
}

248
static void slope_callback(GtkWidget *slider, gpointer user_data)
249 250 251 252
{
  dt_iop_module_t *self = (dt_iop_module_t *)user_data;
  if(self->dt->gui->reset) return;
  dt_iop_rlce_params_t *p = (dt_iop_rlce_params_t *)self->params;
253
  p->slope = dt_bauhaus_slider_get(slider);
254
  dt_dev_add_history_item(darktable.develop, self, TRUE);
255 256 257 258
}



259 260
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)
261 262 263
{
  dt_iop_rlce_params_t *p = (dt_iop_rlce_params_t *)p1;
  dt_iop_rlce_data_t *d = (dt_iop_rlce_data_t *)piece->data;
264

265 266 267 268
  d->radius = p->radius;
  d->slope = p->slope;
}

269
void init_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
270
{
271
  piece->data = calloc(1, sizeof(dt_iop_rlce_data_t));
272 273 274
  self->commit_params(self, self->default_params, pipe, piece);
}

275
void cleanup_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
276 277
{
  free(piece->data);
278
  piece->data = NULL;
279 280 281 282 283 284 285
}

void gui_update(struct dt_iop_module_t *self)
{
  dt_iop_module_t *module = (dt_iop_module_t *)self;
  dt_iop_rlce_gui_data_t *g = (dt_iop_rlce_gui_data_t *)self->gui_data;
  dt_iop_rlce_params_t *p = (dt_iop_rlce_params_t *)module->params;
286 287
  dt_bauhaus_slider_set(g->scale1, p->radius);
  dt_bauhaus_slider_set(g->scale2, p->slope);
288 289 290 291
}

void init(dt_iop_module_t *module)
{
292 293
  module->params = calloc(1, sizeof(dt_iop_rlce_params_t));
  module->default_params = calloc(1, sizeof(dt_iop_rlce_params_t));
294
  module->default_enabled = 0;
Heiko Bauke's avatar
Heiko Bauke committed
295
  module->priority = 897; // module order created by iop_dependencies.py, do not edit!
296 297
  module->params_size = sizeof(dt_iop_rlce_params_t);
  module->gui_data = NULL;
298
  dt_iop_rlce_params_t tmp = (dt_iop_rlce_params_t){ 64, 1.25 };
299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314
  memcpy(module->params, &tmp, sizeof(dt_iop_rlce_params_t));
  memcpy(module->default_params, &tmp, sizeof(dt_iop_rlce_params_t));
}

void cleanup(dt_iop_module_t *module)
{
  free(module->params);
  module->params = NULL;
}

void gui_init(struct dt_iop_module_t *self)
{
  self->gui_data = malloc(sizeof(dt_iop_rlce_gui_data_t));
  dt_iop_rlce_gui_data_t *g = (dt_iop_rlce_gui_data_t *)self->gui_data;
  dt_iop_rlce_params_t *p = (dt_iop_rlce_params_t *)self->params;

315
  self->widget = GTK_WIDGET(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0));
316 317
  g->vbox1 = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_IOP_MODULE_CONTROL_SPACING));
  g->vbox2 = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_IOP_MODULE_CONTROL_SPACING));
318 319
  gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(g->vbox1), FALSE, FALSE, 5);
  gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(g->vbox2), TRUE, TRUE, 5);
320 321 322 323 324

  g->label1 = dtgtk_reset_label_new(_("radius"), self, &p->radius, sizeof(float));
  gtk_box_pack_start(GTK_BOX(g->vbox1), g->label1, TRUE, TRUE, 0);
  g->label2 = dtgtk_reset_label_new(_("amount"), self, &p->slope, sizeof(float));
  gtk_box_pack_start(GTK_BOX(g->vbox1), g->label2, TRUE, TRUE, 0);
325

326 327 328 329
  g->scale1 = dt_bauhaus_slider_new_with_range(NULL, 0.0, 256.0, 1.0,
                                               p->radius, 0);
  g->scale2 = dt_bauhaus_slider_new_with_range(NULL, 1.0, 3.0, 0.05,
                                               p->slope, 2);
330
  // dtgtk_slider_set_format_type(g->scale2,DARKTABLE_SLIDER_FORMAT_PERCENT);
331

332 333
  gtk_box_pack_start(GTK_BOX(g->vbox2), GTK_WIDGET(g->scale1), TRUE, TRUE, 0);
  gtk_box_pack_start(GTK_BOX(g->vbox2), GTK_WIDGET(g->scale2), TRUE, TRUE, 0);
334 335
  gtk_widget_set_tooltip_text(GTK_WIDGET(g->scale1), _("size of features to preserve"));
  gtk_widget_set_tooltip_text(GTK_WIDGET(g->scale2), _("strength of the effect"));
336

337 338
  g_signal_connect(G_OBJECT(g->scale1), "value-changed", G_CALLBACK(radius_callback), self);
  g_signal_connect(G_OBJECT(g->scale2), "value-changed", G_CALLBACK(slope_callback), self);
339 340 341 342 343 344 345 346
}

void gui_cleanup(struct dt_iop_module_t *self)
{
  free(self->gui_data);
  self->gui_data = NULL;
}

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