7

Note: I can't use JavaScript, because this is for a CSS Zen Garden sort of challenge. Please do not suggest a JS library.

I have 2 ideas that I'm not making headway on:

  1. Use a SVG filter to just pixelate the dang image; I've been playing with <feMorphology operator="erode"/> and punching the contrast up after, but it looks bad.

  2. Filter the image to be smaller, then scale it up using CSS and image-rendering to be all blocky. The hard part is Step A; I can't find any filter operations that scale the input.

Am I missing something? How can I get a "pixelated" effect using an SVG filter?

Tigt
  • 1,330
  • 2
  • 19
  • 40
  • 1
    I would suggest to add your [codepen](https://codepen.io/tigt/pen/akYqAg) as a separate answer as it is quite good and people searching can find a good answer. Deleting my answer. – Nikos M. Mar 30 '20 at 16:58

2 Answers2

13

You can pixelate images if you have the right "magic" displacementMap. Feel free to use the one referenced below (courtesy of Zoltan Fegyver).

Update: Changed the sample code to inline the displacementmap image as a data: URI (thanks for the code IllidanS4.)

The original answer had the displacementMap image hosted on a different domain. This used to work - but browsers implemented the new Filters security measures that disallow this. For production code today, you need the displacement map image served from the same domain as the source graphic's file or you need to inline the displacementMap.

Update 2: You may have to tweak the size of feImage and feGaussianBlur to avoid bugs in feTile that adds artifacts. For example - this seems to work better:

<feGaussianBlur stdDeviation="8" in="SourceGraphic" result="smoothed" />
 <feImage width="15.4" height="15.4" 

<svg x="0px" y="0px" width="810px" height="600px" viewBox="0 0 810 600" color-interpolation-filters="sRGB">
  <defs>
<filter id="pixelate" x="0%" y="0%" width="100%" height="100%">
  <!--Thanks to Zoltan Fegyver for figuring out pixelation and producing the awesome pixelation map. -->
  <feGaussianBlur stdDeviation="2" in="SourceGraphic" result="smoothed" />
  <feImage width="15" height="15" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAIAAAACDbGyAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAWSURBVAgdY1ywgOEDAwKxgJhIgFQ+AP/vCNK2s+8LAAAAAElFTkSuQmCC" result="displacement-map" />
  <feTile in="displacement-map" result="pixelate-map" />
  <feDisplacementMap in="smoothed" in2="pixelate-map" xChannelSelector="R" yChannelSelector="G" scale="50" result="pre-final"/>
  <feComposite operator="in" in2="SourceGraphic"/>
</filter>
  </defs>

  <image filter="url(#pixelate)" width="810" height="600" preserveAspectRatio="xMidYMid meet" xlink:href="http://uploads2.wikiart.org/images/vincent-van-gogh/the-starry-night-1889(1).jpg"/>
</svg>
Michael Mullany
  • 30,283
  • 6
  • 81
  • 105
  • Hm. Works okay in WebKits, but it's just a blur in Firefox. – Tigt May 26 '16 at 05:00
  • 2
    That's because the displacementMap is using a cross-domain image. If you host the displacementMap image on the same domain as the source it should work just fine. (Firefox has implemented the filter security spec - webkit has not) – Michael Mullany May 26 '16 at 15:23
  • Oh, that's awesome; if you could put that in the answer for other people, I'd happily accept! – Tigt May 26 '16 at 17:54
  • Added to the answer – Michael Mullany May 27 '16 at 16:08
  • 1
    You can embed the map directly into the image with the URI `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAIAAAACDbGyAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAWSURBVAgdY1ywgOEDAwKxgJhIgFQ+AP/vCNK2s+8LAAAAAElFTkSuQmCC`. – IS4 Nov 05 '20 at 10:26
  • Can you explain a bit about how and why this works? – msfeldstein Oct 13 '21 at 04:05
  • Dirk Weber has a great article in smashing magazine about displacement maps: https://www.smashingmagazine.com/2021/09/deep-dive-wonderful-world-svg-displacement-filtering/ – Michael Mullany Oct 13 '21 at 11:31
  • In case you're trying to run this from a local file and getting a namespace error, try adding these attributes to the main SVG tag: xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" this is explained here: https://stackoverflow.com/questions/59138117/svg-namespace-prefix-xlink-for-href-on-image-is-not-defined – Dror Feb 16 '22 at 12:55
3

The filter in Michael Mullany's answer didn't work for me, instead I found this filter by Taylor Hunt:

<svg>
  <filter id="pixelate" x="0" y="0">
    <feFlood x="4" y="4" height="2" width="2"/>
    <feComposite width="10" height="10"/>
    <feTile result="a"/>
    <feComposite in="SourceGraphic" in2="a" operator="in"/>
    <feMorphology operator="dilate" radius="5"/>
  </filter>
</svg>

(use it in the same way as the other filter: By giving an image the attribute filter="url(#pixelate)")

In action in this CodePen: https://codepen.io/tigt/pen/aZYqrg

However, both these filters seem unable to handle SVGs where the drawing doesn't take up the entire viewBox.

JLubberger
  • 43
  • 7
  • 1
    feTile has bugs when it tries to tile content that is "too small" at certain magic numbers. You can fix it by tweaking the size of what you're trying to tile until it falls outside the magic number range. Please help by starring this issue: https://bugs.chromium.org/p/chromium/issues/detail?id=1314516&q=feTile&can=2 – Michael Mullany Jul 28 '22 at 15:09
  • 2
    The funny part about this answer is that I _am_ Taylor Hunt. Obviously I have now upvoted it – Tigt Jul 28 '22 at 20:59
  • 1
    NO WAY! Small world, I guess :-) Can't upvote the question yet (not enough rep), but will do as soon as I can! – JLubberger Jul 29 '22 at 07:58