Tigt
Tigt

Reputation: 1477

Can one pixelate images with an SVG filter?

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?

Upvotes: 12

Views: 7018

Answers (3)

Quinn Keaveney
Quinn Keaveney

Reputation: 1574

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

Upvotes: 1

JLubberger
JLubberger

Reputation: 63

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.

Upvotes: 5

Michael Mullany
Michael Mullany

Reputation: 31705

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>

Upvotes: 17

Related Questions