"Fossies" - the Fresh Open Source Software Archive

Member "darktable-2.6.3/src/iop/zonesystem.c" (20 Oct 2019, 33970 Bytes) of package /linux/misc/darktable-2.6.3.tar.xz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "zonesystem.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 2.6.2_vs_2.6.3.

    1 /*
    2     This file is part of darktable,
    3     copyright (c) 2010 Henrik Andersson.
    4 
    5     darktable is free software: you can redistribute it and/or modify
    6     it under the terms of the GNU General Public License as published by
    7     the Free Software Foundation, either version 3 of the License, or
    8     (at your option) any later version.
    9 
   10     darktable is distributed in the hope that it will be useful,
   11     but WITHOUT ANY WARRANTY; without even the implied warranty of
   12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   13     GNU General Public License for more details.
   14 
   15     You should have received a copy of the GNU General Public License
   16     along with darktable.  If not, see <http://www.gnu.org/licenses/>.
   17 */
   18 #ifdef HAVE_CONFIG_H
   19 #include "config.h"
   20 #endif
   21 #include <assert.h>
   22 #include <math.h>
   23 #include <stdlib.h>
   24 #include <string.h>
   25 
   26 #include "common/darktable.h"
   27 #include "common/gaussian.h"
   28 #include "common/opencl.h"
   29 #include "control/conf.h"
   30 #include "control/control.h"
   31 #include "develop/develop.h"
   32 #include "develop/imageop.h"
   33 #include "develop/imageop_math.h"
   34 #include "dtgtk/drawingarea.h"
   35 #include "dtgtk/gradientslider.h"
   36 #include "dtgtk/togglebutton.h"
   37 #include "gui/gtk.h"
   38 #include "gui/presets.h"
   39 #include "iop/iop_api.h"
   40 #include "common/iop_group.h"
   41 
   42 #if defined(__SSE__)
   43 #include <xmmintrin.h>
   44 #endif
   45 
   46 #include <librsvg/rsvg.h>
   47 // ugh, ugly hack. why do people break stuff all the time?
   48 #ifndef RSVG_CAIRO_H
   49 #include <librsvg/rsvg-cairo.h>
   50 #endif
   51 
   52 
   53 #define CLIP(x) (((x) >= 0) ? ((x) <= 1.0 ? (x) : 1.0) : 0.0)
   54 DT_MODULE_INTROSPECTION(1, dt_iop_zonesystem_params_t)
   55 #define MAX_ZONE_SYSTEM_SIZE 24
   56 
   57 /** gui params. */
   58 typedef struct dt_iop_zonesystem_params_t
   59 {
   60   int size;
   61   float zone[MAX_ZONE_SYSTEM_SIZE + 1];
   62 } dt_iop_zonesystem_params_t;
   63 
   64 /** and pixelpipe data is just the same */
   65 typedef struct dt_iop_zonesystem_data_t
   66 {
   67   dt_iop_zonesystem_params_t params;
   68   float rzscale;
   69   float zonemap_offset[MAX_ZONE_SYSTEM_SIZE];
   70   float zonemap_scale[MAX_ZONE_SYSTEM_SIZE];
   71 } dt_iop_zonesystem_data_t;
   72 
   73 
   74 /*
   75 void init_presets (dt_iop_module_so_t *self)
   76 {
   77 //   DT_DEBUG_SQLITE3_EXEC(darktable.db, "begin", NULL, NULL, NULL);
   78 
   79   dt_gui_presets_add_generic(_("Fill-light 0.25EV with 4 zones"), self->op, self->version(),
   80 &(dt_iop_zonesystem_params_t){0.25,0.25,4.0} , sizeof(dt_iop_zonesystem_params_t), 1);
   81   dt_gui_presets_add_generic(_("Fill-shadow -0.25EV with 4 zones"), self->op, self->version(),
   82 &(dt_iop_zonesystem_params_t){-0.25,0.25,4.0} , sizeof(dt_iop_zonesystem_params_t), 1);
   83 
   84 //   DT_DEBUG_SQLITE3_EXEC(darktable.db, "commit", NULL, NULL, NULL);
   85 }
   86 */
   87 
   88 typedef struct dt_iop_zonesystem_global_data_t
   89 {
   90   int kernel_zonesystem;
   91 } dt_iop_zonesystem_global_data_t;
   92 
   93 
   94 typedef struct dt_iop_zonesystem_gui_data_t
   95 {
   96   guchar *in_preview_buffer;
   97   guchar *out_preview_buffer;
   98   int preview_width, preview_height;
   99   GtkWidget *preview;
  100   GtkWidget *zones;
  101   float press_x, press_y, mouse_x, mouse_y;
  102   gboolean hilite_zone;
  103   gboolean is_dragging;
  104   int current_zone;
  105   int zone_under_mouse;
  106   int mouse_over_output_zones;
  107   dt_pthread_mutex_t lock;
  108 
  109   cairo_surface_t *image;
  110   guint8 *image_buffer;
  111   int image_width, image_height;
  112 
  113 } dt_iop_zonesystem_gui_data_t;
  114 
  115 
  116 const char *name()
  117 {
  118   return _("zone system");
  119 }
  120 
  121 int flags()
  122 {
  123   return IOP_FLAGS_SUPPORTS_BLENDING | IOP_FLAGS_INCLUDE_IN_STYLES | IOP_FLAGS_ALLOW_TILING
  124          | IOP_FLAGS_PREVIEW_NON_OPENCL;
  125 }
  126 
  127 int groups()
  128 {
  129   return dt_iop_get_group("zone system", IOP_GROUP_TONE);
  130 }
  131 
  132 /* get the zone index of pixel lightness from zonemap */
  133 static inline int _iop_zonesystem_zone_index_from_lightness(float lightness, float *zonemap, int size)
  134 {
  135   for(int k = 0; k < size - 1; k++)
  136     if(zonemap[k + 1] >= lightness) return k;
  137   return size - 1;
  138 }
  139 
  140 /* calculate a zonemap with scale values for each zone based on controlpoints from param */
  141 static inline void _iop_zonesystem_calculate_zonemap(struct dt_iop_zonesystem_params_t *p, float *zonemap)
  142 {
  143   int steps = 0;
  144   int pk = 0;
  145 
  146   for(int k = 0; k < p->size; k++)
  147   {
  148     if((k > 0 && k < p->size - 1) && p->zone[k] == -1)
  149       steps++;
  150     else
  151     {
  152       /* set 0 and 1.0 for first and last element in zonesystem size, or the actually parameter value */
  153       zonemap[k] = k == 0 ? 0.0 : k == (p->size - 1) ? 1.0 : p->zone[k];
  154 
  155       /* for each step from pk to k, calculate values
  156           for now this is linear distributed
  157       */
  158       for(int l = 1; l <= steps; l++)
  159         zonemap[pk + l] = zonemap[pk] + (((zonemap[k] - zonemap[pk]) / (steps + 1)) * l);
  160 
  161       /* store k into pk and reset zone steps for next range*/
  162       pk = k;
  163       steps = 0;
  164     }
  165   }
  166 }
  167 
  168 #define GAUSS(a, b, c, x) (a * pow(2.718281828, (-pow((x - b), 2) / (pow(c, 2)))))
  169 
  170 static void process_common_setup(struct dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece,
  171                                  const void *const ivoid, void *const ovoid, const dt_iop_roi_t *const roi_in,
  172                                  const dt_iop_roi_t *const roi_out)
  173 {
  174   const int width = roi_out->width;
  175   const int height = roi_out->height;
  176 
  177   if(self->dev->gui_attached && piece->pipe->type == DT_DEV_PIXELPIPE_PREVIEW)
  178   {
  179     dt_iop_zonesystem_gui_data_t *g = (dt_iop_zonesystem_gui_data_t *)self->gui_data;
  180     dt_pthread_mutex_lock(&g->lock);
  181     if(g->in_preview_buffer == NULL || g->out_preview_buffer == NULL || g->preview_width != width
  182        || g->preview_height != height)
  183     {
  184       g_free(g->in_preview_buffer);
  185       g_free(g->out_preview_buffer);
  186       g->in_preview_buffer = g_malloc_n((size_t)width * height, sizeof(guchar));
  187       g->out_preview_buffer = g_malloc_n((size_t)width * height, sizeof(guchar));
  188       g->preview_width = width;
  189       g->preview_height = height;
  190     }
  191     dt_pthread_mutex_unlock(&g->lock);
  192   }
  193 }
  194 
  195 static void process_common_cleanup(struct dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece,
  196                                    const void *const ivoid, void *const ovoid,
  197                                    const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
  198 {
  199   dt_iop_zonesystem_data_t *d = (dt_iop_zonesystem_data_t *)piece->data;
  200   dt_iop_zonesystem_gui_data_t *g = (dt_iop_zonesystem_gui_data_t *)self->gui_data;
  201 
  202   const int width = roi_out->width;
  203   const int height = roi_out->height;
  204   const int ch = piece->colors;
  205   const int size = d->params.size;
  206 
  207   if(piece->pipe->mask_display & DT_DEV_PIXELPIPE_DISPLAY_MASK) dt_iop_alpha_copy(ivoid, ovoid, width, height);
  208 
  209   /* if gui and have buffer lets gaussblur and fill buffer with zone indexes */
  210   if(self->dev->gui_attached && piece->pipe->type == DT_DEV_PIXELPIPE_PREVIEW && g && g->in_preview_buffer
  211      && g->out_preview_buffer)
  212   {
  213     float Lmax[] = { 100.0f };
  214     float Lmin[] = { 0.0f };
  215 
  216     /* setup gaussian kernel */
  217     const int radius = 8;
  218     const float sigma = 2.5 * (radius * roi_in->scale / piece->iscale);
  219 
  220     dt_gaussian_t *gauss = dt_gaussian_init(width, height, 1, Lmax, Lmin, sigma, DT_IOP_GAUSSIAN_ZERO);
  221 
  222     float *tmp = g_malloc_n((size_t)width * height, sizeof(float));
  223 
  224     if(gauss && tmp)
  225     {
  226 #ifdef _OPENMP
  227 #pragma omp parallel for default(none) \
  228       dt_omp_firstprivate(ch, height, width, ivoid) \
  229       shared(tmp) \
  230       schedule(static)
  231 #endif
  232       for(size_t k = 0; k < (size_t)width * height; k++) tmp[k] = ((float *)ivoid)[ch * k];
  233 
  234       dt_gaussian_blur(gauss, tmp, tmp);
  235 
  236       /* create zonemap preview for input */
  237       dt_pthread_mutex_lock(&g->lock);
  238 #ifdef _OPENMP
  239 #pragma omp parallel for default(none) \
  240       dt_omp_firstprivate(height, size, width) \
  241       shared(tmp, g) \
  242       schedule(static)
  243 #endif
  244       for(size_t k = 0; k < (size_t)width * height; k++)
  245       {
  246         g->in_preview_buffer[k] = CLAMPS(tmp[k] * (size - 1) / 100.0f, 0, size - 2);
  247       }
  248       dt_pthread_mutex_unlock(&g->lock);
  249 
  250 
  251 #ifdef _OPENMP
  252 #pragma omp parallel for default(none) \
  253       dt_omp_firstprivate(ch, height, ovoid, width) \
  254       shared(tmp) \
  255       schedule(static)
  256 #endif
  257       for(size_t k = 0; k < (size_t)width * height; k++) tmp[k] = ((float *)ovoid)[ch * k];
  258 
  259       dt_gaussian_blur(gauss, tmp, tmp);
  260 
  261 
  262       /* create zonemap preview for output */
  263       dt_pthread_mutex_lock(&g->lock);
  264 #ifdef _OPENMP
  265 #pragma omp parallel for default(none) \
  266       dt_omp_firstprivate(height, size, width) \
  267       shared(tmp, g) \
  268       schedule(static)
  269 #endif
  270       for(size_t k = 0; k < (size_t)width * height; k++)
  271       {
  272         g->out_preview_buffer[k] = CLAMPS(tmp[k] * (size - 1) / 100.0f, 0, size - 2);
  273       }
  274       dt_pthread_mutex_unlock(&g->lock);
  275     }
  276 
  277     g_free(tmp);
  278     if(gauss) dt_gaussian_free(gauss);
  279   }
  280 }
  281 
  282 void process(struct dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
  283              void *const ovoid, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
  284 {
  285   const dt_iop_zonesystem_data_t *const d = (const dt_iop_zonesystem_data_t *const)piece->data;
  286 
  287   process_common_setup(self, piece, ivoid, ovoid, roi_in, roi_out);
  288 
  289   const int ch = piece->colors;
  290   const int size = d->params.size;
  291 
  292   const float *const in = (const float *const)ivoid;
  293   float *const out = (float *const)ovoid;
  294 
  295 #ifdef _OPENMP
  296 #pragma omp parallel for SIMD() default(none) \
  297   dt_omp_firstprivate(ch, d, in, out, roi_out, size) \
  298   schedule(static) \
  299   collapse(2)
  300 #endif
  301   for(size_t k = 0; k < (size_t)ch * roi_out->width * roi_out->height; k += ch)
  302   {
  303     for(int c = 0; c < 3; c++)
  304     {
  305       /* remap lightness into zonemap and apply lightness */
  306       const int rz = CLAMPS(in[k] * d->rzscale, 0, size - 2); // zone index
  307       const float zs = ((rz > 0) ? (d->zonemap_offset[rz] / in[k]) : 0) + d->zonemap_scale[rz];
  308 
  309       const size_t p = (size_t)k + c;
  310       out[p] = in[p] * zs;
  311     }
  312   }
  313 
  314   process_common_cleanup(self, piece, ivoid, ovoid, roi_in, roi_out);
  315 }
  316 
  317 #if defined(__SSE__)
  318 void process_sse2(struct dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
  319                   void *const ovoid, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
  320 {
  321   const dt_iop_zonesystem_data_t *const d = (const dt_iop_zonesystem_data_t *const)piece->data;
  322 
  323   process_common_setup(self, piece, ivoid, ovoid, roi_in, roi_out);
  324 
  325   const int ch = piece->colors;
  326   const int size = d->params.size;
  327 
  328 #ifdef _OPENMP
  329 #pragma omp parallel for default(none) \
  330   dt_omp_firstprivate(ch, d, ivoid, ovoid, roi_out, size) \
  331   schedule(static)
  332 #endif
  333   for(int j = 0; j < roi_out->height; j++)
  334   {
  335     for(int i = 0; i < roi_out->width; i++)
  336     {
  337       /* remap lightness into zonemap and apply lightness */
  338       const float *in = (float *)ivoid + ch * ((size_t)j * roi_out->width + i);
  339       float *out = (float *)ovoid + ch * ((size_t)j * roi_out->width + i);
  340 
  341       const int rz = CLAMPS(in[0] * d->rzscale, 0, size - 2); // zone index
  342 
  343       const float zs = ((rz > 0) ? (d->zonemap_offset[rz] / in[0]) : 0) + d->zonemap_scale[rz];
  344 
  345       _mm_stream_ps(out, _mm_mul_ps(_mm_load_ps(in), _mm_set1_ps(zs)));
  346     }
  347   }
  348 
  349   _mm_sfence();
  350 
  351   process_common_cleanup(self, piece, ivoid, ovoid, roi_in, roi_out);
  352 }
  353 #endif
  354 
  355 #ifdef HAVE_OPENCL
  356 int process_cl(struct dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, cl_mem dev_in, cl_mem dev_out,
  357                const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
  358 {
  359   dt_iop_zonesystem_data_t *data = (dt_iop_zonesystem_data_t *)piece->data;
  360   dt_iop_zonesystem_global_data_t *gd = (dt_iop_zonesystem_global_data_t *)self->data;
  361   cl_mem dev_zmo, dev_zms = NULL;
  362   cl_int err = -999;
  363 
  364   const int devid = piece->pipe->devid;
  365   const int width = roi_in->width;
  366   const int height = roi_in->height;
  367 
  368   /* calculate zonemap */
  369   const int size = data->params.size;
  370   float zonemap[MAX_ZONE_SYSTEM_SIZE] = { -1 };
  371   float zonemap_offset[ROUNDUP(MAX_ZONE_SYSTEM_SIZE, 16)] = { -1 };
  372   float zonemap_scale[ROUNDUP(MAX_ZONE_SYSTEM_SIZE, 16)] = { -1 };
  373 
  374   _iop_zonesystem_calculate_zonemap(&(data->params), zonemap);
  375 
  376   /* precompute scale and offset */
  377   for(int k = 0; k < size - 1; k++) zonemap_scale[k] = (zonemap[k + 1] - zonemap[k]) * (size - 1);
  378   for(int k = 0; k < size - 1; k++) zonemap_offset[k] = 100.0f * ((k + 1) * zonemap[k] - k * zonemap[k + 1]);
  379 
  380   dev_zmo = dt_opencl_copy_host_to_device_constant(devid, sizeof(float) * ROUNDUP(MAX_ZONE_SYSTEM_SIZE, 16),
  381                                                    zonemap_offset);
  382   if(dev_zmo == NULL) goto error;
  383   dev_zms = dt_opencl_copy_host_to_device_constant(devid, sizeof(float) * ROUNDUP(MAX_ZONE_SYSTEM_SIZE, 16),
  384                                                    zonemap_scale);
  385   if(dev_zms == NULL) goto error;
  386 
  387   size_t sizes[] = { ROUNDUPWD(width), ROUNDUPHT(height), 1 };
  388 
  389   dt_opencl_set_kernel_arg(devid, gd->kernel_zonesystem, 0, sizeof(cl_mem), (void *)&dev_in);
  390   dt_opencl_set_kernel_arg(devid, gd->kernel_zonesystem, 1, sizeof(cl_mem), (void *)&dev_out);
  391   dt_opencl_set_kernel_arg(devid, gd->kernel_zonesystem, 2, sizeof(int), (void *)&width);
  392   dt_opencl_set_kernel_arg(devid, gd->kernel_zonesystem, 3, sizeof(int), (void *)&height);
  393   dt_opencl_set_kernel_arg(devid, gd->kernel_zonesystem, 4, sizeof(int), (void *)&size);
  394   dt_opencl_set_kernel_arg(devid, gd->kernel_zonesystem, 5, sizeof(cl_mem), (void *)&dev_zmo);
  395   dt_opencl_set_kernel_arg(devid, gd->kernel_zonesystem, 6, sizeof(cl_mem), (void *)&dev_zms);
  396   err = dt_opencl_enqueue_kernel_2d(devid, gd->kernel_zonesystem, sizes);
  397 
  398   if(err != CL_SUCCESS) goto error;
  399   dt_opencl_release_mem_object(dev_zmo);
  400   dt_opencl_release_mem_object(dev_zms);
  401   return TRUE;
  402 
  403 error:
  404   dt_opencl_release_mem_object(dev_zmo);
  405   dt_opencl_release_mem_object(dev_zms);
  406   dt_print(DT_DEBUG_OPENCL, "[opencl_zonesystem] couldn't enqueue kernel! %d\n", err);
  407   return FALSE;
  408 }
  409 #endif
  410 
  411 
  412 
  413 void init_global(dt_iop_module_so_t *module)
  414 {
  415   const int program = 2; // basic.cl, from programs.conf
  416   dt_iop_zonesystem_global_data_t *gd
  417       = (dt_iop_zonesystem_global_data_t *)malloc(sizeof(dt_iop_zonesystem_global_data_t));
  418   module->data = gd;
  419   gd->kernel_zonesystem = dt_opencl_create_kernel(program, "zonesystem");
  420 }
  421 
  422 void cleanup_global(dt_iop_module_so_t *module)
  423 {
  424   dt_iop_zonesystem_global_data_t *gd = (dt_iop_zonesystem_global_data_t *)module->data;
  425   dt_opencl_free_kernel(gd->kernel_zonesystem);
  426   free(module->data);
  427   module->data = NULL;
  428 }
  429 
  430 
  431 void commit_params(struct dt_iop_module_t *self, dt_iop_params_t *p1, dt_dev_pixelpipe_t *pipe,
  432                    dt_dev_pixelpipe_iop_t *piece)
  433 {
  434   dt_iop_zonesystem_params_t *p = (dt_iop_zonesystem_params_t *)p1;
  435 
  436   dt_iop_zonesystem_data_t *d = (dt_iop_zonesystem_data_t *)piece->data;
  437 
  438   d->params = *p;
  439   d->rzscale = (d->params.size - 1) / 100.0f;
  440 
  441   /* calculate zonemap */
  442   float zonemap[MAX_ZONE_SYSTEM_SIZE] = { -1 };
  443   _iop_zonesystem_calculate_zonemap(&(d->params), zonemap);
  444 
  445   const int size = d->params.size;
  446 
  447   // precompute scale and offset
  448   for(int k = 0; k < size - 1; k++) d->zonemap_scale[k] = (zonemap[k + 1] - zonemap[k]) * (size - 1);
  449   for(int k = 0; k < size - 1; k++)
  450     d->zonemap_offset[k] = 100.0f * ((k + 1) * zonemap[k] - k * zonemap[k + 1]);
  451 }
  452 
  453 void init_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
  454 {
  455   piece->data = calloc(1, sizeof(dt_iop_zonesystem_data_t));
  456   self->commit_params(self, self->default_params, pipe, piece);
  457 }
  458 
  459 void cleanup_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
  460 {
  461   free(piece->data);
  462   piece->data = NULL;
  463 }
  464 
  465 void gui_update(struct dt_iop_module_t *self)
  466 {
  467   //  dt_iop_module_t *module = (dt_iop_module_t *)self;
  468   dt_iop_zonesystem_gui_data_t *g = (dt_iop_zonesystem_gui_data_t *)self->gui_data;
  469   // dt_iop_zonesystem_params_t *p = (dt_iop_zonesystem_params_t *)module->params;
  470   gtk_widget_queue_draw(GTK_WIDGET(g->zones));
  471 }
  472 
  473 void init(dt_iop_module_t *module)
  474 {
  475   module->params = calloc(1, sizeof(dt_iop_zonesystem_params_t));
  476   module->default_params = calloc(1, sizeof(dt_iop_zonesystem_params_t));
  477   module->default_enabled = 0;
  478   module->priority = 671; // module order created by iop_dependencies.py, do not edit!
  479   module->params_size = sizeof(dt_iop_zonesystem_params_t);
  480   module->gui_data = NULL;
  481   dt_iop_zonesystem_params_t tmp = (dt_iop_zonesystem_params_t){
  482     10, { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }
  483   };
  484   memcpy(module->params, &tmp, sizeof(dt_iop_zonesystem_params_t));
  485   memcpy(module->default_params, &tmp, sizeof(dt_iop_zonesystem_params_t));
  486 }
  487 
  488 void cleanup(dt_iop_module_t *module)
  489 {
  490   free(module->params);
  491   module->params = NULL;
  492 }
  493 
  494 
  495 static void _iop_zonesystem_redraw_preview_callback(gpointer instance, gpointer user_data);
  496 
  497 static gboolean dt_iop_zonesystem_preview_draw(GtkWidget *widget, cairo_t *crf, dt_iop_module_t *self);
  498 
  499 static gboolean dt_iop_zonesystem_bar_draw(GtkWidget *widget, cairo_t *crf, dt_iop_module_t *self);
  500 static gboolean dt_iop_zonesystem_bar_motion_notify(GtkWidget *widget, GdkEventMotion *event,
  501                                                     dt_iop_module_t *self);
  502 static gboolean dt_iop_zonesystem_bar_leave_notify(GtkWidget *widget, GdkEventCrossing *event,
  503                                                    dt_iop_module_t *self);
  504 static gboolean dt_iop_zonesystem_bar_button_press(GtkWidget *widget, GdkEventButton *event,
  505                                                    dt_iop_module_t *self);
  506 static gboolean dt_iop_zonesystem_bar_button_release(GtkWidget *widget, GdkEventButton *event,
  507                                                      dt_iop_module_t *self);
  508 static gboolean dt_iop_zonesystem_bar_scrolled(GtkWidget *widget, GdkEventScroll *event,
  509                                                dt_iop_module_t *self);
  510 
  511 
  512 static void size_allocate_callback(GtkWidget *widget, GtkAllocation *allocation, gpointer user_data)
  513 {
  514   dt_iop_module_t *self = (dt_iop_module_t *)user_data;
  515   dt_iop_zonesystem_gui_data_t *g = (dt_iop_zonesystem_gui_data_t *)self->gui_data;
  516 
  517   if(g->image) cairo_surface_destroy(g->image);
  518   free(g->image_buffer);
  519 
  520   /* load the dt logo as a brackground */
  521   g->image = dt_util_get_logo(MIN(allocation->width, allocation->height) * 0.75);
  522   if(g->image)
  523   {
  524     g->image_buffer = cairo_image_surface_get_data(g->image);
  525     g->image_width = cairo_image_surface_get_width(g->image);
  526     g->image_height = cairo_image_surface_get_height(g->image);
  527   }
  528   else
  529   {
  530     g->image_buffer = NULL;
  531     g->image_width = 0;
  532     g->image_height = 0;
  533   }
  534 }
  535 
  536 void gui_init(struct dt_iop_module_t *self)
  537 {
  538   self->gui_data = malloc(sizeof(dt_iop_zonesystem_gui_data_t));
  539   dt_iop_zonesystem_gui_data_t *g = (dt_iop_zonesystem_gui_data_t *)self->gui_data;
  540   g->in_preview_buffer = g->out_preview_buffer = NULL;
  541   g->is_dragging = FALSE;
  542   g->hilite_zone = FALSE;
  543   g->preview_width = g->preview_height = 0;
  544   g->mouse_over_output_zones = FALSE;
  545 
  546   dt_pthread_mutex_init(&g->lock, NULL);
  547 
  548   self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_IOP_MODULE_CONTROL_SPACING);
  549   dt_gui_add_help_link(self->widget, dt_get_help_url(self->op));
  550 
  551   g->preview = dtgtk_drawing_area_new_with_aspect_ratio(1.0);
  552   g_signal_connect(G_OBJECT(g->preview), "size-allocate", G_CALLBACK(size_allocate_callback), self);
  553   g_signal_connect(G_OBJECT(g->preview), "draw", G_CALLBACK(dt_iop_zonesystem_preview_draw), self);
  554   gtk_widget_add_events(GTK_WIDGET(g->preview), GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK
  555                                                 | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
  556                                                 | GDK_LEAVE_NOTIFY_MASK);
  557 
  558   /* create the zonesystem bar widget */
  559   g->zones = gtk_drawing_area_new();
  560   gtk_widget_set_tooltip_text(g->zones, _("lightness zones\nuse mouse scrollwheel to change the number of zones\n"
  561                                           "left-click on a border to create a marker\n"
  562                                           "right-click on a marker to delete it"));
  563   g_signal_connect(G_OBJECT(g->zones), "draw", G_CALLBACK(dt_iop_zonesystem_bar_draw), self);
  564   g_signal_connect(G_OBJECT(g->zones), "motion-notify-event", G_CALLBACK(dt_iop_zonesystem_bar_motion_notify),
  565                    self);
  566   g_signal_connect(G_OBJECT(g->zones), "leave-notify-event", G_CALLBACK(dt_iop_zonesystem_bar_leave_notify),
  567                    self);
  568   g_signal_connect(G_OBJECT(g->zones), "button-press-event", G_CALLBACK(dt_iop_zonesystem_bar_button_press),
  569                    self);
  570   g_signal_connect(G_OBJECT(g->zones), "button-release-event",
  571                    G_CALLBACK(dt_iop_zonesystem_bar_button_release), self);
  572   g_signal_connect(G_OBJECT(g->zones), "scroll-event", G_CALLBACK(dt_iop_zonesystem_bar_scrolled), self);
  573   gtk_widget_add_events(GTK_WIDGET(g->zones), GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK
  574                                               | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
  575                                               | GDK_LEAVE_NOTIFY_MASK | darktable.gui->scroll_mask);
  576   gtk_widget_set_size_request(g->zones, -1, DT_PIXEL_APPLY_DPI(40));
  577 
  578   gtk_box_pack_start(GTK_BOX(self->widget), g->preview, TRUE, TRUE, 0);
  579   gtk_box_pack_start(GTK_BOX(self->widget), g->zones, TRUE, TRUE, 0);
  580 
  581   /* add signal handler for preview pipe finish to redraw the preview */
  582   dt_control_signal_connect(darktable.signals, DT_SIGNAL_DEVELOP_PREVIEW_PIPE_FINISHED,
  583                             G_CALLBACK(_iop_zonesystem_redraw_preview_callback), self);
  584 
  585 
  586   g->image = NULL;
  587   g->image_buffer = NULL;
  588   g->image_width = 0;
  589   g->image_height = 0;
  590 }
  591 
  592 void gui_cleanup(struct dt_iop_module_t *self)
  593 {
  594   dt_control_signal_disconnect(darktable.signals, G_CALLBACK(_iop_zonesystem_redraw_preview_callback), self);
  595 
  596   dt_iop_zonesystem_gui_data_t *g = (dt_iop_zonesystem_gui_data_t *)self->gui_data;
  597   g_free(g->in_preview_buffer);
  598   g_free(g->out_preview_buffer);
  599   if(g->image) cairo_surface_destroy(g->image);
  600   free(g->image_buffer);
  601   dt_pthread_mutex_destroy(&g->lock);
  602   self->request_color_pick = DT_REQUEST_COLORPICK_OFF;
  603   free(self->gui_data);
  604   self->gui_data = NULL;
  605 }
  606 
  607 #define DT_ZONESYSTEM_INSET DT_PIXEL_APPLY_DPI(5)
  608 #define DT_ZONESYSTEM_BAR_SPLIT_WIDTH 0.0
  609 #define DT_ZONESYSTEM_REFERENCE_SPLIT 0.30
  610 static gboolean dt_iop_zonesystem_bar_draw(GtkWidget *widget, cairo_t *crf, dt_iop_module_t *self)
  611 {
  612   dt_iop_zonesystem_gui_data_t *g = (dt_iop_zonesystem_gui_data_t *)self->gui_data;
  613   dt_iop_zonesystem_params_t *p = (dt_iop_zonesystem_params_t *)self->params;
  614 
  615   const int inset = DT_ZONESYSTEM_INSET;
  616   GtkAllocation allocation;
  617   gtk_widget_get_allocation(widget, &allocation);
  618   int width = allocation.width, height = allocation.height;
  619   cairo_surface_t *cst = dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
  620   cairo_t *cr = cairo_create(cst);
  621 
  622   /* clear background */
  623   cairo_set_source_rgb(cr, .15, .15, .15);
  624   cairo_paint(cr);
  625 
  626 
  627   /* translate and scale */
  628   width -= 2 * inset;
  629   height -= 2 * inset;
  630   cairo_save(cr);
  631   cairo_translate(cr, inset, inset);
  632   cairo_scale(cr, width, height);
  633 
  634   /* render the bars */
  635   float zonemap[MAX_ZONE_SYSTEM_SIZE] = { 0 };
  636   _iop_zonesystem_calculate_zonemap(p, zonemap);
  637   float s = (1. / (p->size - 2));
  638   cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
  639   for(int i = 0; i < p->size - 1; i++)
  640   {
  641     /* draw the reference zone */
  642     float z = s * i;
  643     cairo_rectangle(cr, (1. / (p->size - 1)) * i, 0, (1. / (p->size - 1)),
  644                     DT_ZONESYSTEM_REFERENCE_SPLIT - DT_ZONESYSTEM_BAR_SPLIT_WIDTH);
  645     cairo_set_source_rgb(cr, z, z, z);
  646     cairo_fill(cr);
  647 
  648     /* draw zone mappings */
  649     cairo_rectangle(cr, zonemap[i], DT_ZONESYSTEM_REFERENCE_SPLIT + DT_ZONESYSTEM_BAR_SPLIT_WIDTH,
  650                     (zonemap[i + 1] - zonemap[i]), 1.0 - DT_ZONESYSTEM_REFERENCE_SPLIT);
  651     cairo_set_source_rgb(cr, z, z, z);
  652     cairo_fill(cr);
  653   }
  654   cairo_set_antialias(cr, CAIRO_ANTIALIAS_DEFAULT);
  655   cairo_restore(cr);
  656 
  657   /* render zonebar control lines */
  658   cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
  659   cairo_set_line_width(cr, 1.);
  660   cairo_rectangle(cr, inset, inset, width, height);
  661   cairo_set_source_rgb(cr, .1, .1, .1);
  662   cairo_stroke(cr);
  663   cairo_set_antialias(cr, CAIRO_ANTIALIAS_DEFAULT);
  664 
  665   /* render control points handles */
  666   cairo_set_source_rgb(cr, 0.6, 0.6, 0.6);
  667   cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.));
  668   const float arrw = DT_PIXEL_APPLY_DPI(7.0f);
  669   for(int k = 1; k < p->size - 1; k++)
  670   {
  671     float nzw = zonemap[k + 1] - zonemap[k];
  672     float pzw = zonemap[k] - zonemap[k - 1];
  673     if((((g->mouse_x / width) > (zonemap[k] - (pzw / 2.0)))
  674         && ((g->mouse_x / width) < (zonemap[k] + (nzw / 2.0)))) || p->zone[k] != -1)
  675     {
  676       gboolean is_under_mouse = ((width * zonemap[k]) - arrw * .5f < g->mouse_x
  677                                  && (width * zonemap[k]) + arrw * .5f > g->mouse_x);
  678 
  679       cairo_move_to(cr, inset + (width * zonemap[k]), height + (2 * inset) - 1);
  680       cairo_rel_line_to(cr, -arrw * .5f, 0);
  681       cairo_rel_line_to(cr, arrw * .5f, -arrw);
  682       cairo_rel_line_to(cr, arrw * .5f, arrw);
  683       cairo_close_path(cr);
  684 
  685       if(is_under_mouse)
  686         cairo_fill(cr);
  687       else
  688         cairo_stroke(cr);
  689     }
  690   }
  691 
  692 
  693   /* push mem surface into widget */
  694   cairo_destroy(cr);
  695   cairo_set_source_surface(crf, cst, 0, 0);
  696   cairo_paint(crf);
  697   cairo_surface_destroy(cst);
  698 
  699   return TRUE;
  700 }
  701 
  702 static gboolean dt_iop_zonesystem_bar_button_press(GtkWidget *widget, GdkEventButton *event,
  703                                                    dt_iop_module_t *self)
  704 {
  705   dt_iop_zonesystem_params_t *p = (dt_iop_zonesystem_params_t *)self->params;
  706   dt_iop_zonesystem_gui_data_t *g = (dt_iop_zonesystem_gui_data_t *)self->gui_data;
  707   const int inset = DT_ZONESYSTEM_INSET;
  708   GtkAllocation allocation;
  709   gtk_widget_get_allocation(widget, &allocation);
  710   int width = allocation.width - 2 * inset; /*, height = allocation.height - 2*inset;*/
  711 
  712   /* calculate zonemap */
  713   float zonemap[MAX_ZONE_SYSTEM_SIZE] = { -1 };
  714   _iop_zonesystem_calculate_zonemap(p, zonemap);
  715 
  716   /* translate mouse into zone index */
  717   int k = _iop_zonesystem_zone_index_from_lightness(g->mouse_x / width, zonemap, p->size);
  718   float zw = zonemap[k + 1] - zonemap[k];
  719   if((g->mouse_x / width) > zonemap[k] + (zw / 2)) k++;
  720 
  721 
  722   if(event->button == 1)
  723   {
  724     if(p->zone[k] == -1)
  725     {
  726       p->zone[k] = zonemap[k];
  727       dt_dev_add_history_item(darktable.develop, self, TRUE);
  728     }
  729     g->is_dragging = TRUE;
  730     g->current_zone = k;
  731   }
  732   else if(event->button == 3)
  733   {
  734     /* clear the controlpoint */
  735     p->zone[k] = -1;
  736     dt_dev_add_history_item(darktable.develop, self, TRUE);
  737   }
  738 
  739   return TRUE;
  740 }
  741 
  742 static gboolean dt_iop_zonesystem_bar_button_release(GtkWidget *widget, GdkEventButton *event,
  743                                                      dt_iop_module_t *self)
  744 {
  745   dt_iop_zonesystem_gui_data_t *g = (dt_iop_zonesystem_gui_data_t *)self->gui_data;
  746   if(event->button == 1)
  747   {
  748     g->is_dragging = FALSE;
  749   }
  750   return TRUE;
  751 }
  752 
  753 static gboolean dt_iop_zonesystem_bar_scrolled(GtkWidget *widget, GdkEventScroll *event, dt_iop_module_t *self)
  754 {
  755   dt_iop_zonesystem_params_t *p = (dt_iop_zonesystem_params_t *)self->params;
  756   int cs = CLAMP(p->size, 4, MAX_ZONE_SYSTEM_SIZE);
  757 
  758   int delta_y;
  759   if(dt_gui_get_scroll_unit_deltas(event, NULL, &delta_y))
  760   {
  761     p->size = CLAMP(p->size - delta_y, 4, MAX_ZONE_SYSTEM_SIZE);
  762     p->zone[cs] = -1;
  763     dt_dev_add_history_item(darktable.develop, self, TRUE);
  764     gtk_widget_queue_draw(widget);
  765   }
  766 
  767   return TRUE;
  768 }
  769 
  770 static gboolean dt_iop_zonesystem_bar_leave_notify(GtkWidget *widget, GdkEventCrossing *event,
  771                                                    dt_iop_module_t *self)
  772 {
  773   dt_iop_zonesystem_gui_data_t *g = (dt_iop_zonesystem_gui_data_t *)self->gui_data;
  774   g->hilite_zone = FALSE;
  775   gtk_widget_queue_draw(g->preview);
  776   return TRUE;
  777 }
  778 
  779 static gboolean dt_iop_zonesystem_bar_motion_notify(GtkWidget *widget, GdkEventMotion *event,
  780                                                     dt_iop_module_t *self)
  781 {
  782   dt_iop_zonesystem_params_t *p = (dt_iop_zonesystem_params_t *)self->params;
  783   dt_iop_zonesystem_gui_data_t *g = (dt_iop_zonesystem_gui_data_t *)self->gui_data;
  784   const int inset = DT_ZONESYSTEM_INSET;
  785   GtkAllocation allocation;
  786   gtk_widget_get_allocation(widget, &allocation);
  787   int width = allocation.width - 2 * inset, height = allocation.height - 2 * inset;
  788 
  789   /* calculate zonemap */
  790   float zonemap[MAX_ZONE_SYSTEM_SIZE] = { -1 };
  791   _iop_zonesystem_calculate_zonemap(p, zonemap);
  792 
  793   /* record mouse position within control */
  794   g->mouse_x = CLAMP(event->x - inset, 0, width);
  795   g->mouse_y = CLAMP(height - 1 - event->y + inset, 0, height);
  796 
  797   if(g->is_dragging)
  798   {
  799     if((g->mouse_x / width) > zonemap[g->current_zone - 1]
  800        && (g->mouse_x / width) < zonemap[g->current_zone + 1])
  801     {
  802       p->zone[g->current_zone] = (g->mouse_x / width);
  803       dt_dev_add_history_item(darktable.develop, self, TRUE);
  804     }
  805   }
  806   else
  807   {
  808     /* decide which zone the mouse is over */
  809     if(g->mouse_y >= height * (1.0 - DT_ZONESYSTEM_REFERENCE_SPLIT))
  810     {
  811       g->zone_under_mouse = (g->mouse_x / width) / (1.0 / (p->size - 1));
  812       g->mouse_over_output_zones = TRUE;
  813     }
  814     else
  815     {
  816       float xpos = g->mouse_x / width;
  817       for(int z = 0; z < p->size - 1; z++)
  818       {
  819         if(xpos >= zonemap[z] && xpos < zonemap[z + 1])
  820         {
  821           g->zone_under_mouse = z;
  822           break;
  823         }
  824       }
  825       g->mouse_over_output_zones = FALSE;
  826     }
  827     g->hilite_zone = (g->mouse_y < height) ? TRUE : FALSE;
  828   }
  829 
  830   gtk_widget_queue_draw(self->widget);
  831   gtk_widget_queue_draw(g->preview);
  832   return TRUE;
  833 }
  834 
  835 
  836 static gboolean dt_iop_zonesystem_preview_draw(GtkWidget *widget, cairo_t *crf, dt_iop_module_t *self)
  837 {
  838   const int inset = DT_PIXEL_APPLY_DPI(2);
  839   GtkAllocation allocation;
  840   gtk_widget_get_allocation(widget, &allocation);
  841   int width = allocation.width, height = allocation.height;
  842 
  843   dt_iop_zonesystem_gui_data_t *g = (dt_iop_zonesystem_gui_data_t *)self->gui_data;
  844   dt_iop_zonesystem_params_t *p = (dt_iop_zonesystem_params_t *)self->params;
  845 
  846   cairo_surface_t *cst = dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
  847   cairo_t *cr = cairo_create(cst);
  848 
  849   /* clear background */
  850   GtkStyleContext *context = gtk_widget_get_style_context(self->expander);
  851   gtk_render_background(context, cr, 0, 0, allocation.width, allocation.height);
  852 
  853   width -= 2 * inset;
  854   height -= 2 * inset;
  855   cairo_translate(cr, inset, inset);
  856 
  857   dt_pthread_mutex_lock(&g->lock);
  858   if(g->in_preview_buffer && g->out_preview_buffer && self->enabled)
  859   {
  860     /* calculate the zonemap */
  861     float zonemap[MAX_ZONE_SYSTEM_SIZE] = { -1 };
  862     _iop_zonesystem_calculate_zonemap(p, zonemap);
  863 
  864     /* let's generate a pixbuf from pixel zone buffer */
  865     guchar *image = g_malloc_n((size_t)4 * g->preview_width * g->preview_height, sizeof(guchar));
  866     guchar *buffer = g->mouse_over_output_zones ? g->out_preview_buffer : g->in_preview_buffer;
  867     for(int k = 0; k < g->preview_width * g->preview_height; k++)
  868     {
  869       int zone = 255 * CLIP(((1.0 / (p->size - 1)) * buffer[k]));
  870       image[4 * k + 2] = (g->hilite_zone && buffer[k] == g->zone_under_mouse) ? 255 : zone;
  871       image[4 * k + 1] = (g->hilite_zone && buffer[k] == g->zone_under_mouse) ? 255 : zone;
  872       image[4 * k + 0] = (g->hilite_zone && buffer[k] == g->zone_under_mouse) ? 0 : zone;
  873     }
  874     dt_pthread_mutex_unlock(&g->lock);
  875 
  876     const int wd = g->preview_width, ht = g->preview_height;
  877     const float scale = fminf(width / (float)wd, height / (float)ht);
  878     const int stride = cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, wd);
  879     cairo_surface_t *surface = cairo_image_surface_create_for_data(image, CAIRO_FORMAT_RGB24, wd, ht, stride);
  880     cairo_translate(cr, width / 2.0, height / 2.0f);
  881     cairo_scale(cr, scale, scale);
  882     cairo_translate(cr, -.5f * wd, -.5f * ht);
  883 
  884     cairo_rectangle(cr, DT_PIXEL_APPLY_DPI(1), DT_PIXEL_APPLY_DPI(1), wd - DT_PIXEL_APPLY_DPI(2),
  885                     ht - DT_PIXEL_APPLY_DPI(2));
  886     cairo_set_source_surface(cr, surface, 0, 0);
  887     cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_GOOD);
  888     cairo_fill_preserve(cr);
  889     cairo_surface_destroy(surface);
  890 
  891     cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.0));
  892     cairo_set_source_rgb(cr, .1, .1, .1);
  893     cairo_stroke(cr);
  894 
  895     g_free(image);
  896   }
  897   else
  898   {
  899     dt_pthread_mutex_unlock(&g->lock);
  900     // draw a big, subdued dt logo
  901     if(g->image)
  902     {
  903       GdkRGBA *color;
  904       gtk_style_context_get(context, gtk_widget_get_state_flags(self->expander), "background-color", &color,
  905                             NULL);
  906 
  907       cairo_set_source_surface(cr, g->image, (width - g->image_width) * 0.5, (height - g->image_height) * 0.5);
  908       cairo_rectangle(cr, 0, 0, width, height);
  909       cairo_set_operator(cr, CAIRO_OPERATOR_HSL_LUMINOSITY);
  910       cairo_fill_preserve(cr);
  911       cairo_set_operator(cr, CAIRO_OPERATOR_DARKEN);
  912       cairo_set_source_rgb(cr, color->red + 0.02, color->green + 0.02, color->blue + 0.02);
  913       cairo_fill_preserve(cr);
  914       cairo_set_operator(cr, CAIRO_OPERATOR_LIGHTEN);
  915       cairo_set_source_rgb(cr, color->red - 0.02, color->green - 0.02, color->blue - 0.02);
  916       cairo_fill(cr);
  917 
  918       gdk_rgba_free(color);
  919     }
  920   }
  921 
  922   cairo_destroy(cr);
  923   cairo_set_source_surface(crf, cst, 0, 0);
  924   cairo_paint(crf);
  925   cairo_surface_destroy(cst);
  926 
  927   return TRUE;
  928 }
  929 
  930 void _iop_zonesystem_redraw_preview_callback(gpointer instance, gpointer user_data)
  931 {
  932   dt_iop_module_t *self = (dt_iop_module_t *)user_data;
  933   dt_iop_zonesystem_gui_data_t *g = (dt_iop_zonesystem_gui_data_t *)self->gui_data;
  934 
  935   dt_control_queue_redraw_widget(g->preview);
  936 }
  937 
  938 // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
  939 // vim: shiftwidth=2 expandtab tabstop=2 cindent
  940 // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;