5 ms·
Amateur attempt at writing SVG filters with a grounding in DSP/image processing: <div id="comparator"> <img src="https://meyerweb.com/eric/thoughts/wp-con
by nyanpasu64 3y ago
Amateur attempt at writing SVG filters with a grounding in DSP/image processing:
<div id="comparator">
<img src="https://meyerweb.com/eric/thoughts/wp-content/uploads/wikipedia-home.png" alt="">
<img src="https://meyerweb.com/eric/thoughts/wp-content/uploads/wikipedia-home.png" alt="">
</div>
<svg xmlns='http://www.w3.org/2000/svg'>
<filter id='pixelate' x='0' y='0'>
<!-- <feComposite result="box-blur"/> -->
<feConvolveMatrix order="3" kernelMatrix="1 1 1 1 1 1 1 1 1" result="box-blur" />
<!-- generate tiling pattern -->
<feFlood x='3' y='3' height='1' width='1'/>
<feComposite width='3' height='3'/>
<feTile result='tiles'/>
<!-- apply tiling pattern -->
<feComposite in='box-blur' operator='in'/>
<!-- expand pixels to (2r + 1) large -->
<feMorphology operator='dilate' radius='1'/>
</filter>
</svg>
The feConvolveMatrix is only needed if you want input pixel averaging rather than point sampling. (It automatically divides by the sum of taps.)
Note that:
- I'm told that feConvolveMatrix becomes slower for higher orders. For composite input sizes (like powers of 2), you can instead combine multiple passes of sampling with increasing amounts of zero-spacing between taps (the other meaning of dilation LMAO). I don't know if browsers will optimize the filter to skip processing input data, to make the filtering runtime O(log n) rather than O(n^2).
- I don't know any way to make <feMorphology operator='dilate'> expand point-sampled images to even pixel sizes. You may need to start with a different source point-sampling filter.
- tommyquant 3y agoThis is pretty much what the author is looking for. Here is my version with some optimizations and using 5x5 cells: <div id="comparator"> <img src="https://meyerweb.com/eric/thoughts/wp-content/uploads/wikipedia-home.png" alt=""> <img src="https://meyerweb.com/eric/thoughts/wp-content/uploads/wikipedia-home.png" alt=""> </div> <svg xmlns='http://www.w3.org/2000/svg'> <filter id='pixelate'> <!-- Perform two 1D blurs (vertical and horizontal pass). This is a more efficient way of doing a 2D box blur --> <feConvolveMatrix order="5 1" kernelMatrix="1 1 1 1 1"/> <feConvolveMatrix order="1 5" kernelMatrix="1 1 1 1 1" result="blur"/> <!-- Generate tiling pattern --> <feFlood x="2" y="2" height="1" width="1"/> <feComposite width="5" height="5"/> <feTile/> <!-- Combine tiling pattern and blurred image --> <feComposite in="blur" operator="in"/> <!-- Dilate each pixel to fill each cell of our pixelation grid --> <feMorphology operator="dilate" radius="2"/> </filter> </svg>
- pseudosavant 3y agoThat is the closest so far I think. It is interesting that the filter blurs raster images differently than it does a block of text. I applied your filter on this page: https://svg-video-filters.glitch.me/index-2.html https://svg-video-filters.glitch.me/index-2.html. I had to add `x="0" y="0"` to <filter> to make it work. What are the attributes in the tiling patterned supposed to do? Changing them seemed to have no effect on the image in my test page.
- tommyquant 3y agoThe attributes in the tiling pattern are for isolating the middle pixel in every cell. Consider a 5x5 cell. The middle pixel would be x=2, y=2. The width and height being 1 is so that we only isolate a single pixel. The reason for isolating the middle pixel is because it is the only pixel in the cell that contains the average color of the cell (after box blurring). Once isolated, we can use feMorphology to dilate this pixel so that it fills the cell. Here is a React version to more clearly demonstrate the relationship between cell size and all of the attributes: https://codepen.io/tommyquant/pen/yLwyoGO https://codepen.io/tommyquant/pen/yLwyoGO
- pseudosavant 3y agoThat is great! Trying it out in my test harness didn't work, but apparently there was an extra <def> surrounding all of my filter definitions that caused a problem, but only with this one filter. Thanks for the explanation!