-
Notifications
You must be signed in to change notification settings - Fork 242
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Anti-aliased edges of patterns with non-integer width/height #539
Comments
Yes, this is sort of expected. resvg renders a pattern to an image first and then uses it as a fill source. And an image has an integer size. Not sure how it can be fixed. Manually tiling patterns would be a performance nightmare. Also note that the SVG spec doesn't specify this behaviour (afaik). Meaning every app/library can render patters in whatever way they want. Writing a reproducible SVG is impossible. |
Right, I meant putting 4 copies of the pattern around it when rendering to the image. Still a bit of a performance hit, but probably not huge? If it's only the right and bottom pixel from ceiling the width and height, then perhaps it's even enough to do it with a copy to the right and bottom? |
I'm not sure what exactly are you trying to do. The reason you see gaps is because the tile is 101x101, but the fill area is 100.65x100.65 So the right and bottom edges are semitransparent. |
I think just considering the vector space the pattern of 20.13 x 20.13 should tile correctly and the result ought to be seamless. I understand that there are difficulties to do this correctly in pixel space. Manually rendering each instance of the pattern is far too slow, of course. There might be a way to generate relevant versions of the 101 x 101 image based on the shifts that can accumulate (e.g. if the width is x.5 there'd be one with offset 0.0, one with 0.5, they'd alternate, for x.333 there'd be three, etc.), but that also would be complicated and probably not worth it. But I think the result can still be improved with a single 101 x 101 image if it is rendered as if the pattern repeated to the right of it and below it, and while drawing the following example I realized it'd also have to be repeated to the bottom-right to get that last pixel right: The blue square is the rectangle of pixels after ceiling the width and height to the nearest integer. In the first case there's the 0.35 pixel of transparency that the edge of the actual pattern is anti-aliased with, if I understanding you correctly. My example is just a minimal case, but of course there might be more complex continuations and other colours that the right edge of the pattern then gets correctly anti-aliased against. |
Chrome and Inkscape render this seamlessly, also with more complex patterns, which is how I noticed it. |
Well, I don't have such low-level control over patterns. I simply delegate it to I just checked what librsvg does, and it simply rounds down. No magic involved. |
Interesting, that definitely is simpler and faster, and probably good enough. In the meantime: would you consider flooring the pixmap dimensions, so the anti-aliasing issue goes away? |
Yes, in the next release whenever it will be. I just want to clarify once again that getting a reproducible output from SVG is a fruitless endeavor. There are no such thing as "a correctly rendered" SVG. The only "solution" is to keep your SVG as simple as possible. |
Awesome, thanks. And thank you for your work in general, I recently replaced a rather slow and hefty workflow using a headless chromium process to convert SVGs with I get that there won't be an absolute truth in reducing vector graphics to a pixel representation, but I still think the goal can be to be as faithful as possible, and I like that resvg has a focus on correctness and edge cases (and I think these aren't even edge cases). So I tried to dig a bit deeper and compare it to inkscape and librsvg... and don't get me wrong, I'm not at all suggesting that resvg has to follow suit no matter what, but it's still worth checking out. Apologies in advance for the long post! I made this test SVG: <svg id="svg1" viewBox="0 0 570 570" xmlns="http://www.w3.org/2000/svg">
<title>Pattern with non-integer width and height</title>
<rect x="0" y="0" width="570" height="570" fill="#ffffff"/>
<pattern id="patt1" width="30.00" height="30.00" patternUnits="userSpaceOnUse">
<rect x="0" y="0" width="30.00" height="30.00" fill="#000000"/>
<path d="M0,0L30.00,30.00" stroke="red" stroke-width="1"/>
<path d="M30.00,0L0,30.00" stroke="red" stroke-width="1"/>
</pattern>
<!-- aligned with ideal grid point, ideal dimensions -->
<rect id="rect3" x="30.00" y="30.00" width="150.00" height="150.00" fill="url(#patt1)"/>
<pattern id="patt2" width="30.25" height="30.25" patternUnits="userSpaceOnUse">
<rect x="0" y="0" width="30.25" height="30.25" fill="#000000"/>
<path d="M0,0L30.25,30.25" stroke="red" stroke-width="1"/>
<path d="M30.25,0L0,30.25" stroke="red" stroke-width="1"/>
</pattern>
<!-- aligned with ideal grid point, ideal dimensions -->
<rect x="211.75" y="30.25" width="151.25" height="151.25" fill="url(#patt2)"/>
<!-- aligned with floored grid point, ideal dimensions -->
<rect x="210.00" y="210.00" width="151.25" height="151.25" fill="url(#patt2)"/>
<!-- aligned with ceiled grid point, rounded dimensions -->
<rect x="217.00" y="403.00" width="151.25" height="151.25" fill="url(#patt2)"/>
<pattern id="patt3" width="30.75" height="30.75" patternUnits="userSpaceOnUse">
<rect x="0" y="0" width="30.75" height="30.75" fill="#000000"/>
<path d="M0,0L30.75,30.75" stroke="red" stroke-width="1"/>
<path d="M30.75,0L0,30.75" stroke="red" stroke-width="1"/>
</pattern>
<!-- aligned with ideal grid point, ideal dimensions -->
<rect x="399.75" y="30.75" width="153.75" height="153.75" fill="url(#patt3)"/>
<!-- aligned with floored grid point, ideal dimensions -->
<rect x="390.00" y="210.00" width="153.75" height="153.75" fill="url(#patt3)"/>
<!-- aligned with ceiled grid point, ideal dimensions -->
<rect x="403.00" y="403.00" width="153.75" height="153.75" fill="url(#patt3)"/>
</svg> So there are three pattern dimensions, each with a red cross from the corners:
The patterns are used in a This is Inkscape's result, and only the first row is relevant, because it seems to align the pattern very well to the ideal rect position and dimensions (tho there seems to be a glitch in the top and bottom edges of the big rectangle): Also note that the pattern is not just repeating, however it is done, it shifts slightly to accomodate the sub-pixel issues and that makes sure the pattern ends up to fill the space as one'd hope: Similarly the result of librsvg, only the first row is relevant, it also aligns the rect to the ideal position and fills it properly, adjusting the pattern: Zooming in shows that the pattern here also shift slightly:
The ideal positions in the first row no longer align with the grid. For the first pattern the second Finally, I compiled a let img_size = usvg::Size::new((r.width() * sx).floor(), (r.height() * sy).floor())?.to_screen_size(); The anti-aliasing is gone, and now both of the second So the flooring definitely is an improvement, but for many cases the result is not expected, namely large patterned areas where the shift becomes very noticeable or areas that align with the ideal pattern space but are positioned farther away from (0, 0). I have no idea how inkscape and librsvg do it, but I think it's worth considering it. You'll have much more experience with SVG processing and the code, but I'll look into it a bit more, maybe I can figure out some details. |
Thanks for the investigation and a test case. I do think that this is a bug, but for now I have no idea how to fix it. |
Duplicate of #628 |
Hallo, I've noticed that patterns result in some anti-aliasing issues at the edges, if they have non-integer dimensions and the shapes inside the pattern go right against the edge.
To illustrate the case I made a large white rectangle covered by a rectangle on top, which has a pattern of 20.13x20.13 containing just a black rectangle filling the pattern exactly. In the PNG result you can see the white shining through, but the whole area should be black.
(Note: the viewBox is only 0 0 200 200, but the image has to be larger to see the effect, so its width and height are set to 1000)
Any of these workarounds make it render correctly:
but I believe the result should be the same without any of them.
Perhaps the pattern should be copied on all four sides while rendering its content?
The text was updated successfully, but these errors were encountered: