Beispiel #1
0
static gboolean _view_map_motion_notify_callback(GtkWidget *w, GdkEventMotion *e, dt_view_t *self)
{
  dt_map_t *lib = (dt_map_t*)self->data;
  const int ts = 64;

  if(lib->start_drag && lib->selected_image > 0)
  {
    for(GSList *iter = lib->images; iter != NULL; iter = iter->next)
    {
      dt_map_image_t *entry = (dt_map_image_t*)iter->data;
      OsmGpsMapImage *image = entry->image;
      if(entry->imgid == lib->selected_image)
      {
        osm_gps_map_image_remove(lib->map, image);
        break;
      }
    }

    lib->start_drag = FALSE;
    GtkTargetList *targets = gtk_target_list_new(target_list_all, n_targets_all);

    dt_mipmap_buffer_t buf;
    dt_mipmap_size_t mip = dt_mipmap_cache_get_matching_size(darktable.mipmap_cache, ts, ts);
    dt_mipmap_cache_read_get(darktable.mipmap_cache, &buf, lib->selected_image, mip, DT_MIPMAP_BLOCKING);

    if(buf.buf)
    {
      uint8_t *scratchmem = dt_mipmap_cache_alloc_scratchmem(darktable.mipmap_cache);
      uint8_t *buf_decompressed = dt_mipmap_cache_decompress(&buf, scratchmem);

      uint8_t *rgbbuf = g_malloc((buf.width+2)*(buf.height+2)*3);
      memset(rgbbuf, 64, (buf.width+2)*(buf.height+2)*3);
      for(int i=1; i<=buf.height; i++)
        for(int j=1; j<=buf.width; j++)
          for(int k=0; k<3; k++)
            rgbbuf[(i*(buf.width+2)+j)*3+k] = buf_decompressed[((i-1)*buf.width+j-1)*4+2-k];

      int w=ts, h=ts;
      if(buf.width < buf.height) w = (buf.width*ts)/buf.height; // portrait
      else                       h = (buf.height*ts)/buf.width; // landscape

      GdkPixbuf *source = gdk_pixbuf_new_from_data(rgbbuf, GDK_COLORSPACE_RGB, FALSE, 8, (buf.width+2), (buf.height+2), (buf.width+2)*3, NULL, NULL);
      GdkPixbuf *scaled = gdk_pixbuf_scale_simple(source, w, h, GDK_INTERP_HYPER);
      GdkDragContext * context = gtk_drag_begin(GTK_WIDGET(lib->map), targets, GDK_ACTION_COPY, 1, (GdkEvent*)e);
      gtk_drag_set_icon_pixbuf(context, scaled, 0, 0);

      if(source)
        g_object_unref(source);
      if(scaled)
        g_object_unref(scaled);
      g_free(rgbbuf);
    }

    dt_mipmap_cache_read_release(darktable.mipmap_cache, &buf);

    gtk_target_list_unref(targets);
    return TRUE;
  }
  return FALSE;
}
Beispiel #2
0
static void _lib_filmstrip_dnd_begin_callback(GtkWidget *widget, GdkDragContext *context, gpointer user_data)
{
  const int ts = DT_PIXEL_APPLY_DPI(64);

  dt_lib_module_t *self = (dt_lib_module_t *)user_data;
  dt_lib_filmstrip_t *strip = (dt_lib_filmstrip_t *)self->data;

  int imgid = strip->mouse_over_id;

  // imgid part of selection -> do nothing
  // otherwise               -> select the current image
  strip->select = DT_LIB_FILMSTRIP_SELECT_NONE;
  sqlite3_stmt *stmt;
  DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db),
                              "SELECT imgid FROM main.selected_images WHERE imgid=?1 LIMIT 1", -1, &stmt, NULL);
  DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, imgid);
  if(sqlite3_step(stmt) != SQLITE_ROW)
  {
    dt_selection_select_single(darktable.selection, imgid);
    /* redraw filmstrip */
    if(darktable.view_manager->proxy.filmstrip.module)
      gtk_widget_queue_draw(darktable.view_manager->proxy.filmstrip.module->widget);
  }
  sqlite3_finalize(stmt);

  // if we are dragging a single image -> use the thumbnail of that image
  // otherwise use the generic d&d icon
  // TODO: have something pretty in the 2nd case, too.
  if(dt_collection_get_selected_count(NULL) == 1)
  {
    dt_mipmap_buffer_t buf;
    dt_mipmap_size_t mip = dt_mipmap_cache_get_matching_size(darktable.mipmap_cache, ts, ts);
    dt_mipmap_cache_get(darktable.mipmap_cache, &buf, imgid, mip, DT_MIPMAP_BLOCKING, 'r');

    if(buf.buf)
    {
      for(size_t i = 3; i < (size_t)4 * buf.width * buf.height; i += 4) buf.buf[i] = UINT8_MAX;

      int w = ts, h = ts;
      if(buf.width < buf.height)
        w = (buf.width * ts) / buf.height; // portrait
      else
        h = (buf.height * ts) / buf.width; // landscape

      GdkPixbuf *source = gdk_pixbuf_new_from_data(buf.buf, GDK_COLORSPACE_RGB, TRUE, 8, buf.width,
                                                   buf.height, buf.width * 4, NULL, NULL);
      GdkPixbuf *scaled = gdk_pixbuf_scale_simple(source, w, h, GDK_INTERP_HYPER);
      gtk_drag_set_icon_pixbuf(context, scaled, 0, h);

      if(source) g_object_unref(source);
      if(scaled) g_object_unref(scaled);
    }

    dt_mipmap_cache_release(darktable.mipmap_cache, &buf);
  }
}
Beispiel #3
0
void gui_post_expose(dt_lib_module_t *self, cairo_t *cri, int32_t width, int32_t height, int32_t pointerx, int32_t pointery)
{
  dt_lib_duplicate_t *d = (dt_lib_duplicate_t *)self->data;

  if (d->imgid == 0) return;

  const int32_t tb = DT_PIXEL_APPLY_DPI(dt_conf_get_int("plugins/darkroom/ui/border_size"));
  int nw = width-2*tb;
  int nh = height-2*tb;

  dt_mipmap_buffer_t buf;
  dt_mipmap_size_t mip = dt_mipmap_cache_get_matching_size(darktable.mipmap_cache, nw, nh);
  dt_mipmap_cache_get(darktable.mipmap_cache, &buf, d->imgid, mip, DT_MIPMAP_BEST_EFFORT, 'r');

  int img_wd = buf.width;
  int img_ht = buf.height;

  dt_mipmap_cache_release(darktable.mipmap_cache, &buf);

  // and now we get the values to "fit the screen"
  int px,py,nimgw,nimgh;
  if (img_ht*nw > img_wd*nh)
  {
    nimgh = nh;
    nimgw = img_wd*nh/img_ht;
    py=0;
    px=(nw-nimgw)/2;
  }
  else
  {
    nimgw = nw;
    nimgh = img_ht*nw/img_wd;
    px=0;
    py=(nh-nimgh)/2;
  }

  // we erase everything
  cairo_set_source_rgb(cri, .2, .2, .2);
  cairo_paint(cri);

  //we draw the cached image
  dt_view_image_over_t image_over = DT_VIEW_DESERT;
  dt_view_image_expose(&image_over, d->imgid, cri, nw, nh, 1, px+tb, py+tb, TRUE, TRUE);

  //and the nice border line
  cairo_rectangle(cri, tb+px, tb+py, nimgw, nimgh);
  cairo_set_line_width(cri, 1.0);
  cairo_set_source_rgb(cri, .3, .3, .3);
  cairo_stroke(cri);
}
Beispiel #4
0
static void
_lib_filmstrip_dnd_begin_callback(GtkWidget *widget, GdkDragContext *context, gpointer user_data)
{
  const int ts = 64;

  dt_lib_module_t *self = (dt_lib_module_t *)user_data;
  dt_lib_filmstrip_t *strip = (dt_lib_filmstrip_t *)self->data;

  int imgid = strip->mouse_over_id;

  // imgid part of selection -> do nothing
  // otherwise               -> select the current image
  strip->select = DT_LIB_FILMSTRIP_SELECT_NONE;
  sqlite3_stmt *stmt;
  DT_DEBUG_SQLITE3_PREPARE_V2(dt_database_get(darktable.db), "select imgid from selected_images where imgid=?1", -1, &stmt, NULL);
  DT_DEBUG_SQLITE3_BIND_INT(stmt, 1, imgid);
  if(sqlite3_step(stmt) != SQLITE_ROW)
  {
    dt_selection_select_single(darktable.selection, imgid);
    /* redraw filmstrip */
    if(darktable.view_manager->proxy.filmstrip.module)
      gtk_widget_queue_draw(darktable.view_manager->proxy.filmstrip.module->widget);
  }
  sqlite3_finalize(stmt);

  // if we are dragging a single image -> use the thumbnail of that image
  // otherwise use the generic d&d icon
  // TODO: have something pretty in the 2nd case, too.
  if(dt_collection_get_selected_count(NULL) == 1)
  {
    dt_mipmap_buffer_t buf;
    dt_mipmap_size_t mip = dt_mipmap_cache_get_matching_size(darktable.mipmap_cache, ts, ts);
    dt_mipmap_cache_read_get(darktable.mipmap_cache, &buf, imgid, mip, DT_MIPMAP_BLOCKING);

    if(buf.buf)
    {
      uint8_t *scratchmem = dt_mipmap_cache_alloc_scratchmem(darktable.mipmap_cache);
      uint8_t *buf_decompressed = dt_mipmap_cache_decompress(&buf, scratchmem);

      uint8_t *rgbbuf = g_malloc((buf.width+2)*(buf.height+2)*3);
      memset(rgbbuf, 64, (buf.width+2)*(buf.height+2)*3);
      for(int i=1; i<=buf.height; i++)
        for(int j=1; j<=buf.width; j++)
          for(int k=0; k<3; k++)
            rgbbuf[(i*(buf.width+2)+j)*3+k] = buf_decompressed[((i-1)*buf.width+j-1)*4+2-k];

      int w=ts, h=ts;
      if(buf.width < buf.height) w = (buf.width*ts)/buf.height; // portrait
      else                       h = (buf.height*ts)/buf.width; // landscape

      GdkPixbuf *source = gdk_pixbuf_new_from_data(rgbbuf, GDK_COLORSPACE_RGB, FALSE, 8, (buf.width+2), (buf.height+2), (buf.width+2)*3, NULL, NULL);
      GdkPixbuf *scaled = gdk_pixbuf_scale_simple(source, w, h, GDK_INTERP_HYPER);
      gtk_drag_set_icon_pixbuf(context, scaled, 0, 0);

      if(source)
        g_object_unref(source);
      if(scaled)
        g_object_unref(scaled);
      free(scratchmem);
      g_free(rgbbuf);
    }

    dt_mipmap_cache_read_release(darktable.mipmap_cache, &buf);
  }
}
Beispiel #5
0
static void
expose_filemanager (dt_view_t *self, cairo_t *cr, int32_t width, int32_t height, int32_t pointerx, int32_t pointery)
{
  dt_library_t *lib = (dt_library_t *)self->data;

  gboolean offset_changed = FALSE;

  /* query new collection count */
  lib->collection_count = dt_collection_get_count (darktable.collection);

  if(darktable.gui->center_tooltip == 1)
    darktable.gui->center_tooltip = 2;

  /* get grid stride */
  const int iir = dt_conf_get_int("plugins/lighttable/images_in_row");

  /* get image over id */
  lib->image_over = DT_VIEW_DESERT;
  int32_t mouse_over_id, mouse_over_group = -1;
  DT_CTL_GET_GLOBAL(mouse_over_id, lib_image_mouse_over_id);

  /* fill background */
  cairo_set_source_rgb (cr, .2, .2, .2);
  cairo_paint(cr);

  if(lib->first_visible_zoomable >= 0)
  {
    lib->offset = lib->first_visible_zoomable;
  }
  lib->first_visible_zoomable = -1;

  /* check if offset has been changed */
  if(lib->track >  2) lib->offset += iir;
  if(lib->track < -2) lib->offset -= iir;
  lib->track = 0;
  if(lib->center) lib->offset = 0;
  lib->center = 0;
  int offset = lib->offset;

  /* if offset differs then flag as changed */
  if (offset != lib->first_visible_filemanager)
    offset_changed = TRUE;

  lib->first_visible_filemanager = offset;
  static int oldpan = 0;
  const int pan = lib->pan;

  const float wd = width/(float)iir;
  const float ht = width/(float)iir;

  int pi = pointerx / (float)wd;
  int pj = pointery / (float)ht;
  if(pointerx < 0 || pointery < 0) pi = pj = -1;
  //const int pidx = grid_to_index(pj, pi, iir, offset);

  const int img_pointerx = iir == 1 ? pointerx : fmodf(pointerx, wd);
  const int img_pointery = iir == 1 ? pointery : fmodf(pointery, ht);

  const int max_rows = 1 + (int)((height)/ht + .5);
  const int max_cols = iir;

  int id;
  int clicked1 = (oldpan == 0 && pan == 1 && lib->button == 1);

  /* get the count of current collection */

  if(lib->collection_count == 0)
  {
    const float fs = 15.0f;
    const float ls = 1.5f*fs;
    const float offy = height*0.2f;
    const float offx = 60;
    const float at = 0.3f;
    cairo_set_font_size(cr, fs);
    cairo_set_source_rgba(cr, .7, .7, .7, 1.0f);
    cairo_select_font_face (cr, "sans-serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
    cairo_move_to(cr, offx, offy);
    cairo_show_text(cr, _("there are no images in this collection"));
    cairo_move_to(cr, offx, offy + 2*ls);
    cairo_show_text(cr, _("if you have not imported any images yet"));
    cairo_move_to(cr, offx, offy + 3*ls);
    cairo_show_text(cr, _("you can do so in the import module"));
    cairo_move_to(cr, offx - 10.0f, offy + 3*ls - ls*.25f);
    cairo_line_to(cr, 0.0f, 10.0f);
    cairo_set_source_rgba(cr, .7, .7, .7, at);
    cairo_stroke(cr);
    cairo_move_to(cr, offx, offy + 5*ls);
    cairo_set_source_rgba(cr, .7, .7, .7, 1.0f);
    cairo_show_text(cr, _("try to relax the filter settings in the top panel"));
    cairo_rel_move_to(cr, 10.0f, -ls*.25f);
    cairo_line_to(cr, width*0.5f, 0.0f);
    cairo_set_source_rgba(cr, .7, .7, .7, at);
    cairo_stroke(cr);
    cairo_move_to(cr, offx, offy + 6*ls);
    cairo_set_source_rgba(cr, .7, .7, .7, 1.0f);
    cairo_show_text(cr, _("or add images in the collection module in the left panel"));
    cairo_move_to(cr, offx - 10.0f, offy + 6*ls - ls*0.25f);
    cairo_rel_line_to(cr, - offx + 10.0f, 0.0f);
    cairo_set_source_rgba(cr, .7, .7, .7, at);
    cairo_stroke(cr);

    return;
  }

  /* do we have a main query collection statement */
  if(!lib->statements.main_query)
    return;

  if(offset < 0)
    lib->offset = offset = 0;

  while(offset >= lib->collection_count)
    lib->offset = (offset -= iir);

  /* update scroll borders */
  dt_view_set_scrollbar(self, 0, 1, 1, offset, lib->collection_count, max_rows*iir);

  /* let's reset and reuse the main_query statement */
  DT_DEBUG_SQLITE3_CLEAR_BINDINGS(lib->statements.main_query);
  DT_DEBUG_SQLITE3_RESET(lib->statements.main_query);

  /* setup offset and row for the main query */
  DT_DEBUG_SQLITE3_BIND_INT(lib->statements.main_query, 1, offset);
  DT_DEBUG_SQLITE3_BIND_INT(lib->statements.main_query, 2, max_rows*iir);

  if(mouse_over_id != -1)
  {
    const dt_image_t *mouse_over_image = dt_image_cache_read_get(darktable.image_cache, mouse_over_id);
    mouse_over_group = mouse_over_image->group_id;
    dt_image_cache_read_release(darktable.image_cache, mouse_over_image);
    DT_DEBUG_SQLITE3_CLEAR_BINDINGS(lib->statements.is_grouped);
    DT_DEBUG_SQLITE3_RESET(lib->statements.is_grouped);
    DT_DEBUG_SQLITE3_BIND_INT(lib->statements.is_grouped, 1, mouse_over_group);
    DT_DEBUG_SQLITE3_BIND_INT(lib->statements.is_grouped, 2, mouse_over_id);
    if(sqlite3_step(lib->statements.is_grouped) != SQLITE_ROW)
      mouse_over_group = -1;
  }

  // prefetch the ids so that we can peek into the future to see if there are adjacent images in the same group.
  int *query_ids = g_malloc0(max_rows*max_cols*sizeof(int));
  for(int row = 0; row < max_rows; row++)
  {
    for(int col = 0; col < max_cols; col++)
    {
      if(sqlite3_step(lib->statements.main_query) == SQLITE_ROW)
        query_ids[row*iir+col] = sqlite3_column_int(lib->statements.main_query, 0);
      else goto end_query_cache;
    }
  }

end_query_cache:

  cairo_save(cr);
  for(int row = 0; row < max_rows; row++)
  {
    for(int col = 0; col < max_cols; col++)
    {
      //curidx = grid_to_index(row, col, iir, offset);

      id = query_ids[row*iir+col];
      if(id > 0)
      {
        if (iir == 1 && row)
          continue;

        /* set mouse over id if pointer is in current row / col */
        if(pi == col && pj == row)
        {
          mouse_over_id = id;
          DT_CTL_SET_GLOBAL(lib_image_mouse_over_id, mouse_over_id);
        }

        /* handle mouse click on current row / col
           this could easily and preferable be moved to button_pressed()
         */
        if (clicked1 && (pi == col && pj == row))
        {
          if ((lib->modifiers & (GDK_SHIFT_MASK|GDK_CONTROL_MASK)) == 0)
            dt_selection_select_single(darktable.selection, id);
          else if ((lib->modifiers & (GDK_CONTROL_MASK)) == GDK_CONTROL_MASK)
            dt_selection_toggle(darktable.selection, id);
          else if ((lib->modifiers & (GDK_SHIFT_MASK)) == GDK_SHIFT_MASK)
            dt_selection_select_range(darktable.selection, id);
        }

        cairo_save(cr);
        // if(iir == 1) dt_image_prefetch(image, DT_IMAGE_MIPF);
        dt_view_image_expose(&(lib->image_over), id, cr, wd, iir == 1 ? height : ht, iir, img_pointerx, img_pointery);

        cairo_restore(cr);
      }
      else
        goto failure;

      cairo_translate(cr, wd, 0.0f);
    }
    cairo_translate(cr, -max_cols*wd, ht);
  }
  cairo_restore(cr);

  // and now the group borders
  for(int row = 0; row < max_rows; row++)
  {
    for(int col = 0; col < max_cols; col++)
    {
      id = query_ids[row*iir+col];
      if(id > 0)
      {
        const dt_image_t *image = dt_image_cache_read_get(darktable.image_cache, id);
        int group_id = -1;
        if(image)
          group_id = image->group_id;
        dt_image_cache_read_release(darktable.image_cache, image);

        if (iir == 1 && row)
          continue;

        cairo_save(cr);

        gboolean paint_border = FALSE;
        // regular highlight border
        if(group_id != -1)
        {
          if(mouse_over_group == group_id && iir > 1 && ((!darktable.gui->grouping && dt_conf_get_bool("plugins/lighttable/draw_group_borders")) || group_id == darktable.gui->expanded_group_id))
          {
            cairo_set_source_rgb(cr, 1, 0.8, 0);
            paint_border = TRUE;
          }
          // border of expanded group
          else if(darktable.gui->grouping && group_id == darktable.gui->expanded_group_id && iir > 1)
          {
            cairo_set_source_rgb(cr, 0, 0, 1);
            paint_border = TRUE;
          }
        }

        if(paint_border)
        {
          int neighbour_group = -1;
          // top border
          if(row > 0)
          {
            int _id = query_ids[(row-1)*iir+col];
            if(_id > 0)
            {
              const dt_image_t *_img = dt_image_cache_read_get(darktable.image_cache, _id);
              neighbour_group = _img->group_id;
              dt_image_cache_read_release(darktable.image_cache, _img);
            }
          }
          if(neighbour_group != group_id)
          {
            cairo_move_to(cr, 0, 0);
            cairo_line_to(cr, wd, 0);
          }
          // left border
          neighbour_group = -1;
          if(col > 0)
          {
            int _id = query_ids[row*iir+(col-1)];
            if(_id > 0)
            {
              const dt_image_t *_img = dt_image_cache_read_get(darktable.image_cache, _id);
              neighbour_group = _img->group_id;
              dt_image_cache_read_release(darktable.image_cache, _img);
            }
          }
          if(neighbour_group != group_id)
          {
            cairo_move_to(cr, 0, 0);
            cairo_line_to(cr, 0, ht);
          }
          // bottom border
          neighbour_group = -1;
          if(row < max_rows-1)
          {
            int _id = query_ids[(row+1)*iir+col];
            if(_id > 0)
            {
              const dt_image_t *_img = dt_image_cache_read_get(darktable.image_cache, _id);
              neighbour_group = _img->group_id;
              dt_image_cache_read_release(darktable.image_cache, _img);
            }
          }
          if(neighbour_group != group_id)
          {
            cairo_move_to(cr, 0, ht);
            cairo_line_to(cr, wd, ht);
          }
          // right border
          neighbour_group = -1;
          if(col < max_cols-1)
          {
            int _id = query_ids[row*iir+(col+1)];
            if(_id > 0)
            {
              const dt_image_t *_img = dt_image_cache_read_get(darktable.image_cache, _id);
              neighbour_group = _img->group_id;
              dt_image_cache_read_release(darktable.image_cache, _img);
            }
          }
          if(neighbour_group != group_id)
          {
            cairo_move_to(cr, wd, 0);
            cairo_line_to(cr, wd, ht);
          }
          cairo_set_line_width(cr, 0.01*wd);
          cairo_stroke(cr);
        }

        cairo_restore(cr);
      }
      else
        goto failure;

      cairo_translate(cr, wd, 0.0f);
    }
    cairo_translate(cr, -max_cols*wd, ht);
  }

  /* check if offset was changed and we need to prefetch thumbs */
  if (offset_changed)
  {
    int32_t imgids_num = 0;
    const int prefetchrows = .5*max_rows+1;
    int32_t imgids[prefetchrows*iir];
    /* clear and reset main query */
    DT_DEBUG_SQLITE3_CLEAR_BINDINGS(lib->statements.main_query);
    DT_DEBUG_SQLITE3_RESET(lib->statements.main_query);

    /* setup offest and row for prefetch */
    DT_DEBUG_SQLITE3_BIND_INT(lib->statements.main_query, 1, offset + max_rows*iir);
    DT_DEBUG_SQLITE3_BIND_INT(lib->statements.main_query, 2, prefetchrows*iir);

    // prefetch jobs in inverse order: supersede previous jobs: most important last
    while(sqlite3_step(lib->statements.main_query) == SQLITE_ROW && imgids_num < prefetchrows*iir)
      imgids[imgids_num++] = sqlite3_column_int(lib->statements.main_query, 0);

    float imgwd = iir == 1 ? 0.97 : 0.8;
    dt_mipmap_size_t mip = dt_mipmap_cache_get_matching_size(
                             darktable.mipmap_cache,
                             imgwd*wd, imgwd*(iir==1?height:ht));
    while(imgids_num > 0)
    {
      imgids_num --;
      dt_mipmap_buffer_t buf;
      dt_mipmap_cache_read_get(
        darktable.mipmap_cache,
        &buf,
        imgids[imgids_num],
        mip,
        DT_MIPMAP_PREFETCH);
    }
  }


failure:
  g_free(query_ids);
  oldpan = pan;
  if(darktable.unmuted & DT_DEBUG_CACHE)
    dt_mipmap_cache_print(darktable.mipmap_cache);

  if(darktable.gui->center_tooltip == 1) // set in this round
  {
    char* tooltip = dt_history_get_items_as_string(mouse_over_id);
    if(tooltip != NULL)
    {
      g_object_set(G_OBJECT(dt_ui_center(darktable.gui->ui)), "tooltip-text", tooltip, (char *)NULL);
      g_free(tooltip);
    }
  }
  else if(darktable.gui->center_tooltip == 2)   // not set in this round
  {
    darktable.gui->center_tooltip = 0;
    g_object_set(G_OBJECT(dt_ui_center(darktable.gui->ui)), "tooltip-text", "", (char *)NULL);
  }
}
Beispiel #6
0
static void _view_map_post_expose(cairo_t *cri, int32_t width_i, int32_t height_i,
                                  int32_t pointerx, int32_t pointery, gpointer user_data)
{
  const int ts = 64;
  OsmGpsMapPoint bb[2], *l=NULL, *center=NULL;
  int px,py;
  dt_map_t *lib = (dt_map_t *)user_data;

  /* get bounding box coords */
  osm_gps_map_get_bbox(lib->map, &bb[0], &bb[1]);
  float bb_0_lat = 0.0, bb_0_lon = 0.0, bb_1_lat = 0.0, bb_1_lon = 0.0;
  osm_gps_map_point_get_degrees(&bb[0], &bb_0_lat, &bb_0_lon);
  osm_gps_map_point_get_degrees(&bb[1], &bb_1_lat, &bb_1_lon);

  /* make the bounding box a little bigger to the west and south */
  float lat0 = 0.0, lon0 = 0.0, lat1 = 0.0, lon1 = 0.0;
  OsmGpsMapPoint *pt0 = osm_gps_map_point_new_degrees(0.0, 0.0), *pt1 = osm_gps_map_point_new_degrees(0.0, 0.0);
  osm_gps_map_convert_screen_to_geographic(lib->map, 0, 0, pt0);
  osm_gps_map_convert_screen_to_geographic(lib->map, 1.5*ts, 1.5*ts, pt1);
  osm_gps_map_point_get_degrees(pt0, &lat0, &lon0);
  osm_gps_map_point_get_degrees(pt1, &lat1, &lon1);
  osm_gps_map_point_free(pt0);
  osm_gps_map_point_free(pt1);
  double south_border = lat0 - lat1, west_border = lon1 - lon0;

  /* get map view state and store  */
  int zoom = osm_gps_map_get_zoom(lib->map);
  center = osm_gps_map_get_center(lib->map);
  dt_conf_set_float("plugins/map/longitude", center->rlon);
  dt_conf_set_float("plugins/map/latitude", center->rlat);
  dt_conf_set_int("plugins/map/zoom", zoom);
  osm_gps_map_point_free(center);

  /* let's reset and reuse the main_query statement */
  DT_DEBUG_SQLITE3_CLEAR_BINDINGS(lib->statements.main_query);
  DT_DEBUG_SQLITE3_RESET(lib->statements.main_query);

  /* bind bounding box coords for the main query */
  DT_DEBUG_SQLITE3_BIND_DOUBLE(lib->statements.main_query, 1, bb_0_lon - west_border);
  DT_DEBUG_SQLITE3_BIND_DOUBLE(lib->statements.main_query, 2, bb_1_lon);
  DT_DEBUG_SQLITE3_BIND_DOUBLE(lib->statements.main_query, 3, bb_0_lat);
  DT_DEBUG_SQLITE3_BIND_DOUBLE(lib->statements.main_query, 4, bb_1_lat - south_border);

  /* query collection ids */
  while(sqlite3_step(lib->statements.main_query) == SQLITE_ROW)
  {
    int32_t imgid = sqlite3_column_int(lib->statements.main_query, 0);

    cairo_set_source_rgba(cri, 0, 0, 0, 0.4);

    /* free l if allocated */
    if (l)
      osm_gps_map_point_free(l);

    /* for each image check if within bbox */
    const dt_image_t *cimg = dt_image_cache_read_get(darktable.image_cache, imgid);
    double longitude = cimg->longitude;
    double latitude  = cimg->latitude;
    dt_image_cache_read_release(darktable.image_cache, cimg);
    if(isnan(latitude) || isnan(longitude))
      continue;
    l = osm_gps_map_point_new_degrees(latitude, longitude);

    /* translate l into screen coords */
    osm_gps_map_convert_geographic_to_screen(lib->map, l, &px, &py);

    /* dependent on scale draw different overlays */
    if (zoom >= 14)
    {
      dt_mipmap_buffer_t buf;
      dt_mipmap_size_t mip = dt_mipmap_cache_get_matching_size(darktable.mipmap_cache, ts, ts);
      dt_mipmap_cache_read_get(darktable.mipmap_cache, &buf, imgid, mip, 0);

      cairo_surface_t *surface = NULL;
      if(buf.buf)
      {
        float ms = fminf(
                     ts/(float)buf.width,
                     ts/(float)buf.height);

#if 0
        // this doesn't work since osm-gps-map always gives 0/0 as mouse coords :(
        /* find out if the cursor is over the image */
        if(pointerx >= px && pointerx <= (px + buf.width*ms + 4) && pointery <= (py - 8) && pointery >= (py - buf.height*ms - 8 - 4))
        {
          printf("over\n");
          cairo_set_source_rgba(cri, 1, 0, 0, 0.7);
        }
//         else
//           printf("%d/%d, %d/%d\n", px, py, pointerx, pointery);
#endif
        const int32_t stride = cairo_format_stride_for_width (CAIRO_FORMAT_RGB24, buf.width);
        surface = cairo_image_surface_create_for_data (buf.buf, CAIRO_FORMAT_RGB24,
                  buf.width, buf.height, stride);

        cairo_pattern_set_filter(cairo_get_source(cri), CAIRO_FILTER_NEAREST);
        cairo_save(cri);

        /* first of lets draw a pin */
        cairo_move_to(cri, px, py);
        cairo_line_to(cri, px+8, py-8);
        cairo_line_to(cri, px+4, py-8);
        cairo_fill(cri);

        /* and the frame around image */
        cairo_move_to(cri, px+2, py-8);
        cairo_line_to(cri, px+2 + (buf.width*ms) + 4, py-8);
        cairo_line_to(cri, px+2 + (buf.width*ms) + 4 , py-8-(buf.height*ms) - 4);
        cairo_line_to(cri, px+2 , py-8-(buf.height*ms) - 4);
        cairo_fill(cri);


        /* draw image*/
        cairo_translate(cri, px+4, py - 8 - (buf.height*ms) - 2);
        cairo_scale(cri, ms, ms);
        cairo_set_source_surface (cri, surface, 0, 0);
        cairo_paint(cri);

        cairo_restore(cri);

        cairo_surface_destroy(surface);

      }
    }
    else
    {
      /* just draw a patch indicating that there is images at the location */
      cairo_rectangle(cri, px-8, py-8, 16, 16);
      cairo_fill(cri);
    }
  }

}
Beispiel #7
0
static void _view_map_changed_callback(OsmGpsMap *map, dt_view_t *self)
{
  dt_map_t *lib = (dt_map_t *)self->data;

  const int ts = 64;
  OsmGpsMapPoint bb[2];

  /* get bounding box coords */
  osm_gps_map_get_bbox(map, &bb[0], &bb[1]);
  float bb_0_lat = 0.0, bb_0_lon = 0.0, bb_1_lat = 0.0, bb_1_lon = 0.0;
  osm_gps_map_point_get_degrees(&bb[0], &bb_0_lat, &bb_0_lon);
  osm_gps_map_point_get_degrees(&bb[1], &bb_1_lat, &bb_1_lon);

  /* make the bounding box a little bigger to the west and south */
  float lat0 = 0.0, lon0 = 0.0, lat1 = 0.0, lon1 = 0.0;
  OsmGpsMapPoint *pt0 = osm_gps_map_point_new_degrees(0.0, 0.0), *pt1 = osm_gps_map_point_new_degrees(0.0, 0.0);
  osm_gps_map_convert_screen_to_geographic(map, 0, 0, pt0);
  osm_gps_map_convert_screen_to_geographic(map, 1.5*ts, 1.5*ts, pt1);
  osm_gps_map_point_get_degrees(pt0, &lat0, &lon0);
  osm_gps_map_point_get_degrees(pt1, &lat1, &lon1);
  osm_gps_map_point_free(pt0);
  osm_gps_map_point_free(pt1);
  double south_border = lat0 - lat1, west_border = lon1 - lon0;

  /* get map view state and store  */
  int zoom;
  float center_lat, center_lon;
  g_object_get(G_OBJECT(map), "zoom", &zoom, "latitude", &center_lat, "longitude", &center_lon, NULL);
  dt_conf_set_float("plugins/map/longitude", center_lon);
  dt_conf_set_float("plugins/map/latitude", center_lat);
  dt_conf_set_int("plugins/map/zoom", zoom);

  /* let's reset and reuse the main_query statement */
  DT_DEBUG_SQLITE3_CLEAR_BINDINGS(lib->statements.main_query);
  DT_DEBUG_SQLITE3_RESET(lib->statements.main_query);

  /* bind bounding box coords for the main query */
  DT_DEBUG_SQLITE3_BIND_DOUBLE(lib->statements.main_query, 1, bb_0_lon - west_border);
  DT_DEBUG_SQLITE3_BIND_DOUBLE(lib->statements.main_query, 2, bb_1_lon);
  DT_DEBUG_SQLITE3_BIND_DOUBLE(lib->statements.main_query, 3, bb_0_lat);
  DT_DEBUG_SQLITE3_BIND_DOUBLE(lib->statements.main_query, 4, bb_1_lat - south_border);
  DT_DEBUG_SQLITE3_BIND_DOUBLE(lib->statements.main_query, 5, center_lat);
  DT_DEBUG_SQLITE3_BIND_DOUBLE(lib->statements.main_query, 6, center_lon);

  /* remove the old images */
  osm_gps_map_image_remove_all(map);
  if(lib->images)
  {
    g_slist_foreach(lib->images, (GFunc) g_free, NULL);
    g_slist_free(lib->images);
    lib->images = NULL;
  }

  /* add  all images to the map */
  gboolean needs_redraw = FALSE;
  dt_mipmap_size_t mip = dt_mipmap_cache_get_matching_size(darktable.mipmap_cache, ts, ts);
  while(sqlite3_step(lib->statements.main_query) == SQLITE_ROW)
  {
    int imgid = sqlite3_column_int(lib->statements.main_query, 0);
    dt_mipmap_buffer_t buf;
    dt_mipmap_cache_read_get(darktable.mipmap_cache, &buf, imgid, mip, DT_MIPMAP_BEST_EFFORT);

    if(buf.buf)
    {
      uint8_t *scratchmem = dt_mipmap_cache_alloc_scratchmem(darktable.mipmap_cache);
      uint8_t *buf_decompressed = dt_mipmap_cache_decompress(&buf, scratchmem);

      uint8_t *rgbbuf = g_malloc((buf.width+2)*(buf.height+2)*3);
      memset(rgbbuf, 64, (buf.width+2)*(buf.height+2)*3);
      for(int i=1; i<=buf.height; i++)
        for(int j=1; j<=buf.width; j++)
          for(int k=0; k<3; k++)
            rgbbuf[(i*(buf.width+2)+j)*3+k] = buf_decompressed[((i-1)*buf.width+j-1)*4+2-k];

      int w=ts, h=ts;
      if(buf.width < buf.height) w = (buf.width*ts)/buf.height; // portrait
      else                       h = (buf.height*ts)/buf.width; // landscape

      GdkPixbuf *source = gdk_pixbuf_new_from_data(rgbbuf, GDK_COLORSPACE_RGB, FALSE, 8, (buf.width+2), (buf.height+2), (buf.width+2)*3, NULL, NULL);
      GdkPixbuf *scaled = gdk_pixbuf_scale_simple(source, w, h, GDK_INTERP_HYPER);
      //TODO: add back the arrow on the left lower corner of the image, pointing to the location
      const dt_image_t *cimg = dt_image_cache_read_get(darktable.image_cache, imgid);
      dt_map_image_t *entry = (dt_map_image_t*)g_malloc(sizeof(dt_map_image_t));
      entry->imgid = imgid;
      entry->image = osm_gps_map_image_add_with_alignment(map, cimg->latitude, cimg->longitude, scaled, 0, 1);
      entry->width = w;
      entry->height = h;
      lib->images = g_slist_prepend(lib->images, entry);
      dt_image_cache_read_release(darktable.image_cache, cimg);

      if(source)
        g_object_unref(source);
      if(scaled)
        g_object_unref(scaled);
      g_free(rgbbuf);
    }
    else
      needs_redraw = TRUE;
    dt_mipmap_cache_read_release(darktable.mipmap_cache, &buf);
  }

  // not exactly thread safe, but should be good enough for updating the display
  static int timeout_event_source = 0;
  if(needs_redraw && timeout_event_source == 0)
    timeout_event_source = g_timeout_add_seconds(1, _view_map_redraw, self); // try again in a second, maybe some pictures have loaded by then
  else
    timeout_event_source = 0;
}
Beispiel #8
0
/*
    This file is part of darktable,
    copyright (c) 2009--2011 johannes hanika.
    copyright (c) 2012 tobias ellinghaus.
    copyright (c) 2014 henrik andersson.

    darktable is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

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

    You should have received a copy of the GNU General Public License
    along with darktable.  If not, see <http://www.gnu.org/licenses/>.
*/
#include "bauhaus/bauhaus.h"
#include "common/camera_control.h"
#include "common/darktable.h"
#include "common/image_cache.h"
#include "common/mipmap_cache.h"
#include "control/conf.h"
#include "control/control.h"
#include "control/jobs.h"
#include "dtgtk/button.h"
#include "gui/accelerators.h"
#include "gui/gtk.h"
#include "gui/guides.h"
#include "libs/lib.h"
#include "libs/lib_api.h"
#include <gdk/gdkkeysyms.h>

typedef enum dt_lib_live_view_flip_t
{
  FLAG_FLIP_NONE = 0,
  FLAG_FLIP_HORIZONTAL = 1<<0,
  FLAG_FLIP_VERTICAL = 1<<1,
  FLAG_FLIP_BOTH = FLAG_FLIP_HORIZONTAL|FLAG_FLIP_VERTICAL
} dt_lib_live_view_flip_t;

typedef enum dt_lib_live_view_overlay_t
{
  OVERLAY_NONE = 0,
  OVERLAY_SELECTED,
  OVERLAY_ID
} dt_lib_live_view_overlay_t;

#define HANDLE_SIZE 0.02

static const cairo_operator_t _overlay_modes[] = {
  CAIRO_OPERATOR_OVER, CAIRO_OPERATOR_XOR, CAIRO_OPERATOR_ADD, CAIRO_OPERATOR_SATURATE
#if(CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 10, 0))
  ,
  CAIRO_OPERATOR_MULTIPLY, CAIRO_OPERATOR_SCREEN, CAIRO_OPERATOR_OVERLAY, CAIRO_OPERATOR_DARKEN,
  CAIRO_OPERATOR_LIGHTEN, CAIRO_OPERATOR_COLOR_DODGE, CAIRO_OPERATOR_COLOR_BURN, CAIRO_OPERATOR_HARD_LIGHT,
  CAIRO_OPERATOR_SOFT_LIGHT, CAIRO_OPERATOR_DIFFERENCE, CAIRO_OPERATOR_EXCLUSION, CAIRO_OPERATOR_HSL_HUE,
  CAIRO_OPERATOR_HSL_SATURATION, CAIRO_OPERATOR_HSL_COLOR, CAIRO_OPERATOR_HSL_LUMINOSITY
#endif
};

DT_MODULE(1)

typedef struct dt_lib_live_view_t
{
  int imgid;
  int splitline_rotation;
  double overlay_x0, overlay_x1, overlay_y0, overlay_y1;
  double splitline_x, splitline_y; // 0..1
  gboolean splitline_dragging;

  GtkWidget *live_view, *live_view_zoom, *rotate_ccw, *rotate_cw, *flip;
  GtkWidget *focus_out_small, *focus_out_big, *focus_in_small, *focus_in_big;
  GtkWidget *guide_selector, *flip_guides, *guides_widgets;
  GList *guides_widgets_list;
  GtkWidget *overlay, *overlay_id_box, *overlay_id, *overlay_mode, *overlay_splitline;
} dt_lib_live_view_t;

static void guides_presets_set_visibility(dt_lib_live_view_t *lib, int which)
{
  if(which == 0)
  {
    gtk_widget_set_no_show_all(lib->guides_widgets, TRUE);
    gtk_widget_hide(lib->guides_widgets);
    gtk_widget_set_no_show_all(lib->flip_guides, TRUE);
    gtk_widget_hide(lib->flip_guides);
  }
  else
  {
    GtkWidget *widget = g_list_nth_data(lib->guides_widgets_list, which - 1);
    if(widget)
    {
      gtk_widget_set_no_show_all(lib->guides_widgets, FALSE);
      gtk_widget_show_all(lib->guides_widgets);
      gtk_stack_set_visible_child(GTK_STACK(lib->guides_widgets), widget);
    }
    else
    {
      gtk_widget_set_no_show_all(lib->guides_widgets, TRUE);
      gtk_widget_hide(lib->guides_widgets);
    }
    gtk_widget_set_no_show_all(lib->flip_guides, FALSE);
    gtk_widget_show_all(lib->flip_guides);
  }

  // TODO: add a support_flip flag to guides to hide the flip gui?
}

static void guides_presets_changed(GtkWidget *combo, dt_lib_live_view_t *lib)
{
  int which = dt_bauhaus_combobox_get(combo);
  guides_presets_set_visibility(lib, which);
}

static void overlay_changed(GtkWidget *combo, dt_lib_live_view_t *lib)
{
  int which = dt_bauhaus_combobox_get(combo);
  if(which == OVERLAY_NONE)
  {
    gtk_widget_set_visible(GTK_WIDGET(lib->overlay_mode), FALSE);
    gtk_widget_set_visible(GTK_WIDGET(lib->overlay_splitline), FALSE);
  }
  else
  {
    gtk_widget_set_visible(GTK_WIDGET(lib->overlay_mode), TRUE);
    gtk_widget_set_visible(GTK_WIDGET(lib->overlay_splitline), TRUE);
  }

  if(which == OVERLAY_ID)
    gtk_widget_set_visible(GTK_WIDGET(lib->overlay_id_box), TRUE);
  else
    gtk_widget_set_visible(GTK_WIDGET(lib->overlay_id_box), FALSE);
}


const char *name(dt_lib_module_t *self)
{
  return _("live view");
}

const char **views(dt_lib_module_t *self)
{
  static const char *v[] = {"tethering", NULL};
  return v;
}

uint32_t container(dt_lib_module_t *self)
{
  return DT_UI_CONTAINER_PANEL_RIGHT_CENTER;
}


void gui_reset(dt_lib_module_t *self)
{
}

int position()
{
  return 998;
}

void init_key_accels(dt_lib_module_t *self)
{
  dt_accel_register_lib(self, NC_("accel", "toggle live view"), GDK_KEY_v, 0);
  dt_accel_register_lib(self, NC_("accel", "zoom live view"), GDK_KEY_z, 0);
  dt_accel_register_lib(self, NC_("accel", "rotate 90 degrees CCW"), 0, 0);
  dt_accel_register_lib(self, NC_("accel", "rotate 90 degrees CW"), 0, 0);
  dt_accel_register_lib(self, NC_("accel", "flip horizontally"), 0, 0);
  dt_accel_register_lib(self, NC_("accel", "move focus point in (big steps)"), 0, 0);
  dt_accel_register_lib(self, NC_("accel", "move focus point in (small steps)"), 0, 0);
  dt_accel_register_lib(self, NC_("accel", "move focus point out (small steps)"), 0, 0);
  dt_accel_register_lib(self, NC_("accel", "move focus point out (big steps)"), 0, 0);
}

void connect_key_accels(dt_lib_module_t *self)
{
  dt_lib_live_view_t *lib = (dt_lib_live_view_t *)self->data;

  dt_accel_connect_button_lib(self, "toggle live view", GTK_WIDGET(lib->live_view));
  dt_accel_connect_button_lib(self, "zoom live view", GTK_WIDGET(lib->live_view_zoom));
  dt_accel_connect_button_lib(self, "rotate 90 degrees CCW", GTK_WIDGET(lib->rotate_ccw));
  dt_accel_connect_button_lib(self, "rotate 90 degrees CW", GTK_WIDGET(lib->rotate_cw));
  dt_accel_connect_button_lib(self, "flip horizontally", GTK_WIDGET(lib->flip));
  dt_accel_connect_button_lib(self, "move focus point in (big steps)", GTK_WIDGET(lib->focus_in_big));
  dt_accel_connect_button_lib(self, "move focus point in (small steps)", GTK_WIDGET(lib->focus_in_small));
  dt_accel_connect_button_lib(self, "move focus point out (small steps)", GTK_WIDGET(lib->focus_out_small));
  dt_accel_connect_button_lib(self, "move focus point out (big steps)", GTK_WIDGET(lib->focus_out_big));
}

static void _rotate_ccw(GtkWidget *widget, gpointer user_data)
{
  dt_camera_t *cam = (dt_camera_t *)darktable.camctl->active_camera;
  cam->live_view_rotation = (cam->live_view_rotation + 1) % 4; // 0 -> 1 -> 2 -> 3 -> 0 -> ...
}

static void _rotate_cw(GtkWidget *widget, gpointer user_data)
{
  dt_camera_t *cam = (dt_camera_t *)darktable.camctl->active_camera;
  cam->live_view_rotation = (cam->live_view_rotation + 3) % 4; // 0 -> 3 -> 2 -> 1 -> 0 -> ...
}

// Congratulations to Simon for being the first one recognizing live view in a screen shot ^^
static void _toggle_live_view_clicked(GtkWidget *widget, gpointer user_data)
{
  if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)) == TRUE)
  {
    if(dt_camctl_camera_start_live_view(darktable.camctl) == FALSE)
      gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), FALSE);
  }
  else
  {
    dt_camctl_camera_stop_live_view(darktable.camctl);
  }
}

// TODO: using a toggle button would be better, but this setting can also be changed by right clicking on the
// canvas (src/views/capture.c).
//       maybe using a signal would work? i have no idea.
static void _zoom_live_view_clicked(GtkWidget *widget, gpointer user_data)
{
  dt_camera_t *cam = (dt_camera_t *)darktable.camctl->active_camera;
  if(cam->is_live_viewing)
  {
    cam->live_view_zoom = !cam->live_view_zoom;
    if(cam->live_view_zoom == TRUE)
      dt_camctl_camera_set_property_string(darktable.camctl, NULL, "eoszoom", "5");
    else
      dt_camctl_camera_set_property_string(darktable.camctl, NULL, "eoszoom", "1");
  }
}

static void _focus_button_clicked(GtkWidget *widget, gpointer user_data)
{
  int focus = GPOINTER_TO_INT(user_data);
  dt_camctl_camera_set_property_choice(darktable.camctl, NULL, "manualfocusdrive", focus);
}

static void _toggle_flip_clicked(GtkWidget *widget, gpointer user_data)
{
  dt_camera_t *cam = (dt_camera_t *)darktable.camctl->active_camera;
  cam->live_view_flip = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
}

static void _overlay_id_changed(GtkWidget *widget, gpointer user_data)
{
  dt_lib_live_view_t *lib = (dt_lib_live_view_t *)user_data;
  lib->imgid = gtk_spin_button_get_value(GTK_SPIN_BUTTON(widget));
  dt_conf_set_int("plugins/lighttable/live_view/overlay_imgid", lib->imgid);
}

static void _overlay_mode_changed(GtkWidget *combo, gpointer user_data)
{
  dt_conf_set_int("plugins/lighttable/live_view/overlay_mode", dt_bauhaus_combobox_get(combo));
}

static void _overlay_splitline_changed(GtkWidget *combo, gpointer user_data)
{
  dt_conf_set_int("plugins/lighttable/live_view/splitline", dt_bauhaus_combobox_get(combo));
}

void gui_init(dt_lib_module_t *self)
{
  self->data = calloc(1, sizeof(dt_lib_live_view_t));

  // Setup lib data
  dt_lib_live_view_t *lib = self->data;
  lib->splitline_x = lib->splitline_y = 0.5;

  // Setup gui
  self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
  dt_gui_add_help_link(self->widget, "live_view.html#live_view");
  GtkWidget *box;

  box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
  gtk_box_pack_start(GTK_BOX(self->widget), box, TRUE, TRUE, 0);
  lib->live_view = dtgtk_togglebutton_new(dtgtk_cairo_paint_eye, CPF_STYLE_FLAT | CPF_DO_NOT_USE_BORDER, NULL);
  lib->live_view_zoom = dtgtk_button_new(
      dtgtk_cairo_paint_zoom, CPF_STYLE_FLAT | CPF_DO_NOT_USE_BORDER, NULL); // TODO: see _zoom_live_view_clicked
  lib->rotate_ccw = dtgtk_button_new(dtgtk_cairo_paint_refresh, CPF_STYLE_FLAT | CPF_DO_NOT_USE_BORDER, NULL);
  lib->rotate_cw = dtgtk_button_new(dtgtk_cairo_paint_refresh,
                                    CPF_STYLE_FLAT | CPF_DO_NOT_USE_BORDER | CPF_DIRECTION_UP, NULL);
  lib->flip = dtgtk_togglebutton_new(dtgtk_cairo_paint_flip,
                                     CPF_STYLE_FLAT | CPF_DO_NOT_USE_BORDER | CPF_DIRECTION_UP, NULL);

  gtk_box_pack_start(GTK_BOX(box), lib->live_view, TRUE, TRUE, 0);
  gtk_box_pack_start(GTK_BOX(box), lib->live_view_zoom, TRUE, TRUE, 0);
  gtk_box_pack_start(GTK_BOX(box), lib->rotate_ccw, TRUE, TRUE, 0);
  gtk_box_pack_start(GTK_BOX(box), lib->rotate_cw, TRUE, TRUE, 0);
  gtk_box_pack_start(GTK_BOX(box), lib->flip, TRUE, TRUE, 0);

  gtk_widget_set_tooltip_text(lib->live_view, _("toggle live view"));
  gtk_widget_set_tooltip_text(lib->live_view_zoom, _("zoom live view"));
  gtk_widget_set_tooltip_text(lib->rotate_ccw, _("rotate 90 degrees ccw"));
  gtk_widget_set_tooltip_text(lib->rotate_cw, _("rotate 90 degrees cw"));
  gtk_widget_set_tooltip_text(lib->flip, _("flip live view horizontally"));

  g_signal_connect(G_OBJECT(lib->live_view), "clicked", G_CALLBACK(_toggle_live_view_clicked), lib);
  g_signal_connect(G_OBJECT(lib->live_view_zoom), "clicked", G_CALLBACK(_zoom_live_view_clicked), lib);
  g_signal_connect(G_OBJECT(lib->rotate_ccw), "clicked", G_CALLBACK(_rotate_ccw), lib);
  g_signal_connect(G_OBJECT(lib->rotate_cw), "clicked", G_CALLBACK(_rotate_cw), lib);
  g_signal_connect(G_OBJECT(lib->flip), "clicked", G_CALLBACK(_toggle_flip_clicked), lib);

  // focus buttons
  box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
  gtk_box_pack_start(GTK_BOX(self->widget), box, TRUE, TRUE, 0);
  lib->focus_in_big = dtgtk_button_new(dtgtk_cairo_paint_solid_triangle,
                                       CPF_STYLE_FLAT | CPF_DO_NOT_USE_BORDER | CPF_DIRECTION_LEFT, NULL);
  lib->focus_in_small
      = dtgtk_button_new(dtgtk_cairo_paint_arrow, CPF_STYLE_FLAT | CPF_DO_NOT_USE_BORDER
                                                  | CPF_DIRECTION_LEFT, NULL); // TODO icon not centered
  lib->focus_out_small = dtgtk_button_new(dtgtk_cairo_paint_arrow, CPF_STYLE_FLAT | CPF_DO_NOT_USE_BORDER
                                                                   | CPF_DIRECTION_RIGHT, NULL); // TODO same here
  lib->focus_out_big = dtgtk_button_new(dtgtk_cairo_paint_solid_triangle,
                                        CPF_STYLE_FLAT | CPF_DO_NOT_USE_BORDER | CPF_DIRECTION_RIGHT, NULL);

  gtk_box_pack_start(GTK_BOX(box), lib->focus_in_big, TRUE, TRUE, 0);
  gtk_box_pack_start(GTK_BOX(box), lib->focus_in_small, TRUE, TRUE, 0);
  gtk_box_pack_start(GTK_BOX(box), lib->focus_out_small, TRUE, TRUE, 0);
  gtk_box_pack_start(GTK_BOX(box), lib->focus_out_big, TRUE, TRUE, 0);

  gtk_widget_set_tooltip_text(lib->focus_in_big, _("move focus point in (big steps)"));
  gtk_widget_set_tooltip_text(lib->focus_in_small, _("move focus point in (small steps)"));
  gtk_widget_set_tooltip_text(lib->focus_out_small, _("move focus point out (small steps)"));
  gtk_widget_set_tooltip_text(lib->focus_out_big, _("move focus point out (big steps)"));

  // Near 3
  g_signal_connect(G_OBJECT(lib->focus_in_big), "clicked", G_CALLBACK(_focus_button_clicked),
                   GINT_TO_POINTER(2));
  // Near 1
  g_signal_connect(G_OBJECT(lib->focus_in_small), "clicked", G_CALLBACK(_focus_button_clicked),
                   GINT_TO_POINTER(0));
  // Far 1
  g_signal_connect(G_OBJECT(lib->focus_out_small), "clicked", G_CALLBACK(_focus_button_clicked),
                   GINT_TO_POINTER(4));
  // Far 3
  g_signal_connect(G_OBJECT(lib->focus_out_big), "clicked", G_CALLBACK(_focus_button_clicked),
                   GINT_TO_POINTER(6));

  // Guides
  lib->guide_selector = dt_bauhaus_combobox_new(NULL);
  dt_bauhaus_widget_set_label(lib->guide_selector, NULL, _("guides"));
  gtk_box_pack_start(GTK_BOX(self->widget), lib->guide_selector, TRUE, TRUE, 0);

  lib->guides_widgets = gtk_stack_new();
  gtk_stack_set_homogeneous(GTK_STACK(lib->guides_widgets), FALSE);
  gtk_box_pack_start(GTK_BOX(self->widget), lib->guides_widgets, TRUE, TRUE, 0);

  dt_bauhaus_combobox_add(lib->guide_selector, _("none"));
  int i = 0;
  for(GList *iter = darktable.guides; iter; iter = g_list_next(iter), i++)
  {
    GtkWidget *widget = NULL;
    dt_guides_t *guide = (dt_guides_t *)iter->data;
    dt_bauhaus_combobox_add(lib->guide_selector, _(guide->name));
    if(guide->widget)
    {
      // generate some unique name so that we can have the same name several times
      char name[5];
      snprintf(name, sizeof(name), "%d", i);
      widget = guide->widget(NULL, guide->user_data);
      gtk_widget_show_all(widget);
      gtk_stack_add_named(GTK_STACK(lib->guides_widgets), widget, name);
    }
    lib->guides_widgets_list = g_list_append(lib->guides_widgets_list, widget);
  }
  gtk_widget_set_no_show_all(lib->guides_widgets, TRUE);

  gtk_widget_set_tooltip_text(lib->guide_selector, _("display guide lines to help compose your photograph"));
  g_signal_connect(G_OBJECT(lib->guide_selector), "value-changed", G_CALLBACK(guides_presets_changed), lib);

  lib->flip_guides = dt_bauhaus_combobox_new(NULL);
  dt_bauhaus_widget_set_label(lib->flip_guides, NULL, _("flip"));
  dt_bauhaus_combobox_add(lib->flip_guides, _("none"));
  dt_bauhaus_combobox_add(lib->flip_guides, _("horizontally"));
  dt_bauhaus_combobox_add(lib->flip_guides, _("vertically"));
  dt_bauhaus_combobox_add(lib->flip_guides, _("both"));
  gtk_widget_set_tooltip_text(lib->flip_guides, _("flip guides"));
  gtk_box_pack_start(GTK_BOX(self->widget), lib->flip_guides, TRUE, TRUE, 0);

  lib->overlay = dt_bauhaus_combobox_new(NULL);
  dt_bauhaus_widget_set_label(lib->overlay, NULL, _("overlay"));
  dt_bauhaus_combobox_add(lib->overlay, _("none"));
  dt_bauhaus_combobox_add(lib->overlay, _("selected image"));
  dt_bauhaus_combobox_add(lib->overlay, _("id"));
  gtk_widget_set_tooltip_text(lib->overlay, _("overlay another image over the live view"));
  g_signal_connect(G_OBJECT(lib->overlay), "value-changed", G_CALLBACK(overlay_changed), lib);
  gtk_box_pack_start(GTK_BOX(self->widget), lib->overlay, TRUE, TRUE, 0);

  lib->overlay_id_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
  GtkWidget *label = gtk_label_new(_("image id"));
  gtk_widget_set_halign(label, GTK_ALIGN_START);
  lib->overlay_id = gtk_spin_button_new_with_range(0, 1000000000, 1);
  gtk_spin_button_set_digits(GTK_SPIN_BUTTON(lib->overlay_id), 0);
  gtk_widget_set_tooltip_text(lib->overlay_id, _("enter image id of the overlay manually"));
  g_signal_connect(G_OBJECT(lib->overlay_id), "value-changed", G_CALLBACK(_overlay_id_changed), lib);
  gtk_spin_button_set_value(GTK_SPIN_BUTTON(lib->overlay_id),
                            dt_conf_get_int("plugins/lighttable/live_view/overlay_imgid"));
  gtk_box_pack_start(GTK_BOX(lib->overlay_id_box), label, TRUE, TRUE, 0);
  gtk_box_pack_start(GTK_BOX(lib->overlay_id_box), lib->overlay_id, TRUE, TRUE, 0);
  gtk_box_pack_start(GTK_BOX(self->widget), lib->overlay_id_box, TRUE, TRUE, 0);
  gtk_widget_show(lib->overlay_id);
  gtk_widget_show(label);

  lib->overlay_mode = dt_bauhaus_combobox_new(NULL);
  dt_bauhaus_widget_set_label(lib->overlay_mode, NULL, _("overlay mode"));
  dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "normal"));
  dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "xor"));
  dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "add"));
  dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "saturate"));
#if(CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 10, 0))
  dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "multiply"));
  dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "screen"));
  dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "overlay"));
  dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "darken"));
  dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "lighten"));
  dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "color dodge"));
  dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "color burn"));
  dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "hard light"));
  dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "soft light"));
  dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "difference"));
  dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "exclusion"));
  dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "HSL hue"));
  dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "HSL saturation"));
  dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "HSL color"));
  dt_bauhaus_combobox_add(lib->overlay_mode, C_("blendmode", "HSL luminosity"));
#endif
  gtk_widget_set_tooltip_text(lib->overlay_mode, _("mode of the overlay"));
  dt_bauhaus_combobox_set(lib->overlay_mode, dt_conf_get_int("plugins/lighttable/live_view/overlay_mode"));
  g_signal_connect(G_OBJECT(lib->overlay_mode), "value-changed", G_CALLBACK(_overlay_mode_changed), lib);
  gtk_box_pack_start(GTK_BOX(self->widget), lib->overlay_mode, TRUE, TRUE, 0);

  lib->overlay_splitline = dt_bauhaus_combobox_new(NULL);
  dt_bauhaus_widget_set_label(lib->overlay_splitline, NULL, _("split line"));
  dt_bauhaus_combobox_add(lib->overlay_splitline, _("off"));
  dt_bauhaus_combobox_add(lib->overlay_splitline, _("on"));
  gtk_widget_set_tooltip_text(lib->overlay_splitline, _("only draw part of the overlay"));
  dt_bauhaus_combobox_set(lib->overlay_splitline, dt_conf_get_int("plugins/lighttable/live_view/splitline"));
  g_signal_connect(G_OBJECT(lib->overlay_splitline), "value-changed", G_CALLBACK(_overlay_splitline_changed),
                   lib);
  gtk_box_pack_start(GTK_BOX(self->widget), lib->overlay_splitline, TRUE, TRUE, 0);

  gtk_widget_set_visible(GTK_WIDGET(lib->overlay_mode), FALSE);
  gtk_widget_set_visible(GTK_WIDGET(lib->overlay_id_box), FALSE);
  gtk_widget_set_visible(GTK_WIDGET(lib->overlay_splitline), FALSE);

  gtk_widget_set_no_show_all(GTK_WIDGET(lib->overlay_mode), TRUE);
  gtk_widget_set_no_show_all(GTK_WIDGET(lib->overlay_id_box), TRUE);
  gtk_widget_set_no_show_all(GTK_WIDGET(lib->overlay_splitline), TRUE);

  guides_presets_set_visibility(lib, 0);
}

void gui_cleanup(dt_lib_module_t *self)
{
  // dt_lib_live_view_t *lib = self->data;

  // g_list_free(lib->guides_widgets_list);
  // INTENTIONAL. it's supposed to be leaky until lua is fixed.

  free(self->data);
  self->data = NULL;
}

void view_enter(struct dt_lib_module_t *self,struct dt_view_t *old_view,struct dt_view_t *new_view)
{
  // disable buttons that won't work with this camera
  // TODO: initialize tethering mode outside of libs/camera.s so we can use darktable.camctl->active_camera
  // here
  dt_lib_live_view_t *lib = self->data;
  const dt_camera_t *cam = darktable.camctl->active_camera;
  if(cam == NULL) cam = darktable.camctl->wanted_camera;

  gboolean sensitive = cam && cam->can_live_view_advanced;

  gtk_widget_set_sensitive(lib->live_view_zoom, sensitive);
  gtk_widget_set_sensitive(lib->focus_in_big, sensitive);
  gtk_widget_set_sensitive(lib->focus_in_small, sensitive);
  gtk_widget_set_sensitive(lib->focus_out_big, sensitive);
  gtk_widget_set_sensitive(lib->focus_out_small, sensitive);
}

// TODO: find out where the zoom window is and draw overlay + grid accordingly
#define MARGIN 20
#define BAR_HEIGHT 18 /* see libs/camera.c */
void gui_post_expose(dt_lib_module_t *self, cairo_t *cr, int32_t width, int32_t height, int32_t pointerx,
                     int32_t pointery)
{
  dt_camera_t *cam = (dt_camera_t *)darktable.camctl->active_camera;
  dt_lib_live_view_t *lib = self->data;

  if(cam->is_live_viewing == FALSE || cam->live_view_zoom == TRUE) return;

  dt_pthread_mutex_lock(&cam->live_view_pixbuf_mutex);
  if(GDK_IS_PIXBUF(cam->live_view_pixbuf) == FALSE)
  {
    dt_pthread_mutex_unlock(&cam->live_view_pixbuf_mutex);
    return;
  }
  double w = width - (MARGIN * 2.0f);
  double h = height - (MARGIN * 2.0f) - BAR_HEIGHT;
  gint pw = gdk_pixbuf_get_width(cam->live_view_pixbuf);
  gint ph = gdk_pixbuf_get_height(cam->live_view_pixbuf);
  lib->overlay_x0 = lib->overlay_x1 = lib->overlay_y0 = lib->overlay_y1 = 0.0;

  gboolean use_splitline = (dt_bauhaus_combobox_get(lib->overlay_splitline) == 1);

  // OVERLAY
  int imgid = 0;
  switch(dt_bauhaus_combobox_get(lib->overlay))
  {
    case OVERLAY_SELECTED:
      imgid = dt_view_tethering_get_selected_imgid(darktable.view_manager);
      break;
    case OVERLAY_ID:
      imgid = lib->imgid;
      break;
  }
  if(imgid > 0)
  {
    cairo_save(cr);
    const dt_image_t *img = dt_image_cache_testget(darktable.image_cache, imgid, 'r');
    // if the user points at this image, we really want it:
    if(!img) img = dt_image_cache_get(darktable.image_cache, imgid, 'r');

    const float imgwd = 0.97f;
    dt_mipmap_buffer_t buf;
    dt_mipmap_size_t mip = dt_mipmap_cache_get_matching_size(darktable.mipmap_cache, imgwd * w, imgwd * h);
    dt_mipmap_cache_get(darktable.mipmap_cache, &buf, imgid, mip, 0, 'r');

    float scale = 1.0;
    cairo_surface_t *surface = NULL;
    if(buf.buf)
    {
      const int32_t stride = cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, buf.width);
      surface = cairo_image_surface_create_for_data(buf.buf, CAIRO_FORMAT_RGB24, buf.width,
                                                    buf.height, stride);
      scale = fminf(fminf(w, pw) / (float)buf.width, fminf(h, ph) / (float)buf.height);
    }

    // draw centered and fitted:
    cairo_translate(cr, width / 2.0, (height + BAR_HEIGHT) / 2.0f);
    cairo_scale(cr, scale, scale);

    if(buf.buf)
    {
      cairo_translate(cr, -.5f * buf.width, -.5f * buf.height);

      if(use_splitline)
      {
        double x0, y0, x1, y1;
        switch(lib->splitline_rotation)
        {
          case 0:
            x0 = 0.0;
            y0 = 0.0;
            x1 = buf.width * lib->splitline_x;
            y1 = buf.height;
            break;
          case 1:
            x0 = 0.0;
            y0 = 0.0;
            x1 = buf.width;
            y1 = buf.height * lib->splitline_y;
            break;
          case 2:
            x0 = buf.width * lib->splitline_x;
            y0 = 0.0;
            x1 = buf.width;
            y1 = buf.height;
            break;
          case 3:
            x0 = 0.0;
            y0 = buf.height * lib->splitline_y;
            x1 = buf.width;
            y1 = buf.height;
            break;
          default:
            fprintf(stderr, "OMFG, the world will collapse, this shouldn't be reachable!\n");
            dt_pthread_mutex_unlock(&cam->live_view_pixbuf_mutex);
            return;
        }

        cairo_rectangle(cr, x0, y0, x1, y1);
        cairo_clip(cr);
      }

      cairo_set_source_surface(cr, surface, 0, 0);
      // set filter no nearest:
      // in skull mode, we want to see big pixels.
      // in 1 iir mode for the right mip, we want to see exactly what the pipe gave us, 1:1 pixel for pixel.
      // in between, filtering just makes stuff go unsharp.
      if((buf.width <= 8 && buf.height <= 8) || fabsf(scale - 1.0f) < 0.01f)
        cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_NEAREST);
      cairo_rectangle(cr, 0, 0, buf.width, buf.height);
      int overlay_modes_index = dt_bauhaus_combobox_get(lib->overlay_mode);
      if(overlay_modes_index >= 0)
      {
        cairo_operator_t mode = _overlay_modes[overlay_modes_index];
        cairo_set_operator(cr, mode);
      }
      cairo_fill(cr);
      cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
      cairo_surface_destroy(surface);
    }
    cairo_restore(cr);
    if(buf.buf) dt_mipmap_cache_release(darktable.mipmap_cache, &buf);
    if(img) dt_image_cache_read_release(darktable.image_cache, img);

    // ON CANVAS CONTROLS
    if(use_splitline)
    {
      scale = fminf(1.0, fminf(w / pw, h / ph));

      // image coordinates
      lib->overlay_x0 = 0.5 * (width - pw * scale);
      lib->overlay_y0 = 0.5 * (height - ph * scale + BAR_HEIGHT);
      lib->overlay_x1 = lib->overlay_x0 + pw * scale;
      lib->overlay_y1 = lib->overlay_y0 + ph * scale;

      // splitline position to absolute coords:
      double sl_x = lib->overlay_x0 + lib->splitline_x * pw * scale;
      double sl_y = lib->overlay_y0 + lib->splitline_y * ph * scale;

      int x0 = sl_x, y0 = 0.0, x1 = x0, y1 = height;
      if(lib->splitline_rotation % 2 != 0)
      {
        x0 = 0.0;
        y0 = sl_y;
        x1 = width;
        y1 = y0;
      }
      gboolean mouse_over_control = (lib->splitline_rotation % 2 == 0) ? (fabs(sl_x - pointerx) < 5)
                                                                       : (fabs(sl_y - pointery) < 5);
      cairo_save(cr);
      cairo_set_source_rgb(cr, .7, .7, .7);
      cairo_set_line_width(cr, (mouse_over_control ? 2.0 : 0.5));

      cairo_move_to(cr, x0, y0);
      cairo_line_to(cr, x1, y1);
      cairo_stroke(cr);

      /* if mouse over control lets draw center rotate control, hide if split is dragged */
      if(!lib->splitline_dragging && mouse_over_control)
      {
        cairo_set_line_width(cr, 0.5);
        double s = width * HANDLE_SIZE;
        dtgtk_cairo_paint_refresh(cr, sl_x - (s * 0.5), sl_y - (s * 0.5), s, s, 1, NULL);
      }

      cairo_restore(cr);
    }
  }

  // GUIDES
  if(cam->live_view_rotation % 2 == 1)
  {
    gint tmp = pw;
    pw = ph;
    ph = tmp;
  }
  float scale = 1.0;
  //   if(cam->live_view_zoom == FALSE)
  //   {
  if(pw > w) scale = w / pw;
  if(ph > h) scale = fminf(scale, h / ph);
  //   }
  double sw = scale * pw;
  double sh = scale * ph;

  // draw guides
  int guide_flip = dt_bauhaus_combobox_get(lib->flip_guides);
  double left = (width - sw) * 0.5;
  double top = (height + BAR_HEIGHT - sh) * 0.5;

  double dashes = 5.0;

  cairo_save(cr);
  cairo_rectangle(cr, left, top, sw, sh);
  cairo_clip(cr);
  cairo_set_dash(cr, &dashes, 1, 0);

  // Move coordinates to local center selection.
  cairo_translate(cr, (sw / 2 + left), (sh / 2 + top));

  // Flip horizontal.
  if(guide_flip & FLAG_FLIP_HORIZONTAL) cairo_scale(cr, -1, 1);
  // Flip vertical.
  if(guide_flip & FLAG_FLIP_VERTICAL) cairo_scale(cr, 1, -1);

  int which = dt_bauhaus_combobox_get(lib->guide_selector);
  dt_guides_t *guide = (dt_guides_t *)g_list_nth_data(darktable.guides, which - 1);
  if(guide)
  {
    guide->draw(cr, -sw / 2, -sh / 2, sw, sh, 1.0, guide->user_data);
    cairo_stroke_preserve(cr);
    cairo_set_dash(cr, &dashes, 0, 0);
    cairo_set_source_rgba(cr, .3, .3, .3, .8);
    cairo_stroke(cr);
  }
  cairo_restore(cr);
  dt_pthread_mutex_unlock(&cam->live_view_pixbuf_mutex);
}
Beispiel #9
0
void
dt_view_image_expose(
    dt_view_image_over_t *image_over,
    uint32_t imgid,
    cairo_t *cr,
    int32_t width,
    int32_t height,
    int32_t zoom,
    int32_t px,
    int32_t py)
{
  cairo_save (cr);
  float bgcol = 0.4, fontcol = 0.425, bordercol = 0.1, outlinecol = 0.2;
  int selected = 0, altered = 0, imgsel;
  DT_CTL_GET_GLOBAL(imgsel, lib_image_mouse_over_id);
  // if(img->flags & DT_IMAGE_SELECTED) selected = 1;

  /* clear and reset statements */
  DT_DEBUG_SQLITE3_CLEAR_BINDINGS(darktable.view_manager->statements.is_selected);
  DT_DEBUG_SQLITE3_CLEAR_BINDINGS(darktable.view_manager->statements.have_history);
  DT_DEBUG_SQLITE3_RESET(darktable.view_manager->statements.is_selected);
  DT_DEBUG_SQLITE3_RESET(darktable.view_manager->statements.have_history);

  /* bind imgid to prepared statments */
  DT_DEBUG_SQLITE3_BIND_INT(darktable.view_manager->statements.is_selected, 1, imgid);
  DT_DEBUG_SQLITE3_BIND_INT(darktable.view_manager->statements.have_history, 1, imgid); 

  /* lets check if imgid is selected */
  if(sqlite3_step(darktable.view_manager->statements.is_selected) == SQLITE_ROW) 
    selected = 1;

  /* lets check if imgid has history */
  if(sqlite3_step(darktable.view_manager->statements.have_history) == SQLITE_ROW) 
    altered = 1;
  
  const dt_image_t *img = dt_image_cache_read_testget(darktable.image_cache, imgid);
  if(selected == 1)
  {
    outlinecol = 0.4;
    bgcol = 0.6;
    fontcol = 0.5;
  }
  if(imgsel == imgid)
  {
    bgcol = 0.8;  // mouse over
    fontcol = 0.7;
    outlinecol = 0.6;
    // if the user points at this image, we really want it:
    if(!img)
      img = dt_image_cache_read_get(darktable.image_cache, imgid);
  }
  float imgwd = 0.90f;
  if(zoom == 1)
  {
    imgwd = .97f;
    // cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
  }
  else
  {
    double x0 = 1, y0 = 1, rect_width = width-2, rect_height = height-2, radius = 5;
    double x1, y1, off, off1;

    x1=x0+rect_width;
    y1=y0+rect_height;
    off=radius*0.666;
    off1 = radius-off;
    cairo_move_to  (cr, x0, y0 + radius);
    cairo_curve_to (cr, x0, y0+off1, x0+off1 , y0, x0 + radius, y0);
    cairo_line_to (cr, x1 - radius, y0);
    cairo_curve_to (cr, x1-off1, y0, x1, y0+off1, x1, y0 + radius);
    cairo_line_to (cr, x1 , y1 - radius);
    cairo_curve_to (cr, x1, y1-off1, x1-off1, y1, x1 - radius, y1);
    cairo_line_to (cr, x0 + radius, y1);
    cairo_curve_to (cr, x0+off1, y1, x0, y1-off1, x0, y1- radius);
    cairo_close_path (cr);
    cairo_set_source_rgb(cr, bgcol, bgcol, bgcol);
    cairo_fill_preserve(cr);
    cairo_set_line_width(cr, 0.005*width);
    cairo_set_source_rgb(cr, outlinecol, outlinecol, outlinecol);
    cairo_stroke(cr);

    if(img)
    {
      const char *ext = img->filename + strlen(img->filename);
      while(ext > img->filename && *ext != '.') ext--;
      ext++;
      cairo_set_source_rgb(cr, fontcol, fontcol, fontcol);
      cairo_select_font_face (cr, "sans-serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);
      cairo_set_font_size (cr, .25*width);

      cairo_move_to (cr, .01*width, .24*height);
      cairo_show_text (cr, ext);
    }
  }

  float scale = 1.0;
  dt_mipmap_buffer_t buf;
  dt_mipmap_size_t mip = 
    dt_mipmap_cache_get_matching_size(
      darktable.mipmap_cache,
      imgwd*width, imgwd*height);
  dt_mipmap_cache_read_get(
      darktable.mipmap_cache,
      &buf,
      imgid,
      mip,
      0);
  cairo_surface_t *surface = NULL;
  if(buf.buf)
  {
    const int32_t stride = cairo_format_stride_for_width (CAIRO_FORMAT_RGB24, buf.width);
    surface = cairo_image_surface_create_for_data (buf.buf, CAIRO_FORMAT_RGB24, buf.width, buf.height, stride);
    if(zoom == 1)
    {
      scale = fminf(
			 fminf(darktable.thumbnail_width, width) / (float)buf.width, 
			 fminf(darktable.thumbnail_height, height) / (float)buf.height
			 );
    }
    else scale = fminf(width*imgwd/(float)buf.width, height*imgwd/(float)buf.height);
  }

  // draw centered and fitted:
  cairo_save(cr);
  cairo_translate(cr, width/2.0, height/2.0f);
  cairo_scale(cr, scale, scale);

  if(buf.buf)
  {
    cairo_translate(cr, -.5f*buf.width, -.5f*buf.height);
    cairo_set_source_surface (cr, surface, 0, 0);
    if(buf.width <= 8 && buf.height <= 8)
      cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_NEAREST);
    cairo_rectangle(cr, 0, 0, buf.width, buf.height);
    cairo_fill(cr);
    cairo_surface_destroy (surface);

    if(zoom == 1) cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_BEST);
    cairo_rectangle(cr, 0, 0, buf.width, buf.height);
  }

  // border around image
  const float border = zoom == 1 ? 16/scale : 2/scale;
  cairo_set_source_rgb(cr, bordercol, bordercol, bordercol);
  if(buf.buf && selected)
  {
    cairo_set_line_width(cr, 1./scale);
    if(zoom == 1)
    {
      // draw shadow around border
      cairo_set_source_rgb(cr, 0.2, 0.2, 0.2);
      cairo_stroke(cr);
      // cairo_new_path(cr);
      cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD);
      float alpha = 1.0f;
      for(int k=0; k<16; k++)
      {
        cairo_rectangle(cr, 0, 0, buf.width, buf.height);
        cairo_new_sub_path(cr);
        cairo_rectangle(cr, -k/scale, -k/scale, buf.width+2.*k/scale, buf.height+2.*k/scale);
        cairo_set_source_rgba(cr, 0, 0, 0, alpha);
        alpha *= 0.6f;
        cairo_fill(cr);
      }
    }
    else
    {
      cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD);
      cairo_new_sub_path(cr);
      cairo_rectangle(cr, -border, -border, buf.width+2.*border, buf.height+2.*border);
      cairo_stroke_preserve(cr);
      cairo_set_source_rgb(cr, 1.0-bordercol, 1.0-bordercol, 1.0-bordercol);
      cairo_fill(cr);
    }
  }
  else if(buf.buf)
  {
    cairo_set_line_width(cr, 1);
    cairo_stroke(cr);
  }
  cairo_restore(cr);
  if(buf.buf)
    dt_mipmap_cache_read_release(darktable.mipmap_cache, &buf);

  const float fscale = fminf(width, height);
  if(imgsel == imgid)
  {
    // draw mouseover hover effects, set event hook for mouse button down!
    *image_over = DT_VIEW_DESERT;
    cairo_set_line_width(cr, 1.5);
    cairo_set_source_rgb(cr, outlinecol, outlinecol, outlinecol);
    cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
    float r1, r2;
    if(zoom != 1)
    {
      r1 = 0.05*width;
      r2 = 0.022*width;
    }
    else
    {
      r1 = 0.015*fscale;
      r2 = 0.007*fscale;
    }

    float x, y;
    if(zoom != 1) y = 0.90*height;
    else y = .12*fscale;

    if(img) for(int k=0; k<5; k++)
      {
        if(zoom != 1) x = (0.41+k*0.12)*width;
        else x = (.08+k*0.04)*fscale;

        if((img->flags & 0x7) != 6) //if rejected: draw no stars
        {
          dt_view_star(cr, x, y, r1, r2);
          if((px - x)*(px - x) + (py - y)*(py - y) < r1*r1)
          {
            *image_over = DT_VIEW_STAR_1 + k;
            cairo_fill(cr);
          }
          else if((img->flags & 0x7) > k)
          {
            cairo_fill_preserve(cr);
            cairo_set_source_rgb(cr, 1.0-bordercol, 1.0-bordercol, 1.0-bordercol);
            cairo_stroke(cr);
            cairo_set_source_rgb(cr, outlinecol, outlinecol, outlinecol);
          }
          else cairo_stroke(cr);
        }
      }

    //Image rejected?
    if(zoom !=1) x = 0.11*width;
    else x = .04*fscale;

    if((px - x)*(px - x) + (py - y)*(py - y) < r1*r1)
    {
      *image_over = DT_VIEW_REJECT; //mouse sensitive
      cairo_new_sub_path(cr);
      cairo_arc(cr, x, y, (r1+r2)*.5, 0, 2.0f*M_PI);
      cairo_stroke(cr);
    }
    else if (img && ((img->flags & 0x7) == 6))
    {
      cairo_set_source_rgb(cr, 1., 0., 0.);
      cairo_new_sub_path(cr);
      cairo_arc(cr, x, y, (r1+r2)*.5, 0, 2.0f*M_PI);
      cairo_stroke(cr);
      cairo_set_line_width(cr, 2.5);
    }

    //reject cross:
    cairo_move_to(cr, x-r2, y-r2);
    cairo_line_to(cr, x+r2, y+r2);
    cairo_move_to(cr, x+r2, y-r2);
    cairo_line_to(cr, x-r2, y+r2);
    cairo_close_path(cr);
    cairo_stroke(cr);
    cairo_set_source_rgb(cr, outlinecol, outlinecol, outlinecol);
    cairo_set_line_width(cr, 1.5);

    // image altered?
    if(altered)
    {
      // align to right
      float s = (r1+r2)*.5;
      if(zoom != 1)
      {
        x = width*0.9;
        y = height*0.1;
      }
      else x = (.04+7*0.04)*fscale;
      dt_view_draw_altered(cr, x, y, s);
      //g_print("px = %d, x = %.4f, py = %d, y = %.4f\n", px, x, py, y);
      if(img && abs(px-x) <= 1.2*s && abs(py-y) <= 1.2*s) // mouse hovers over the altered-icon -> history tooltip!
      {
        darktable.gui->center_tooltip = 1;
      }
    }
  }

  // kill all paths, in case img was not loaded yet, or is blocked:
  cairo_new_path(cr);

  // TODO: make mouse sensitive, just as stars!
  // TODO: cache in image struct!
  {
    // color labels:
    const float x = zoom == 1 ? (0.07)*fscale : .21*width;
    const float y = zoom == 1 ? 0.17*fscale: 0.1*height;
    const float r = zoom == 1 ? 0.01*fscale : 0.03*width;

    /* clear and reset prepared statement */
    DT_DEBUG_SQLITE3_CLEAR_BINDINGS(darktable.view_manager->statements.get_color);
    DT_DEBUG_SQLITE3_RESET(darktable.view_manager->statements.get_color); 

    /* setup statement and iterate rows */
    DT_DEBUG_SQLITE3_BIND_INT(darktable.view_manager->statements.get_color, 1, imgid);
    while(sqlite3_step(darktable.view_manager->statements.get_color) == SQLITE_ROW)
    {
      cairo_save(cr);
      int col = sqlite3_column_int(darktable.view_manager->statements.get_color, 0);
      // see src/dtgtk/paint.c
      dtgtk_cairo_paint_label(cr, x+(3*r*col)-5*r, y-r, r*2, r*2, col);
      cairo_restore(cr);
    }
  }

  if(img && (zoom == 1))
  {
    // some exif data
    cairo_set_source_rgb(cr, .7, .7, .7);
    cairo_select_font_face (cr, "sans-serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);
    cairo_set_font_size (cr, .025*fscale);

    cairo_move_to (cr, .02*fscale, .04*fscale);
    // cairo_show_text(cr, img->filename);
    cairo_text_path(cr, img->filename);
    char exifline[50];
    cairo_move_to (cr, .02*fscale, .08*fscale);
    dt_image_print_exif(img, exifline, 50);
    cairo_text_path(cr, exifline);
    cairo_fill_preserve(cr);
    cairo_set_line_width(cr, 1.0);
    cairo_set_source_rgb(cr, 0.3, 0.3, 0.3);
    cairo_stroke(cr);
  }

  if(img) dt_image_cache_read_release(darktable.image_cache, img);
  cairo_restore(cr);
  // if(zoom == 1) cairo_set_antialias(cr, CAIRO_ANTIALIAS_DEFAULT);
}
Beispiel #10
0
void
dt_view_image_expose(
    dt_view_image_over_t *image_over,
    uint32_t imgid,
    cairo_t *cr,
    int32_t width,
    int32_t height,
    int32_t zoom,
    int32_t px,
    int32_t py)
{
    const double start = dt_get_wtime();
    // some performance tuning stuff, for your pleasure.
    // on my machine with 7 image per row it seems grouping has the largest
    // impact from around 400ms -> 55ms per redraw.
#define DRAW_THUMB 1
#define DRAW_COLORLABELS 1
#define DRAW_GROUPING 1
#define DRAW_SELECTED 1
#define DRAW_HISTORY 1

#if DRAW_THUMB == 1
    // this function is not thread-safe (gui-thread only), so we
    // can safely allocate this leaking bit of memory to decompress thumbnails:
    static int first_time = 1;
    static uint8_t *scratchmem = NULL;
    if(first_time)
    {
        // scratchmem might still be NULL after this, if compression is off.
        scratchmem = dt_mipmap_cache_alloc_scratchmem(darktable.mipmap_cache);
        first_time = 0;
    }
#endif

    cairo_save (cr);
    float bgcol = 0.4, fontcol = 0.425, bordercol = 0.1, outlinecol = 0.2;
    int selected = 0, altered = 0, imgsel = -1, is_grouped = 0;
    // this is a gui thread only thing. no mutex required:
    imgsel = darktable.control->global_settings.lib_image_mouse_over_id;

#if DRAW_SELECTED == 1
    /* clear and reset statements */
    DT_DEBUG_SQLITE3_CLEAR_BINDINGS(darktable.view_manager->statements.is_selected);
    DT_DEBUG_SQLITE3_RESET(darktable.view_manager->statements.is_selected);
    /* bind imgid to prepared statments */
    DT_DEBUG_SQLITE3_BIND_INT(darktable.view_manager->statements.is_selected, 1, imgid);
    /* lets check if imgid is selected */
    if(sqlite3_step(darktable.view_manager->statements.is_selected) == SQLITE_ROW)
        selected = 1;
#endif

#if DRAW_HISTORY == 1
    DT_DEBUG_SQLITE3_CLEAR_BINDINGS(darktable.view_manager->statements.have_history);
    DT_DEBUG_SQLITE3_RESET(darktable.view_manager->statements.have_history);
    DT_DEBUG_SQLITE3_BIND_INT(darktable.view_manager->statements.have_history, 1, imgid);

    /* lets check if imgid has history */
    if(sqlite3_step(darktable.view_manager->statements.have_history) == SQLITE_ROW)
        altered = 1;
#endif

    const dt_image_t *img = dt_image_cache_read_testget(darktable.image_cache, imgid);

#if DRAW_GROUPING == 1
    DT_DEBUG_SQLITE3_CLEAR_BINDINGS(darktable.view_manager->statements.get_grouped);
    DT_DEBUG_SQLITE3_RESET(darktable.view_manager->statements.get_grouped);
    DT_DEBUG_SQLITE3_BIND_INT(darktable.view_manager->statements.get_grouped, 1, imgid);
    DT_DEBUG_SQLITE3_BIND_INT(darktable.view_manager->statements.get_grouped, 2, imgid);

    /* lets check if imgid is in a group */
    if(sqlite3_step(darktable.view_manager->statements.get_grouped) == SQLITE_ROW)
        is_grouped = 1;
    else if(img && darktable.gui->expanded_group_id == img->group_id)
        darktable.gui->expanded_group_id = -1;
#endif

    if(selected == 1)
    {
        outlinecol = 0.4;
        bgcol = 0.6;
        fontcol = 0.5;
    }
    if(imgsel == imgid)
    {
        bgcol = 0.8;  // mouse over
        fontcol = 0.7;
        outlinecol = 0.6;
        // if the user points at this image, we really want it:
        if(!img)
            img = dt_image_cache_read_get(darktable.image_cache, imgid);
    }
    float imgwd = 0.90f;
    if(zoom == 1)
    {
        imgwd = .97f;
        // cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
    }
    else
    {
        double x0 = 1, y0 = 1, rect_width = width-2, rect_height = height-2, radius = 5;
        double x1, y1, off, off1;

        x1=x0+rect_width;
        y1=y0+rect_height;
        off=radius*0.666;
        off1 = radius-off;
        cairo_move_to  (cr, x0, y0 + radius);
        cairo_curve_to (cr, x0, y0+off1, x0+off1 , y0, x0 + radius, y0);
        cairo_line_to (cr, x1 - radius, y0);
        cairo_curve_to (cr, x1-off1, y0, x1, y0+off1, x1, y0 + radius);
        cairo_line_to (cr, x1 , y1 - radius);
        cairo_curve_to (cr, x1, y1-off1, x1-off1, y1, x1 - radius, y1);
        cairo_line_to (cr, x0 + radius, y1);
        cairo_curve_to (cr, x0+off1, y1, x0, y1-off1, x0, y1- radius);
        cairo_close_path (cr);
        cairo_set_source_rgb(cr, bgcol, bgcol, bgcol);
        cairo_fill_preserve(cr);
        cairo_set_line_width(cr, 0.005*width);
        cairo_set_source_rgb(cr, outlinecol, outlinecol, outlinecol);
        cairo_stroke(cr);

        if(img)
        {
            const char *ext = img->filename + strlen(img->filename);
            while(ext > img->filename && *ext != '.') ext--;
            ext++;
            cairo_set_source_rgb(cr, fontcol, fontcol, fontcol);
            cairo_select_font_face (cr, "sans-serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);
            cairo_set_font_size (cr, .25*width);
            cairo_text_extents_t text_extends;
            cairo_text_extents (cr, ext, &text_extends);
            cairo_move_to (cr, .025*width - text_extends.x_bearing, .24*height);
            cairo_show_text (cr, ext);
        }
    }

    dt_mipmap_buffer_t buf;
    dt_mipmap_size_t mip =
        dt_mipmap_cache_get_matching_size(
            darktable.mipmap_cache,
            imgwd*width, imgwd*height);
    dt_mipmap_cache_read_get(
        darktable.mipmap_cache,
        &buf,
        imgid,
        mip,
        0);
#if DRAW_THUMB == 1
    float scale = 1.0;
    // decompress image, if necessary. if compression is off, scratchmem will be == NULL,
    // so get the real pointer back:
    uint8_t *buf_decompressed = dt_mipmap_cache_decompress(&buf, scratchmem);

    cairo_surface_t *surface = NULL;
    if(buf.buf)
    {
        const int32_t stride = cairo_format_stride_for_width (CAIRO_FORMAT_RGB24, buf.width);
        surface = cairo_image_surface_create_for_data (buf_decompressed, CAIRO_FORMAT_RGB24, buf.width, buf.height, stride);
        if(zoom == 1)
        {
            scale = fminf(
                        fminf(darktable.thumbnail_width, width) / (float)buf.width,
                        fminf(darktable.thumbnail_height, height) / (float)buf.height
                    );
        }
        else scale = fminf(width*imgwd/(float)buf.width, height*imgwd/(float)buf.height);
    }

    // draw centered and fitted:
    cairo_save(cr);
    cairo_translate(cr, width/2.0, height/2.0f);
    cairo_scale(cr, scale, scale);

    if(buf.buf)
    {
        cairo_translate(cr, -.5f*buf.width, -.5f*buf.height);
        cairo_set_source_surface (cr, surface, 0, 0);
        // set filter no nearest:
        // in skull mode, we want to see big pixels.
        // in 1 iir mode for the right mip, we want to see exactly what the pipe gave us, 1:1 pixel for pixel.
        // in between, filtering just makes stuff go unsharp.
        if((buf.width <= 8 && buf.height <= 8) || fabsf(scale - 1.0f) < 0.01f)
            cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_NEAREST);
        cairo_rectangle(cr, 0, 0, buf.width, buf.height);
        cairo_fill(cr);
        cairo_surface_destroy (surface);

        cairo_rectangle(cr, 0, 0, buf.width, buf.height);
    }

    // border around image
    const float border = zoom == 1 ? 16/scale : 2/scale;
    cairo_set_source_rgb(cr, bordercol, bordercol, bordercol);
    if(buf.buf && selected)
    {
        cairo_set_line_width(cr, 1./scale);
        if(zoom == 1)
        {
            // draw shadow around border
            cairo_set_source_rgb(cr, 0.2, 0.2, 0.2);
            cairo_stroke(cr);
            // cairo_new_path(cr);
            cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD);
            float alpha = 1.0f;
            for(int k=0; k<16; k++)
            {
                cairo_rectangle(cr, 0, 0, buf.width, buf.height);
                cairo_new_sub_path(cr);
                cairo_rectangle(cr, -k/scale, -k/scale, buf.width+2.*k/scale, buf.height+2.*k/scale);
                cairo_set_source_rgba(cr, 0, 0, 0, alpha);
                alpha *= 0.6f;
                cairo_fill(cr);
            }
        }
        else
        {
            cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD);
            cairo_new_sub_path(cr);
            cairo_rectangle(cr, -border, -border, buf.width+2.*border, buf.height+2.*border);
            cairo_stroke_preserve(cr);
            cairo_set_source_rgb(cr, 1.0-bordercol, 1.0-bordercol, 1.0-bordercol);
            cairo_fill(cr);
        }
    }
    else if(buf.buf)
    {
        cairo_set_line_width(cr, 1);
        cairo_stroke(cr);
    }
    cairo_restore(cr);
#endif
    if(buf.buf)
        dt_mipmap_cache_read_release(darktable.mipmap_cache, &buf);

    const float fscale = fminf(width, height);
    if(imgsel == imgid)
    {
        // draw mouseover hover effects, set event hook for mouse button down!
        *image_over = DT_VIEW_DESERT;
        cairo_set_line_width(cr, 1.5);
        cairo_set_source_rgb(cr, outlinecol, outlinecol, outlinecol);
        cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
        float r1, r2;
        if(zoom != 1)
        {
            r1 = 0.05*width;
            r2 = 0.022*width;
        }
        else
        {
            r1 = 0.015*fscale;
            r2 = 0.007*fscale;
        }

        float x, y;
        if(zoom != 1) y = 0.90*height;
        else y = .12*fscale;
        gboolean image_is_rejected = (img && ((img->flags & 0x7) == 6));

        if(img) for(int k=0; k<5; k++)
            {
                if(zoom != 1) x = (0.41+k*0.12)*width;
                else x = (.08+k*0.04)*fscale;

                if(!image_is_rejected) //if rejected: draw no stars
                {
                    dt_view_star(cr, x, y, r1, r2);
                    if((px - x)*(px - x) + (py - y)*(py - y) < r1*r1)
                    {
                        *image_over = DT_VIEW_STAR_1 + k;
                        cairo_fill(cr);
                    }
                    else if((img->flags & 0x7) > k)
                    {
                        cairo_fill_preserve(cr);
                        cairo_set_source_rgb(cr, 1.0-bordercol, 1.0-bordercol, 1.0-bordercol);
                        cairo_stroke(cr);
                        cairo_set_source_rgb(cr, outlinecol, outlinecol, outlinecol);
                    }
                    else cairo_stroke(cr);
                }
            }

        //Image rejected?
        if(zoom !=1) x = 0.11*width;
        else x = .04*fscale;

        if (image_is_rejected)
            cairo_set_source_rgb(cr, 1., 0., 0.);

        if((px - x)*(px - x) + (py - y)*(py - y) < r1*r1)
        {
            *image_over = DT_VIEW_REJECT; //mouse sensitive
            cairo_new_sub_path(cr);
            cairo_arc(cr, x, y, (r1+r2)*.5, 0, 2.0f*M_PI);
            cairo_stroke(cr);
        }

        if (image_is_rejected)
            cairo_set_line_width(cr, 2.5);

        //reject cross:
        cairo_move_to(cr, x-r2, y-r2);
        cairo_line_to(cr, x+r2, y+r2);
        cairo_move_to(cr, x+r2, y-r2);
        cairo_line_to(cr, x-r2, y+r2);
        cairo_close_path(cr);
        cairo_stroke(cr);
        cairo_set_source_rgb(cr, outlinecol, outlinecol, outlinecol);
        cairo_set_line_width(cr, 1.5);


        // image part of a group?
        if(is_grouped && darktable.gui && darktable.gui->grouping)
        {
            // draw grouping icon and border if the current group is expanded
            // align to the right, left of altered
            float s = (r1+r2)*.75;
            float _x, _y;
            if(zoom != 1)
            {
                _x = width*0.9 - s*2.5;
                _y = height*0.1 - s*.4;
            }
            else
            {
                _x = (.04+7*0.04-1.1*.04)*fscale;
                _y = y - (.17*.04)*fscale;
            }
            cairo_save(cr);
            if(img && (imgid != img->group_id))
                cairo_set_source_rgb(cr, fontcol, fontcol, fontcol);
            dtgtk_cairo_paint_grouping(cr, _x, _y, s, s, 23);
            cairo_restore(cr);
            // mouse is over the grouping icon
            if(img && abs(px-_x-.5*s) <= .8*s && abs(py-_y-.5*s) <= .8*s)
                *image_over = DT_VIEW_GROUP;
        }

        // image altered?
        if(altered)
        {
            // align to right
            float s = (r1+r2)*.5;
            if(zoom != 1)
            {
                x = width*0.9;
                y = height*0.1;
            }
            else x = (.04+7*0.04)*fscale;
            dt_view_draw_altered(cr, x, y, s);
            //g_print("px = %d, x = %.4f, py = %d, y = %.4f\n", px, x, py, y);
            if(img && abs(px-x) <= 1.2*s && abs(py-y) <= 1.2*s) // mouse hovers over the altered-icon -> history tooltip!
            {
                darktable.gui->center_tooltip = 1;
            }
        }
    }

    // kill all paths, in case img was not loaded yet, or is blocked:
    cairo_new_path(cr);

#if DRAW_COLORLABELS == 1
    // TODO: make mouse sensitive, just as stars!
    // TODO: cache in image struct!
    {
        // color labels:
        const float x = zoom == 1 ? (0.07)*fscale : .21*width;
        const float y = zoom == 1 ? 0.17*fscale: 0.1*height;
        const float r = zoom == 1 ? 0.01*fscale : 0.03*width;

        /* clear and reset prepared statement */
        DT_DEBUG_SQLITE3_CLEAR_BINDINGS(darktable.view_manager->statements.get_color);
        DT_DEBUG_SQLITE3_RESET(darktable.view_manager->statements.get_color);

        /* setup statement and iterate rows */
        DT_DEBUG_SQLITE3_BIND_INT(darktable.view_manager->statements.get_color, 1, imgid);
        while(sqlite3_step(darktable.view_manager->statements.get_color) == SQLITE_ROW)
        {
            cairo_save(cr);
            int col = sqlite3_column_int(darktable.view_manager->statements.get_color, 0);
            // see src/dtgtk/paint.c
            dtgtk_cairo_paint_label(cr, x+(3*r*col)-5*r, y-r, r*2, r*2, col);
            cairo_restore(cr);
        }
    }
#endif

    if(img && (zoom == 1))
    {
        // some exif data
        cairo_set_source_rgb(cr, .7, .7, .7);
        cairo_select_font_face (cr, "sans-serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);
        cairo_set_font_size (cr, .025*fscale);

        cairo_move_to (cr, .02*fscale, .04*fscale);
        // cairo_show_text(cr, img->filename);
        cairo_text_path(cr, img->filename);
        char exifline[50];
        cairo_move_to (cr, .02*fscale, .08*fscale);
        dt_image_print_exif(img, exifline, 50);
        cairo_text_path(cr, exifline);
        cairo_fill_preserve(cr);
        cairo_set_line_width(cr, 1.0);
        cairo_set_source_rgb(cr, 0.3, 0.3, 0.3);
        cairo_stroke(cr);
    }

    if(img) dt_image_cache_read_release(darktable.image_cache, img);
    cairo_restore(cr);
    // if(zoom == 1) cairo_set_antialias(cr, CAIRO_ANTIALIAS_DEFAULT);

    const double end = dt_get_wtime();
    dt_print(DT_DEBUG_PERF, "[lighttable] image expose took %0.04f sec\n", end-start);
}