Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
409 changes: 345 additions & 64 deletions src/dtgtk/culling.c

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions src/dtgtk/culling.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,17 @@ void dt_culling_change_offset_image(dt_culling_t *table,
void dt_culling_zoom_max(dt_culling_t *table);
void dt_culling_zoom_fit(dt_culling_t *table);

// zoom by zoom_delta (in th->zoom units) centered on culling-local (x_culling, y_culling).
// equivalent to _thumbs_zoom_add.
gboolean dt_culling_zoom_add(dt_culling_t *table, float zoom_delta, float x_culling, float y_culling, int state);
// Finalise a zoom gesture: reload surfaces at the correct resolution for thumbnails
// that were in deferred-preview mode during the gesture.
void dt_culling_zoom_end(dt_culling_t *table);

// translate all zoomed thumbnails by (dx, dy) screen pixels.
// state is used to optionally restrict panning to the hovered image (GDK_SHIFT_MASK).
gboolean dt_culling_pan_move(dt_culling_t *table, float dx, float dy, int state);

// set the overlays type
void dt_culling_set_overlays_mode(dt_culling_t *table,
const dt_thumbnail_overlay_t over);
Expand Down
122 changes: 104 additions & 18 deletions src/dtgtk/thumbnail.c
Original file line number Diff line number Diff line change
Expand Up @@ -353,19 +353,40 @@ static void _thumb_draw_image(dt_thumbnail_t *thumb,
{
cairo_save(cr);
const float scaler = 1.0f / darktable.gui->ppd_thb;

// During an active zoom gesture (zoom_preview_pending) the surface may have been
// rendered at a different zoom level (zoom_rendered) than the current th->zoom.
// Compute an extra scale factor so the stale surface is displayed at the correct
// visual size without needing to reload/recreate it. When zoom_rendered == th->zoom
// (normal case) extra_scale collapses to 1.0 and the code path is identical to before.
float extra_scale = 1.0f;
if(w > 0 && thumb->img_width > 0 && thumb->zoom > 0.0f)
{
const float zoom_rendered = (float)thumb->img_width
/ ((float)w * darktable.gui->ppd_thb);
if(zoom_rendered > 0.0f)
extra_scale = CLAMP(thumb->zoom / zoom_rendered, 0.1f, 20.0f);
}

// Apply outer scaler (handles HiDPI) for frame; apply extra_scale inner for image.
cairo_scale(cr, scaler, scaler);

cairo_set_source_surface(cr, thumb->img_surf, thumb->zoomx * darktable.gui->ppd,
thumb->zoomy * darktable.gui->ppd);
// Draw the image with the zoom-corrected scale.
cairo_save(cr);
cairo_scale(cr, extra_scale, extra_scale);
cairo_set_source_surface(cr, thumb->img_surf,
thumb->zoomx * darktable.gui->ppd / extra_scale,
thumb->zoomy * darktable.gui->ppd / extra_scale);

// get the transparency value
GdkRGBA im_color;
gtk_style_context_get_color(context,
gtk_widget_get_state_flags(thumb->w_image),
&im_color);
cairo_paint_with_alpha(cr, im_color.alpha);
cairo_restore(cr);

// and eventually the image border
// and eventually the image border (at scaler-only, not affected by extra_scale)
gtk_render_frame(context, cr, 0, 0,
w * darktable.gui->ppd_thb,
h * darktable.gui->ppd_thb);
Expand Down Expand Up @@ -744,10 +765,16 @@ static gboolean _event_image_draw(GtkWidget *widget,
if(zoom100 > 1.0f)
thumb->zoom = MIN(thumb->zoom, zoom100);
}
res = dt_view_image_get_surface(thumb->imgid,
image_w * thumb->zoom,
image_h * thumb->zoom,
&img_surf, FALSE);
// Use the cached variant: on zoom events where image content
// hasn't changed, this reuses the native-resolution
// color-converted surface and only re-scales it, skipping the
// expensive mipmap fetch + calloc + color transform.
res = dt_view_image_get_surface_cached(thumb->imgid,
image_w * thumb->zoom,
image_h * thumb->zoom,
&img_surf, FALSE,
&thumb->img_surf_mip_native,
&thumb->img_surf_mip_level);
}
else
{
Expand Down Expand Up @@ -1223,8 +1250,12 @@ static void _dt_preview_updated_callback(gpointer instance,
|| darktable.develop->preview_pipe->output_imgid == thumb->imgid)
&& darktable.develop->preview_pipe->backbuf)
{
// reset surface
// reset surface and invalidate native mipmap cache (content changed)
thumb->img_surf_dirty = TRUE;
if(thumb->img_surf_mip_native
&& cairo_surface_get_reference_count(thumb->img_surf_mip_native) > 0)
cairo_surface_destroy(thumb->img_surf_mip_native);
thumb->img_surf_mip_native = NULL;
gtk_widget_queue_draw(thumb->w_main);
}
}
Expand All @@ -1240,8 +1271,12 @@ static void _dt_mipmaps_updated_callback(gpointer instance,
// we recompte the history tooltip if needed
_thumb_update_altered_tooltip(thumb);

// reset surface
// reset surface and invalidate native mipmap cache (content changed)
thumb->img_surf_dirty = TRUE;
if(thumb->img_surf_mip_native
&& cairo_surface_get_reference_count(thumb->img_surf_mip_native) > 0)
cairo_surface_destroy(thumb->img_surf_mip_native);
thumb->img_surf_mip_native = NULL;
gtk_widget_queue_draw(thumb->w_main);
}

Expand Down Expand Up @@ -2189,6 +2224,54 @@ void dt_thumbnail_set_drop(dt_thumbnail_t *thumb,
void dt_thumbnail_image_refresh(dt_thumbnail_t *thumb)
{
thumb->img_surf_dirty = TRUE;
// Invalidate the native mipmap surface cache: the image content may
// have changed (edit, processing, profile change) so any cached
// color-converted surface is now stale.
if(thumb->img_surf_mip_native
&& cairo_surface_get_reference_count(thumb->img_surf_mip_native) > 0)
cairo_surface_destroy(thumb->img_surf_mip_native);
thumb->img_surf_mip_native = NULL;

// we ensure that the image is not completely outside the thumbnail,
// otherwise the image_draw is not triggered
if(gtk_widget_get_margin_start(thumb->w_image_box) >= thumb->width
|| gtk_widget_get_margin_top(thumb->w_image_box) >= thumb->height)
{
gtk_widget_set_margin_start(thumb->w_image_box, 0);
gtk_widget_set_margin_top(thumb->w_image_box, 0);
}
gtk_widget_queue_draw(thumb->w_main);
}

// force redraw for zoom-only changes: marks the surface dirty but keeps
// the native mipmap surface cache so the expensive calloc + color-transform
// is skipped on the next draw if the zoom stays in the same mipmap bucket.
void dt_thumbnail_image_refresh_zoom(dt_thumbnail_t *thumb)
{
thumb->img_surf_dirty = TRUE;
thumb->zoom_preview_pending = FALSE;
// img_surf_mip_native is intentionally NOT cleared here — the mipmap
// content hasn't changed, only the zoom level.

// we ensure that the image is not completely outside the thumbnail,
// otherwise the image_draw is not triggered
if(gtk_widget_get_margin_start(thumb->w_image_box) >= thumb->width
|| gtk_widget_get_margin_top(thumb->w_image_box) >= thumb->height)
{
gtk_widget_set_margin_start(thumb->w_image_box, 0);
gtk_widget_set_margin_top(thumb->w_image_box, 0);
}
gtk_widget_queue_draw(thumb->w_main);
}

// Instant zoom preview: does NOT set img_surf_dirty, so the expensive
// mipmap reload is skipped. _thumb_draw_image will scale the existing
// surface via an extra cairo transform to show the new zoom immediately.
// Call dt_culling_zoom_end() when the gesture finishes to trigger the
// proper surface reload at the final zoom level.
void dt_thumbnail_image_preview_zoom(dt_thumbnail_t *thumb)
{
thumb->zoom_preview_pending = TRUE;

// we ensure that the image is not completely outside the thumbnail,
// otherwise the image_draw is not triggered
Expand Down Expand Up @@ -2275,18 +2358,16 @@ void dt_thumbnail_set_overlay(dt_thumbnail_t *thumb,
void dt_thumbnail_image_refresh_position(dt_thumbnail_t *thumb)
{
// let's sanitize and apply panning values
// here we have to make sure to properly align according to ppd
// Use thumb->zoom (always current) rather than img_width/img_height: during a
// deferred pinch-zoom gesture the surface may still be at zoom=1.0 while
// thumb->zoom has been advanced, so img_width-based bounds would clamp to 0
// and discard the in-flight pan. After a surface reload these formulas are
// algebraically identical since img_width = iw * ppd_thb * zoom.
int iw = 0;
int ih = 0;
gtk_widget_get_size_request(thumb->w_image, &iw, &ih);
thumb->zoomx =
CLAMP(thumb->zoomx,
(iw * darktable.gui->ppd_thb - thumb->img_width) / darktable.gui->ppd_thb,
0);
thumb->zoomy =
CLAMP(thumb->zoomy,
(ih * darktable.gui->ppd_thb - thumb->img_height) / darktable.gui->ppd_thb,
0);
thumb->zoomx = CLAMP(thumb->zoomx, iw * (1.0f - thumb->zoom), 0);
thumb->zoomy = CLAMP(thumb->zoomy, ih * (1.0f - thumb->zoom), 0);
gtk_widget_queue_draw(thumb->w_main);
}

Expand Down Expand Up @@ -2376,6 +2457,11 @@ void dt_thumbnail_surface_destroy(dt_thumbnail_t *thumb)
cairo_surface_destroy(thumb->img_surf);
thumb->img_surf = NULL;
thumb->img_surf_dirty = TRUE;
// Also free the cached native mipmap surface.
if(thumb->img_surf_mip_native
&& cairo_surface_get_reference_count(thumb->img_surf_mip_native) > 0)
cairo_surface_destroy(thumb->img_surf_mip_native);
thumb->img_surf_mip_native = NULL;
}

void dt_thumbnail_set_selection(dt_thumbnail_t *thumb,
Expand Down
18 changes: 17 additions & 1 deletion src/dtgtk/thumbnail.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include <gtk/gtk.h>

#include "common/darktable.h"
#include "common/mipmap_cache.h"

#define MAX_STARS 5
#define IMG_TO_FIT 0.0f
Expand Down Expand Up @@ -107,6 +108,12 @@ typedef struct
cairo_surface_t *img_surf; // cached surface at exact dimensions to speed up redraw
gboolean img_surf_preview; // if TRUE, the image is originated from preview pipe
gboolean img_surf_dirty; // if TRUE, we need to recreate the surface on next drawing code
gboolean zoom_preview_pending; // TRUE during active gesture: draw stale surface scaled, defer surface reload
// Cache of the native-resolution (pre-scaling) color-converted mipmap surface.
// Reused across zoom events at the same mipmap level so the expensive
// calloc + color-transform is only done once per mip-level transition.
cairo_surface_t *img_surf_mip_native;
dt_mipmap_size_t img_surf_mip_level; // which mipmap level img_surf_mip_native covers

GtkWidget *w_cursor; // GtkDrawingArea -- triangle to show current image(s) in filmstrip
GtkWidget *w_bottom_eb; // GtkEventBox -- background of the bottom infos area (contains w_bottom)
Expand Down Expand Up @@ -192,8 +199,17 @@ void dt_thumbnail_update_selection(dt_thumbnail_t *thumb);
void dt_thumbnail_set_selection(dt_thumbnail_t *thumb,
const gboolean selected);

// force image recomputing
// force image recomputing (use this when image content changes)
void dt_thumbnail_image_refresh(dt_thumbnail_t *thumb);
// force image recomputing for zoom changes only: marks the surface
// dirty but preserves the native mipmap surface cache so the expensive
// color conversion step can be skipped on the next redraw when the
// zoom stays within the same mipmap bucket.
void dt_thumbnail_image_refresh_zoom(dt_thumbnail_t *thumb);
// instant zoom preview: skip surface reload, just queue a redraw and let
// _thumb_draw_image rescale the stale surface via cairo transform.
// Call dt_culling_zoom_end() to trigger the deferred surface reload.
void dt_thumbnail_image_preview_zoom(dt_thumbnail_t *thumb);

// do we need to display simple overlays or extended ?
void dt_thumbnail_set_overlay(dt_thumbnail_t *thumb,
Expand Down
3 changes: 2 additions & 1 deletion src/gui/gtk.c
Original file line number Diff line number Diff line change
Expand Up @@ -746,7 +746,8 @@ static gboolean _input_event(GtkWidget *widget,
if(event->type == GDK_TOUCHPAD_PINCH)
{
const GdkEventTouchpadPinch *pinch = &event->touchpad_pinch;
if(dt_view_manager_gesture_pinch(darktable.view_manager, pinch->x, pinch->y,
if(dt_view_manager_gesture_pinch(darktable.view_manager, pinch->x_root, pinch->y_root,
pinch->dx, pinch->dy,
pinch->phase, pinch->scale, pinch->state & 0xf))
{
gtk_widget_queue_draw(widget);
Expand Down
2 changes: 2 additions & 0 deletions src/views/darkroom.c
Original file line number Diff line number Diff line change
Expand Up @@ -4238,6 +4238,8 @@ gboolean gesture_pan(dt_view_t *self,
gboolean gesture_pinch(dt_view_t *self,
const double x,
const double y,
const double dx,
const double dy,
const int phase,
const double scale,
const int state)
Expand Down
Loading
Loading