Pixi.js: Real SVG support

Created on 29 Jun 2017  ·  30Comments  ·  Source: pixijs/pixi.js

It would be great for Pixi to be able to create a Graphics hierarchy representation of an SVG (not just load a texture like currently).

@psyrendust ^ Hey, can you PR the one you made into Pixi? :D

Stale 🙏 Feature Request

Most helpful comment

I think this does what you want using webpack.
https://github.com/blunt1337/pixi-svg-loader

All 30 comments

You might want to check this library I made:
https://github.com/bigtimebuddy/pixi-svg
https://bigtimebuddy.github.io/pixi-svg/example/

Because of the limitations of drawing in Pixi, not all svg features are supported.

@trusktr could you give a use case? Do you mean like having groups as Containers and sets of primitives as Sprites?

Cause this is something I've been thinking of. It could be possible to create a texture atlas and use the translates and rotates from SVG to create a hierarchy of Sprites.

One problem I see with this approach is anchors (in case we want to animate something). For example Inkscape adds anchors as custom attributes but they are not in the standard afaik. An example of what I mean:

2017-07-01 08_56_56-_new document 1 - inkscape

The anchor of that group of three paths (top left of the blue rect) is defined as inkscape:transform-center-x and inkscape:transform-center-y.

    <g
       id="g4512"
       inkscape:transform-center-x="-65.892627"
       inkscape:transform-center-y="43.567609"
       transform="rotate(-5.9407468,27.740957,88.735118)">
      <path
         inkscape:connector-curvature="0"
         id="rect4485"
         d="m 24.502618,78.898505 h 50.619309 c 3.986749,0 7.196297,3.209548 7.196297,7.196296 v 70.274069 c 0,3.98675 -3.209548,7.1963 -7.196297,7.1963 H 24.502618 c -3.986748,0 -7.196296,-3.20955 -7.196296,-7.1963 V 86.094801 c 0,-3.986748 3.209548,-7.196296 7.196296,-7.196296 z"
         style="opacity:1;fill:#005e92;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers fill stroke" />
      <path
         inkscape:connector-curvature="0"
         id="rect4485-4"
         d="m 66.160572,123.8869 h 50.619318 c 3.98675,0 7.1963,3.20955 7.1963,7.1963 v 70.27406 c 0,3.98675 -3.20955,7.19631 -7.1963,7.19631 H 66.160572 c -3.986734,0 -7.196284,-3.20956 -7.196284,-7.19631 V 131.0832 c 0,-3.98675 3.20955,-7.1963 7.196284,-7.1963 z"
         style="opacity:1;fill:#920000;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers fill stroke" />
      <path
         inkscape:connector-curvature="0"
         id="rect4485-3"
         d="m 106.60403,73.994048 h 50.61931 c 3.98675,0 7.1963,3.209548 7.1963,7.196296 v 70.274066 c 0,3.98675 -3.20955,7.19631 -7.1963,7.19631 h -50.61931 c -3.98675,0 -7.196288,-3.20956 -7.196288,-7.19631 V 81.190344 c 0,-3.986748 3.209538,-7.196296 7.196288,-7.196296 z"
         style="opacity:1;fill:#009228;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers fill stroke" />
    </g>

Although I appreciate your effort @bigtimebuddy I don't think it's a good idea to render SVG primitives in pixi.

Check this out also:
https://css-tricks.com/rendering-svg-paths-in-webgl/

I think this does what you want using webpack.
https://github.com/blunt1337/pixi-svg-loader

@OSUblake cool, it indeed seems to do what I meant. Gotta try it. 👍

@bigtimebuddy Thanks! I opened a new issue over there. Would be great to have a full Pixi scene graph.

@RanzQ

@trusktr could you give a use case? Do you mean like having groups as Containers and sets of primitives as Sprites?

Yeah, but primitives would be Graphics objects rather than sprites.

One problem I see with this approach is anchors (in case we want to animate something). For example Inkscape adds anchors as custom attributes but they are not in the standard afaik. An example of what I mean:

I don't mind not supporting non-standard stuff, I can live with just the standardized APIs. There could easily be a plugin for supporting inkscape-specific stuff.

Although I appreciate your effort @bigtimebuddy I don't think it's a good idea to render SVG primitives in pixi.

Why exactly do you think that?

@OSUblake

I think this does what you want using webpack.
https://github.com/blunt1337/pixi-svg-loader

That's pretty cool, except limited to Webpack, and also limited to tooled environments, doesn't run on the client. I'm looking for an API I can use with Pixi.js at runtime (not build time).


What I want (I think, but I'm not the most expert Pixi.js user, so advise if something else is better like using sprites) is to have a DisplayObject scene graph that is the same structure as the SVG DOM used to generate the Pixi tree from. This, to me, is powerful for manipulation and animation, without having to re-render the entire SVG every frame just to animate one primitive (out of possibly hundreds or thousands).

What I think I will do, if something open source doesn't yet exist, is to take @bigtimebuddy's pixi-svg or @saschagehlich's pixi-svg-graphics, and modify them (or use similar concepts) to create a Pixi Graphics tree mirroring the SVG tree rather than just draw onto a single Graphics object.

@trusktr I see, for primitive based animations it might be useful to create Graphics objects instead of Sprites. But I think the solution could support both, Graphics for simple SVGs and Sprites for more complex ones.

The reason I thought it's not a good idea is the fact that there have been many efforts to create a custom SVG renderers and in my experience they always end up having this TODO of unsupported stuff.

That will be a problem for an artist since one needs to know what features can be used and what not. We already have a good SVG renderer, the browser. But if the SVG just contains simple primitives without effects, then it's probably ok parse it as Graphics objects.

An example where sprites would work better, the map looks very crispy (maybe just missing antialiasing) and performs poorly since the paths are complex.

Creating textures from an SVG is pretty easy using canvas 2d and the Path2D api.

var path = new Path2D("M10 10 h 80 v 80 h -80 Z");
context.fill(path);

Path morphing demo
https://codepen.io/osublake/pen/oWzVvb

I was going to work on a better polyfill as the one from Google has trouble parsing complex SVG paths.
https://github.com/google/canvas-5-polyfill

@RanzQ

We already have a good SVG renderer, the browser.

But it is so slooooooooow. Does it render in the GPU or the CPU? To me the performance I experience doesn't compare to the speed of similar things drawn with Pixi.js, for example. Seems to me that the native SVG engine is doing too much work.

Even canvas 2d is slow compared to WebGL. Compare here the difference between WebGL and Canvas: http://pixijs.github.io/pixi-particles-editor/ (scroll to bottom to toggle between WebGL and Canvas rendering). Canvas is notably slower.

I just want to draw SVG and have it be as fast as possible. So far, conceptually at least, it seems like drawing SVGs with a WebGL API like Pixi.js or Two.js is the fastest way to go. The only downside at the moment is that not all the SVG features are completely supported.

@OSUblake

Creating textures from an SVG is pretty easy using canvas 2d and the Path2D api.

Canvas 2D is still noticeably slower than WebGL, see that demo I just linked two paragraphs up.

Path morphing demo
https://codepen.io/osublake/pen/oWzVvb

Cool demo! In this case, you are animating a simple path in the PathProxy. This is fast enough for the use case probably. But imagine drawing a bunch of things in an SVG, like 100 nodes, and animating them all, then wanting that whole thing as a texture to wrap around a sphere.

Or, imagine the particles in that demo being done with SVG and animating SVG DOM nodes, then taking the rendering as a texture with drawImage in a canvas and finally putting it on a quad of a 3D webgl app. It would be too slow!

Your demo is fast because that particle animation is in WebGL via Pixi.

If rendering SVGs with Pixi were a supported feature, and a hierarchy that mirrored the SVG hierarchy were created, then it would be possible to map changes to a display:none <svg> element (display:none so that it hopefully that prevents the browser's SVG engine from doing work because nothing needs to be rendered) into changes to a Pixi.js tree.

I think this would be the fastest way to draw SVGs because Pixi is faster, while affording the convenience of a DOM that can be manipulated by React, Angular, etc. I want to have an SVG DOM tree that I can manipulate, who's rendering will be faster than the Browser's slow SVG engine. Maybe I'm asking too much. It just seems like with today's technology, SVG rendering is way too slow by common standards (we want gaming engine speeds).

@trusktr

But it is so slooooooooow.

By that I meant we can draw SVG textures using the browser, it is slow but supports all the features. You wouldn't do animations like that of course. You could create a texture atlas and animate the sprites afterwards. If you'd like to create 2D spinal characters for example, that would be the way to go.

I think this is an example of what you are after: https://github.com/bodymovin/bodymovin/issues/184

The task is not simple and that animation for example is complex enough to play slowly on my oldish Android phone which plays sprite based Pixi scenes fine. I don't see any difference in the SVG and Pixi version. Might be just an optimization issue though.

Check this out also: https://www.gatherdigital.co.uk/community/post/high-performance-webgl/78

So in my opinion, there are two different kind of needs for SVG. One with less SVG features, split into primitives, the other with browser rendered atlas, sprites being the <g> nodes for example.

Another approach might be to find a better interoperable format or tooling for vector graphics than SVG. If you want something scalable, SVG has its limitations in PixiJS for reasons @RanzQ already articulated. There are so many features unsupported in Pixi's drawing capabilities available in SVG that any primative drawing translation is going to be second-rate.

Another approach I'd recommend is PixiAnimate (https://github.com/jiborobot/pixi-animate-extension) it's a native extension (disclaimer, I authored this) for Adobe Animate that does a good job exporting accurate Graphics. Plus you can create whatever hierarchy you want using nested symbols and layers or animate.

@bigtimebuddy Is there a workflow to create texture atlases from vector graphics using Animate? I've been looking for such a workflow where the Pixi scene could be created UI assisted.

It seems to be possible to create atlases in the fresh release of Animate but have you tried your extension for this: http://blogs.adobe.com/contentcorner/2017/07/03/create-a-texture-atlas-with-animate-cc-for-your-favorite-game-engines/

@RanzQ, yes PixiAnimate can export separate images or spritesheets in the format of TexturePackers atlases. Vector shapes, however, are not automatically converted to textures. You need to use "Convert to Bitmap" to do that in Animate.

@trusktr

Your demo is fast because that particle animation is in WebGL via Pixi.

Exactly. I wasn't suggesting to use canvas as the main renderer. Only to generate textures/sprites when the geometry changes. It should be fast enough for most use cases. I think that is how Two.js does path rendering in WebGL.

And using Path2D objects can improve canvas performance as the browser will cache the vectors for a path. This allows you to use the same path object more than once, even with different canvas contexts and resolutions.

@OSUblake

Exactly. I wasn't suggesting to use canvas as the main renderer. Only to generate textures/sprites when the geometry changes. It should be fast enough for most use cases. I think that is how Two.js does path rendering in WebGL.

Yes, this sounds like a good approach. For example, if the only attributes being modified on a <circle> element are cx and cy, this this means that an optimization would be to translate an existing mesh or sprite without creating a new one.

Here's a comparison between Pixi, Two, and native SVG:

Why is Pixi noticeably slower? Is it because it recalculates (re-tessalates or re-rasterizes) based on the draw commands every frame?

With Two.js, a Circle is an object with x, y, and radius properties. It is obvious with that API that the circle doesn't need to be retessalated/rerasterized if modifying only x and y.

It isn't as obvious how this optimization would be made with the drawing-command-style of Pixi.

@trusktr

Why is Pixi noticeably slower? Is it because it recalculates (re-tessalates or re-rasterizes) based on the draw commands every frame?

No. It's just creating a lot of triangles, which is killing GPU performance. You can see the difference if you draw a rectangle without a line style. It will render it as a sprite, so performance will be much better. 10,000 rects.
https://codepen.io/osublake/pen/PjrbWq/

Have you looked into using distance fields for rasterization? I know it works well with fonts if you don't zoom in too much. I'm wondering if it could be used to animate path data.
https://github.com/Tw1ddle/WebGL-Distance-Fields/tree/master/sdf

https://www.shadertoy.com/view/ltXSDB

My previous examples were completely inaccurate because I didn't realize JSBin was breaking out of the for loops early, so on my Macbook Pro it was rendering only 500 circles (even if I set n to 10000). I've updated the examples, so they all render 2000 circles without JSBin's loop-breaking:

The SVG example is still slowest when animating only circle positions (not radius).

Here are Two.js and SVG also animating radii:

I didn't include the Pixi.js version because I wasn't sure how to modify the non-radius-animation version. How would you update the no-radius-animation version using Pixi.js to include the radius animation?

How would you update the no-radius-animation version using Pixi.js to include the radius animation?

Performance may really start to take a nose dive here. You have to clear the graphics object, which resets everything.
https://codepen.io/osublake/pen/8217810e590672c5a5327596e33c4b1a?editors=0010

Now look at this demo using canvas 2d to generate the textures. 3,000 circles running at a solid 60fps on my machine.
https://codepen.io/osublake/full/MoMeMg/

Well, anyway, I think there's a good use case for this, as the example shows: it can be much faster in WebGL in some cases.

@OSUblake Oops, I replied that last one before seeing your new replied.

Performance may really start to take a nose dive here. You have to clear the graphics object, which resets everything.
https://codepen.io/osublake/pen/8217810e590672c5a5327596e33c4b1a?editors=0010

That's what I feared, which is not ideal. It starts approaching native SVG slowness.

Now look at this demo using canvas 2d to generate the textures. 3,000 circles running at a solid 60fps on my machine.
https://codepen.io/osublake/full/MoMeMg/

Holy shit, wow! Okay! So, there we go, that's currently the best way to do it (and with 3000 <canvas> even)!

Why is that so much faster? Maybe native SVG implementors can learn from this (as well as Pixi.js and Two.js)?

Maybe using this approach in Pixi.js would require changes to collision/picking? There'd be a rectangle texture instead of a mesh.

@trusktr Reason that is fast is that you draw on canvas once, then upload it as a texture to the GPU. In that example there is a separate texture generated for each radii rounded to 4px steps (if I understood it corretly 😄). The animation is fast enough so you won't see the texture swap.

You can do similar things with Pixi's drawing API by caching Graphics objects (cacheAsBitmap). That way you can draw your primitives once and re-draw them fast from cache.

In that example there is a separate texture generated for each radii rounded to 4px steps (if I understood it corretly 😄)

It's actually rounding to 0.25 increments. How much sub-pixel accuracy can you actually see?

If that's funny, then check out how Pixi's CanvasTinter works. Or how sprite sheet animations work. You sacrifice some memory in exchange for performance, and with a good caching strategy it can work.

You can do similar things with Pixi's drawing API by caching Graphics objects (cacheAsBitmap). That way you can draw your primitives once and re-draw them fast from cache.

Using cacheAsBitmap might not look good because the graphics will have aliasing. To get antialiased graphics you need to use generateCanvasTexture. So either way you end up with a canvas, but using Pixi's graphics api you can't do simple things, like changing the line dash of the stroke.

You can do similar things with Pixi's drawing API by caching Graphics objects (cacheAsBitmap).

To get antialiased graphics you need to use generateCanvasTexture

With either of those two options, can I animate the radius like in your example @OSUblake ?

(by the way, thanks guys, opening my eyes to rendering techniques I hadn't imagined before).

@trusktr Yeah, you just have to create many Graphics objects with different radius and create a map of the generated textures (so you can then use a cached texture for each radius). With cacheAsBitmap you would map the Graphics objects instead of textures but you wouldn't have antialiasing like @OSUblake said.

This kind of animation is sprite sheet animation. When you can animate using transforms then the texture caching is not needed. Like if you wouldn't care that the stroke of the circles scales too, a simple scale animation would do it.

But I agree, canvas has a better drawing API so I would use it. And for SVG, the hierarchy can be split to an atlas, like pixi-svg-loader does (I think?). Or if you want sprite sheet like animation, that's possible too by drawing the animation states on canvas.

Maybe using this approach in Pixi.js would require changes to collision/picking? There'd be a rectangle texture instead of a mesh.

While not as fast as a polygon, canvas does have method to do hit testing on a path, isPointInPath. See it working on this map.
https://codepen.io/osublake/pen/dzYzab/?editors=0010

There seems to be a svgloader in three.js, perhaps something that can be mapped to pixi.js
https://github.com/mrdoob/three.js/blob/master/examples/js/loaders/SVGLoader.js

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

Was this page helpful?
0 / 5 - 0 ratings