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