2

I'm using C# and P/Invoke to access the GDK libraries. My goal is to convert an set of SVG files into raster images (specifically, png), and using the GDK libraries seems to be the most reliable/accurate.

After doing some reading of the Gnome/Cairo documentation, I have found two basic approaches to accomplish this. One of these approaches uses deprecated functions, and the other does not.

The first approach, which is deprecated, but arguably more simple/straightforward, looks basically like this:

bool result = false;
ptrPixbuf = rsvg_pixbuf_from_file_at_size(svgFilePath, width, height, out ptrError);
if (ptrError == UIntPtr.Zero && ptrPixbuf != UIntPtr.Zero)
{
    bool isSaved = gdk_pixbuf_save(ptrPixbuf, outputFilePath, out ptrError, UIntPtr.Zero);
    if (ptrError == UIntPtr.Zero && isSaved == true)
    {
        result = true;
    }
}
return result;

The second approach, which is not deprecated, involves setting up a Cairo Surface, rendering it, and then saving it to a file. It looks basically like this:

bool result = false;
ptrRsvgHandle = rsvg_handle_new_from_file(svgFilePath, out ptrError);
if (ptrError == UIntPtr.Zero)
{
    ptrCairoSurface = cairo_image_surface_create(CairoFormat.CAIRO_FORMAT_ARGB32, width, height);
    if ((cairo_surface_status(ptrCairoSurface) == CairoStatus.CAIRO_STATUS_SUCCESS)
    {
        ptrcairoContext = cairo_create(ptrCairoSurface);
        if ((cairo_status(ptrCairoContext) == CairoStatus.CAIRO_STATUS_SUCCESS))
        {
            bool isRendered = rsvg_handle_render_cairo(ptrRsvgHandle, ptrCairoContext);
            if (isRendered)
            {
                ptrPixbuf = rsvg_handle_get_pixbuf(ptrRsvgHandle);
                if (ptrPixbuf != UIntPtr.Zero)
                {
                    bool isSaved = gdk_pixbuf_save(ptrPixbuf, outputFilePath, out ptrError, UIntPtr.Zero);
                    if (ptrError == UIntPtr.Zero && isSaved == true)
                    {
                        result = true;
                    }
                }
            }
        }
    }
}
return result;

Both of these approaches seem to work - they generate correct raster image output (although, the "Cairo way" has some bugs when I try to do a bunch of operations in parallel - I end up running out of memory).

My question is this: why is the old/deprecated way (rsvg_pixbuf_from_file_at_size) noticeably faster than the new/Cairo way? My testing shows the first approach being faster across the board (one file/multiple files, standard C# ForEach/Parallel.ForEach).

For example, with 16 input files (output dimensions of 6000x4200) and no parallel processing, the first approach takes ~2:15.89 seconds. The second approach takes around ~2:37.95. With parallel processing (Parallel.Foreach calls my P/Invoke code), the results are similar -- with MaxDegreesOfParallelism set to default, it takes 30.7 seconds with the deprecated approach, and 36.95 with the Cairo approach.

Cairo seems to use substantially more memory too. Furthermore, in addition to simply using more resources for each conversion, Cairo also seems to not know how to avoid using all of my RAM. For example, if I up the number of input files to 720 (from 16), and use the Parallel.ForEach loop, I end up with 0 MB of free RAM, and the system grinds to a halt (eventually, the debug process exits, and my system comes back...but it locks up for a minute or so).

The easy answer to my question is to just use the deprecated approach, but why is it deprecated? Is the Cairo approach better in any way at all?

If anyone wants to see more of my code, let me know, and I will add it. I tried to trim down the code I posted to just the relevant bits (the actual P/Invoke code, and not the C# code calling it).

1 Answers1

1

First, your out of memory error likely occurs because your code has memory leaks. Your example doesn't call any cleanup functions (like cairo_destroy(), cairo_surface_destroy(), gdk_pixbuf_unref(), g_object_unref(), possibly something for any GError that happens).

Secondly, what does your second code actually draw to? You create a cairo surface and context, have rsvg draw to it, but then call rsvg_handle_get_pixbuf() which internally creates a new cairo surface and draws everything again. In other words, shouldn't it be enough for you to call rsvg_handle_new_from_file(), rsvg_handle_get_pixbuf() and gdk_pixbuf_save()?

Uli Schlachter
  • 9,337
  • 1
  • 23
  • 39
  • 1
    Thanks for the reply. As far as cleanup functions, all of my P/Invoke code is contained in a IDisposable class in which I have implemented the necessary C++ cleanup methods in the Dispose() method. However, I'll have to dig into the second part of your comment tomorrow. I'm new to Cairo (actually, I'm new to all of these GDK type things). I'm basing my code after several examples I've found in other SO questions and other websites. I will try simply using rsvg_handle_new_from_file() in conjunction with rsvg_handle_get_pixbuf() and gdk_pixbuf_save() when I get to work in the morning. – Patrick McCaffrey Dec 02 '13 at 04:50
  • Thanks again for your help. I was unclear on how rsvg_handle_get_pixbuf works. After removing the unnecessary code that sets up the Cairo Surface/Context, the program works properly. The "Cairo" way of doing things is now (speed wise) on par with the deprecated method. I do have some remaining problems with pango warnings about fonts, but that seems unrelated. – Patrick McCaffrey Dec 02 '13 at 17:39