Snapping

With the release of 4.23.1, GTK’s renderer will come with a new feature that we’ve called snapping.

How does it work?

Snapping is enabled by calling gtk_snapshot_set_snap(). If enabled, it will slightly adjust the placement of rectangles when drawing so that they align with the pixel grid and don’t cover half a pixel.

GSK_RECT_SNAP_ROUND

Content drawn with GTK is scaled automatically by the desktop’s scale factor. But with the arrival of native fractional scaling, it is no longer possible to know if content is aligned to the pixel grid.

While that is usually not a problem, there are a few cases where it is:

Sprite grids

Gameeky is a learning game that plays on a grid. Unfortunately, on a fractionally scaled machine, it can end up looking like this:

A screenshot of a Gameeky game without snapping. A distracting grid can be seen between the sprites.

Once those sprites are snapped to the pixel grid by rounding to the nearest pixel, the same image looks like this
A screenshot of a Gameeky game with snapping. No grid is visible anymore and it looks like a single image.

Sharp images

Often Applications want to display images in a way that matches the pixels of the image 1:1 with pixels of the monitor. This is a challenge on a fractionally scaled display. Not only is it important to get the scale factor right, it’s also important to align the pixels correctly, or they will appear slightly blurry.

The use case is not just image viewers that want to offer a 1:1 zoom factor, but all applications that redirect drawing, from game emulators to viewers like Boxes or Connections.

Hardware optimizations

And finally, there are optimizations like graphics offload that rely on content being aligned to the pixel grid or the hardware cannot optimize them. So it is important to snap content to the pixel grid for those cases.

Why don’t we just always snap to the grid?

There is one big problem with automatic snapping: smoothness. Because snapping only works on full pixels, doing slow animations causes content to jump from one pixel to the next. And that causes jitter.

The main situation where one can see this is smooth scrolling, like in this example:

Summary

The next GTK release will offer a new way to tame the effects of fractional scaling.  Please try it out and let us know how it works!

sizable news

For the upcoming GTK 4.6, we have overhauled a lot of the sizing infrastructure to make widgets fit even tighter and to make sure our sizing infrastructure actually does what it says.

halign/valign

When using the GtkWidget::halign or GtkWidget::valign properties, GTK 4.4 would look at the default size of the widget and then place the widget accordingly. This leaves a lot of extra space when one of the values was set to fill. In GTK 4.6, GTK will measure the size of the other dimension relative to the filled dimension. This makes the widget thinner but avoids extra space.

A centered label with empty space in GTK 4.4
A centered label with empty space in GTK 4.4

A centered label with no extra space in GTK 4.6
A centered label with no extra space in GTK 4.6

What if you like the old behavior?

If you do not use fill in either direction, the behavior will be as before. So update the other dimension to not be the default fill and you should get the old behavior back.

GtkBox

GtkBox has learned to assign size to widgets as needed. In GTK 4.4, size was always distributed equally among children that had the same default size. GTK 4.6 will query the children for their actual size to decide which child to distribute how much of the extra size to.

You can see this in the example, where the box was given enough space for 3, 4, 5 or 6 lines of text.

A left-aligned box in GTK 4.4
A left-aligned box in GTK 4.6

GtkLabel

As you could see above, GtkLabel also learned to properly wrap to any given number of lines. This allows labels to take a lot less widths as before, so they no longer take up empty space when they can just line-break.

xalign and halign

It’s worth pointing out that in a lot of cases applications used GtkWidget::halign = GTK_HALIGN_START; when they should have used GtkLabel::xalign = 0.0;. The first aligns the widget as far to the left as possible while the seconds aligns the text inside the assigned space to the left. So if your widgets suddenly look glued to the left edge, you might want to look into that.

GtkWindow

GtkWindow has learned how to adapt minimum size to the aspect ratio. So you can now resize your windows any way you like and they will never get too small, but they will always get as small as possible, no matter if you want to make them flat and wide or thin and high.

a new warning

While doing this work, we figured out that a few widgets do not conform to measuring requirements and added a new warning. So if you see something like:
Gtk-CRITICAL **: 00:48:33.319: gtk_widget_measure: assertion 'for_size >= minimum opposite size' failed: 23 >= 42
It means you have a widget that reports an minimum size for size -1 that is larger than the minimum size it reports for a different size, and that should never happen. You can use GTK_DEBUG=size-request and redirect to a file to find the offending widget. We also added code to work around any problems that warning, but it should be fixed nonetheless. After all, if a widget reports a wrong size, it’s likely it’s doing something wrong.

textures and paintables

With GTK4, we’ve been trying to find better solution for image data. In GTK3 the objects we used for this were pixbufs and Cairo surfaces. But they don’t fit the bill anymore, so now we have GdkTexture and GdkPaintable.

GdkTexture

GdkTexture is the replacement for GdkPixbuf. Why is it better?
For a start, it is a lot simpler. The API looks like this:

int gdk_texture_get_width (GdkTexture *texture);
int gdk_texture_get_height (GdkTexture *texture);

void gdk_texture_download (GdkTexture *texture,
                           guchar     *data,
                           gsize       stride);

So it is a 2D pixel array and if you want to, you can download the pixels. It is also guaranteed immutable, so the pixels will never change. Lots of constructors exist to create textures from files, resources, data or pixbufs.

But the biggest difference between textures and pixbufs is that they don’t expose the memory that they use to store the pixels. In fact, before gdk_texture_download() is called, that data doesn’t even need to exist.
And this is used in the GL texture. The GtkGLArea widget for example uses this method to pass data around. GStreamer is expected to pass video in the form of GL textures, too.

GdkPaintable

But sometimes, you have something more complex than an immutable bunch of pixels. For example you could have an animated GIF or a scalable SVG. That’s where GdkPaintable comes in.
In abstract terms, GdkPaintable is an interface for objects that know how to render themselves at any size. Inspired by CSS images, they can optionally provide intrinsic sizing information that GTK widgets can use to place them.
So the core of the GdkPaintable interface are the function make the paintable render itself and the 3 functions that provide sizing information:

void gdk_paintable_snapshot (GdkPaintable *paintable,
                             GdkSnapshot  *snapshot,
                             double        width,
                             double        height);

int gdk_paintable_get_intrinsic_width (GdkPaintable *paintable);
int gdk_paintable_get_intrinsic_height (GdkPaintable *paintable);
double gdk_paintable_get_intrinsic_aspect_ratio (GdkPaintable *paintable);

On top of that, the paintable can emit the “invalidate-contents” and “invalidate-size” signals when its contents or size changes.

To make this more concrete, let’s take a scalable SVG as an example: The paintable implementation would return no intrinsic size (the return value 0 for those sizing function achieves that) and whenever it is drawn, it would draw itself pixel-exact at the given size.
Or take the example of the animated GIF: It would provide its pixel size as its intrinsic size and draw the current frame of the animation scaled to the given size. And whenever the next frame of the animation should be displayed, it would emit the “invalidate-size” signal.
And last but not least, GdkTexture implements this interface.

We’re currently in the process of changing all the code that in GTK3 accepted GdkPixbuf to now accept GdkPaintable. The GtkImage widget of course has been changed already, as have the drag’n’drop icons or GtkAboutDialog. Experimental patches exist to let applications provide paintables to the GTK CSS engine.

And if you now put all this information together about GStreamer potentially providing textures backed by GL images and creating paintables that do animations that can then be uploaded to CSS, you can maybe see where this is going