Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Running into problems while trying to scale Cairo surface to take up maximum space at specific aspect ratio

1 view
Skip to first unread message

Blue-Maned_Hawk

unread,
Jun 11, 2023, 7:14:58 PM6/11/23
to

​​Hello!

I'm looking for help with a problem i'm running into with the Cairo
graphics library. I want to paint a surface onto another surface (in
this case an XLib window) in such a way that it will take up as much
space as possible while staying at a 4:3 aspect ratio and be centered
within the window. However, i seemingly can't figure out the right
parameters for cairo_scale(), and am running into issues with a
stretched image and an image that doesn't take up the right amount of space.

Minimum example program demonstrating this behavior below; compile with
`$CC tmp.c -lX11 -lm -lcairo`. Rescale the window and you'll see the
problem i'm facing. (I am fully aware this will lead to a pixelated
output [not visible in the example program where it's just pure white];
this is intentional and exactly what i want to happen.)

#include <X11/Xlib.h>
#include <cairo/cairo.h>
#include <cairo/cairo-xlib.h>
#include <stddef.h>
#include <math.h>

const int width = 640, height = 480;
const long double aspect_ratio = 4.0l/3.0l, inverse_aspect_ratio =
3.0l/4.0l;

int main(void)
{
Display * display = XOpenDisplay(NULL);
int screen = DefaultScreen(display);
Window window = XCreateSimpleWindow(display, RootWindow(display,
screen), 10, 10, width, height, 1, BlackPixel(display, screen),
WhitePixel(display, screen));
XSelectInput(display, window, StructureNotifyMask);
XMapWindow(display, window);

cairo_surface_t * surface = cairo_xlib_surface_create(display,
window, DefaultVisual(display, screen), width, height);
cairo_xlib_surface_set_size(surface, width, height);
cairo_t * root_instance = cairo_create(surface);
cairo_surface_destroy(surface);
surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width,
height);
cairo_t * instance = cairo_create(surface);

cairo_set_source_rgb(instance, 1, 1, 1);
cairo_paint(instance);

for (;;) {
for (XEvent e; XPending(display) != 0; XNextEvent(display, &e))
if (e.type == ConfigureNotify) {
cairo_identity_matrix(root_instance);
if (e.xconfigure.width < width || e.xconfigure.height <
height) {
XResizeWindow(display, window, fmax(width,
e.xconfigure.width), fmax(width, e.xconfigure.height));

cairo_xlib_surface_set_size(cairo_get_target(root_instance), fmax(width,
e.xconfigure.width), fmax(height, e.xconfigure.height));
} else {

cairo_xlib_surface_set_size(cairo_get_target(root_instance),
e.xconfigure.width, e.xconfigure.height);
if (e.xconfigure.width >= aspect_ratio *
e.xconfigure.height) {
cairo_translate(root_instance,
(e.xconfigure.width - e.xconfigure.height * aspect_ratio) / 2, 0);
cairo_scale(root_instance, (e.xconfigure.height
* aspect_ratio) / width, e.xconfigure.height / height);
} else {
cairo_translate(root_instance, 0,
(e.xconfigure.height - e.xconfigure.width * inverse_aspect_ratio) / 2);
cairo_scale(root_instance, e.xconfigure.width /
width, (e.xconfigure.height * inverse_aspect_ratio) / height);
}
}
}

cairo_push_group(root_instance);
cairo_set_source_rgb(root_instance, 0, 0, 0);
cairo_paint(root_instance);
cairo_set_source_surface(root_instance, surface, 0, 0);
cairo_paint(root_instance);
cairo_pop_group_to_source(root_instance);
cairo_paint(root_instance);
cairo_surface_flush(cairo_get_target(root_instance));
}
}

--
⚗︎ | /blu.mɛin.dʰak/ | shortens to "Hawk" | he/him/his/himself/Mr.
bluemanedhawk.github.io
Bitches stole my whole ass ␔🭖᷿᪳𝼗᷍⏧𒒫𐻾ࣛ↉�⃣ quoted-printable, can't
have shit in Thunderbird 😩

Ben Bacarisse

unread,
Jun 11, 2023, 8:07:38 PM6/11/23
to
Blue-Maned_Hawk <bluema...@gmail.com> writes:

> ​​Hello!
>
> I'm looking for help with a problem i'm running into with the Cairo
> graphics library. I want to paint a surface onto another surface (in this
> case an XLib window) in such a way that it will take up as much space as
> possible while staying at a 4:3 aspect ratio and be centered within the
> window. However, i seemingly can't figure out the right parameters for
> cairo_scale(), and am running into issues with a stretched image and an
> image that doesn't take up the right amount of space.

There are quite a few issues. This is what jumped out at me:
This is busy waiting which will ramp up the CPU use. Presumably this is
just for testing, but even so...

> if (e.type == ConfigureNotify) {
> cairo_identity_matrix(root_instance);
> if (e.xconfigure.width < width || e.xconfigure.height <
> height) {
> XResizeWindow(display, window, fmax(width,
> e.xconfigure.width), fmax(width, e.xconfigure.height));

Surely you wanted fmax(height, e.xconfigure.height) here?

> cairo_xlib_surface_set_size(cairo_get_target(root_instance), fmax(width,
> e.xconfigure.width), fmax(height, e.xconfigure.height));

This case looks odd. What's the intent? Do you want stop the user
making the window narrower than width or shorted than height? If that's
what you want you should probably limit e.xconfigure.{width,height}, set
the X window size and then carry on as before because you still need to
set the offset and scale and offset the source in this case as well.

> } else {
>
> cairo_xlib_surface_set_size(cairo_get_target(root_instance),
> e.xconfigure.width, e.xconfigure.height);
> if (e.xconfigure.width >= aspect_ratio *
> e.xconfigure.height) {
> cairo_translate(root_instance, (e.xconfigure.width
> - e.xconfigure.height * aspect_ratio) / 2, 0);
> cairo_scale(root_instance, (e.xconfigure.height *
> aspect_ratio) / width, e.xconfigure.height /
> height);

I'm pretty sure you did not intend to divide two integers here. Both
e.xconfigure.height and height have integer type.

But there's is a bigger issue. To maintain the aspect ration, the x and y
scaling should be the same. Once you have decided whether it's the
window width or the height that will determine the scaling, you should
calculate the scale factor and use that in both arguments.

> } else {
> cairo_translate(root_instance, 0,
> (e.xconfigure.height - e.xconfigure.width *
> inverse_aspect_ratio) / 2);
> cairo_scale(root_instance, e.xconfigure.width /
> width, (e.xconfigure.height * inverse_aspect_ratio)
> / height);
> }
> }
> }
>
> cairo_push_group(root_instance);
> cairo_set_source_rgb(root_instance, 0, 0, 0);
> cairo_paint(root_instance);
> cairo_set_source_surface(root_instance, surface, 0, 0);
> cairo_paint(root_instance);
> cairo_pop_group_to_source(root_instance);
> cairo_paint(root_instance);
> cairo_surface_flush(cairo_get_target(root_instance));

Do you realise that this is in the outer for (;;) loop? That's a lot of
busy work!

> }
> }

--
Ben.

Blue-Maned_Hawk

unread,
Jun 11, 2023, 8:56:26 PM6/11/23
to
On 6/11/23 20:07, Ben Bacarisse wrote:
> Blue-Maned_Hawk <bluema...@gmail.com> writes:
>
> <snip/>
>
>> XResizeWindow(display, window, fmax(width,
>> e.xconfigure.width), fmax(width, e.xconfigure.height));
>
> Surely you wanted fmax(height, e.xconfigure.height) here?
>

Oops, yes. That was an accident.

>> cairo_xlib_surface_set_size(cairo_get_target(root_instance), fmax(width,
>> e.xconfigure.width), fmax(height, e.xconfigure.height));
>
> This case looks odd. What's the intent? Do you want stop the user
> making the window narrower than width or shorted than height?

Yes, i do. I didn't know of any other way to do this.

> If that's
> what you want you should probably limit e.xconfigure.{width,height}, set
> the X window size and then carry on as before because you still need to
> set the offset and scale and offset the source in this case as well.
>

How would i do that?

>> } else {
>>
>> cairo_xlib_surface_set_size(cairo_get_target(root_instance),
>> e.xconfigure.width, e.xconfigure.height);
>> if (e.xconfigure.width >= aspect_ratio *
>> e.xconfigure.height) {
>> cairo_translate(root_instance, (e.xconfigure.width
>> - e.xconfigure.height * aspect_ratio) / 2, 0);
>> cairo_scale(root_instance, (e.xconfigure.height *
>> aspect_ratio) / width, e.xconfigure.height /
>> height);
>
> I'm pretty sure you did not intend to divide two integers here. Both
> e.xconfigure.height and height have integer type.
>
> But there's is a bigger issue. To maintain the aspect ration, the x and y
> scaling should be the same. Once you have decided whether it's the
> window width or the height that will determine the scaling, you should
> calculate the scale factor and use that in both arguments.
>

…oh! You're right! It seems like replacing the cairo_scale() calls
with cairo_scale(root_instance, (double)e.xconfigure.height /
(double)height, (double)e.xconfigure.height / (double)height); and the
corresponding call for the width case has worked! Thank you so much!

>> cairo_push_group(root_instance);
>> cairo_set_source_rgb(root_instance, 0, 0, 0);
>> cairo_paint(root_instance);
>> cairo_set_source_surface(root_instance, surface, 0, 0);
>> cairo_paint(root_instance);
>> cairo_pop_group_to_source(root_instance);
>> cairo_paint(root_instance);
>> cairo_surface_flush(cairo_get_target(root_instance));
>
> Do you realise that this is in the outer for (;;) loop? That's a lot of
> busy work!
>

In the actual application, the screen will be getting updated pretty
much every frame, and the loop will be limited to only run once per frame.

Ben Bacarisse

unread,
Jun 12, 2023, 7:24:35 AM6/12/23
to
Blue-Maned_Hawk <bluema...@gmail.com> writes:

> On 6/11/23 20:07, Ben Bacarisse wrote:
>> Blue-Maned_Hawk <bluema...@gmail.com> writes:
<cut>
>>> cairo_xlib_surface_set_size(cairo_get_target(root_instance), fmax(width,
>>> e.xconfigure.width), fmax(height, e.xconfigure.height));
>> This case looks odd. What's the intent? Do you want stop the user
>> making the window narrower than width or shorted than height?
>
> Yes, i do. I didn't know of any other way to do this.
>
>> If that's
>> what you want you should probably limit e.xconfigure.{width,height}, set
>> the X window size and then carry on as before because you still need to
>> set the offset and scale and offset the source in this case as well.
>
> How would i do that?

You could change e.xconfigure.width (and height) but rather than do that
I'd base all the code on two new sizes

int new_width = e.xconfigure.width, new_height = e.xconfigure.height;
if (new_width < width || new_height < height) {
new_width = fmax(width, new_width);
ditto height
set Xwindow size to new_width/new_height
}
what goes here is what was your else clause but using new_width and
new_height rather than the e.xconfigure versions

<cut>
--
Ben.

Ben Bacarisse

unread,
Jun 12, 2023, 9:48:48 AM6/12/23
to
Correction. This is a bad idea! The correct way to do this is to rope
in the window manager. Include X11/Xutil.h and then, after creating the
window, do

XSizeHints sizeHints = {
.flags = PMinSize, .min_width = width, .min_height = height
};
XSetWMNormalHints(display, window, &sizeHints);

--
Ben.

Blue-Maned_Hawk

unread,
Jun 12, 2023, 2:26:06 PM6/12/23
to
​Thank you! This does exactly what i was trying to do!
0 new messages