Search code examples
d3.jssvgsafarimobile-safarisvg-filters

SVG filters fuzzy in Safari under some circumstances


I have an page with an interactive SVG which looks fine on all browsers (Firefox, Chrome, even IE/Edge) except Safari where everything affected by one of the SVG filters turns into a fuzzy mush (looks like something rendered onto a low-resolution canvas which got scaled up using bilinear interpolation).

Here now a small test case where the problem also appears:

<svg>
  <defs>
    <filter id="filter" y="-100" x="-100" height="300" width="300">
      <feGaussianBlur in="SourceAlpha" stdDeviation="3.5"></feGaussianBlur>
      <feColorMatrix type="matrix" values="0 0 0 2 0   0 0 0 2 0   0 0 0 0 0   0 0 0 1 0" result="lightenedBlur"></feColorMatrix>
      <feMerge>
        <feMergeNode in="lightenedBlur"></feMergeNode>
        <feMergeNode in="SourceGraphic"></feMergeNode>
      </feMerge>
    </filter>
  </defs>
  <g>
    <rect x="10" y="10" width="100" height="100" fill="blue" filter="url(#filter)"></rect>
  </g>
</svg>

How it looks on Apple Safari 11 (on OS X 10.13):
Apple Safari 11 (on OS X 10.13) showing SVG filter issue

Compare that to Google Chrome and Mozilla Firefox respectively:
Google Chrome not showing SVG filter issue Mozilla Firefox not showing SVG filter issue

When looking at other SVG filter demo pages on the web though the effect apparently isn't there. Not quite sure what exactly causes it. What I noticed is that the issue becomes more apparent the larger the filter area is (controlled through the width/height attributes of <filter>).

Is this a known issue? Under what circumstances does it occur? What are reasonable workarounds?


Solution

  • This is not a bug. Safari is punishing you for incorrect syntax in your filter declaration:

    <filter id="filter" y="-100" x="-100" height="300" width="300">
    

    According to spec, this should be read as height="30000%" and width="30000%". Safari is saying "ok I guess you meant this" and adjusting the filter resolution automatically so it doesn't allocate a huge piece of memory to this very large buffer -> hence crappy resolution.

    If you meant 300% - then you need to put 300%. This is one fix:

    <filter id="filter" y="-100%" x="-100%" height="300%" width="300%">
    

    If you meant 300px (really userSpace units) - then this is another fix:

    <filter id="filter" y="100" x="-100" height="300" width="300" filterUnits="userSpaceOnUse">
    

    You must explicitly tell Safari that you mean pixels by specifying userSpaceOnUse (otherwise it uses the silent default objectBoundingBox)

    Another fix - is to over-ride Safari's filter resolution adjustment by explicitly specifying a filterRes. filterRes has been deprecated in the new Filters 1.0 spec and already removed from latest Chrome & Firefox, but Safari still supports it. Since this will result in a big memory hit(and it's hard to believe that you meant to size your filter as you did) - this is not recommended. But whatever - for completeness.

    <filter id="filter" y="-100" x="-100" height="300" width="300" filterRes="100000">
    

    (Two other minor notes - you can make your filters less wordy by using self-closing elements. And the blur adjustment you're doing doesn't lighten the blur, it just dials up the opacity fwiw.)