lowlight.c 30.8 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
/*
    This file is part of darktable,
    copyright (c) 2011 Rostyslav Pidgornyi

    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
#include "config.h"
#endif
21
#include "bauhaus/bauhaus.h"
22
#include "common/colorspaces_inline_conversions.h"
23 24
#include "common/darktable.h"
#include "common/debug.h"
25
#include "common/opencl.h"
26
#include "control/conf.h"
27 28
#include "control/control.h"
#include "develop/develop.h"
29
#include "dtgtk/drawingarea.h"
30
#include "gui/accelerators.h"
31
#include "gui/draw.h"
32
#include "gui/gtk.h"
33
#include "gui/presets.h"
34 35 36 37 38
#include "iop/iop_api.h"
#include <inttypes.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
39

40
DT_MODULE_INTROSPECTION(1, dt_iop_lowlight_params_t)
41

42
#define DT_IOP_LOWLIGHT_INSET DT_PIXEL_APPLY_DPI(5)
43 44 45 46 47 48 49 50
#define DT_IOP_LOWLIGHT_RES 64
#define DT_IOP_LOWLIGHT_BANDS 6
#define DT_IOP_LOWLIGHT_LUT_RES 0x10000

typedef struct dt_iop_lowlight_params_t
{
  float blueness;
  float transition_x[DT_IOP_LOWLIGHT_BANDS], transition_y[DT_IOP_LOWLIGHT_BANDS];
51
} dt_iop_lowlight_params_t;
52 53 54

typedef struct dt_iop_lowlight_gui_data_t
{
55
  dt_draw_curve_t *transition_curve; // curve for gui to draw
56

57
  GtkWidget *scale_blueness;
58 59 60 61 62 63 64 65 66
  GtkDrawingArea *area;
  double mouse_x, mouse_y, mouse_pick;
  float mouse_radius;
  dt_iop_lowlight_params_t drag_params;
  int dragging;
  int x_move;
  float draw_xs[DT_IOP_LOWLIGHT_RES], draw_ys[DT_IOP_LOWLIGHT_RES];
  float draw_min_xs[DT_IOP_LOWLIGHT_RES], draw_min_ys[DT_IOP_LOWLIGHT_RES];
  float draw_max_xs[DT_IOP_LOWLIGHT_RES], draw_max_ys[DT_IOP_LOWLIGHT_RES];
67
} dt_iop_lowlight_gui_data_t;
68 69 70 71 72 73

typedef struct dt_iop_lowlight_data_t
{
  float blueness;
  dt_draw_curve_t *curve;
  float lut[DT_IOP_LOWLIGHT_LUT_RES];
74
} dt_iop_lowlight_data_t;
75

76 77 78
typedef struct dt_iop_lowlight_global_data_t
{
  int kernel_lowlight;
79
} dt_iop_lowlight_global_data_t;
80 81


82
const char *name()
83 84 85 86 87 88
{
  return _("lowlight vision");
}

int flags()
{
89
  return IOP_FLAGS_INCLUDE_IN_STYLES | IOP_FLAGS_SUPPORTS_BLENDING | IOP_FLAGS_ALLOW_TILING;
90 91
}

92
int groups()
93 94 95 96 97
{
  return IOP_GROUP_EFFECT;
}


98
void init_key_accels(dt_iop_module_so_t *self)
99
{
100
  dt_accel_register_slider_iop(self, FALSE, NC_("accel", "blue shift"));
101
}
102 103 104

void connect_key_accels(dt_iop_module_t *self)
{
105
  dt_iop_lowlight_gui_data_t *g = (dt_iop_lowlight_gui_data_t *)self->gui_data;
106
  dt_accel_connect_slider_iop(self, "blue shift", g->scale_blueness);
107 108
}

109
static float lookup(const float *lut, const float i)
110
{
111 112
  const int bin0 = MIN(0xffff, MAX(0, DT_IOP_LOWLIGHT_LUT_RES * i));
  const int bin1 = MIN(0xffff, MAX(0, DT_IOP_LOWLIGHT_LUT_RES * i + 1));
113
  const float f = DT_IOP_LOWLIGHT_LUT_RES * i - bin0;
114
  return lut[bin1] * f + lut[bin0] * (1. - f);
115 116
}

117 118
void process(struct dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, const void *const i, void *const o,
             const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
119 120 121 122 123 124
{
  dt_iop_lowlight_data_t *d = (dt_iop_lowlight_data_t *)(piece->data);
  const int ch = piece->colors;

  // empiric coefficient
  const float c = 0.5f;
125
  const float threshold = 0.01f;
126 127

  // scotopic white, blue saturated
128
  float Lab_sw[3] = { 100.0f, 0, -d->blueness };
129 130 131 132 133
  float XYZ_sw[3];

  dt_Lab_to_XYZ(Lab_sw, XYZ_sw);

#ifdef _OPENMP
134
#pragma omp parallel for default(none) schedule(static) shared(d, XYZ_sw)
135
#endif
136
  for(size_t k = 0; k < (size_t)roi_out->width * roi_out->height; k++)
137
  {
138 139
    float *in = (float *)i + ch * k;
    float *out = (float *)o + ch * k;
140 141 142 143 144 145
    float XYZ[3], XYZ_s[3];
    float V;
    float w;

    dt_Lab_to_XYZ(in, XYZ);

146
    // calculate scotopic luminance
147
    if(XYZ[0] > threshold)
148
    {
Henrik Andersson's avatar
Henrik Andersson committed
149
      // normal flow
150
      V = XYZ[1] * (1.33f * (1.0f + (XYZ[1] + XYZ[2]) / XYZ[0]) - 1.68f);
151 152 153
    }
    else
    {
Henrik Andersson's avatar
Henrik Andersson committed
154
      // low red flow, avoids "snow" on dark noisy areas
155
      V = XYZ[1] * (1.33f * (1.0f + (XYZ[1] + XYZ[2]) / threshold) - 1.68f);
156 157 158
    }

    // scale using empiric coefficient and fit inside limits
159
    V = fminf(1.0f, fmaxf(0.0f, c * V));
160 161

    // blending coefficient from curve
162
    w = lookup(d->lut, in[0] / 100.f);
163 164 165 166 167 168 169

    XYZ_s[0] = V * XYZ_sw[0];
    XYZ_s[1] = V * XYZ_sw[1];
    XYZ_s[2] = V * XYZ_sw[2];

    XYZ[0] = w * XYZ[0] + (1.0f - w) * XYZ_s[0];
    XYZ[1] = w * XYZ[1] + (1.0f - w) * XYZ_s[1];
Henrik Andersson's avatar
Henrik Andersson committed
170 171
    XYZ[2] = w * XYZ[2] + (1.0f - w) * XYZ_s[2];

172
    dt_XYZ_to_Lab(XYZ, out);
173 174

    out[3] = in[3];
175 176 177
  }
}

178
#ifdef HAVE_OPENCL
179
int process_cl(struct dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, cl_mem dev_in, cl_mem dev_out,
180
               const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
181 182 183 184 185 186 187 188 189 190 191 192
{
  dt_iop_lowlight_data_t *d = (dt_iop_lowlight_data_t *)piece->data;
  dt_iop_lowlight_global_data_t *gd = (dt_iop_lowlight_global_data_t *)self->data;

  cl_mem dev_m = NULL;
  cl_int err = -999;
  const int devid = piece->pipe->devid;

  const int width = roi_out->width;
  const int height = roi_out->height;

  // scotopic white, blue saturated
193
  float Lab_sw[3] = { 100.0f, 0.0f, -d->blueness };
194 195 196 197 198
  float XYZ_sw[4];

  dt_Lab_to_XYZ(Lab_sw, XYZ_sw);

  dev_m = dt_opencl_copy_host_to_device(devid, d->lut, 256, 256, sizeof(float));
199
  if(dev_m == NULL) goto error;
200 201 202 203 204 205

  size_t sizes[2] = { ROUNDUPWD(width), ROUNDUPHT(height) };
  dt_opencl_set_kernel_arg(devid, gd->kernel_lowlight, 0, sizeof(cl_mem), &dev_in);
  dt_opencl_set_kernel_arg(devid, gd->kernel_lowlight, 1, sizeof(cl_mem), &dev_out);
  dt_opencl_set_kernel_arg(devid, gd->kernel_lowlight, 2, sizeof(int), &width);
  dt_opencl_set_kernel_arg(devid, gd->kernel_lowlight, 3, sizeof(int), &height);
206
  dt_opencl_set_kernel_arg(devid, gd->kernel_lowlight, 4, 4 * sizeof(float), &XYZ_sw);
207 208 209 210 211 212 213 214
  dt_opencl_set_kernel_arg(devid, gd->kernel_lowlight, 5, sizeof(cl_mem), &dev_m);
  err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_lowlight, sizes);
  if(err != CL_SUCCESS) goto error;

  dt_opencl_release_mem_object(dev_m);
  return TRUE;

error:
215
  dt_opencl_release_mem_object(dev_m);
216 217 218 219 220 221 222 223 224
  dt_print(DT_DEBUG_OPENCL, "[opencl_lowlight] couldn't enqueue kernel! %d\n", err);
  return FALSE;
}
#endif


void init_global(dt_iop_module_so_t *module)
{
  const int program = 2; // basic.cl from programs.conf
225 226
  dt_iop_lowlight_global_data_t *gd
      = (dt_iop_lowlight_global_data_t *)malloc(sizeof(dt_iop_lowlight_global_data_t));
227 228 229 230 231 232 233 234 235 236 237 238 239 240
  module->data = gd;
  gd->kernel_lowlight = dt_opencl_create_kernel(program, "lowlight");
}


void cleanup_global(dt_iop_module_so_t *module)
{
  dt_iop_lowlight_global_data_t *gd = (dt_iop_lowlight_global_data_t *)module->data;
  dt_opencl_free_kernel(gd->kernel_lowlight);
  free(module->data);
  module->data = NULL;
}


241 242
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)
243 244 245
{
  dt_iop_lowlight_data_t *d = (dt_iop_lowlight_data_t *)(piece->data);
  dt_iop_lowlight_params_t *p = (dt_iop_lowlight_params_t *)p1;
246 247 248 249 250
  dt_draw_curve_set_point(d->curve, 0, p->transition_x[DT_IOP_LOWLIGHT_BANDS - 2] - 1.0, p->transition_y[0]);
  for(int k = 0; k < DT_IOP_LOWLIGHT_BANDS; k++)
    dt_draw_curve_set_point(d->curve, k + 1, p->transition_x[k], p->transition_y[k]);
  dt_draw_curve_set_point(d->curve, DT_IOP_LOWLIGHT_BANDS + 1, p->transition_x[1] + 1.0,
                          p->transition_y[DT_IOP_LOWLIGHT_BANDS - 1]);
251
  dt_draw_curve_calc_values(d->curve, 0.0, 1.0, DT_IOP_LOWLIGHT_LUT_RES, NULL, d->lut);
252 253 254
  d->blueness = p->blueness;
}

255
void init_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
256 257 258 259
{
  dt_iop_lowlight_data_t *d = (dt_iop_lowlight_data_t *)malloc(sizeof(dt_iop_lowlight_data_t));
  dt_iop_lowlight_params_t *default_params = (dt_iop_lowlight_params_t *)self->default_params;
  piece->data = (void *)d;
260
  d->curve = dt_draw_curve_new(0.0, 1.0, CATMULL_ROM);
261 262 263
  (void)dt_draw_curve_add_point(d->curve, default_params->transition_x[DT_IOP_LOWLIGHT_BANDS - 2] - 1.0,
                                default_params->transition_y[DT_IOP_LOWLIGHT_BANDS - 2]);
  for(int k = 0; k < DT_IOP_LOWLIGHT_BANDS; k++)
264
    (void)dt_draw_curve_add_point(d->curve, default_params->transition_x[k], default_params->transition_y[k]);
265 266
  (void)dt_draw_curve_add_point(d->curve, default_params->transition_x[1] + 1.0,
                                default_params->transition_y[1]);
267 268
}

269
void cleanup_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
270 271 272 273 274
{
  // clean up everything again.
  dt_iop_lowlight_data_t *d = (dt_iop_lowlight_data_t *)(piece->data);
  dt_draw_curve_destroy(d->curve);
  free(piece->data);
275
  piece->data = NULL;
276 277 278 279 280 281
}

void gui_update(struct dt_iop_module_t *self)
{
  dt_iop_lowlight_gui_data_t *g = (dt_iop_lowlight_gui_data_t *)self->gui_data;
  dt_iop_lowlight_params_t *p = (dt_iop_lowlight_params_t *)self->params;
282
  dt_bauhaus_slider_set(g->scale_blueness, p->blueness);
283 284 285 286 287
  gtk_widget_queue_draw(self->widget);
}

void init(dt_iop_module_t *module)
{
288 289
  module->params = calloc(1, sizeof(dt_iop_lowlight_params_t));
  module->default_params = calloc(1, sizeof(dt_iop_lowlight_params_t));
290
  module->default_enabled = 0; // we're a rather slow and rare op.
Heiko Bauke's avatar
Heiko Bauke committed
291
  module->priority = 617; // module order created by iop_dependencies.py, do not edit!
292 293 294
  module->params_size = sizeof(dt_iop_lowlight_params_t);
  module->gui_data = NULL;
  dt_iop_lowlight_params_t tmp;
295 296
  for(int k = 0; k < DT_IOP_LOWLIGHT_BANDS; k++) tmp.transition_x[k] = k / (DT_IOP_LOWLIGHT_BANDS - 1.0);
  for(int k = 0; k < DT_IOP_LOWLIGHT_BANDS; k++) tmp.transition_y[k] = 0.5f;
297 298 299 300 301 302 303 304 305 306 307
  tmp.blueness = 0.0f;
  memcpy(module->params, &tmp, sizeof(dt_iop_lowlight_params_t));
  memcpy(module->default_params, &tmp, sizeof(dt_iop_lowlight_params_t));
}

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

308
void init_presets(dt_iop_module_so_t *self)
309 310 311
{
  dt_iop_lowlight_params_t p;

312
  DT_DEBUG_SQLITE3_EXEC(dt_database_get(darktable.db), "BEGIN", NULL, NULL, NULL);
313 314 315 316 317 318 319 320

  p.transition_x[0] = 0.000000;
  p.transition_x[1] = 0.200000;
  p.transition_x[2] = 0.400000;
  p.transition_x[3] = 0.600000;
  p.transition_x[4] = 0.800000;
  p.transition_x[5] = 1.000000;

321 322 323 324 325 326
  p.transition_y[0] = 1.000000;
  p.transition_y[1] = 1.000000;
  p.transition_y[2] = 1.000000;
  p.transition_y[3] = 1.000000;
  p.transition_y[4] = 1.000000;
  p.transition_y[5] = 1.000000;
327

328
  p.blueness = 0.0f;
329
  dt_gui_presets_add_generic(_("daylight"), self->op, self->version(), &p, sizeof(p), 1);
330 331 332 333 334 335 336 337

  p.transition_x[0] = 0.000000;
  p.transition_x[1] = 0.200000;
  p.transition_x[2] = 0.400000;
  p.transition_x[3] = 0.600000;
  p.transition_x[4] = 0.800000;
  p.transition_x[5] = 1.000000;

338 339 340 341 342
  p.transition_y[0] = 0.600000;
  p.transition_y[1] = 0.800000;
  p.transition_y[2] = 0.950000;
  p.transition_y[3] = 0.980000;
  p.transition_y[4] = 1.000000;
343 344
  p.transition_y[5] = 1.000000;

345
  p.blueness = 30.0f;
346
  dt_gui_presets_add_generic(_("indoor bright"), self->op, self->version(), &p, sizeof(p), 1);
347 348 349 350 351 352 353 354

  p.transition_x[0] = 0.000000;
  p.transition_x[1] = 0.200000;
  p.transition_x[2] = 0.400000;
  p.transition_x[3] = 0.600000;
  p.transition_x[4] = 0.800000;
  p.transition_x[5] = 1.000000;

355 356 357 358 359
  p.transition_y[0] = 0.300000;
  p.transition_y[1] = 0.500000;
  p.transition_y[2] = 0.700000;
  p.transition_y[3] = 0.850000;
  p.transition_y[4] = 0.970000;
360 361
  p.transition_y[5] = 1.000000;

362
  p.blueness = 30.0f;
363
  dt_gui_presets_add_generic(_("indoor dim"), self->op, self->version(), &p, sizeof(p), 1);
364

365 366 367 368 369 370 371 372 373 374 375 376 377
  p.transition_x[0] = 0.000000;
  p.transition_x[1] = 0.200000;
  p.transition_x[2] = 0.400000;
  p.transition_x[3] = 0.600000;
  p.transition_x[4] = 0.800000;
  p.transition_x[5] = 1.000000;

  p.transition_y[0] = 0.050000;
  p.transition_y[1] = 0.200000;
  p.transition_y[2] = 0.400000;
  p.transition_y[3] = 0.700000;
  p.transition_y[4] = 0.920000;
  p.transition_y[5] = 1.000000;
378

379
  p.blueness = 40.0f;
380
  dt_gui_presets_add_generic(_("indoor dark"), self->op, self->version(), &p, sizeof(p), 1);
Henrik Andersson's avatar
Henrik Andersson committed
381

382 383 384 385 386 387 388
  p.transition_x[0] = 0.000000;
  p.transition_x[1] = 0.200000;
  p.transition_x[2] = 0.400000;
  p.transition_x[3] = 0.600000;
  p.transition_x[4] = 0.800000;
  p.transition_x[5] = 1.000000;

389 390 391 392 393
  p.transition_y[0] = 0.070000;
  p.transition_y[1] = 0.100000;
  p.transition_y[2] = 0.180000;
  p.transition_y[3] = 0.350000;
  p.transition_y[4] = 0.750000;
394 395
  p.transition_y[5] = 1.000000;

396
  p.blueness = 50.0f;
397
  dt_gui_presets_add_generic(_("twilight"), self->op, self->version(), &p, sizeof(p), 1);
398 399 400 401 402 403 404 405

  p.transition_x[0] = 0.000000;
  p.transition_x[1] = 0.200000;
  p.transition_x[2] = 0.400000;
  p.transition_x[3] = 0.600000;
  p.transition_x[4] = 0.800000;
  p.transition_x[5] = 1.000000;

406 407 408 409 410
  p.transition_y[0] = 0.000000;
  p.transition_y[1] = 0.450000;
  p.transition_y[2] = 0.750000;
  p.transition_y[3] = 0.930000;
  p.transition_y[4] = 0.990000;
411 412
  p.transition_y[5] = 1.000000;

413
  p.blueness = 30.0f;
414
  dt_gui_presets_add_generic(_("night street lit"), self->op, self->version(), &p, sizeof(p), 1);
415 416 417 418 419 420 421 422 423

  p.transition_x[0] = 0.000000;
  p.transition_x[1] = 0.200000;
  p.transition_x[2] = 0.400000;
  p.transition_x[3] = 0.600000;
  p.transition_x[4] = 0.800000;
  p.transition_x[5] = 1.000000;

  p.transition_y[0] = 0.000000;
424 425 426 427
  p.transition_y[1] = 0.150000;
  p.transition_y[2] = 0.350000;
  p.transition_y[3] = 0.800000;
  p.transition_y[4] = 0.970000;
428 429
  p.transition_y[5] = 1.000000;

430
  p.blueness = 30.0f;
431
  dt_gui_presets_add_generic(_("night street"), self->op, self->version(), &p, sizeof(p), 1);
432

433 434 435 436 437 438 439 440 441 442 443 444 445 446
  p.transition_x[0] = 0.000000;
  p.transition_x[1] = 0.150000;
  p.transition_x[2] = 0.400000;
  p.transition_x[3] = 0.600000;
  p.transition_x[4] = 0.800000;
  p.transition_x[5] = 1.000000;

  p.transition_y[0] = 0.000000;
  p.transition_y[1] = 0.020000;
  p.transition_y[2] = 0.050000;
  p.transition_y[3] = 0.200000;
  p.transition_y[4] = 0.550000;
  p.transition_y[5] = 1.000000;

447
  p.blueness = 40.0f;
448
  dt_gui_presets_add_generic(_("night street dark"), self->op, self->version(), &p, sizeof(p), 1);
449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464

  p.transition_x[0] = 0.000000;
  p.transition_x[1] = 0.200000;
  p.transition_x[2] = 0.400000;
  p.transition_x[3] = 0.600000;
  p.transition_x[4] = 0.800000;
  p.transition_x[5] = 1.000000;

  p.transition_y[0] = 0.000000;
  p.transition_y[1] = 0.000000;
  p.transition_y[2] = 0.000000;
  p.transition_y[3] = 0.000000;
  p.transition_y[4] = 0.000000;
  p.transition_y[5] = 0.000000;


465
  p.blueness = 50.0f;
466
  dt_gui_presets_add_generic(_("night"), self->op, self->version(), &p, sizeof(p), 1);
467

468
  DT_DEBUG_SQLITE3_EXEC(dt_database_get(darktable.db), "COMMIT", NULL, NULL, NULL);
469 470 471
}

// fills in new parameters based on mouse position (in 0,1)
472 473
static void dt_iop_lowlight_get_params(dt_iop_lowlight_params_t *p, const double mouse_x,
                                       const double mouse_y, const float rad)
474
{
475
  for(int k = 0; k < DT_IOP_LOWLIGHT_BANDS; k++)
476
  {
477 478
    const float f = expf(-(mouse_x - p->transition_x[k]) * (mouse_x - p->transition_x[k]) / (rad * rad));
    p->transition_y[k] = (1 - f) * p->transition_y[k] + f * mouse_y;
479 480 481
  }
}

482
static gboolean lowlight_draw(GtkWidget *widget, cairo_t *crf, gpointer user_data)
483 484 485 486 487
{
  dt_iop_module_t *self = (dt_iop_module_t *)user_data;
  dt_iop_lowlight_gui_data_t *c = (dt_iop_lowlight_gui_data_t *)self->gui_data;
  dt_iop_lowlight_params_t p = *(dt_iop_lowlight_params_t *)self->params;

488 489 490 491 492 493
  dt_draw_curve_set_point(c->transition_curve, 0, p.transition_x[DT_IOP_LOWLIGHT_BANDS - 2] - 1.0,
                          p.transition_y[0]);
  for(int k = 0; k < DT_IOP_LOWLIGHT_BANDS; k++)
    dt_draw_curve_set_point(c->transition_curve, k + 1, p.transition_x[k], p.transition_y[k]);
  dt_draw_curve_set_point(c->transition_curve, DT_IOP_LOWLIGHT_BANDS + 1, p.transition_x[1] + 1.0,
                          p.transition_y[DT_IOP_LOWLIGHT_BANDS - 1]);
494 495

  const int inset = DT_IOP_LOWLIGHT_INSET;
496 497 498
  GtkAllocation allocation;
  gtk_widget_get_allocation(widget, &allocation);
  int width = allocation.width, height = allocation.height;
499 500
  cairo_surface_t *cst = dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
  cairo_t *cr = cairo_create(cst);
501

502
  cairo_set_source_rgb(cr, .2, .2, .2);
503 504 505
  cairo_paint(cr);

  cairo_translate(cr, inset, inset);
506 507
  width -= 2 * inset;
  height -= 2 * inset;
508

509
  cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.0));
510
  cairo_set_source_rgb(cr, .1, .1, .1);
511 512 513
  cairo_rectangle(cr, 0, 0, width, height);
  cairo_stroke(cr);

514
  cairo_set_source_rgb(cr, .3, .3, .3);
515 516 517
  cairo_rectangle(cr, 0, 0, width, height);
  cairo_fill(cr);

518
  // draw grid
519
  cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(.4));
520
  cairo_set_source_rgb(cr, .1, .1, .1);
521 522 523
  dt_draw_grid(cr, 8, 0, 0, width, height);


524 525 526 527
  if(c->mouse_y > 0 || c->dragging)
  {
    // draw min/max curves:
    dt_iop_lowlight_get_params(&p, c->mouse_x, 1., c->mouse_radius);
528 529 530 531 532 533 534 535
    dt_draw_curve_set_point(c->transition_curve, 0, p.transition_x[DT_IOP_LOWLIGHT_BANDS - 2] - 1.0,
                            p.transition_y[0]);
    for(int k = 0; k < DT_IOP_LOWLIGHT_BANDS; k++)
      dt_draw_curve_set_point(c->transition_curve, k + 1, p.transition_x[k], p.transition_y[k]);
    dt_draw_curve_set_point(c->transition_curve, DT_IOP_LOWLIGHT_BANDS + 1, p.transition_x[1] + 1.0,
                            p.transition_y[DT_IOP_LOWLIGHT_BANDS - 1]);
    dt_draw_curve_calc_values(c->transition_curve, 0.0, 1.0, DT_IOP_LOWLIGHT_RES, c->draw_min_xs,
                              c->draw_min_ys);
536 537 538

    p = *(dt_iop_lowlight_params_t *)self->params;
    dt_iop_lowlight_get_params(&p, c->mouse_x, .0, c->mouse_radius);
539 540 541 542 543 544 545 546
    dt_draw_curve_set_point(c->transition_curve, 0, p.transition_x[DT_IOP_LOWLIGHT_BANDS - 2] - 1.0,
                            p.transition_y[0]);
    for(int k = 0; k < DT_IOP_LOWLIGHT_BANDS; k++)
      dt_draw_curve_set_point(c->transition_curve, k + 1, p.transition_x[k], p.transition_y[k]);
    dt_draw_curve_set_point(c->transition_curve, DT_IOP_LOWLIGHT_BANDS + 1, p.transition_x[1] + 1.0,
                            p.transition_y[DT_IOP_LOWLIGHT_BANDS - 1]);
    dt_draw_curve_calc_values(c->transition_curve, 0.0, 1.0, DT_IOP_LOWLIGHT_RES, c->draw_max_xs,
                              c->draw_max_ys);
547 548
  }

549 550
  cairo_save(cr);

551 552
  // draw x positions
  cairo_set_source_rgb(cr, 0.6, 0.6, 0.6);
553 554
  cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.));
  const float arrw = DT_PIXEL_APPLY_DPI(7.0f);
555
  for(int k = 0; k < DT_IOP_LOWLIGHT_BANDS; k++)
556
  {
557 558 559 560
    cairo_move_to(cr, width * p.transition_x[k], height + inset - DT_PIXEL_APPLY_DPI(1));
    cairo_rel_line_to(cr, -arrw * .5f, 0);
    cairo_rel_line_to(cr, arrw * .5f, -arrw);
    cairo_rel_line_to(cr, arrw * .5f, arrw);
561
    cairo_close_path(cr);
562 563 564 565
    if(c->x_move == k)
      cairo_fill(cr);
    else
      cairo_stroke(cr);
566 567 568 569 570 571
  }

  // draw selected cursor
  cairo_translate(cr, 0, height);

  // cairo_set_operator(cr, CAIRO_OPERATOR_ADD);
572
  // cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
573
  cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.));
574 575 576
  cairo_set_source_rgba(cr, .7, .7, .7, 1.0);

  p = *(dt_iop_lowlight_params_t *)self->params;
577 578 579 580 581 582
  dt_draw_curve_set_point(c->transition_curve, 0, p.transition_x[DT_IOP_LOWLIGHT_BANDS - 2] - 1.0,
                          p.transition_y[0]);
  for(int k = 0; k < DT_IOP_LOWLIGHT_BANDS; k++)
    dt_draw_curve_set_point(c->transition_curve, k + 1, p.transition_x[k], p.transition_y[k]);
  dt_draw_curve_set_point(c->transition_curve, DT_IOP_LOWLIGHT_BANDS + 1, p.transition_x[1] + 1.0,
                          p.transition_y[DT_IOP_LOWLIGHT_BANDS - 1]);
583
  dt_draw_curve_calc_values(c->transition_curve, 0.0, 1.0, DT_IOP_LOWLIGHT_RES, c->draw_xs, c->draw_ys);
584 585 586
  cairo_move_to(cr, 0 * width / (float)(DT_IOP_LOWLIGHT_RES - 1), -height * c->draw_ys[0]);
  for(int k = 1; k < DT_IOP_LOWLIGHT_RES; k++)
    cairo_line_to(cr, k * width / (float)(DT_IOP_LOWLIGHT_RES - 1), -height * c->draw_ys[k]);
587 588 589 590
  cairo_stroke(cr);

  // draw dots on knots
  cairo_set_source_rgb(cr, 0.7, 0.7, 0.7);
591
  cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.));
592
  for(int k = 0; k < DT_IOP_LOWLIGHT_BANDS; k++)
593
  {
594 595 596 597 598 599
    cairo_arc(cr, width * p.transition_x[k], -height * p.transition_y[k], DT_PIXEL_APPLY_DPI(3.0), 0.0,
              2.0 * M_PI);
    if(c->x_move == k)
      cairo_fill(cr);
    else
      cairo_stroke(cr);
600 601 602 603 604 605
  }

  if(c->mouse_y > 0 || c->dragging)
  {
    // draw min/max, if selected
    cairo_set_source_rgba(cr, .7, .7, .7, .6);
606 607 608 609 610
    cairo_move_to(cr, 0, -height * c->draw_min_ys[0]);
    for(int k = 1; k < DT_IOP_LOWLIGHT_RES; k++)
      cairo_line_to(cr, k * width / (float)(DT_IOP_LOWLIGHT_RES - 1), -height * c->draw_min_ys[k]);
    for(int k = DT_IOP_LOWLIGHT_RES - 1; k >= 0; k--)
      cairo_line_to(cr, k * width / (float)(DT_IOP_LOWLIGHT_RES - 1), -height * c->draw_max_ys[k]);
611 612 613 614 615 616 617
    cairo_close_path(cr);
    cairo_fill(cr);
    // draw mouse focus circle
    cairo_set_source_rgba(cr, .9, .9, .9, .5);
    const float pos = DT_IOP_LOWLIGHT_RES * c->mouse_x;
    int k = (int)pos;
    const float f = k - pos;
618 619 620
    if(k >= DT_IOP_LOWLIGHT_RES - 1) k = DT_IOP_LOWLIGHT_RES - 2;
    float ht = -height * (f * c->draw_ys[k] + (1 - f) * c->draw_ys[k + 1]);
    cairo_arc(cr, c->mouse_x * width, ht, c->mouse_radius * width, 0, 2. * M_PI);
621 622 623
    cairo_stroke(cr);
  }

624
  cairo_restore(cr);
625

626 627
  cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);

628
  // draw labels:
Asma's avatar
Asma committed
629 630
  PangoLayout *layout;
  PangoRectangle ink;
631 632
  PangoFontDescription *desc = pango_font_description_copy_static(darktable.bauhaus->pango_font_desc);
  pango_font_description_set_weight(desc, PANGO_WEIGHT_BOLD);
Asma's avatar
Asma committed
633 634 635
  pango_font_description_set_absolute_size(desc,(.06 * height) * PANGO_SCALE);
  layout = pango_cairo_create_layout(cr);
  pango_layout_set_font_description(layout, desc);
636 637
  cairo_set_source_rgb(cr, .1, .1, .1);

Asma's avatar
Asma committed
638 639 640
  pango_layout_set_text(layout, _("dark"), -1);
  pango_layout_get_pixel_extents(layout, &ink, NULL);
  cairo_move_to(cr, .02 * width - ink.y, .5 * (height + ink.width));
641 642
  cairo_save(cr);
  cairo_rotate(cr, -M_PI * .5f);
Asma's avatar
Asma committed
643
  pango_cairo_show_layout(cr, layout);
644
  cairo_restore(cr);
645

Asma's avatar
Asma committed
646 647 648
  pango_layout_set_text(layout, _("bright"), -1);
  pango_layout_get_pixel_extents(layout, &ink, NULL);
  cairo_move_to(cr, .98 * width - ink.height, .5 * (height + ink.width));
649 650
  cairo_save(cr);
  cairo_rotate(cr, -M_PI * .5f);
Asma's avatar
Asma committed
651
  pango_cairo_show_layout(cr, layout);
652
  cairo_restore(cr);
653 654


Asma's avatar
Asma committed
655 656 657 658
  pango_layout_set_text(layout, _("day vision"), -1);
  pango_layout_get_pixel_extents(layout, &ink, NULL);
  cairo_move_to(cr, .5 * (width - ink.width), .08 * height - ink.height);
  pango_cairo_show_layout(cr, layout);
659

Asma's avatar
Asma committed
660 661 662 663
  pango_layout_set_text(layout, _("night vision"), -1);
  pango_layout_get_pixel_extents(layout, &ink, NULL);
  cairo_move_to(cr, .5 * (width - ink.width), .97 * height - ink.height);
  pango_cairo_show_layout(cr, layout);
664

Asma's avatar
Asma committed
665 666
  pango_font_description_free(desc);
  g_object_unref(layout);
667 668 669 670
  cairo_destroy(cr);
  cairo_set_source_surface(crf, cst, 0, 0);
  cairo_paint(crf);
  cairo_surface_destroy(cst);
671 672 673
  return TRUE;
}

674
static gboolean lowlight_motion_notify(GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
675 676 677 678 679
{
  dt_iop_module_t *self = (dt_iop_module_t *)user_data;
  dt_iop_lowlight_gui_data_t *c = (dt_iop_lowlight_gui_data_t *)self->gui_data;
  dt_iop_lowlight_params_t *p = (dt_iop_lowlight_params_t *)self->params;
  const int inset = DT_IOP_LOWLIGHT_INSET;
680 681
  GtkAllocation allocation;
  gtk_widget_get_allocation(widget, &allocation);
682 683 684
  int height = allocation.height - 2 * inset, width = allocation.width - 2 * inset;
  if(!c->dragging) c->mouse_x = CLAMP(event->x - inset, 0, width) / (float)width;
  c->mouse_y = 1.0 - CLAMP(event->y - inset, 0, height) / (float)height;
685 686 687 688 689
  if(c->dragging)
  {
    *p = c->drag_params;
    if(c->x_move >= 0)
    {
690 691
      const float mx = CLAMP(event->x - inset, 0, width) / (float)width;
      if(c->x_move > 0 && c->x_move < DT_IOP_LOWLIGHT_BANDS - 1)
692
      {
693 694
        const float minx = p->transition_x[c->x_move - 1] + 0.001f;
        const float maxx = p->transition_x[c->x_move + 1] - 0.001f;
695 696 697 698 699 700 701 702 703 704 705 706
        p->transition_x[c->x_move] = fminf(maxx, fmaxf(minx, mx));
      }
    }
    else
    {
      dt_iop_lowlight_get_params(p, c->mouse_x, c->mouse_y + c->mouse_pick, c->mouse_radius);
    }
    dt_dev_add_history_item(darktable.develop, self, TRUE);
  }
  else if(event->y > height)
  {
    c->x_move = 0;
707
    float dist = fabs(p->transition_x[0] - c->mouse_x);
708
    for(int k = 1; k < DT_IOP_LOWLIGHT_BANDS; k++)
709
    {
710
      float d2 = fabs(p->transition_x[k] - c->mouse_x);
711 712 713 714 715 716 717 718 719 720 721 722 723
      if(d2 < dist)
      {
        c->x_move = k;
        dist = d2;
      }
    }
  }
  else
  {
    c->x_move = -1;
  }
  gtk_widget_queue_draw(widget);
  gint x, y;
724
#if GTK_CHECK_VERSION(3, 20, 0)
725
  gdk_window_get_device_position(event->window,
johannes hanika's avatar
johannes hanika committed
726 727 728
      gdk_seat_get_pointer(gdk_display_get_default_seat(
          gdk_window_get_display(event->window))),
      &x, &y, 0);
729 730 731 732 733 734
#else
  gdk_window_get_device_position(event->window,
                                 gdk_device_manager_get_client_pointer(
                                     gdk_display_get_device_manager(gdk_window_get_display(event->window))),
                                 &x, &y, NULL);
#endif
735 736 737
  return TRUE;
}

738
static gboolean lowlight_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
739
{
740
  dt_iop_module_t *self = (dt_iop_module_t *)user_data;
741
  if(event->button == 1 && event->type == GDK_2BUTTON_PRESS)
742 743 744
  {
    // reset current curve
    dt_iop_lowlight_params_t *p = (dt_iop_lowlight_params_t *)self->params;
745
    dt_iop_lowlight_params_t *d = (dt_iop_lowlight_params_t *)self->default_params;
746
    /*   dt_iop_lowlight_gui_data_t *c = (dt_iop_lowlight_gui_data_t *)self->gui_data; */
747
    for(int k = 0; k < DT_IOP_LOWLIGHT_BANDS; k++)
748 749 750 751 752 753 754 755
    {
      p->transition_x[k] = d->transition_x[k];
      p->transition_y[k] = d->transition_y[k];
    }
    dt_dev_add_history_item(darktable.develop, self, TRUE);
    gtk_widget_queue_draw(self->widget);
  }
  else if(event->button == 1)
756 757 758 759
  {
    dt_iop_lowlight_gui_data_t *c = (dt_iop_lowlight_gui_data_t *)self->gui_data;
    c->drag_params = *(dt_iop_lowlight_params_t *)self->params;
    const int inset = DT_IOP_LOWLIGHT_INSET;
760 761
    GtkAllocation allocation;
    gtk_widget_get_allocation(widget, &allocation);
762 763 764 765
    int height = allocation.height - 2 * inset, width = allocation.width - 2 * inset;
    c->mouse_pick
        = dt_draw_curve_calc_value(c->transition_curve, CLAMP(event->x - inset, 0, width) / (float)width);
    c->mouse_pick -= 1.0 - CLAMP(event->y - inset, 0, height) / (float)height;
766 767 768 769 770 771
    c->dragging = 1;
    return TRUE;
  }
  return FALSE;
}

772
static gboolean lowlight_button_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
773 774 775 776 777 778 779 780 781 782 783
{
  if(event->button == 1)
  {
    dt_iop_module_t *self = (dt_iop_module_t *)user_data;
    dt_iop_lowlight_gui_data_t *c = (dt_iop_lowlight_gui_data_t *)self->gui_data;
    c->dragging = 0;
    return TRUE;
  }
  return FALSE;
}

784
static gboolean lowlight_leave_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
785 786 787
{
  dt_iop_module_t *self = (dt_iop_module_t *)user_data;
  dt_iop_lowlight_gui_data_t *c = (dt_iop_lowlight_gui_data_t *)self->gui_data;
788
  if(!c->dragging) c->mouse_y = -1.0;
789 790 791 792
  gtk_widget_queue_draw(widget);
  return TRUE;
}

793
static gboolean lowlight_scrolled(GtkWidget *widget, GdkEventScroll *event, gpointer user_data)
794 795 796
{
  dt_iop_module_t *self = (dt_iop_module_t *)user_data;
  dt_iop_lowlight_gui_data_t *c = (dt_iop_lowlight_gui_data_t *)self->gui_data;
797

798 799
  gdouble delta_y;
  if(dt_gui_get_scroll_deltas(event, NULL, &delta_y))
800
  {
801
    c->mouse_radius = CLAMP(c->mouse_radius * (1.0 + 0.1 * delta_y), 0.2 / DT_IOP_LOWLIGHT_BANDS, 1.0);
802 803
    gtk_widget_queue_draw(widget);
  }
804

805 806 807
  return TRUE;
}

808
static void blueness_callback(GtkWidget *slider, gpointer user_data)
809 810 811 812
{
  dt_iop_module_t *self = (dt_iop_module_t *)user_data;
  if(self->dt->gui->reset) return;
  dt_iop_lowlight_params_t *p = (dt_iop_lowlight_params_t *)self->params;
813
  p->blueness = dt_bauhaus_slider_get(slider);
814 815 816 817 818 819 820 821 822
  dt_dev_add_history_item(darktable.develop, self, TRUE);
}

void gui_init(struct dt_iop_module_t *self)
{
  self->gui_data = malloc(sizeof(dt_iop_lowlight_gui_data_t));
  dt_iop_lowlight_gui_data_t *c = (dt_iop_lowlight_gui_data_t *)self->gui_data;
  dt_iop_lowlight_params_t *p = (dt_iop_lowlight_params_t *)self->params;

823
  c->transition_curve = dt_draw_curve_new(0.0, 1.0, CATMULL_ROM);
824 825 826 827 828
  (void)dt_draw_curve_add_point(c->transition_curve, p->transition_x[DT_IOP_LOWLIGHT_BANDS - 2] - 1.0,
                                p->transition_y[DT_IOP_LOWLIGHT_BANDS - 2]);
  for(int k = 0; k < DT_IOP_LOWLIGHT_BANDS; k++)
    (void)dt_draw_curve_add_point(c->transition_curve, p->transition_x[k], p->transition_y[k]);
  (void)dt_draw_curve_add_point(c->transition_curve, p->transition_x[1] + 1.0, p->transition_y[1]);
829 830 831 832

  c->mouse_x = c->mouse_y = c->mouse_pick = -1.0;
  c->dragging = 0;
  c->x_move = -1;
833
  c->mouse_radius = 1.0 / DT_IOP_LOWLIGHT_BANDS;
834

835
  self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_BAUHAUS_SPACE);
836

837
  c->area = GTK_DRAWING_AREA(dtgtk_drawing_area_new_with_aspect_ratio(0.75));
838 839 840 841 842

  gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(c->area), FALSE, FALSE, 0);

  gtk_widget_add_events(GTK_WIDGET(c->area), GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK
                                             | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
843
                                             | GDK_LEAVE_NOTIFY_MASK | darktable.gui->scroll_mask);
844
  g_signal_connect(G_OBJECT(c->area), "draw", G_CALLBACK(lowlight_draw), self);
845 846 847 848 849
  g_signal_connect(G_OBJECT(c->area), "button-press-event", G_CALLBACK(lowlight_button_press), self);
  g_signal_connect(G_OBJECT(c->area), "button-release-event", G_CALLBACK(lowlight_button_release), self);
  g_signal_connect(G_OBJECT(c->area), "motion-notify-event", G_CALLBACK(lowlight_motion_notify), self);
  g_signal_connect(G_OBJECT(c->area), "leave-notify-event", G_CALLBACK(lowlight_leave_notify), self);
  g_signal_connect(G_OBJECT(c->area), "scroll-event", G_CALLBACK(lowlight_scrolled), self);
850

851
  c->scale_blueness = dt_bauhaus_slider_new_with_range(self, 0.0, 100.0, 1.0, p->blueness, 2);
852
  dt_bauhaus_widget_set_label(c->scale_blueness, NULL, _("blue shift"));
853
  dt_bauhaus_slider_set_format(c->scale_blueness, "%0.2f%%");
854
  gtk_widget_set_tooltip_text(c->scale_blueness, _("blueness in shadows"));
855

856
  gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(c->scale_blueness), TRUE, TRUE, 5);
857

858
  g_signal_connect(G_OBJECT(c->scale_blueness), "value-changed", G_CALLBACK(blueness_callback), self);
859 860 861 862 863 864 865 866 867 868
}

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

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