Beispiel #1
0
void dt_dev_reprocess_center(dt_develop_t *dev)
{
  if(darktable.gui->reset) return;
  if(dev && dev->gui_attached)
  {
    dev->pipe->changed |= DT_DEV_PIPE_SYNCH;
    dev->pipe->cache_obsolete = 1;

    // invalidate buffers and force redraw of darkroom
    dt_dev_invalidate_all(dev);

    /* redraw */
    dt_control_queue_redraw_center();
  }
}
Beispiel #2
0
void dt_dev_pop_history_items(dt_develop_t *dev, int32_t cnt)
{
  // printf("dev popping all history items >= %d\n", cnt);
  dt_pthread_mutex_lock(&dev->history_mutex);
  darktable.gui->reset = 1;
  dev->history_end = cnt;
  // reset gui params for all modules
  GList *modules = dev->iop;
  while(modules)
  {
    dt_iop_module_t *module = (dt_iop_module_t *)(modules->data);
    memcpy(module->params, module->default_params, module->params_size);
    memcpy(module->blend_params, module->default_blendop_params,sizeof(dt_develop_blend_params_t));
    module->enabled = module->default_enabled;
    modules = g_list_next(modules);
  }
  // go through history and set gui params
  GList *history = dev->history;
  for(int i=0; i<cnt && history; i++)
  {
    dt_dev_history_item_t *hist = (dt_dev_history_item_t *)(history->data);
    memcpy(hist->module->params, hist->params, hist->module->params_size);
    memcpy(hist->module->blend_params, hist->blend_params, sizeof(dt_develop_blend_params_t));

    hist->module->enabled = hist->enabled;
    history = g_list_next(history);
  }
  // update all gui modules
  modules = dev->iop;
  while(modules)
  {
    dt_iop_module_t *module = (dt_iop_module_t *)(modules->data);
    dt_iop_gui_update(module);
    modules = g_list_next(modules);
  }
  dev->pipe->changed |= DT_DEV_PIPE_SYNCH;
  dev->preview_pipe->changed |= DT_DEV_PIPE_SYNCH; // again, fixed topology for now.
  darktable.gui->reset = 0;
  dt_dev_invalidate_all(dev);
  dt_pthread_mutex_unlock(&dev->history_mutex);
  dt_control_queue_redraw_center();
}
Beispiel #3
0
void dt_dev_read_history(dt_develop_t *dev)
{
  if(!dev->image_storage.id) return;
  sqlite3_stmt *stmt;
  DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "select imgid, num, module, operation, op_params, enabled, blendop_params, blendop_version from history where imgid = ?1 order by num", -1, &stmt, NULL);
  DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, dev->image_storage.id);
  dev->history_end = 0;
  while(sqlite3_step(stmt) == SQLITE_ROW)
  {
    // db record:
    // 0-img, 1-num, 2-module_instance, 3-operation char, 4-params blob, 5-enabled, 6-blend_params, 7-blendop_version
    dt_dev_history_item_t *hist = (dt_dev_history_item_t *)malloc(sizeof(dt_dev_history_item_t));
    hist->enabled = sqlite3_column_int(stmt, 5);

    GList *modules = dev->iop;
    const char *opname = (const char *)sqlite3_column_text(stmt, 3);
    if(!opname)
    {
      fprintf(stderr, "[dev_read_history] database history for image `%s' seems to be corrupted!\n", dev->image_storage.filename);
      free(hist);
      continue;
    }
   
    hist->module = NULL;
    while(opname && modules)
    {
      dt_iop_module_t *module = (dt_iop_module_t *)modules->data;
      if(!strcmp(module->op, opname))
      {
        hist->module = module;
        break;
      }
      modules = g_list_next(modules);
    }
    if(!hist->module)
    {
      fprintf(stderr, "[dev_read_history] the module `%s' requested by image `%s' is not installed on this computer!\n", opname, dev->image_storage.filename);
      free(hist);
      continue;
    }
    int modversion = sqlite3_column_int(stmt, 2);
    assert(strcmp((char *)sqlite3_column_text(stmt, 3), hist->module->op) == 0);
    hist->params = malloc(hist->module->params_size);
    hist->blend_params = malloc(sizeof(dt_develop_blend_params_t));
    if(hist->module->version() != modversion || hist->module->params_size != sqlite3_column_bytes(stmt, 4) ||
        strcmp((char *)sqlite3_column_text(stmt, 3), hist->module->op))
    {
      if(!hist->module->legacy_params ||
          hist->module->legacy_params(hist->module, sqlite3_column_blob(stmt, 4), labs(modversion), hist->params, labs(hist->module->version())))
      {
        free(hist->params);
        free(hist->blend_params);
        fprintf(stderr, "[dev_read_history] module `%s' version mismatch: history is %d, dt %d.\n", hist->module->op, modversion, hist->module->version());
        const char *fname = dev->image_storage.filename + strlen(dev->image_storage.filename);
        while(fname > dev->image_storage.filename && *fname != '/') fname --;
        if(fname > dev->image_storage.filename) fname++;
        dt_control_log(_("%s: module `%s' version mismatch: %d != %d"), fname, hist->module->op, hist->module->version(), modversion);
        free(hist);
        continue;
      }
    }
    else
    {
      memcpy(hist->params, sqlite3_column_blob(stmt, 4), hist->module->params_size);
    }

    const void *blendop_params = sqlite3_column_blob(stmt, 6);
    int bl_length = sqlite3_column_bytes(stmt, 6);
    int blendop_version = sqlite3_column_int(stmt, 7);

    if (blendop_params && (blendop_version == dt_develop_blend_version()) && (bl_length == sizeof(dt_develop_blend_params_t)))
    {
      memcpy(hist->blend_params, blendop_params, sizeof(dt_develop_blend_params_t));
    }
    else if (blendop_params && dt_develop_blend_legacy_params(hist->module, blendop_params, blendop_version, hist->blend_params, dt_develop_blend_version(), bl_length) == 0)
    {
      // do nothing
    }
    else
    {
      memcpy(hist->blend_params, hist->module->default_blendop_params, sizeof(dt_develop_blend_params_t));
    }

    // memcpy(hist->module->params, hist->params, hist->module->params_size);
    // hist->module->enabled = hist->enabled;
    // printf("[dev read history] img %d number %d for operation %d - %s params %f %f\n", sqlite3_column_int(stmt, 0), sqlite3_column_int(stmt, 1), instance, hist->module->op, *(float *)hist->params, *(((float*)hist->params)+1));
    dev->history = g_list_append(dev->history, hist);
    dev->history_end ++;
  }

  if(dev->gui_attached)
  {
    dev->pipe->changed |= DT_DEV_PIPE_SYNCH;
    dev->preview_pipe->changed |= DT_DEV_PIPE_SYNCH; // again, fixed topology for now.
    dt_dev_invalidate_all(dev);

    /* signal history changed */
    dt_control_signal_raise(darktable.signals,DT_SIGNAL_DEVELOP_HISTORY_CHANGE);
  }
  sqlite3_finalize (stmt);
}
Beispiel #4
0
void dt_dev_add_history_item(dt_develop_t *dev, dt_iop_module_t *module, gboolean enable)
{
  if(darktable.gui->reset) return;
  dt_pthread_mutex_lock(&dev->history_mutex);
  if(dev->gui_attached)
  {
    GList *history = g_list_nth (dev->history, dev->history_end);
    while(history)
    {
      GList *next = g_list_next(history);
      dt_dev_history_item_t *hist = (dt_dev_history_item_t *)(history->data);
      // printf("removing obsoleted history item: %s\n", hist->module->op);
      free(hist->params);
      free(hist->blend_params);
      free(history->data);
      dev->history = g_list_delete_link(dev->history, history);
      history = next;
    }
    history = g_list_nth(dev->history, dev->history_end-1);
    if(!history || module->instance != ((dt_dev_history_item_t *)history->data)->module->instance)
    {
      // new operation, push new item
      // printf("adding new history item %d - %s\n", dev->history_end, module->op);
      // if(history) printf("because item %d - %s is different operation.\n", dev->history_end-1, ((dt_dev_history_item_t *)history->data)->module->op);
      dev->history_end++;
      dt_dev_history_item_t *hist = (dt_dev_history_item_t *)malloc(sizeof(dt_dev_history_item_t));
      if (enable)
      {
        module->enabled = TRUE;
        if(module->off)
        {
          darktable.gui->reset = 1;
          gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(module->off), module->enabled);
          darktable.gui->reset = 0;
        }
      }
      hist->enabled = module->enabled;
      hist->module = module;
      hist->params = malloc(module->params_size);

      /* allocate and set hist blend_params */
      hist->blend_params = malloc(sizeof(dt_develop_blend_params_t));
      memset(hist->blend_params, 0, sizeof(dt_develop_blend_params_t));

      memcpy(hist->params, module->params, module->params_size);
      if(module->flags() & IOP_FLAGS_SUPPORTS_BLENDING)
        memcpy(hist->blend_params, module->blend_params, sizeof(dt_develop_blend_params_t));

      dev->history = g_list_append(dev->history, hist);
      dev->pipe->changed |= DT_DEV_PIPE_SYNCH;
      dev->preview_pipe->changed |= DT_DEV_PIPE_SYNCH; // topology remains, as modules are fixed for now.
    }
    else
    {
      // same operation, change params
      // printf("changing same history item %d - %s\n", dev->history_end-1, module->op);
      dt_dev_history_item_t *hist = (dt_dev_history_item_t *)history->data;
      memcpy(hist->params, module->params, module->params_size);

      if(module->flags() & IOP_FLAGS_SUPPORTS_BLENDING)
        memcpy(hist->blend_params, module->blend_params, sizeof(dt_develop_blend_params_t));

      // if the user changed stuff and the module is still not enabled, do it:
      if(!hist->enabled && !module->enabled)
      {
        module->enabled = 1;
        if(module->off)
        {
          darktable.gui->reset = 1;
          gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(module->off), module->enabled);
          darktable.gui->reset = 0;
        }
      }
      hist->enabled = module->enabled;
      dev->pipe->changed |= DT_DEV_PIPE_TOP_CHANGED;
      dev->preview_pipe->changed |= DT_DEV_PIPE_TOP_CHANGED;
    }
  }
#if 0
  {
    // debug:
    printf("remaining %d history items:\n", dev->history_end);
    GList *history = dev->history;
    int i = 0;
    while(history)
    {
      dt_dev_history_item_t *hist = (dt_dev_history_item_t *)(history->data);
      printf("%d %s\n", i, hist->module->op);
      history = g_list_next(history);
      i++;
    }
  }
#endif

  /* invalidate image data*/
  dt_similarity_image_dirty(dev->image_storage.id);
  
  // invalidate buffers and force redraw of darkroom
  dt_dev_invalidate_all(dev);
  dt_pthread_mutex_unlock(&dev->history_mutex);

  if(dev->gui_attached)
  {
    /* signal that history has changed */
    dt_control_signal_raise(darktable.signals, DT_SIGNAL_DEVELOP_HISTORY_CHANGE);

    /* redraw */
    dt_control_queue_redraw_center();
  }
}
Beispiel #5
0
void dt_dev_read_history(dt_develop_t *dev)
{
  if(dev->image_storage.id <= 0) return;
  if(!dev->iop) return;

  // maybe prepend auto-presets to history before loading it:
  auto_apply_presets(dev);

  sqlite3_stmt *stmt;
  DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
                              "select imgid, num, module, operation, op_params, enabled, blendop_params, blendop_version, multi_priority, multi_name from history where imgid = ?1 order by num", -1, &stmt, NULL);
  DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, dev->image_storage.id);
  dev->history_end = 0;
  while(sqlite3_step(stmt) == SQLITE_ROW)
  {
    // db record:
    // 0-img, 1-num, 2-module_instance, 3-operation char, 4-params blob, 5-enabled, 6-blend_params, 7-blendop_version, 8 multi_priority, 9 multi_name
    dt_dev_history_item_t *hist = (dt_dev_history_item_t *)malloc(sizeof(dt_dev_history_item_t));
    hist->enabled = sqlite3_column_int(stmt, 5);

    GList *modules = dev->iop;
    const char *opname = (const char *)sqlite3_column_text(stmt, 3);
    int multi_priority = sqlite3_column_int(stmt, 8);
    const char *multi_name = (const char *)sqlite3_column_text(stmt, 9);
    if(!opname)
    {
      fprintf(stderr, "[dev_read_history] database history for image `%s' seems to be corrupted!\n", dev->image_storage.filename);
      free(hist);
      continue;
    }

    hist->module = NULL;
    dt_iop_module_t *find_op = NULL;
    while(opname && modules)
    {
      dt_iop_module_t *module = (dt_iop_module_t *)modules->data;
      if(!strcmp(module->op, opname))
      {
        if (module->multi_priority == multi_priority)
        {
          hist->module = module;
          if(multi_name && strcmp(module->multi_name, multi_name))
            snprintf(module->multi_name, 128, "%s", multi_name);
          break;
        }
        else if (multi_priority > 0)
        {
          //we just say that we find the name, so we just have to add new instance of this module
          find_op = module;
        }
      }
      modules = g_list_next(modules);
    }
    if (!hist->module && find_op)
    {
      //we have to add a new instance of this module and set index to modindex
      dt_iop_module_t *new_module    = (dt_iop_module_t *)malloc(sizeof(dt_iop_module_t));
      if (!dt_iop_load_module(new_module, find_op->so, dev))
      {
        new_module->multi_priority = multi_priority;

        snprintf(new_module->multi_name,128,"%s",multi_name);

        dev->iop = g_list_insert_sorted(dev->iop, new_module, sort_plugins);

        new_module->instance = find_op->instance;
        hist->module = new_module;
      }
    }

    if(!hist->module && opname)
    {
      fprintf(stderr, "[dev_read_history] the module `%s' requested by image `%s' is not installed on this computer!\n", opname, dev->image_storage.filename);
      free(hist);
      continue;
    }

    if(hist->module->flags() & IOP_FLAGS_NO_HISTORY_STACK)
    {
      free(hist);
      continue;
    }

    int modversion = sqlite3_column_int(stmt, 2);
    assert(strcmp((char *)sqlite3_column_text(stmt, 3), hist->module->op) == 0);
    hist->params = malloc(hist->module->params_size);
    hist->blend_params = malloc(sizeof(dt_develop_blend_params_t));
    snprintf(hist->multi_name,128,"%s",multi_name);
    hist->multi_priority = multi_priority;

    const void *blendop_params = sqlite3_column_blob(stmt, 6);
    int bl_length = sqlite3_column_bytes(stmt, 6);
    int blendop_version = sqlite3_column_int(stmt, 7);

    if (blendop_params && (blendop_version == dt_develop_blend_version()) && (bl_length == sizeof(dt_develop_blend_params_t)))
    {
      memcpy(hist->blend_params, blendop_params, sizeof(dt_develop_blend_params_t));
    }
    else if (blendop_params && dt_develop_blend_legacy_params(hist->module, blendop_params, blendop_version, hist->blend_params, dt_develop_blend_version(), bl_length) == 0)
    {
      // do nothing
    }
    else
    {
      memcpy(hist->blend_params, hist->module->default_blendop_params, sizeof(dt_develop_blend_params_t));
    }

    if(hist->module->version() != modversion || hist->module->params_size != sqlite3_column_bytes(stmt, 4) ||
        strcmp((char *)sqlite3_column_text(stmt, 3), hist->module->op))
    {
      if(!hist->module->legacy_params ||
          hist->module->legacy_params(hist->module, sqlite3_column_blob(stmt, 4), labs(modversion), hist->params, labs(hist->module->version())))
      {
        free(hist->params);
        free(hist->blend_params);
        fprintf(stderr, "[dev_read_history] module `%s' version mismatch: history is %d, dt %d.\n", hist->module->op, modversion, hist->module->version());
        const char *fname = dev->image_storage.filename + strlen(dev->image_storage.filename);
        while(fname > dev->image_storage.filename && *fname != '/') fname --;
        if(fname > dev->image_storage.filename) fname++;
        dt_control_log(_("%s: module `%s' version mismatch: %d != %d"), fname, hist->module->op, hist->module->version(), modversion);
        free(hist);
        continue;
      }
    }
    else
    {
      memcpy(hist->params, sqlite3_column_blob(stmt, 4), hist->module->params_size);
    }

    // make sure that always-on modules are always on. duh.
    if(hist->module->default_enabled == 1 && hist->module->hide_enable_button == 1)
    {
      hist->enabled = 1;
    }

    // memcpy(hist->module->params, hist->params, hist->module->params_size);
    // hist->module->enabled = hist->enabled;
    // printf("[dev read history] img %d number %d for operation %d - %s params %f %f\n", sqlite3_column_int(stmt, 0), sqlite3_column_int(stmt, 1), instance, hist->module->op, *(float *)hist->params, *(((float*)hist->params)+1));
    dev->history = g_list_append(dev->history, hist);
    dev->history_end ++;
  }

  if(dev->gui_attached)
  {
    dev->pipe->changed |= DT_DEV_PIPE_SYNCH;
    dev->preview_pipe->changed |= DT_DEV_PIPE_SYNCH; // again, fixed topology for now.
    dt_dev_invalidate_all(dev);

    /* signal history changed */
    dt_control_signal_raise(darktable.signals,DT_SIGNAL_DEVELOP_HISTORY_CHANGE);
  }
  sqlite3_finalize (stmt);
}
Beispiel #6
0
int button_pressed(dt_view_t *self, double x, double y, int which, int type, uint32_t state)
{
  const int32_t capwd = darktable.thumbnail_width;
  const int32_t capht = darktable.thumbnail_height;
  dt_develop_t *dev = (dt_develop_t *)self->data;
  const int32_t width_i  = self->width;
  const int32_t height_i = self->height;
  if(width_i  > capwd) x += (capwd-width_i) *.5f;
  if(height_i > capht) y += (capht-height_i)*.5f;

  int handled = 0;
  if(dev->gui_module && dev->gui_module->request_color_pick && which == 1)
  {
    float zoom_x, zoom_y;
    dt_dev_get_pointer_zoom_pos(dev, x, y, &zoom_x, &zoom_y);
    if(darktable.lib->proxy.colorpicker.size)
    {
      dev->gui_module->color_picker_box[0] = .5f+zoom_x;
      dev->gui_module->color_picker_box[1] = .5f+zoom_y;
      dev->gui_module->color_picker_box[2] = .5f+zoom_x;
      dev->gui_module->color_picker_box[3] = .5f+zoom_y;
    }
    else
    {
      dev->gui_module->color_picker_point[0] = .5f+zoom_x;
      dev->gui_module->color_picker_point[1] = .5f+zoom_y;
      dev->preview_pipe->changed |= DT_DEV_PIPE_SYNCH;
      dt_dev_invalidate_all(dev);
    }
    dt_control_queue_redraw();
    return 1;
  }
  if(dev->gui_module && dev->gui_module->button_pressed) handled = dev->gui_module->button_pressed(dev->gui_module, x, y, which, type, state);
  if(handled) return handled;

  if(which == 1 && type == GDK_2BUTTON_PRESS) return 0;
  if(which == 1)
  {
    dt_control_change_cursor(GDK_HAND1);
    return 1;
  }
  if(which == 2)
  {
    // zoom to 1:1 2:1 and back
    dt_dev_zoom_t zoom;
    int closeup, procw, proch;
    float zoom_x, zoom_y;
    DT_CTL_GET_GLOBAL(zoom, dev_zoom);
    DT_CTL_GET_GLOBAL(closeup, dev_closeup);
    DT_CTL_GET_GLOBAL(zoom_x, dev_zoom_x);
    DT_CTL_GET_GLOBAL(zoom_y, dev_zoom_y);
    dt_dev_get_processed_size(dev, &procw, &proch);
    const float scale = dt_dev_get_zoom_scale(dev, zoom, closeup ? 2 : 1, 0);
    zoom_x += (1.0/scale)*(x - .5f*dev->width )/procw;
    zoom_y += (1.0/scale)*(y - .5f*dev->height)/proch;
    if(zoom == DT_ZOOM_1)
    {
      if(!closeup) closeup = 1;
      else
      {
        zoom = DT_ZOOM_FIT;
        zoom_x = zoom_y = 0.0f;
        closeup = 0;
      }
    }
    else zoom = DT_ZOOM_1;
    dt_dev_check_zoom_bounds(dev, &zoom_x, &zoom_y, zoom, closeup, NULL, NULL);
    DT_CTL_SET_GLOBAL(dev_zoom, zoom);
    DT_CTL_SET_GLOBAL(dev_closeup, closeup);
    DT_CTL_SET_GLOBAL(dev_zoom_x, zoom_x);
    DT_CTL_SET_GLOBAL(dev_zoom_y, zoom_y);
    dt_dev_invalidate(dev);
    return 1;
  }
  return 0;
}
Beispiel #7
0
void mouse_moved(dt_view_t *self, double x, double y, int which)
{
  const int32_t capwd = darktable.thumbnail_width;
  const int32_t capht = darktable.thumbnail_height;
  dt_develop_t *dev = (dt_develop_t *)self->data;

  // if we are not hovering over a thumbnail in the filmstrip -> show metadata of opened image.
  int32_t mouse_over_id = -1;
  DT_CTL_GET_GLOBAL(mouse_over_id, lib_image_mouse_over_id);
  if(mouse_over_id == -1)
  {
    mouse_over_id = dev->image_storage.id;
    DT_CTL_SET_GLOBAL(lib_image_mouse_over_id, mouse_over_id);
  }

  dt_control_t *ctl = darktable.control;
  const int32_t width_i  = self->width;
  const int32_t height_i = self->height;
  int32_t offx = 0.0f, offy = 0.0f;
  if(width_i  > capwd) offx = (capwd-width_i) *.5f;
  if(height_i > capht) offy = (capht-height_i)*.5f;
  int handled = 0;
  x += offx;
  y += offy;
  if(dev->gui_module && dev->gui_module->request_color_pick &&
      ctl->button_down &&
      ctl->button_down_which == 1)
  {
    // module requested a color box
    float zoom_x, zoom_y, bzoom_x, bzoom_y;
    dt_dev_get_pointer_zoom_pos(dev, x, y, &zoom_x, &zoom_y);
    dt_dev_get_pointer_zoom_pos(dev, ctl->button_x + offx, ctl->button_y + offy, &bzoom_x, &bzoom_y);
    if(darktable.lib->proxy.colorpicker.size)
    {
      dev->gui_module->color_picker_box[0] = fmaxf(0.0, fminf(.5f+bzoom_x, .5f+zoom_x));
      dev->gui_module->color_picker_box[1] = fmaxf(0.0, fminf(.5f+bzoom_y, .5f+zoom_y));
      dev->gui_module->color_picker_box[2] = fminf(1.0, fmaxf(.5f+bzoom_x, .5f+zoom_x));
      dev->gui_module->color_picker_box[3] = fminf(1.0, fmaxf(.5f+bzoom_y, .5f+zoom_y));
    }
    else
    {
      dev->gui_module->color_picker_point[0] = .5f + zoom_x;
      dev->gui_module->color_picker_point[1] = .5f + zoom_y;
    }

    dev->preview_pipe->changed |= DT_DEV_PIPE_SYNCH;
    dt_dev_invalidate_all(dev);
    dt_control_queue_redraw();
    return;
  }
  if(dev->gui_module && dev->gui_module->mouse_moved) handled = dev->gui_module->mouse_moved(dev->gui_module, x, y, which);
  if(handled) return;

  if(darktable.control->button_down && darktable.control->button_down_which == 1)
  {
    // depending on dev_zoom, adjust dev_zoom_x/y.
    dt_dev_zoom_t zoom;
    int closeup;
    DT_CTL_GET_GLOBAL(zoom, dev_zoom);
    DT_CTL_GET_GLOBAL(closeup, dev_closeup);
    int procw, proch;
    dt_dev_get_processed_size(dev, &procw, &proch);
    const float scale = dt_dev_get_zoom_scale(dev, zoom, closeup ? 2 : 1, 0);
    float old_zoom_x, old_zoom_y;
    DT_CTL_GET_GLOBAL(old_zoom_x, dev_zoom_x);
    DT_CTL_GET_GLOBAL(old_zoom_y, dev_zoom_y);
    float zx = old_zoom_x - (1.0/scale)*(x - ctl->button_x - offx)/procw;
    float zy = old_zoom_y - (1.0/scale)*(y - ctl->button_y - offy)/proch;
    dt_dev_check_zoom_bounds(dev, &zx, &zy, zoom, closeup, NULL, NULL);
    DT_CTL_SET_GLOBAL(dev_zoom_x, zx);
    DT_CTL_SET_GLOBAL(dev_zoom_y, zy);
    ctl->button_x = x - offx;
    ctl->button_y = y - offy;
    dt_dev_invalidate(dev);
    dt_control_queue_redraw();
  }
}