linebender / resvg

An SVG rendering library.
Mozilla Public License 2.0
2.79k stars 225 forks source link

Question related to the resvg_render function #695

Closed nico-robert closed 9 months ago

nico-robert commented 9 months ago

I try to reproduce your example.c , It works as expected (cairo surface), It's more a question/help wanted than a bug in your library. My knowledge of image transformation is fairly limited, but I'd like to use resvg to integrate it into Tk, via the api C. According to my structure and my code :

    /* my svg
    <svg id="svg1" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
    <circle r="32" cx="35" cy="65" fill="#F00" opacity="0.5"/>
    <circle r="32" cx="65" cy="65" fill="#0F0" opacity="0.5"/>
    <circle r="32" cx="50" cy="35" fill="#00F" opacity="0.5"/>
    </svg>
    */
    // ...
    unsigned char *imgData;
    imgData = (unsigned char *)Tcl_Alloc(width*height*4);

    resvg_render(tree, resvg_transform_identity(), width, height, imgData);

    Tk_PhotoImageBlock block;
    Tk_PhotoGetImage(source, &block);

    block.pixelPtr   = imgData;
    block.width      = width;
    block.height     = height;
    block.pitch      = width * 4;
    block.pixelSize  = 4;
    block.offset[0]  = 0;
    block.offset[1]  = 1;
    block.offset[2]  = 2;
    block.offset[3]  = 3;

    Tk_PhotoPutBlock(interp, source, &block, 0, 0, width, height, TK_PHOTO_COMPOSITE_SET);
    resvg_tree_destroy(tree);
20 29 18

The left image is my svg file and the right image is my Tk photo , the colours are darker than expected side Tk. Can you tell me if I've done everything right on the resvg side?

RazrFalcon commented 9 months ago

Yes, the resvg part is fine. Try rendering an image without transparency. Does it still has muted colors? If so, you have to make sure Tcl expects premultiplied alpha.

nico-robert commented 9 months ago

Without transparency , it seems better :

<svg viewBox='0 0 104 97' xmlns='http://www.w3.org/2000/svg'>
  <path d='M14,85l3,9h72c0,0,5-9,4-10c-2-2-79,0-79,1' fill='#7C4E32'/>
  <path d='M19,47c0,0-9,7-13,14c-5,6,3,7,3,7l1,14c0,0,10,8,23,8c14,0,26,1,28,0c2-1,9-2,9-4c1-1,27,1,27-9c0-10,7-20-11-29c-17-9-67-1-67-1' fill='#E30000'/>
  <path d='M17,32c-3,48,80,43,71-3 l-35-15' fill='#FFE1C4'/>
  <path d="M17,32c9-36,61-32,71-3c-20-9-40-9-71,3" fill="#8ED8F8"/>
  <path d='M54,35a10 8 60 1 1 0,0.1zM37,38a10 8 -60 1 1 0,0.1z' fill='#FFF'/>
  <path d='M41,6c1-1,4-3,8-3c3-0,9-1,14,3l-1,2h-2h-2c0,0-3,1-5,0c-2-1-1-1-1-1l-3,1l-2-1h-1c0,0-1,2-3,2c0,0-2-1-2-3M17,34l0-2c0,0,35-20,71-3v2c0,0-35-17-71,3M5,62c3-2,5-2,8,0c3,2,13,6,8,11c-2,2-6,0-8,0c-1,1-4,2-6,1c-4-3-6-8-2-12M99,59c0,0-9-2-11,4l-3,5c0,1-2,3,3,3c5,0,5,2,7,2c3,0,7-1,7-4c0-4-1-11-3-10' fill='#FFF200'/>
  <path d='M56,78v1M55,69v1M55,87v1' stroke='#000' stroke-linecap='round'/>
  <path d='M60,36a1 1 0 1 1 0-0.1M49,36a1 1 0 1 1 0-0.1M57,55a2 3 0 1 1 0-0.1M12,94c0,0,20-4,42,0c0,0,27-4,39,0z'/>
  <path d='M50,59c0,0,4,3,10,0M56,66l2,12l-2,12M25,50c0,0,10,12,23,12c13,0,24,0,35-15' fill='none' stroke='#000' stroke-width='0.5'/>
</svg>
19 15 12

I don't know what you think but the colours seem a little different, darker on the Tk side.

If so, you have to make sure Tcl expects premultiplied alpha.

It's with this one line that my level declines a little more, I have no idea.

RazrFalcon commented 9 months ago

After resvg_render run something like:

    for (int i = 0; i < width * height * 4; i += 4)
    {
        float a = float(imgData[i + 3]) / 255.0;
        imgData[i + 0] = (unsigned char)(float(imgData[i + 0]) / a + 0.5)
        imgData[i + 1] = (unsigned char)(float(imgData[i + 1]) / a + 0.5)
        imgData[i + 2] = (unsigned char)(float(imgData[i + 2]) / a + 0.5)
    }

As for slightly different colors - what app and color space do you use to preview SVG? resvg produces sRGB. I assume Tcl uses the same one.

There are basically only tree possible issues: premultiplied alpha, RGBA <-> ARGB components order, non-sRGB color space. All of this should be specified somewhere in Tcl docs.

nico-robert commented 9 months ago

There are basically only tree possible issues: premultiplied alpha, RGBA <-> ARGB components order, non-sRGB color space. All of this should be specified somewhere in Tcl docs.

Below my doc :

typedef struct Tk_PhotoImageBlock {
    unsigned char *pixelPtr;    /* Pointer to the first pixel. */
    int width;          /* Width of block, in pixels. */
    int height;         /* Height of block, in pixels. */
    int pitch;          /* Address difference between corresponding
                 * pixels in successive lines. */
    int pixelSize;      /* Address difference between successive
                 * pixels in the same line. */
    int offset[4];      /* Address differences between the red, green,
                 * blue and alpha components of the pixel and
                 * the pixel as a whole. */
} Tk_PhotoImageBlock;

what app and color space do you use to preview SVG?

Chrome

For you loop , I corrected the errors during compilation and look at the result with my first svg ! If your time permits, could you explain your method to me ?

21 25 09

it's almost the same...

21 28 56

I find the borders of the circles a little too pixelised. Perhaps you know the reason?
In any case, thank you for your patience and for taking the time.

RazrFalcon commented 9 months ago

Yes, Tk_PhotoImageBlock docs kinda useless. No way they do not mention the internal pixels representation anywhere. It's not like there is only one way to store them...

If your time permits, could you explain your method to me ?

You mean the code sample I gave you? It's called alpha demultiplication. You can find more here: https://en.wikipedia.org/wiki/Alpha_compositing#Straight_versus_premultiplied Basically, in a RGBA image, RGB values can be stored as is or multiplied by alpha (which has its benefits). resvg uses premultiplied alpha, while Tk, as it seems, uses non-premultiplied. This is perfectly normal to adjust image representation for different graphics libraries. In the cairo example, as you have probably noticed, we do RGBA to BGRA conversion, but no demultiplication, because cairo uses premultipled alpha as well.

As for bad anti-aliasing, try rendering at higher resolution first. It seems like you're rendering a very small image, while Chrome upscales vector data, not raster one. For example, to do 2x scaling you have to multiply the input buffer width/height by 2 and then pass a 2x scale transform like: resvg_transform { 2.0, 0.0, 0.0, 2.0, 0.0, 0.0 }

nico-robert commented 9 months ago

You mean the code sample I gave you?

Yes thanks.

For my bad anti-aliasing, I've tried to apply your method, but it doesn't seem to have improved my edges. It's probably not on your side or the problem is. Saving the file as a png is much better (image below), I deduce that it's the display in my window that isn't correct.
ph

RazrFalcon commented 9 months ago

I've tried comparing you png to one produced by resvg CLI and it's exactly the same. So the rendering part is fine. You just have to render at higher resolution to match Chrome output here.

Also make sure that Tk properly respects HiDPI, if you're using one. This can cause rough edges as well.

Otherwise there is not much left to do. Just make sure that during demultiplication you keep the original alpha. Only RGB channels must be affected.

nico-robert commented 9 months ago

I've tried comparing you png to one produced by resvg CLI and it's exactly the same. So the rendering part is fine

It's a good news

Otherwise there is not much left to do.

I think so 👍

Thanks for all, really interesting.