diff --git a/CHANGELOG.md b/CHANGELOG.md index 7be7b28..7aace20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/src/imagedisplay.c b/src/imagedisplay.c index bb83501..01c319b 100644 --- a/src/imagedisplay.c +++ b/src/imagedisplay.c @@ -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; @@ -126,6 +131,9 @@ enum { */ PROP_DEBUG, + /* Read out display density with this. + */ + PROP_PIXEL_SIZE, }; enum { @@ -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); @@ -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; @@ -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; @@ -649,10 +672,6 @@ 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. */ @@ -660,29 +679,42 @@ imagedisplay_snapshot(GtkWidget *widget, GtkSnapshot *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); } @@ -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, diff --git a/src/imageui.c b/src/imageui.c index 0cc9c85..9078ec4 100644 --- a/src/imageui.c +++ b/src/imageui.c @@ -125,6 +125,7 @@ enum { PROP_ZOOM, PROP_X, PROP_Y, + PROP_PIXEL_SIZE, PROP_LAST }; @@ -194,6 +195,10 @@ imageui_property_name(guint prop_id) return "Y"; break; + case PROP_PIXEL_SIZE: + return "PIXEL_SIZE"; + break; + default: return ""; } @@ -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; @@ -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; @@ -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) { @@ -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 @@ -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; @@ -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, diff --git a/src/imagewindow.c b/src/imagewindow.c index 1ed3a08..01a682b 100644 --- a/src/imagewindow.c +++ b/src/imagewindow.c @@ -377,11 +377,13 @@ 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, @@ -389,7 +391,15 @@ imagewindow_get_zoom(Imagewindow *win) 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 diff --git a/src/tilecache.c b/src/tilecache.c index b9111bd..99b577f 100644 --- a/src/tilecache.c +++ b/src/tilecache.c @@ -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; @@ -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. @@ -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); @@ -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; @@ -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); diff --git a/src/tilecache.h b/src/tilecache.h index 4dcef1d..3e54755 100644 --- a/src/tilecache.h +++ b/src/tilecache.h @@ -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*/