Cairo rendering story

Bertram Felgenhauer <bertram.felgenhauer-gM/[email protected]>
Newsgroups gmane.comp.gnome.lib.librsvg.devel
Message-ID <[email protected]>
Hi,

I'm writing this to explain a bit about how cairo rendering in rsvg
currently works, and to point out a few issues that this approach has.
I'll make references to an "old way" which is the way librsvg used to
do things before #412682 [1].

Needless to say this is my personal understanding of librsvg's inner
workings. Please point out any mistakes you find.

I use "front end" for the device independent part of librsvg which drives
the cairo "back end", which does the actual drawing.

librsvg was designed to do its own rendering. To do that, its front end
maintains a current transformation matrix (in RsvgState::affine) that
translates user space coordinates to device coordinates. Back end
drawing functions get passed coordinates that are already in device
space.

Now cairo enters a picture. Cairo maintains its own transformation
matrix. But changing librsvg to take advantage of that instead of
using its own transformation matrix is hard, as it would require
changing most of the front end. So instead, librsvg cheats by
basically replacing cairo's transformation matrix with its own
whenever necessary. The old way of doing that was to apply librsvg's
transformation matrix on top of cairo's initial transformation
matrix (which is carefully preserved by use of cairo_save() and
cairo_restore()).

This worked well actually as long as librsvg never allocates any
temporary surfaces. However, when a new surface is allocated, which
happens for patterns, before applying filters, and for transparent
elements, librsvg assumed that its own affine transformation maps
to device coordinates and created an intermediate surface of a fixed
size, independent of the initial transformation of the svg. (this
is the core problem that underlies #412682 [1]).

The new way of handling transformation matrices is that librsvg
queries cairo's transformation matrix before it starts rendering
and adjusts its own affine transformation so that it includes
cairo's as well, in effect reducing cairo's transformation to
the identity.

But then why the device offset hack that lead to #500787 [2]?
As far as I could find out there is another fundamental assumption
in librsvg, namely that the upper left corner of the device space
bounding box of the image is (0, 0). So what the cairo back end
actually does right now is to take the image's bounding box,
transform it according to cairo's current transformation, and
find a new bounding box for the result. It then translates that
bounding box so that its upper left corner is (0, 0).

Of course this translation needs to be undone for the actual
rendering. This was surprisingly tricky to get right so I
initially settled for the device offset hack which seemed to be
the simplest solution at the time. (And it would be, if only it
always worked.)

Besides maintaining the transformation matrix itself, librsvg
makes good use of the cairo API.

There are still a few issues.

- Temporary surfaces are currently created at cairo's device unit
  resolution.

  The old way was even worse, as explained above.

  This works for pixel oriented surfaces where a device unit is a pixel,
  but will have poor results for, say, Postscript surfaces. Cairo
  provides a fallback resolution mechanism to deal with this issue,
  but librsvg completely ignores that.

  It would be really nice to use cairo_push_group() and
  cairo_pop_group[_to_source]() instead of allocating our own surfaces.

  However, this does not work for filters like Gaussian blur, because
  there's no way to control the type of the surface that
  cairo_push_group() creates and thus no reliable way to get at the
  underlying data for applying the filter.

- Temporary surfaces are clipped at the transformed bounding box
  as explained above. This is far from ideal. However, it's not clear
  to me what the right thing is here - what result should rendering
  parts of the SVG that lie outside its viewBox give?

- The code in rsvg_compile_bg() probably only works for pixel oriented
  surfaces (xlib, image, probably win32), because it uses target surfaces
  as source images.

  Btw, there seems to be a low hanging performance fruit here - most of
  the time the compiled background is not used at all.

- The code uses cairo_fill_extents() resp. cairo_stroke_extents() for
  finding path bounding boxes, but those functions don't always return
  tight bounding boxes (they actually return a bounding box of the tight
  device space bounding box, transformed to user space), which makes
  their result pretty much useless for SVG purposes. Carl Worth
  suggested that this should be fixed in cairo.

  This is rather easy to fix in librsvg - create a path in a temporary
  cairo context with an identity ctm and get the extents from there.

- Filtering interacts badly with transformations. If you take an SVG
  that uses blurring filters, like tests/svg1.1/svg/filters-gauss-01-b.svg ,
  and rotate it: http://int-e.home.tlink.de/rsvg/blur.png . I don't
  understand the code well enough to speculate about how to fix that.

Thanks for reading so far,

Bertram

[1] http://bugzilla.gnome.org/show_bug.cgi?id=412682
[2] http://bugzilla.gnome.org/show_bug.cgi?id=500787

-------------------------------------------------------------------------
SF.Net email is sponsored by: The Future of Linux Business White Paper
from Novell.  From the desktop to the data center, Linux is going
mainstream.  Let it simplify your IT future.
http://altfarm.mediaplex.com/ad/ck/8857-50307-18918-4
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.