linebender / resvg

An SVG rendering library.
Apache License 2.0
2.85k stars 228 forks source link

Bleed issue rendering images as patterns, or SVG bug? #794

Closed jermy closed 4 months ago

jermy commented 4 months ago

I've been trying to chase down a rendering quality issue with some example SVG content, where an embedded PNG would exhibit extra pixels that shouldn't be present.

I've generated a test file using the same SVG structure as that file:

<svg width="122" height="190" viewBox="0 0 300 300" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="50" y="0" width="200" height="124" fill="url(#patternId)"/>
<defs>
<pattern id="patternId" patternContentUnits="objectBoundingBox" width="1" height="1">
<use href="#imageId" transform="matrix(0.0033 0 0 0.005 0 0)"/>
</pattern>
<image id="imageId" width="300" height="200" href="data:image/png;base64,
iVBORw0KGgoAAAANSUhEUgAAASwAAADICAYAAABS39xVAAABhGlDQ1BJQ0MgcHJvZmlsZQAAKJF9
kT1Iw0AcxV9TpSItDi0i4pChOtmlijjWKhShQqgVWnUwufQLmrQkKS6OgmvBwY/FqoOLs64OroIg
+AHi7OCk6CIl/i8ptIjx4Lgf7+497t4BQqvKNLMvAWi6ZWRSSTGXXxUDrwhgGCGEEZeZWZ+TpDQ8
x9c9fHy9i/Es73N/jpBaMBngE4kTrG5YxBvEM5tWnfM+cYSVZZX4nHjSoAsSP3JdcfmNc8lhgWdG
jGxmnjhCLJZ6WOlhVjY04mniqKrplC/kXFY5b3HWqg3WuSd/YbCgryxzneYYUljEEiSIUNBABVVY
iNGqk2IiQ/tJD/+o45fIpZCrAkaOBdSgQXb84H/wu1uzOBV3k4JJoP/Ftj/GgcAu0G7a9vexbbdP
AP8zcKV3/bUWMPtJerOrRY+AoW3g4rqrKXvA5Q4w8lSXDdmR/DSFYhF4P6NvygPhW2Bwze2ts4/T
ByBLXaVvgINDYKJE2ese7x7o7e3fM53+fgCy9HLADSd0zAAAAAZiS0dEAP8A/wD/oL2nkwAAAAlw
SFlzAAAuIwAALiMBeKU/dgAAAAd0SU1FB+gHFg0eJ4gC/vsAAAAZdEVYdENvbW1lbnQAQ3JlYXRl
ZCB3aXRoIEdJTVBXgQ4XAAACNUlEQVR42u3WwQkDAQwDQTn996xUENBxec6UIMziC7MmtcLmkrPC
eFd1V6uPCQDBAhAsQLAABAtAsADBAhAsAMECBAtAsAAECxAsAMECECxAsAAEC0CwAMECECwAwQIE
C0CwAAQLECwAwQIQLECwAAQLECwAwQIQLECwAAQLQLAAwQIQLADBAgQLQLAABAsQLADBAhAsQLAA
BAtAsADBAhAsgF8uac0wz3U22LTuar8qd+XDAgQLQLAABAsQLADBAhAsQLAABAtAsADBAhAsAMEC
BAtAsADBAhAsAMECBAtAsAAECxAsAMECECxAsAAEC0CwAMECECwAwQIEC0CwAAQLECwAwQIQLECw
AAQLQLAAwQIQLADBAgQLQLAAwQIQLADBAgQLQLAAXjsTPFITuK3/X1XdlQ8LECwAwQIQLECwAAQL
QLAAwQIQLADBAgQLQLAABAsQLADBAgQLQLAABAsQLADBAhAsQLAABAtAsADBAhAsAMECBAtAsAAE
CxAsAMECECxAsAAEC0CwAMECECwAwQIEC0CwAAQLECwAwQIEC0CwAAQLECwAwQJ469LUDPNaZ4RN
U3e1n5W78mEBggUgWACCBQgWgGABCBYgWACCBSBYgGABCBaAYAGCBSBYgGABCBaAYAGCBSBYAIIF
CBaAYAEIFiBYAIIFIFiAYAEIFoBgAYIFIFgAggUIFoBgAQgWIFgAggUgWIBgAQgWgGABggUgWIBg
AQgWgGABggUgWABvfQF4KRWHEshN6gAAAABJRU5ErkJggg=="/>
</defs>
</svg>

where I wasn't expecting a pixel of the colour on the top/left sides of the image repeated on the bottom/right. image (resvg render to PNG then scaled 2x in feh)

I'd assumed I'd broken sampling in relation to a mipmap implementation in tiny-skia I'd added, so was surprised to find it wasn't related to that change at all. I can obviously fix this one render in resvg code, with something like:

--- a/source/crates/resvg/src/path.rs
+++ b/source/crates/resvg/src/path.rs
@@ -61,7 +61,7 @@ pub fn fill_path(
             pattern_pixmap = patt_pix;
             paint.shader = tiny_skia::Pattern::new(
                 pattern_pixmap.as_ref(),
-                tiny_skia::SpreadMode::Repeat,
+                tiny_skia::SpreadMode::Pad,
                 tiny_skia::FilterQuality::Bicubic,
                 fill.opacity().get(),
                 patt_ts,

but patterns are meant to repeat normally, so unless I disable that only for patterns-that-are-actually-images it's clearly not the right answer. The sizes aren't completely arbitrary - the issue will only occur at certain scales, suggesting a rounding/sampling issue depending on if you're lucky or not.

This graphic also fails to render in Firefox and Chromium, so is this actually a bug/feature of SVGs where the use of objectBoundingBox and width/height doesn't actually mean it'll only ever fill that space and sometimes repeated pixels will be visible?

RazrFalcon commented 4 months ago

Yes, I'm seeing a similar behavior in Chrome and Safari, which suggests that it's perfectly normal.

Also, SVG isn't designed for "precise" rendering. Every library renders it in whatever way it wants. Moreover, resvg/tiny-skia do not support fractional pattern offsets (#628), which might be one of the reasons for this behavior.

jermy commented 4 months ago

Thanks, that makes sense. I might see if there's a straightforward way to clamp the sampling for my use-case - perhaps not quite full subpixel offset handling - and otherwise encourage users not to generate graphics like this.