Skip to content
Merged
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
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
69 changes: 54 additions & 15 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,40 +672,49 @@ imagedisplay_snapshot(GtkWidget *widget, GtkSnapshot *snapshot)

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

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

/* 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)));

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;
gtk_snapshot_save(snapshot);

/* 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.
/* 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);
double pixel_size = 1.0 / gdk_surface_get_scale(surface);
g_object_set(imagedisplay, "pixel-size", pixel_size, NULL);

#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);

graphene_rect_t paint;
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);

// draw any overlays
// undo snap and scale
gtk_snapshot_restore(snapshot);

// draw any overlays back in the regular coordinate space
imagedisplay_overlay_snapshot(imagedisplay, snapshot);

// end of clip
gtk_snapshot_pop(snapshot);
}

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
27 changes: 8 additions & 19 deletions src/tilecache.c
Original file line number Diff line number Diff line change
Expand Up @@ -836,19 +836,13 @@ tilecache_draw_bounds(GtkSnapshot *snapshot,
}

#ifndef HAVE_GTK_SNAPSHOT_SET_SNAP
/* Snap a graphene rect to a hardware pixel boundary on the output surface. We
* need to do this if the gtk snap mechachanism is missing or we'll get thin
* white lines on tile edges.
*/
static void
tilecache_snap_rect_to_boundary(graphene_rect_t *bounds, double pixel_size)
tilecache_snap_rect(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 +854,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 +931,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(&backdrop);
#endif /*!HAVE_GTK_SNAPSHOT_SET_SNAP*/
gtk_snapshot_push_repeat(snapshot, &backdrop, NULL);

Expand All @@ -965,7 +955,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,9 +966,8 @@ 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(&bounds);
#endif /*!HAVE_GTK_SNAPSHOT_SET_SNAP*/

gtk_snapshot_append_scaled_texture(snapshot,
tile_get_texture(tile), filter, &bounds);

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*/