memononen / nanovg

Antialiased 2D vector drawing library on top of OpenGL for UI and visualizations.
zlib License
5.16k stars 775 forks source link

Support for clipping paths #112

Open jacereda opened 10 years ago

jacereda commented 10 years ago

Hi,

Is it possible to implement something like the clipPath tag in SVG with the current API?

Something like the attached image.

Thanks!

clipped

memononen commented 10 years ago

Currently the core supports only rectangular clipping path (aka. scissor). I have not found a good approach to make general anti-aliased clipping paths. I could all rounded rect and circle scissor shape if that is any help?

jacereda commented 10 years ago

Rect and circle are not enough in my case, I really need general paths. But the scenes I'm rendering happen to use only a clipping path. Would that yield an easy solution in my case? Maybe rendering the clipping path to a stencil buffer and use it to render the rest of the scene? Perhaps rendering two scenes to FBO, one with the clipping path and the other with the rest and composing them afterwards? I guess this last solution wouldn't need any modification to nanovg? Thanks.

starwing commented 10 years ago

you can try to do this:

now the graph is looked just like "clipped" by the path.

jacereda commented 10 years ago

@starwing I'm not sure that would do the trick. The clipping path itself has a paint style (plain color AFAICS) and possibly (I'd need to verify this) a stroke path. Perhaps rendering the clipping path twice (once with its own color and another one using the FBO texture) would work? Will the nvgImagePattern() use the alpha channel?

starwing commented 10 years ago

Yes, image pattern has alpha channel, and you can specify both pattern alpha and global alpha.

you can render the clipped-graph first, then draw clipping path's paint on the top of it (draw a filled rect, can use color, shadow, gradient or image), then using the clipped-graph as the paint. finally you can draw the stroke of clipping path.

or, if clipped-graph is constant but can move after clipping path (that you mean, you needn't draw clipped-graph several times). you can use clipping-graph as fill, and then filled with clipping-path's fill again. since NanoVG use the pre-multiply alpha, so the result is the same.

lieff commented 7 years ago

I need this too. May be stencil buffer can be used instead of render to separate FBO? At least without AA.

fabioarnold commented 9 months ago

Hi, I also needed this feature to render Rive animation files. They use clip paths extensively. So implemented it in my for fork of nanovg: https://github.com/fabioarnold/nanovg-zig/pull/13

Here's a short demo:

https://github.com/memononen/nanovg/assets/473672/007baf48-deb4-479b-b7ff-a8b33d69bfaf

Unfortunately, my fork is partially rewritten in zig (zig is also a C/C++ compiler, allowing me to do this). But maybe this could still be helpful as a reference. I use the stencil buffer to achieve this. The clip path is stenciled like a normal non-convex path (incrementing and decrementing) and then transferred to the highest bit (0x80) using a cover fill. When filling or stroking a normal path, fragments without this bit are then discarded.

The API usage is as follows:

vg.beginPath();
vg.circle(0, 0, 110);
vg.clip(); // Use paths recorded since `beginPath` up to this point as clip path. Every path that follows can be part of fill/stroke.
vg.rotate(t);
vg.rect(-100,-100, 200,200);
vg.fill(); // Fill rect path, clipped by circle

However, I later discovered that in Skia and HTML5 canvas the clip path is part of the current state can be saved and restored. There's no function to clear the current clip (except by restoring). I like the simplicity of my current approach, but I'm thinking of changing the behavior to match the canvas/Skia approach.

Any thoughts?

PS: It would also be possible to implement an even-odd fill rule by masking with 0x1 when drawing the cover fill (#598). I haven't implemented this yet because all my Rive test files use the non-zero fill rule.