memononen / nanosvg

Simple stupid SVG parser
zlib License
1.68k stars 355 forks source link

How to nanovg to render SVG with the data parse from nanosvg? #58

Open aababy opened 8 years ago

aababy commented 8 years ago

This is not an issue, but I don't know the way to contact with you. Hi, I'm look to use nanovg to render SVG with the data parse from nanosvg. Could you help me correct this code to support full feature (just like open the svg file with a svg viewer)? I have search the google, but find nothing about this. Could you append it into readme.md. Thank you.

NSVGimage* g_image = = nsvgParseFromFile("drawing.svg", "px", 96.0f);
NSVGshape * shape;
NSVGpath * path;
int i;
nvgStrokeColor(vg, nvgRGBA(0,0,0,255));
for (shape = g_image->shapes; shape != NULL; shape = shape->next) {

    nvgStrokeWidth(vg, shape->strokeWidth);

   for (path = shape->paths; path != NULL; path = path->next) {
        for (i = 0; i < path->npts-1; i += 3) {
            float* p = &path->pts[i*2];

            nvgBeginPath(vg);
            nvgMoveTo(vg, p[0], p[1]);
            nvgBezierTo(vg, p[2], p[3], p[4], p[5], p[6], p[7]);
            nvgStroke(vg);
        }
    }
}
memononen commented 8 years ago

Almost correct! :)

for (path = shape->paths; path != NULL; path = path->next) {
        nvgMoveTo(vg, path->pts[0], path->pts[1]);
        for (i = 0; i < path->npts-1; i += 3) {
            float* p = &path->pts[i*2];
            nvgBeginPath(vg);
            nvgBezierTo(vg, p[2], p[3], p[4], p[5], p[6], p[7]);
            nvgStroke(vg);
        }
    }

If you want to draw filled shapes, Nanovg requires you to define holes explicitly. The following paper shows you how to calculate the area of a bezier poly, which you can use to set the solid/hole. https://tug.org/TUGboat/tb33-1/tb103jackowski.pdf

aababy commented 8 years ago

Hi, Mikko, thank you for your response. But when I use your code. It show me a blank canvas.

for (shape = g_image->shapes; shape != NULL; shape = shape->next) {
    nvgStrokeWidth(vg, shape->strokeWidth);
    for (path = shape->paths; path != NULL; path = path->next) {
        nvgMoveTo(vg, path->pts[0], path->pts[1]);
        for (i = 0; i < path->npts-1; i += 3) {
            float* p = &path->pts[i*2];
            nvgBeginPath(vg);
            nvgBezierTo(vg, p[2], p[3], p[4], p[5], p[6], p[7]);
            nvgStroke(vg);
        }
    }
}
memononen commented 8 years ago

How about this?

for (shape = g_image->shapes; shape != NULL; shape = shape->next) {
    nvgStrokeWidth(vg, shape->strokeWidth);
    for (path = shape->paths; path != NULL; path = path->next) {
        nvgBeginPath(vg);
        nvgMoveTo(vg, path->pts[0], path->pts[1]);
        for (i = 0; i < path->npts-1; i += 3) {
            float* p = &path->pts[i*2];
            nvgBeginPath(vg);
            nvgBezierTo(vg, p[2], p[3], p[4], p[5], p[6], p[7]);
        }
        if (path->closed)
            nvgLineTo(vg, path->pts[0], path->pts[1]);
        nvgStroke(vg);
    }
}
aababy commented 8 years ago

Thank you for your help, I have modify the code, and it almost render all the files in nanosvg sample.(drawing.svg, 23.svg). But when I rendering "nano.svg", it show me a black rectangle. Am I need to calculate the area of a bezier poly? I also reference from nanosvgrast.h. https://tug.org/TUGboat/tb33-1/tb103jackowski.pdf but the pdf is too complex. and it's too difficult to understand the relationship between the pdf and nanovg. How could I do it?

for (shape = g_image->shapes; shape != NULL; shape = shape->next) {

    if (!(shape->flags & NSVG_FLAGS_VISIBLE))
        continue;

    nvgFillColor(vg, getNVGColor(shape->fill.color));
    nvgStrokeColor(vg, getNVGColor(shape->stroke.color));
    nvgStrokeWidth(vg, shape->strokeWidth);

    for (path = shape->paths; path != NULL; path = path->next) {
        nvgBeginPath(vg);
        nvgMoveTo(vg, path->pts[0], path->pts[1]);
        for (i = 0; i < path->npts-1; i += 3) {
            float* p = &path->pts[i*2];
            nvgBezierTo(vg, p[2], p[3], p[4], p[5], p[6], p[7]);
        }
        if (path->closed)
            nvgLineTo(vg, path->pts[0], path->pts[1]);

        if(shape->fill.type)
            nvgFill(vg);

        if(shape->stroke.type)
            nvgStroke(vg);
    }
}
mlaass commented 8 years ago

Hi,

I have a further question along the line of using nanosvg with nanovg. Specifically I am trying to get linear gradients working. I understand, that the contents of shape->fill.gradient->xform contain some kind of transform of the gradient coordinates. and the paint object of nanoVG also contain an xform array. When I tried copying, it did not work and I tried manually creating a gradient with nvgLinearGradient(), but I could not figure out how to generate the gradient coordinates from gradient.xform.

So my question would be how do I generate a linear gradient for NanoVG from the gradient object in NanoSVG? And while we are at it I would not mind getting the radial gradient to work aswell :)

memononen commented 8 years ago

the gradient transform is inverse transform, that is, if you multiply a world position with the matrix, you'll get a point in the gradient, where start is 0 and end is 1. If you take inverse of the gradient matrix, then you should be able to figure out the world positions from that.

mlaass commented 8 years ago

Thanks! That worked well.

Rado-1 commented 7 years ago

Inverse gradient matrix more-or-less helped for linear gradients. However, using this code:

NVGpaint createLinearGradient(NSVGgradient* gradient, float alpha) {
    float inverse[6];
    float sx, sy, ex, ey;

    nvgTransformInverse(inverse, gradient->xform);
    nvgTransformPoint(&sx, &sy, inverse, 0, 0);
    nvgTransformPoint(&ex, &ey, inverse, 0, 1);

    return nvgLinearGradient(vg, sx, sy, ex, ey,
        SVGCOLOR4(gradient->stops[0].color, alpha),
        SVGCOLOR4(gradient->stops[gradient->nstops - 1].color, alpha));
}

there is still some inaccuracy. Original SVG picture (created in Inkscape): scr1 is rendered as: scr2

The same shift of positions happens also in Y axis. Mikko, could you please help me to fix it? Do you have some idea of what's wrong?

Another issue is radial gradient. Could you please give me some code snippet or at least hint how to transform NSVGgradient to NVGpaint for radial gradients? Center can be obtained by transforming point (0,0) with inverse gradient matrix, but I have no clue how to compute radius.

Thanks in advance for your help.

memononen commented 7 years ago

Could you post the above svg here?

The circular gradient is drawn so that the circle center is at (0,0), and the radius is 1 units. So, for a very simple case:

nvgTransformPoint(&sx, &sy, inverse, 0, 0);
nvgTransformPoint(&ex, &ey, inverse, 0, 1);
radius = ey - sy;

Currently there's not enough API to pass scaled/skewed radial gradient to NanoVG.

Rado-1 commented 7 years ago

Zipped SVG image is attached. x.zip

Rado-1 commented 7 years ago

Mikko, BTW circular gradient works fine. Thanks!

However, also here I can observe a problem with transforming positions far from origin of the picture. So also gradient radius works well if the object is placed near to the page origin, but is incorrect for objects place far from it. Example: y.zip with two identical rectangles having identical radial gradient placed at different positions is rendered as: scr3 Upper left rectangle is within page boundaries, lower right rectangle is outside/far from origin.

Rado-1 commented 7 years ago

After several other experiments with radial gradient I found that the problem is not distance from page origin, but distance from its diagonal. Above diagonal, rendered radius is smaller, below diagonal the radius is bigger. See this example: scr4 z.zip

memononen commented 7 years ago

I checked with nanosvg raserizer and it works fine. So things should be ok.

Can you post the exact code whch you use to create the gradient?

Rado-1 commented 7 years ago

Sure, here is my current working version of the source file for ZgeNano library - a wrapper for NanoVG and NanoSVG libraries used in ZGameEditor. Please have a look at the nsvg_Draw function. Maybe there's something wrong also in nvg_Init() (???). Thanks for your time.

ZgeNano.zip

memononen commented 7 years ago

This is not correct:

    nvgTransformPoint(&cx, &cy, inverse, 0, 0);
    nvgTransformPoint(&r1, &r2, inverse, 0, 1);
    outr = r2 - r1;

It should be:

    nvgTransformPoint(&cx, &cy, inverse, 0, 0);
    nvgTransformPoint(&r1, &r2, inverse, 0, 1);
    outr = r2 - cy;

The point r1,r2 is essentially a point on the top (or bottom, can remember), of the gradient circle. So you get radius by subtracting the center. You could also take the distance between the points.

memononen commented 7 years ago

That does not explain the linear gradient problem. Maybe I gave you the wrong point for the linear gradient, you could try:

nvgTransformPoint(&ex, &ey, inverse, 1, 0);
Rado-1 commented 7 years ago

Sure, radial gradients now work fine. Thanks.

nvgTransformPoint(&ex, &ey, inverse, 1, 0);

does not help - it rotates linear gradient by 90 degrees. Does your SVG rendered process it correctly?

Rado-1 commented 7 years ago

Mikko, I tested the current rendering on several free and quite complex svg pictures (e.g. this) and rendering is fine. It's maybe problem of that particular image (x.svg) I used for testing before. I do not think shifting of linear gradient is some serious and practical problem, so not sure it is worth of debugging/fixing. But it is up to you.

BTW do you plan to support also blur, clipping and/or masking in NanoSVG nd NanoVG?

memononen commented 7 years ago

The linear gradient problem seems to happen also with the rasterizer, so there's something wrong with the parser. Would you mind opening another issues about that? include the image and zip file with it.

Blurring and masking would be awesome, but they complicate the rendering a lot, so no plans to do that for the time being.

Rado-1 commented 7 years ago

A new issue #75 has been created for linear gradients.

Blurring and masking would be awesome, but they complicate the rendering a lot, so no plans to do that for the time being.

That's pity, at least blur would help, because it's being used quite frequently.

AndrewBelt commented 6 years ago

Is comparing the area by Boguslaw Jackowski the most straightfoward way to determine the whether nvgPathWinding(vg, NVG_SOLID) vs nvgPathWinding(vg, NVG_HOLE) should be used after drawing the path? I wouldn't be opposed to implementing this in my nanovg/nanosvg renderer, but I'm wondering if there's an easier trick than that.

Actually, now that I think about it, comparing areas doesn't seem like it would suffice. What if I want to support paths like this? It can be drawn in nanovg explicitly, but I'm not sure how I can extract the evenness/oddness of a subpath from nanosvg's data structure.

path3487

iongion commented 6 years ago

Maybe related, maybe even trivial, but I would like to use the rasterized image as paint, I am following the example here https://github.com/memononen/nanosvg/blob/master/example/example2.c but don't really know how to go from unsigned char* img to something that behaves like the data returned by nvgCreateImage which can be used as fill, any tips ?

kpeeters commented 6 years ago

@AndrewBelt, have you come up with a generic solution to handle SVG's with holes?

AndrewBelt commented 6 years ago

@kpeeters I don't remember if I added full support or not and I'm on my mobile device so I didn't look too deeply, but if I did, it would be here. https://github.com/VCVRack/Rack/blob/v0.6/src/widgets/SVGWidget.cpp#L91

geoffthemedio commented 5 years ago

I was able to get nanosvg and nanovg working together using @AndrewBelt's code with minimal modification. As of now, the actively developed version of VCVRack with the latest version of the glue code is here.

Since the rest of the GUI I'm working in isn't rendered with nanovg or Rack widgets, I needed some additional nanovg calls to get the SVG rendering to be within the desired screen space: nvgScissor, nvgTranslate, and nvgScale calls before the svgDraw call.

I do get some stdout error message spam, Error 00000501 after fill simple when the rendered area quite small, though. This appears to be coming from within nanovg

Not really relevant to nanosvg, but I thought the follow-up could be helpful...