[cairo] RFC: duplicating, storing and serializing drawing operations

Nicolas George nicolas.george at ens.fr
Thu Dec 28 04:37:46 PST 2006


Le septidi 7 nivôse, an CCXV, Bill Spitzak a écrit :
> I think the original request can be satisfied with a new cairo backend. 
> I'm not sure if this is possible with the current cairo implementation, 
> if not I recommend cairo should be fixed so this *is* possible, whether 
> or not such a backend is added to it.

It is not possible as-is. The problem is that the behavior of the functions
acting on cairo_t is hardcoded. For example, cairo_move_to will always
convert the given coordinates according to the current transformation and
append the path element to the current path. The actual difference of
behavior only takes place when the cairo_t actually commits its drawing to
the cairo_surface_t. In OO words, cairo_surface_t is an object, with methods
(in the form of pointers to functions) while cairo_t is not.

To achieve what you describe, it would be necessary to have a bunch of
pointers in cairo_t too. For example, cairo_move_to would become:

void
_cairo_move_to_generic (cairo_t *cr, double x, double y)
{
    <actual code for the current implementation>
}

void
cairo_move_to (cairo_t *cr, double x, double y)
{
    cr->methods->move_to(cr, x, y);
}

That has a cost, of course, but it is probably small in regard to the actual
drawing operation on raster-type surfaces. Anyway, you know what they say:
"profile, don't speculate".

> I'll call it a "cairo_recorder". The api would be something like this:
> 
> cairo_t = cairo_recorder_create();
> cairo_recorder_clear(cairo_t)
> cairo_recorder_playback(cairo_t recorder, cairo_t destination)
> cairo_recorder_save(filename)
> cairo_recorder_load(filename)
> 
> I don't think much else is needed.

I believe it is not enough. If your program to display something in a
window, you want the drawing operations to actually go to the window
immediately. If you also want it recorded for later use, you need to issue
it twice, or have something do it for you.

Furthermore, if you want to send drawing operations over a pipe, you need to
be able to _save and _load just the last operation.

Therefore, I suggest the following API :

cairo_t *cairo_recorder_create(void);
void cairo_recorder_playback(cairo_t *rec, cairo_t *destination);
(plus clear, iterators, etc.)

That is the recorder API you just mentioned.

cairo_t *cairo_duplicate_create(void);
cairo_t *cairo_duplicate_create_for(cairo_t *, ...);
void cairo_duplicate_add(cairo_t *dup, cairo_t *destination);

This is a duplicating API: every drawing operation you call on dup is
immediately called as is on all the destinations.

Therefore, to draw in a window and record for later use, you write:

recorder = cairo_recorder_create();
dup = cairo_duplicate_create_for(window, recorder, NULL);

Then, the serializing/unserializing API:

cairo_t *cairo_serialize_create(cairo_serialize_callback_t write, void *data);
void cairo_unserialize(cairo_t *destination, void *buffer, size_t buffer_size);

The callback is a function similar to the Unix write function. For every
drawing operation on the serializing cairo_t, a representation as a byte
buffer is created, and the callback is called.

On the other hand, cairo_unserialize decodes such a buffer and applied the
resulting drawing operation to the destination.

With this API, it is possible to write your cairo_recorder_save function:

void cairo_recorder_save(cairo_t *rec, const char *filename)
{
    int fd = open(filename, O_WRONLY, 0777);
    cairo_t *ser = cairo_serialize_create(write, (void *)fd);
    cairo_recorder_playback(rec, ser);
    cairo_destroy(ser);
    close(fd);
}

(this is broken on 64 bit architectures, and error checks are not done; I
just went for the simplest to show my point)

>					  I recommend the stream format be 
> text, but highly compressed (ie not xml, and numbers printed with %g 
> with leading zeros stripped).

I mostly agree. There is the problem that %g will not represent exactly most
non-integers, but that is a detail.

>				I suspect this can also be used to cut & 
> paste graphics between Linux programs.

That would be great.

> This is a tiny bit more difficult than what the original poster wanted, 
> as they will have to watch for exposure events and cause the playback 
> when they happen.

Of course; I never expected Cairo to do all that work for me, that is not
its task. But if the low-level API exist, writing a library that creates a
window, takes care of exposures and resizes, and provides a GUI for zooming
and printing, is quite straightforward. Without these API, it would require
to rewrite a set of drawing primitives.

> One annoyance is that due to all the calls to get data from Cairo, this 
> can't be a simple recorder. It will have to emulate the path building 
> and the fonts. Just ignore any problems with fonts not matching between 
> the recorder and playback.

I would go a step farther: I think it would be perfectly acceptable to just
say that operations that query information from a cairo_t are not supported
on "virtual" cairo_t such as the recoder and serializer. As for the
duplicator, querying the first destination would be a simple solution.

Regards,

-- 
  Nicolas George
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 185 bytes
Desc: Digital signature
Url : http://lists.freedesktop.org/archives/cairo/attachments/20061228/194f863d/attachment.pgp


More information about the cairo mailing list