14

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?

1
  • 1
    I would suggest to add your codepen as a separate answer as it is quite good and people searching can find a good answer. Deleting my answer. Commented Mar 30, 2020 at 16:58

3 Answers 3

17

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>

Sign up to request clarification or add additional context in comments.

8 Comments

Hm. Works okay in WebKits, but it's just a blur in Firefox.
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)
Oh, that's awesome; if you could put that in the answer for other people, I'd happily accept!
Added to the answer
You can embed the map directly into the image with the URI data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAIAAAACDbGyAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAWSURBVAgdY1ywgOEDAwKxgJhIgFQ+AP/vCNK2s+8LAAAAAElFTkSuQmCC.
|
5

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

html,
body {
  height: 100%;
  margin: 0;
  background: peachpuff;
}

svg {
  display: block;
  width: 90%;
  height: 90%;
  padding: 5vh 0 0 5vw;
}
<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>
  
  <image width="100%" height="100%" 
         preserveAspectRatio="xMidYMid slice"
         filter="url(#pixelate)"
         xlink:href="https://s3-us-west-2.amazonaws.com/s.cdpn.io/183091/grateful_dog.jpg"/>
  <image width="100%" height="100%" 
         preserveAspectRatio="xMidYMid slice"
         clip-path="url(#half)"
         xlink:href="https://s3-us-west-2.amazonaws.com/s.cdpn.io/183091/grateful_dog.jpg"/>
  
  <clipPath id="half">
    <rect width="100%" height="50%"/>
  </clipPath>
</svg>

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

3 Comments

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: bugs.chromium.org/p/chromium/issues/…
The funny part about this answer is that I am Taylor Hunt. Obviously I have now upvoted it
NO WAY! Small world, I guess :-) Can't upvote the question yet (not enough rep), but will do as soon as I can!
3

I've extended Jlubberger's & Tigt's solution.

Improvements:

  • Multiple sizes
  • Animated
  • No more white space on edges where you can't SVG tile

example strength of filter being applied

  .imgAni {
    animation: pixelAni 5s;
  }
  @keyframes pixelAni {
    99.9% {
      filter: url("#pixelateAni");
    }
  }
  
  img {
    width: 100%;
    height: 100%;
  }
<img class="imgAni" src="https://www.hartz.com/wp-content/uploads/2022/04/small-dog-owners-1.jpg"/>

<svg>
  <filter id="pixelateAni" x="0" y="0" width="100%" height="100%">

    <!-- First layer: Normal pixelation effect-->
    <feflood x="1" y="1" height="1" width="1"></feflood>
    <fecomposite id="composite" in2="SourceGraphic" operator="in" width="2" height="2"></fecomposite>
    <fetile result="tiled"></fetile>
    <fecomposite in="SourceGraphic" in2="tiled" operator="in"></fecomposite>
    <femorphology id="morphology" operator="dilate" radius="1" result="dilatedPixelation"></femorphology>

    <!-- Second layer: Fallback with full-width tiling-->
    <feflood x="1" y="1" height="1" width="1" result="floodFallbackX"></feflood>
    <fecomposite id="compositeX" in2="SourceGraphic" operator="in" width="1" height="2"></fecomposite>
    <fetile result="fullTileX"></fetile>
    <fecomposite in="SourceGraphic" in2="fullTileX" operator="in"></fecomposite>
    <femorphology id="morphologyX" operator="dilate" radius="1" result="dilatedFallbackX"></femorphology>

    <!-- Third layer: Fallback with full-height tiling-->
    <feflood x="1" y="1" height="1" width="1"></feflood>
    <fecomposite id="compositeY" in2="SourceGraphic" operator="in" width="2" height="1"></fecomposite>
    <fetile result="fullTileY"></fetile>
    <fecomposite in="SourceGraphic" in2="fullTileY" operator="in"></fecomposite>
    <femorphology id="morphologyY" operator="dilate" radius="1" result="dilatedFallbackY"></femorphology>

    <!-- Lets animate them -->
    <animate calcmode="discrete" xlink:href="#composite" attributename="width" values="128; 64; 32; 16; 8; 4; 2" dur="5s" fill="freeze" repeatcount="once"></animate>
    <animate calcmode="discrete" xlink:href="#composite" attributename="height" values="128; 64; 32; 16; 8; 4; 2" dur="5s" fill="freeze" repeatcount="once"></animate>
    <animate calcmode="discrete" xlink:href="#morphology" attributename="radius" values="64; 32; 16; 8; 4; 2; 1" dur="5s" fill="freeze" repeatcount="once"></animate>
    
    <animate calcmode="discrete" xlink:href="#compositeX" attributename="width" values="64; 32; 16; 8; 4; 2; 1" dur="5s" fill="freeze" repeatcount="once"></animate>
    <animate calcmode="discrete" xlink:href="#compositeX" attributename="height" values="128; 64; 32; 16; 8; 4; 2" dur="5s" fill="freeze" repeatcount="once"></animate>
    <animate calcmode="discrete" xlink:href="#morphologyX" attributename="radius" values="64; 32; 16; 8; 4; 2; 1" dur="5s" fill="freeze" repeatcount="once"></animate>
    
    <animate calcmode="discrete" xlink:href="#compositeY" attributename="width" values="128; 64; 32; 16; 8; 4; 2" dur="5s" fill="freeze" repeatcount="once"></animate>
    <animate calcmode="discrete" xlink:href="#compositeY" attributename="height" values="64; 32; 16; 8; 4; 2; 1" dur="5s" fill="freeze" repeatcount="once"></animate>
    <animate calcmode="discrete" xlink:href="#morphologyY" attributename="radius" values="64; 32; 16; 8; 4; 2; 1" dur="5s" fill="freeze" repeatcount="once"></animate>
    
    <!-- Combine all three layers-->
    <femerge>
      <femergenode in="dilatedFallbackX"></femergenode>
      <femergenode in="dilatedFallbackY"></femergenode>
      <femergenode in="dilatedPixelation"></femergenode>
    </femerge>
  </filter>
</svg>

Check out more variations: https://codepen.io/QuiteQuinn/pen/qBeWaEW

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.