I figured out how to render SVG effects in JUCE

This Rust library parses AND renders most of the SVG spec:

What is missing is animations. But all the features for static images are there. The author helpfully included code that compiles Rust to both static and dynamic libraries along with a C header file. Adapting the example.c code from the c-api/examples/cairo/ folder, I was able to render SVG directly to juce::Image like so:

#include <resvg.h>

// load SVG into internal tree representation
resvg_render_tree *tree;
resvg_init_log();    
resvg_options *opt = resvg_options_create();
resvg_options_load_system_fonts(opt);
int err = resvg_parse_tree_from_file("/path/to/file.svg", opt, &tree);
resvg_options_destroy(opt);
if (err != RESVG_OK)
{
    printf("Error id: %i\n", err);
    abort();
}

// get dimensions, assume painting at origin
resvg_size size = resvg_get_image_size(tree);
int width = (int)size.width;
int height = (int)size.height;
int x = 0;
int y = 0;

// setup image to render to
juce::Image img (juce::Image::PixelFormat::ARGB, width, height, true);
juce::Image::BitmapData bmp (img, x, y, width, height);
    
// fit SVG parse tree to original, width, height, or zoom factor
resvg_fit_to fit_to = { RESVG_FIT_TO_TYPE_ORIGINAL, 0 };

// render directly into image data (no cairo surface necessary)
resvg_render(tree, fit_to, resvg_transform_identity(), width, height, (char*)bmp.data);
resvg_tree_destroy(tree);
    
// RGBA -> BGRA
for (int i = 0; i < width * height * 4; i += 4)
{
    unsigned char r = bmp.data[i + 0];
    bmp.data[i + 0] = bmp.data[i + 2];
    bmp.data[i + 2] = r;
}
  
g.drawImageAt(img, x, y);

(Of course, in your component class you’ll probably want to keep the parse tree as a member variable and free it in the destructor.)

The downside to this approach is an extra ~9 MB to final executable size. And since it is in C there is a tiny bit of memory management to handle. Also, it isn’t “JUCE native” in the sense of converting to paths and fills.

But overall this is the simplest, easiest solution I have found for getting SVG effects into my JUCE project, and to link everything up it only took an extra 3 lines in CMake. I hope this is helpful! Took a bit of trial and error to figure this out, and I didn’t find any satisfactory solutions in the forums here or in the Audio Programmer Discord. The next step may be to wrap this library into a more user-friendly C++ API.

Here is an example render from SVG filter effects - Wikipedia

Textures: feTurbulence, feDiffuseLighting, feDistantLight, feComposite and feBlend
Shadows: feGaussianBlur, feOffset, feColorMatrix and feBlend

3 Likes

This is really cool - I’ll definitely be giving this a go!

One thing that I’m skeptical of is the performance… drawing a juce::Image is almost always more expensive than rendering an SVG of the same size. Using this approach, not only do you have a potentially more expensive SVG rendering since it supports the more advanced SVG feautres, but you then have the expensive operation of drawing the resultant image to the graphics context. I suppose for static graphics that would be totally fine, but for dynamic content wth animations I wonder what sort of framerate you’d get.

Is that something you’ve looked at? Have you benchmarked this approach and/or measured framerate while repainting repeatedly?

Maybe if the SVG is a single path with a solid color. If you draw anything more complex and with gradients, the juce::Image will draw faster.

The resvg library doesn’t handle any animations at all, so that’s a moot point.

I haven’t benchmarked performance, but for my particular application – drawing effects on 20x20 px elements – I have assumed that any performance hit will be negligible.

Really what I am comparing this against is resizing and applying effects to PNGs, rather than against a full-fledged replacement for JUCE’s current SVG capability. That might require a dedicated backend like Cairo.

I’ve always found drawing a juce::Drawable constructed from an SVG element to render much faster than an Image - but maybe there’s other platforms and things I’ve not considered.

Ahh sorry, I misread your post!

Yeah for an image that small I wouldn’t expect to run into any massive issues with performance!

I see, yeah I can see this being a better solution for that. I was hoping this might serve as a good replacement for JUCE’s SVG rendering and could be a good solution for rendering shadows, blurs, etc. on components - especially animated ones that move and change size.