Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
master

- do our own tile snapping if gtk has no snap mechanism
- high-dpi support

## 4.1.0 26/07/25

Expand Down
71 changes: 55 additions & 16 deletions src/imagedisplay.c
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ struct _Imagedisplay {
double scale;
double x, y;

/* The size of physical display pixels in gtk coordinates, eg. for a 200%
* desktop this would be 0.5.
*/
double pixel_size;

/* Draw the screen in debug mode.
*/
gboolean debug;
Expand Down Expand Up @@ -126,6 +131,9 @@ enum {
*/
PROP_DEBUG,

/* Read out display density with this.
*/
PROP_PIXEL_SIZE,
};

enum {
Expand Down Expand Up @@ -476,6 +484,8 @@ imagedisplay_set_property(GObject *object,
{
Imagedisplay *imagedisplay = (Imagedisplay *) object;

double d;

#ifdef DEBUG
{
g_autofree char *str = g_strdup_value_contents(value);
Expand Down Expand Up @@ -573,6 +583,15 @@ imagedisplay_set_property(GObject *object,
gtk_widget_queue_draw(GTK_WIDGET(imagedisplay));
break;

case PROP_PIXEL_SIZE:
d = g_value_get_double(value);
if (imagedisplay->pixel_size != d) {
imagedisplay->pixel_size = d;
imagedisplay_layout(imagedisplay);
gtk_widget_queue_draw(GTK_WIDGET(imagedisplay));
}
break;

default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
break;
Expand Down Expand Up @@ -632,6 +651,10 @@ imagedisplay_get_property(GObject *object,
g_value_set_boolean(value, imagedisplay->debug);
break;

case PROP_PIXEL_SIZE:
g_value_set_double(value, imagedisplay->pixel_size);
break;

default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
break;
Expand All @@ -649,41 +672,50 @@ imagedisplay_snapshot(GtkWidget *widget, GtkSnapshot *snapshot)

GTK_WIDGET_CLASS(imagedisplay_parent_class)->snapshot(widget, snapshot);

/* This can change on each repaint as windows are dragged.
*/
GtkNative *native = gtk_widget_get_native(widget);
GdkSurface *surface = gtk_native_get_surface(native);
double pixel_size = 1.0 / gdk_surface_get_scale_factor(surface);
g_object_set(imagedisplay, "pixel-size", pixel_size, NULL);

gtk_snapshot_save(snapshot);

#ifdef HAVE_GTK_SNAPSHOT_SET_SNAP
gtk_snapshot_set_snap(snapshot, GSK_RECT_SNAP_ROUND);
#endif /*HAVE_GTK_SNAPSHOT_SET_SNAP*/

gtk_snapshot_scale(snapshot, pixel_size, pixel_size);

/* Clip to the widget area, or we may paint over the display control
* bar.
*/
gtk_snapshot_push_clip(snapshot,
&GRAPHENE_RECT_INIT(0, 0,
gtk_widget_get_width(widget), gtk_widget_get_height(widget)));
gtk_widget_get_width(widget) / pixel_size,
gtk_widget_get_height(widget) / pixel_size));

graphene_rect_t paint;
paint.origin.x = imagedisplay->paint_rect.left;
paint.origin.y = imagedisplay->paint_rect.top;
paint.size.width = imagedisplay->paint_rect.width;
paint.size.height = imagedisplay->paint_rect.height;

/* If there's no gtk snapping, we do our own based on the hardware pixel
* size for the surface this snapshot will be rendered to.
*/
GtkNative *native = gtk_widget_get_native(widget);
GdkSurface *surface = gtk_native_get_surface(native);
double pixel_size = 1.0 / gdk_surface_get_scale_factor(surface);
paint.origin.x = imagedisplay->paint_rect.left / pixel_size;
paint.origin.y = imagedisplay->paint_rect.top / pixel_size;
paint.size.width = imagedisplay->paint_rect.width / pixel_size;
paint.size.height = imagedisplay->paint_rect.height / pixel_size;

if (imagedisplay->tilecache &&
imagedisplay->tilecache->n_levels > 0)
tilecache_snapshot(imagedisplay->tilecache, snapshot,
pixel_size,
imagedisplay->scale, imagedisplay->x, imagedisplay->y,
imagedisplay->scale / pixel_size,
imagedisplay->x / pixel_size,
imagedisplay->y / pixel_size,
&paint, imagedisplay->debug);

// end of clip
gtk_snapshot_pop(snapshot);

gtk_snapshot_restore(snapshot);

// draw any overlays
imagedisplay_overlay_snapshot(imagedisplay, snapshot);

gtk_snapshot_pop(snapshot);
}

static void
Expand Down Expand Up @@ -828,6 +860,13 @@ imagedisplay_class_init(ImagedisplayClass *class)
FALSE,
G_PARAM_READWRITE));

g_object_class_install_property(gobject_class, PROP_PIXEL_SIZE,
g_param_spec_double("pixel-size",
_("Pixel size"),
_("Size of hardware display pixels in gtk coordinates"),
0.0, 10.0, 0.0,
G_PARAM_READWRITE));

g_object_class_override_property(gobject_class,
PROP_HADJUSTMENT, "hadjustment");
g_object_class_override_property(gobject_class,
Expand Down
39 changes: 37 additions & 2 deletions src/imageui.c
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ enum {
PROP_ZOOM,
PROP_X,
PROP_Y,
PROP_PIXEL_SIZE,

PROP_LAST
};
Expand Down Expand Up @@ -194,6 +195,10 @@ imageui_property_name(guint prop_id)
return "Y";
break;

case PROP_PIXEL_SIZE:
return "PIXEL_SIZE";
break;

default:
return "<unknown>";
}
Expand Down Expand Up @@ -253,6 +258,11 @@ imageui_set_property(GObject *object,
"y", value);
break;

case PROP_PIXEL_SIZE:
g_object_set_property(G_OBJECT(imageui->imagedisplay),
"pixel-size", value);
break;

default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
break;
Expand Down Expand Up @@ -296,6 +306,11 @@ imageui_get_property(GObject *object,
g_object_get_property(G_OBJECT(imageui->imagedisplay), "y", value);
break;

case PROP_PIXEL_SIZE:
g_object_get_property(G_OBJECT(imageui->imagedisplay),
"pixel-size", value);
break;

default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
break;
Expand Down Expand Up @@ -429,6 +444,18 @@ imageui_get_zoom(Imageui *imageui)
return zoom;
}

double
imageui_get_pixel_size(Imageui *imageui)
{
double pixel_size;

g_object_get(imageui,
"pixel-size", &pixel_size,
NULL);

return pixel_size;
}

static gboolean
imageui_tick(GtkWidget *widget, GdkFrameClock *frame_clock, gpointer user_data)
{
Expand Down Expand Up @@ -551,7 +578,7 @@ imageui_magout(Imageui *imageui)
void
imageui_oneone(Imageui *imageui)
{
imageui_zoom_to_eased(imageui, 1.0);
imageui_zoom_to_eased(imageui, imageui_get_pixel_size(imageui));
}

static void
Expand Down Expand Up @@ -785,7 +812,8 @@ imageui_key_pressed(GtkEventControllerKey *self,
if (state & GDK_CONTROL_MASK)
zoom = 1.0 / zoom;

imageui_zoom_to_eased(imageui, zoom);
imageui_zoom_to_eased(imageui,
zoom * imageui_get_pixel_size(imageui));

handled = TRUE;
break;
Expand Down Expand Up @@ -1011,6 +1039,13 @@ imageui_class_init(ImageuiClass *class)
-VIPS_MAX_COORD, VIPS_MAX_COORD, 0,
G_PARAM_READWRITE));

g_object_class_install_property(gobject_class, PROP_PIXEL_SIZE,
g_param_spec_double("pixel_size",
_("Pixel size"),
_("Size of hardware display pixels in gtk coordinates"),
0.0, 10.0, 0.0,
G_PARAM_READWRITE));

imageui_signals[SIG_CHANGED] = g_signal_new("changed",
G_TYPE_FROM_CLASS(class),
G_SIGNAL_RUN_LAST,
Expand Down
14 changes: 12 additions & 2 deletions src/imagewindow.c
Original file line number Diff line number Diff line change
Expand Up @@ -377,19 +377,29 @@ imagewindow_files_set(Imagewindow *win, char **files, int n_files)
}
}

/* Get the effective zoom, ie. the zoom considering the hardware pixel display
* density.
*/
double
imagewindow_get_zoom(Imagewindow *win)
{
double zoom;

if (win->imageui)
g_object_get(win->imageui,
"zoom", &zoom,
NULL);
else
zoom = 1.0;

return zoom;
double pixel_size;
if (win->imageui)
g_object_get(win->imageui,
"pixel_size", &pixel_size,
NULL);
else
pixel_size = 1.0;

return zoom / pixel_size;
}

void
Expand Down
22 changes: 8 additions & 14 deletions src/tilecache.c
Original file line number Diff line number Diff line change
Expand Up @@ -841,14 +841,12 @@ tilecache_draw_bounds(GtkSnapshot *snapshot,
* white lines on tile edges.
*/
static void
tilecache_snap_rect_to_boundary(graphene_rect_t *bounds, double pixel_size)
tilecache_snap_rect_to_boundary(graphene_rect_t *bounds)
{
double left = rint(bounds->origin.x * pixel_size) / pixel_size;
double top = rint(bounds->origin.y * pixel_size) / pixel_size;
double right =
rint((bounds->origin.x + bounds->size.width) * pixel_size) / pixel_size;
double bottom =
rint((bounds->origin.y + bounds->size.height) * pixel_size) / pixel_size;
double left = rint(bounds->origin.x);
double top = rint(bounds->origin.y);
double right = rint(bounds->origin.x + bounds->size.width);
double bottom = rint(bounds->origin.y + bounds->size.height);

bounds->origin.x = left;
bounds->origin.y = top;
Expand All @@ -860,16 +858,12 @@ tilecache_snap_rect_to_boundary(graphene_rect_t *bounds, double pixel_size)
/* Scale is how much the level0 image has been scaled, x/y is the position of
* the top-left corner of @paint in the scaled image.
*
* @pixel_scale is gdk_surface_get_scale() for the surface this snapshot will
* be rendered to.
*
* @paint is the pixel area in gtk coordinates that we paint in the widget.
*
* Set debug to draw tile boundaries for debugging.
*/
void
tilecache_snapshot(Tilecache *tilecache, GtkSnapshot *snapshot,
double pixel_size,
double scale, double x, double y, graphene_rect_t *paint, gboolean debug)
{
/* In debug mode, scale and offset so we can see tile clipping.
Expand Down Expand Up @@ -941,7 +935,7 @@ tilecache_snapshot(Tilecache *tilecache, GtkSnapshot *snapshot,
*/
graphene_rect_t backdrop = *paint;
#ifndef HAVE_GTK_SNAPSHOT_SET_SNAP
tilecache_snap_rect_to_boundary(&backdrop, pixel_size);
tilecache_snap_rect_to_boundary(&backdrop);
#endif /*!HAVE_GTK_SNAPSHOT_SET_SNAP*/
gtk_snapshot_push_repeat(snapshot, &backdrop, NULL);

Expand All @@ -965,7 +959,7 @@ tilecache_snapshot(Tilecache *tilecache, GtkSnapshot *snapshot,
* blur the image. For zooming out, we want trilinear to get
* mipmaps and antialiasing.
*/
GskScalingFilter filter = scale >= pixel_size ?
GskScalingFilter filter = scale >= 1.0 ?
GSK_SCALING_FILTER_NEAREST : GSK_SCALING_FILTER_TRILINEAR;

graphene_rect_t bounds;
Expand All @@ -976,7 +970,7 @@ tilecache_snapshot(Tilecache *tilecache, GtkSnapshot *snapshot,
bounds.size.height = tile->bounds0.height * scale;

#ifndef HAVE_GTK_SNAPSHOT_SET_SNAP
tilecache_snap_rect_to_boundary(&bounds, pixel_size);
tilecache_snap_rect_to_boundary(&bounds);
#endif /*!HAVE_GTK_SNAPSHOT_SET_SNAP*/

gtk_snapshot_append_scaled_texture(snapshot,
Expand Down
1 change: 0 additions & 1 deletion src/tilecache.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,6 @@ Tilecache *tilecache_new();
/* Render the tiles to a snapshot.
*/
void tilecache_snapshot(Tilecache *tilecache, GtkSnapshot *snapshot,
double pixel_size,
double scale, double x, double y, graphene_rect_t *paint, gboolean debug);

#endif /*__TILECACHE_H*/