[prev in list] [next in list] [prev in thread] [next in thread] 

List:       cairo
Subject:    Re: [cairo] How to get extents of transformed graphics
From:       Uli Schlachter <psychon () znc ! in>
Date:       2022-09-18 16:49:39
Message-ID: e5b1d1bd-a00f-93ed-96f4-a0b3e2db20dc () znc ! in
[Download RAW message or body]

Am 18.09.22 um 18:39 schrieb Michal Sudolsky:
> On Sun, Sep 18, 2022 at 10:52 AM Uli Schlachter <psychon@znc.in> wrote:
> 
>> Hi,
>>
>> Am 17.09.22 um 15:31 schrieb Andreas Falkenhahn:
>>> On 17.09.2022 at 13:47 Uli Schlachter wrote:
>> [...]
>>> It's a bit frustrating that nobody corrected this misconception because I
>>> actually started my very first email on 28th August with it. I wrote:
>>>
>>> "Is there any way to get the *exact* extents of transformed graphics?
>> AFAICS,
>>> cairo_fill_extents() is in user coordinates, i.e. it doesn't take any
>> transformation
>>> settings into account."
>>>
>>> If someone had just said "Hey, cairo_fill_extents() *does* take the
>> transformation
>>> settings into account" it would have spared me a lot of trouble :-(
>>
>> Sorry for that. I am (currently - I guess also back then)
>> misunderstanding this question as what the attached example
>> demonstrates. The "exact" made me think you meant this.
>>
>> Left part of the image: A rectangle and its extents according to
>> cairo_path_extents().
>> Right part: The same thing, but with the user coordinate space rotated
>> by 45 °.
>>
>> To everyone who wonders what is going on: Cairo internally works in
>> device space. All translation from user to device space is done at the
>> API boundary. Thus, cairo_path_extents() internally gets the extents in
>> device space. This describes a rectangle. This rectangle is then
>> translated to user space.
> 
> 
> 
>> Thus, the white rectangle in the image would
>> be "exact" if I had drawn a rectangle instead of a circle.
>>
> 
> I changed line with "cairo_arc(cr, 50, 50, 20, 0, 2 * M_PI);" to
> "cairo_rectangle(cr, 30, 30, 40, 40);" and got this:
> 
> [image: tmp.png]
> 
> I do not understand why white rectangle here is even bigger.

Heh. I shouldn't have picked a circle. :-)

Cairo internally computes things in device space. Thus, it has the white 
rectangle on the left (well, okay, things are slightly bigger than that, 
but from the idea it is close to the white rectangle on the left).

Then it has to translate things to user space. This code only gets the 
white rectangle as its input and then transforms this, resulting in the 
right rectangle on the right.

TL;DR: The code over-approximates things when applying the 
transformation matrix.

Attached is another example demonstrating this. The red rectangle show 
the extents of the image in device space. This describes the extents of 
the actual image tightly. cairo_path_extents() then uses the extents of 
this red rectangle transformed to user space as its result value. This 
produces the green rectangle. The green rectangle is also tightly, but 
it is tight to the red rectangle and not the actual drawing.

Cheers,
Uli
-- 
- He made himself, me nothing, you nothing out of the dust
- Er machte sich mir nichts, dir nichts aus dem Staub

["tmp.png" (image/png)]
["test.c" (text/x-csrc)]

#include <cairo.h>
#include <math.h>

void draw(cairo_t *cr) {
	double x1a, x2a, y1a, y2a;
	double x1b, x2b, y1b, y2b;

	cairo_new_path(cr);
	cairo_rectangle(cr, 30, 30, 40, 40);

	cairo_set_source_rgb(cr, 0, 1, 1);
	cairo_fill_preserve(cr);

	// Get the path extents under the current ctm
	cairo_path_extents(cr, &x1a, &y1a, &x2a, &y2a);

	// Get the path again, but with all transformations unset
	cairo_save(cr);
	cairo_identity_matrix(cr);
	cairo_path_extents(cr, &x1b, &y1b, &x2b, &y2b);
	cairo_restore(cr);

	// Now draw both extents
	cairo_new_path(cr);

	cairo_set_source_rgb(cr, 0, 1, 0);
	cairo_rectangle(cr, x1a, y1a, x2a - x1a, y2a - y1a);
	cairo_stroke(cr);

	cairo_identity_matrix(cr);
	cairo_set_source_rgb(cr, 1, 0, 0);
	cairo_rectangle(cr, x1b, y1b, x2b - x1b, y2b - y1b);
	cairo_stroke(cr);
}

int main() {
	cairo_surface_t *s = cairo_image_surface_create(CAIRO_FORMAT_RGB24, 200, 100);
	cairo_t *cr = cairo_create(s);
	cairo_set_line_width(cr, 2);

	// First, an axis-aligned circle
	draw(cr);

	// Now, let's do the same with a bit of rotation
	// We want to rotate around the center of the circle. Hence, we first
	// translate so that the center is at (0, 0)...
	cairo_translate(cr, 100 + 50, 50);
	// ..then we rotate...
	cairo_rotate(cr, M_PI/4);
	// ..then we translate back a bit
	cairo_translate(cr, -50, -50);

	draw(cr);

	cairo_surface_write_to_png(s, "tmp.png");

	cairo_surface_destroy(s);
	cairo_destroy(cr);

	return 0;
}


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic