SVG image element with crisp edges - svg

I have an SVG which includes a PNG image using <image>. This included image is a pixel art and I would like it to show pixelated.
But instead it is showing blurry.
Can I change the display so it is pixelated?
Minimal test case:
<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<image
width="100"
height="100"
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAABCAIAAAB7QOjdAAAAD0lEQVR4AWP48OotAwMDAA3tAsiMG69RAAAAAElFTkSuQmCC"
/>
</svg>
Inside is a PNG with dimensions two pixels wide and one pixel tall.
Here is how it displayed on macOS 12.1 (21C52) / Safari 15.2 (17612.3.6.1.6).
But I want it to look like this:

Styling the <image> element with image-rendering: pixelated achieves the desired result:
<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<image style="image-rendering: pixelated;" width="100" height="100" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAABCAIAAAB7QOjdAAAAD0lEQVR4AWP48OotAwMDAA3tAsiMG69RAAAAAElFTkSuQmCC" />
</svg>
The image-rendering CSS property sets an image scaling algorithm. The property applies to an element itself, to any images set in its other properties, and to its descendants.
— MDN Web Docs: Image Rendering
Note, this doesn't appear to render correctly in Safari.

Well the right way would be to use image-rendering: pixelated - but that's not supported on Safari yet.
Until then - this filter will work on black and white pixel art - transforming all RG & B values between 0 and 127 -> 0 and all values from 128 to 255 -> 255. (If you have scaled up anti-aliasing that you want to squash, you should add another <feFuncA type="discrete" tableValues="0 1"/> to that list of feFunc's in the filter).
<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" color-interpolation-filters="sRGB">
<defs>
<filter id="crispen">
<feComponentTransfer>
<feFuncR type="discrete" tableValues="0 1"/>
<feFuncG type="discrete" tableValues="0 1"/>
<feFuncB type="discrete" tableValues="0 1"/>
</feComponentTransfer>
</filter>
</defs>
<image
filter="url(#crispen)"
width="100"
height="100"
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAABCAIAAAB7QOjdAAAAD0lEQVR4AWP48OotAwMDAA3tAsiMG69RAAAAAElFTkSuQmCC"
/>
</svg>
You can also generalize this to duo-tone pixel art by specifying different arrays for tableValues. If your dark tones is rgb(40,60,70) and your light tone is rgb (250,200,180) then your feComponentTransfer would be:
<feComponentTransfer>
<feFuncR type="discrete" tableValues="0.156 .98"/>
<feFuncG type="discrete" tableValues="0.235 .784"/>
<feFuncB type="discrete" tableValues="0.274 .706"/>
</feComponentTransfer>
(e.g. - when you unitize the lower red value of 40 - you get 40/255 = .156)
Note that this only works when all the lower and upper RGB values are lower and higher than 127.5. For example, if your light tone is rgb(200,50,50) and your dark tone is (50,60,60) - then this will result in the wrong answer. In this case you have to create a longer (and more complex) tableValues array so that the source colors are converted to the right destination color.

Related

SVG filter to create waves from straight lines

I am try to reply the effect in the picture with SVG. Do you have any hint on how I can obtain that (other than manually drawing the paths?).
Basically I was thinking to draw or create a pattern of vertically straight lines and then apply an effect on them. The result doesn't have to be 100% equal to the one provided, but similar.
The best you can do without drawing the lines specifically is to distort them using a feDisplacementMap filter - but the results aren't even close to what you're hoping for because filters work in bitmaps.
<svg width="800px" height="600px" color-interpolation-filters="sRGB">
<defs>
<pattern id="diagonal" patternUnits="userSpaceOnUse" width="10" height="10">
<path d="M0 0 l 10 10"
stroke="black" stroke-width="1"/>
</pattern>
<filter id="distort">
<feTurbulence baseFrequency = "0.015" type="fractalNoise" />
<feDisplacementMap in='SourceGraphic' xChannelSelector="R" yChannelSelector="G" scale="60"/>
<feGaussianBlur stdDeviation="1"/>
<feComponentTransfer>
<feFuncA type="table" tableValues="0 .1 .5 1 1 1 1 1 1 1 1 1 1"/>
<feComponentTransfer>
</filter>
</defs>
<g filter="url(#distort)">
<rect x="10" y="10" width="300" height="300" fill="url(#diagonal)"/>
</g>
</svg>

Generating images with non-repeating random patterns

I've been trying to create a special random pattern for some time. For example random black dots, like this:
https://picload.org/thumbnail/riogwpll/pattern2.jpg
However, I need a much larger image with about 100,000 points / circles. In principle, no problem, however, the SVG with several MB then becomes too large to open it, for example, with Inkscape, because each circle is drawn individually. Any ideas how this could be realize better resulting in a smaller file. I have already tried something with pattern. Problem is that it should be a truly random, non-repeating pattern.
It's not necessary to do this with dots it could also look like this:
[enter image description here][1]
https://picload.org/thumbnail/riogwwdr/pattern1.jpg
For ideas / suggestions, I am grateful.
Is it something like this that you are after?
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="600" height="600">
<defs>
<filter id="dots" filterUnits="objectBoundingBox" x="0%" y="0%" width="100%" height="100%" color-interpolation-filters="sRGB">
<feTurbulence baseFrequency=".1" numOctaves="1" seed="42" />
<feColorMatrix type="saturate" values="0"/>
<feGaussianBlur result="blur" stdDeviation="2" />
<feComponentTransfer>
<feFuncA type="discrete" tableValues="0 1 1 1 1"/>
</feComponentTransfer>
</filter>
</defs>
<rect x="0" y="0" width="600" height="600" style="fill:#888; stroke:#bbd; stroke-width:2px; filter: url(#dots)" />
</svg>
How this works:
<feTurbulence baseFrequency=".1" numOctaves="1" seed="42" /> generates some random noise. Remove the seed attribute if you want a different pattern each time.
<feColorMatrix type="saturate" values="0"/> converts the noise to greyscale.
<feGaussianBlur result="blur" stdDeviation="2" /> blurs the noisey pattern so that the dots merge together a little. Experiment with this value to vary the "blobbiness".
<feComponentTransfer> thresholds the grey values to either black or white.

How to get "crispEdges" for SVG text?

Svg shapes other than text are affected by the shape-rendering attribute which can be set to the crispEdges value (https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/shape-rendering). This value seems to turn anti-aliasing off.
But text is only affected by text-rendering. However, this does not provide the crispEdges value (https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/text-rendering). Why? Is there another way to get non-anti-alias?
For really crisp edges, you can use a filter to posterize your text.
<svg width="400px" height="400px">
<defs>
<filter id="crispify">
<feComponentTransfer>
<feFuncA type="discrete" tableValues="0 1"/>
</feComponentTransfer>
</filter>
</defs>
<text filter="url(#crispify)" font-size="60" y="60" >Some crispy text</text>
</svg>

How do I use the luminance in an image to control the opacity of a fill in SVG

I would like to use an image to control the alpha channel of a fill in SVG.
I can't figure out the right way to combine SourceGraphic with the alpha. Here is what I have right now:
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 0 300 300">
<defs>
<filter id="FILTER_pencil" primitiveUnits="userSpaceOnUse" filterUnits="objectBoundingBox" width="100%" height="100%">
<feImage result="result1" width="128" height="128" xlink:href="http://lightartpixel.com/FILTER_pencil.png"/>
<feColorMatrix type="luminanceToAlpha" result="alpha" in="result1"/>
<feComposite operator="atop" in="alpha" in2="SourceGraphic"></feComposite>
</filter>
</defs>
<rect fill="#5d496a" stroke="#5d496a" filter="url(#FILTER_pencil)" width="100" height="100"/>
</svg>
Here is a jsfiddle that shows the current output. Which seems to be combining the luminance with the fill color.
You don't want to use atop, that renders both the black shape with variable opacity that you got from the luminanceToAlpha as well as the source graphic. Instead, you want to use the "in" operator, which only renders the source graphic, but uses the alpha values from the luminanceToAlpha as alpha. You also need to reverse the order of your "ins". This gets you what you want.
<feComposite operator="in" in2="alpha" in="SourceGraphic"></feComposite>

SVG: getting rid of shape outlines using feBlend multiply blending

I have an SVG where I'm using filters to blend white shapes on top of black backgrounds using multiply blending. Since I'm using multiply, I would expect the white shape not to show up at all, but this is not the case. In this example I'm drawing a square in its own filter, then a smaller circle (also in its own filter):
http://jsfiddle.net/mugwhump/SwcjL/2/
However, there is a faint, pixel-wide white outline surrounding the circle. The color and opacity of this outline depends on the color and opacity used to draw the circle.
It disappears if I move the circle into the same filter group as the square, but unfortunately that's not an option.
Any idea how to get rid of that outline? This happens in basically all browsers/renderers.
Here's the code for the svg:
<svg contentScriptType="text/ecmascript" width="530"
xmlns:xlink="http://www.w3.org/1999/xlink" zoomAndPan="magnify"
contentStyleType="text/css" viewBox="0 0 530 530" height="530"
preserveAspectRatio="xMidYMid meet" xmlns="http://www.w3.org/2000/svg"
version="1.1">
<defs id="defs">
<!--Blend using multiply. -->
<filter color-interpolation-filters="sRGB" x="-1.0" y="-1.0" width="3.0"
xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple"
xlink:actuate="onLoad" height="3.0"
xlink:show="other" id="blend-square-multiply">
<feFlood result="blackness" flood-color="black"/>
<feComposite result="blackness clip" in="blackness" in2="SourceGraphic" operator="in"/>
<feColorMatrix in="SourceGraphic" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0" type="matrix" result="color-trans"/>
<feBlend mode="multiply" in="blackness clip" in2="color-trans"/>
</filter>
</defs>
<!-- White square blended with multiply over black feFlood -->
<g filter="url(#blend-square-multiply)">
<g fill="white" >
<rect x="200" width="100" y="200" height="100" />
</g>
</g>
<!-- White circle blended with multiply over black feFlood
Changing stroke width does nothing, but changing fill color changes
the color of the outline (changing stroke-color does not).
The outline disappears when opacity is set to 0. -->
<g filter="url(#blend-square-multiply)">
<g fill="white" opacity="1">
<circle stroke-width="0" cx="250" cy="250" r="50"/>
</g>
</g>
Remember that source graphics are rasterized before they're handed to the filter pipeline and hence shed their vectorish nature when they enter the threshold of filterdom.
You are getting that effect because the circle element has an edge of anti-aliasing aka semi-opaque pixels around its edges. When those are multiplied with black, you get grey.
Use shape-rendering="crispEdges" to avoid this. Or use an feMorphology erode filter to clip the edges of the antialiasing. Or use an feFuncA filter to dial those edges up to full opacity first. On second thought -- that first idea.
<circle shape-rendering="crispEdges" stroke-width="0" cx="250" cy="250" r="50"/>
I think the enable-background attribute may be the answer to your problem.
http://www.w3.org/TR/SVG/filters.html#EnableBackgroundProperty

Resources