šØš»āšØ SVG effects for Instagram-like photo editing adjustments
Note, this article is going to try to be as simple as possible. I'm not going into the depths of SVG functions and elements... more just a reference on how to do specific things. It's a cobbled collection of information I've found from other people rather than any critical-thinking on my part. Check out the links for more in-depth info on each topic.
Just some background info...
There you are, developing away on your latest project and CSS is kicking ass. It's allowing you to do all of those cool blend-modes and effects but you want to combine it with some SVG goodness.
This was what I was trying to achieve on my latest project for my company websiteĀ LondonParkour.com
I essentially built aĀ WordPress pluginĀ (It's super alpha stage - don't judge me) to build SVGs with a post image as a base. I could then layer effects and vector shapes over the top of the image and do whatever I liked with it. This gave me the flexibility to apply, in bulk, to any group of posts I wanted.
The thinking was to have some generative-art part to it which creates random shapes and what-not, (you can see the results on the londonparkour.comĀ article section), but I'm digressing...
What I also wanted to achieve was Instagram-like adjustments to the images - in SVG. So that's what this article is all about - how to get those effects. I'll link to all of the posts I used to help me figure this all out too, but essentially, this is how to do the basics of image adjustments on bitmap images using SVG filters and shapes.
Main Links of where I found this information are:
- https://vanseodesign.com/css/filters-to-adjust-brightness-contrast-opacity-and-inversion/
- https://una.im/CSSgram/
- https://webplatform.github.io/docs/css/functions/hue-rotate/
- https://yoksel.github.io/svg-gradient-map/#/
- https://yoksel.github.io/svg-filters/#/
- https://github.com/jorgeatgu/fildrop/blob/master/demo.html
- https://github.com/una/CSSgram/tree/master/source/scss
- https://pleeease.io/play/
- https://css-tricks.com/using-svg-to-create-a-duotone-image-effect/
- https://tympanus.net/codrops/2019/02/05/svg-filter-effects-duotone-images-with-fecomponenttransfer/
- https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feFlood
- https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feComponentTransfer
- https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feColorMatrix
Big thanks to all of these people and their efforts. I've just copied their hard work and tweaked it a little to do what I needed, these are the real innovators who figured out how the filters worked.
š The Adjustments
- Saturation
- Brightness
- Contrast
- Sharpening
- Hue rotation
- Overlays & Blend modes
- Chaining filters
- Black & White
- Instagram effects
š The beginning
So, we need an image to start with. I'm going to use this one I took of Big Ben.
Our base SVG will be this:
<svg viewBox="0 0 1500 1000">
<image xlink:href="bigben.jpg" width="1500" height="1000"></image>
</svg>
I'm not going to bother with theĀ xmlns="http://www.w3.org/2000/svg"
Ā and theĀ xmlns:xlink="http://www.w3.org/1999/xlink"
Ā for now. I want to make this all as easy to read as possible.
Which gives us this:
Alright, let's get our mitts dirty. We need to define (as in, use theĀ <def></def>
Ā tags) some filters to use on the image. So let's start with:
šØ Saturation
This is an easy one to get going with. The magic of saturation is this code:
<feColorMatrix
type="saturate"
in="SourceGraphic"
values="3"
/>
Saturation Filter
Now, to use this code, we need to define a filter using theĀ <filter id="overSaturateThis"></filter>
Ā tags with an ID to use. We then tell the image to use this filter. Pretty simple.
The input is theĀ sourceGraphic
Ā (the image) and we're just using a 'saturate' on the image. The value can be changed up or down from 1. 1 being no change. 3 is hugely oversaturated.
So the code becomes:
<svg viewBox="0 0 1500 1000">
<defs>
<filter id="overSaturateThis">
<feColorMatrix
type="saturate"
in="SourceGraphic"
values="3"
></feColorMatrix>
</filter>
</defs>
<image xlink:href="bigben.jpg" width="1500" height="1000" filter="url(#overSaturateThis)"></image>
</svg>
š Brightness
Brightness is slightly more complex, but not much. Essentially, we can fiddle with the Red, Green & Blue elements of the pixels to increase or decrease the brightness.
<feComponentTransfer in="sourceGraphic">
<feFuncR type="linear" slope="0.5"/>
<feFuncG type="linear" slope="0.5"/>
<feFuncB type="linear" slope="0.5"/>
</feComponentTransfer>
Brightness filter
You can see theĀ feFuncR
Ā controls the Red,Ā feFuncG
Ā the green andĀ feFuncB
Ā the blue. TheĀ slope
Ā allows us to increase or decrease the brightness by giving a number lower or higher than 1.
Let's bring the brightness down by half (0.5) in this example.
<svg viewBox="0 0 1500 1000">
<defs>
<filter id="lowerTheBrightness">
<feComponentTransfer in="sourceGraphic">
<feFuncR type="linear" slope="0.5"/>
<feFuncG type="linear" slope="0.5"/>
<feFuncB type="linear" slope="0.5"/>
</feComponentTransfer>
</filter>
</defs>
<image xlink:href="bigben.jpg" width="1500" height="1000" filter="url(#lowerTheBrightness)"></image>
</svg>
ā¬ļøā¬ļø Contrast
Alright, this one requires a little maths... Nothing difficult though.
Start with the amount of contrast you want to add / remove from the picture... let's say increase contrast toĀ 1.2
Half the value :Ā 0.6
Invert it :Ā -0.6
Add 0.5 onto it :Ā -0.1
And thats the calculated value you're going to use in theĀ intercept
Ā part of the filter. TheĀ slope
Ā is the original contrast value you want to use.
<feComponentTransfer in="sourceImage">
<feFuncR type="linear" slope="1.2" intercept="-0.1"/>
<feFuncG type="linear" slope="1.2" intercept="-0.1"/>
<feFuncB type="linear" slope="1.2" intercept="-0.1"/>
</feComponentTransfer>
Contrast Filter
Plug it into the full code to effect the image and you've got this.
<svg viewBox="0 0 1500 1000">
<defs>
<filter id="enhanceTheContrast">
<feComponentTransfer in="sourceImage">
<feFuncR type="linear" slope="1.2" intercept="-0.1"/>
<feFuncG type="linear" slope="1.2" intercept="-0.1"/>
<feFuncB type="linear" slope="1.2" intercept="-0.1"/>
</feComponentTransfer>
</filter>
</defs>
<image xlink:href="bigben.jpg" width="1500" height="1000" filter="url(#enhanceTheContrast)"></image>
</svg>
šŖ Sharpening
Yeah! That's right. You can sharpen SVG images too. That's pretty bad-ass if you ask me.
And it's another one liner to boot.
<feConvolveMatrix in="SourceGraphic" order="3" preserveAlpha="true" kernelMatrix="-1 0 0 0 4 0 0 0 -1"/>
Sharpening Filter
Playing with theĀ -1
Ā up and down can increase or decrease the sharpness. Play around with the numbers to see what you can come up with.
<svg viewBox="0 0 1500 1000">
<defs>
<filter id="sharpenTheImage">
<feConvolveMatrix
in="SourceGraphic"
order="3"
preserveAlpha="true"
kernelMatrix="-1 0 0 0 4 0 0 0 -1"
/>
</filter>
</defs>
<image xlink:href="bigben.jpg" width="1500" height="1000" filter="url(#sharpenTheImage)"></image>
</svg>
š¢ Hue Rotation
Lets get funky in this mother... Rotate the hue to change the colours of the image. Very simple effect too.
<feColorMatrix type="hueRotate" in="SourceGraphic" values="45"/>
Hue Rotate Filter
Define theĀ degrees
Ā of the hue rotation with the values
. Gives some funky results too.ā
š„ Overlays & Blending Modes
OK, so we've gotten this far with just some simple filters. Now we need to add a couple more elements to achieve what we want. Again, I'm going to try to keep this as simple as possible.
So, we want Ā two things this time.
- A solid overlay of a particular colour.
- A filter to blend the image and the solid overlay.
Let's tackle the solid overlay. This is called aĀ Flood
Ā filter effect.
<filter id="blendingTest">
<feFlood
flood-color="#FF0000"
flood-opacity="0.9"
result="floodOut"
></feFlood>
</filter>
Flood Filter
The flood has a specific colourĀ #FF0000
Ā in this instance. Notice is also has aĀ result
Ā flag too. This is for chaining multiple filter effects together. One filter'sĀ result
Ā can be another'sĀ in
Ā or input.
Just to add to the spice, we can also add flood-opacity too, which is a nice little addition to control the amount of the colour.
Secondly, the blending of theĀ feFlood
Ā and theĀ sourceGraphic
Ā with a blend-mode.
<filter id="blendingTest">
<feFlood
flood-color="#FF0000"
flood-opacity="0.0"
result="floodOut"
></feFlood>
<feBlend mode="multiply" in="SourceGraphic" in2="floodOut"/>
</filter>
Blend Mode Added
So now, we have two in's...Ā in
Ā andĀ in2
. These are now blended together with the 'multiply' blend-mode.
So the full code would be:
<svg viewBox="0 0 1500 1000">
<defs>
<filter id="blendingTest">
<feFlood
flood-color="#FF0000"
flood-opacity="0.95"
result="floodOut"
></feFlood>
<feBlend mode="multiply" in="SourceGraphic" in2="floodOut"/>
</filter>
</defs>
<image xlink:href="bigben.jpg" width="1500" height="1000" filter="url(#blendingTest)"></image>
</svg>
āChaining Filters Together
You got a little taste in the previous 'overlay' example, but the crux is that you can chain multiple effects together to create one epic filter. To do this, you specify aĀ result
Ā keyword that is then used on theĀ in
Ā of the next one.
So chaining three of the previous filters together should be easy. Let's put together:
- Saturate
- HueRotate
- Sharpen
<filter id="epicTriple">
<feColorMatrix
in="SourceGraphic"
type="saturate"
values="3"
result="saturateOUT"
/>
<feColorMatrix
in="saturateOUT"
type="hueRotate"
values="45"
result="hueOUT"
/>
<feConvolveMatrix
in="hueOUT"
order="3"
preserveAlpha="true"
kernelMatrix="-1 0 0 0 4 0 0 0 -1"
result="sharpenOUT"
/>
</filter>
As you can see, I've labeled each effectĀ in
Ā andĀ result
Ā and chained them together.
Image >>> saturateOUT >>> hueOUT >>> sharpenOUT
This gives us the code:
<svg viewBox="0 0 1500 1000">
<defs>
<filter id="epicTriple">
<feColorMatrix
in="SourceGraphic"
type="saturate"
values="3"
result="saturateOUT"
/>
<feColorMatrix
in="saturateOUT"
type="hueRotate"
values="45"
result="hueOUT"
/>
<feConvolveMatrix
in="hueOUT"
order="3"
preserveAlpha="true"
kernelMatrix="-1 0 0 0 4 0 0 0 -1"
result="sharpenOUT"
/>
</filter>
</defs>
<image xlink:href="bigben.jpg" width="1500" height="1000" filter="url(#epicTriple)"></image>
</svg>
š Black & White
Theres actually a bunch of ways to do this. I'll give you four here.
First, just desaturate the image completely toĀ 0
. Easy-as. We've done this above.
<svg viewBox="0 0 1500 1000">
<defs>
<filter id="blackAndWhite">
<feColorMatrix
type="saturate"
in="SourceGraphic"
values="0"
></feColorMatrix>
</filter>
</defs>
<image xlink:href="bigben.jpg" width="1500" height="1000" filter="url(#blackAndWhite)"></image>
</svg>
Simple Black & White Filter
Secondly, we can use the more complex colourMatrix to control the RGB values that make the image up. This is using the red channel to desaturate the image.
<svg viewBox="0 0 1500 1000">
<defs>
<filter id="blackAndWhiteRed">
<feColorMatrix type="matrix" values="
.33 0 0 0 0
.33 0 0 0 0
.33 0 0 0 0
0 0 0 1 0">
</feColorMatrix>
</filter>
</defs>
<image xlink:href="bigben.jpg" width="1500" height="1000" filter="url(#blackAndWhiteRed)"></image>
</svg>
Red-Channel B&W
We have the green channel:
<svg viewBox="0 0 1500 1000">
<defs>
<filter id="blackAndWhiteGreen">
<feColorMatrix type="matrix" values="
0 .33 0 0 0
0 .33 0 0 0
0 .33 0 0 0
0 0 0 1 0">
</feColorMatrix>
</filter>
</defs>
<image xlink:href="bigben.jpg" width="1500" height="1000" filter="url(#blackAndWhiteGreen)"></image>
</svg>
Green Channel B&W
And the Blue Channel:
<svg viewBox="0 0 1500 1000">
<defs>
<filter id="blackAndWhiteBlue">
<feColorMatrix type="matrix" values="
0 0 .33 0 0
0 0 .33 0 0
0 0 .33 0 0
0 0 0 1 0">
</feColorMatrix>
</filter>
</defs>
<image xlink:href="bigben.jpg" width="1500" height="1000" filter="url(#blackAndWhiteBlue)"></image>
</svg>
Blue Channel B&W
š Instagram Effects
Take a look atĀ Una KravetsĀ fantastic work onĀ CSSGramĀ so you can see what's possible with CSS and how she's recreated famous Instagram filters.
Thanks to her generosity, we can take a look at the underlying SCSS code she used to make those effects on her github repository:
Let's take a look at one of her effects and re-create it now using SVG filters:
The 'aden' effect is right here:Ā https://github.com/una/CSSgram/blob/master/source/scss/aden.scss
[
una/CSSgram
CSS library for Instagram filters. Contribute to una/CSSgram development by creating an account on GitHub.
GitHubuna
](https://github.com/una/CSSgram)
In it, you can see she used the following CSS filter:
@mixin aden($filters...) {
@include filter-base;
filter:
hue-rotate(-20deg)
contrast(.9)
saturate(.85)
brightness(1.2)
$filters;
&::after {
background:
linear-gradient(to right, rgba(66, 10, 14, .2), transparent);
mix-blend-mode: darken;
}
@content;
}
Alright. A hue-rotate, contrast, saturate, brightness and a colour overlay with a 'darken' blend mode. With all our previous knowledge, I'm sure we can recreate that.
I'm also going to add on a sneaky sharpen too - just for my own effect.
Chaining the filters together is now as easy as pie:
<svg viewBox="0 0 1500 1000">
<defs>
<filter id="aden" filterUnits="objectBoundingBox">
<!-- HUE ROTATE -20deg -->
<feColorMatrix
type="hueRotate"
in="SourceGraphic"
values="-20"
result="hueRotateOut"
/>
<!-- DESATURATE 0.85 -->
<feColorMatrix
type="saturate"
in="hueRotateOut"
values="0.85"
result="saturateOut"
/>
<!-- BRIGHTNESS 0.9 -->
<feComponentTransfer in="saturateOut" result="brightnessOut">
<feFuncR type="linear" slope="0.9"/>
<feFuncG type="linear" slope="0.9"/>
<feFuncB type="linear" slope="0.9"/>
</feComponentTransfer>
<!-- CONTRAST 1.2 -->
<feComponentTransfer in="brightnessOut" result="contrastOut">
<feFuncR type="linear" slope="1.2" intercept="0.05"/>
<feFuncG type="linear" slope="1.2" intercept="0.05"/>
<feFuncB type="linear" slope="1.2" intercept="0.05"/>
</feComponentTransfer>
<!-- FLOOD OVERLAY rgba(66, 10, 14, .2) -->
<feFlood
flood-color="#420A0E"
flood-opacity="0.2"
out="floodOut"
/>
<!-- BLEND FLOOD & CONTRASTOUT -->
<feBlend
mode="darken"
in="contrastOut"
in2="floodOut"
result="blendOut"
/>
<!-- SHARPEN -->
<feConvolveMatrix
in="blendOut"
order="3"
preserveAlpha="true"
kernelMatrix="-1 0 0 0 4 0 0 0 -1"
result="sharpenOut"
/>
</filter>
</defs>
<image xlink:href="bigben.jpg" width="1500" height="1000" filter="url(#aden)"></image>
</svg>
Which gives us this wonderful result:
Just for reference, all of her effects are here:Ā https://github.com/una/CSSgram/tree/master/source/scss
Final Thoughts
During this little project I've really only brushed the surface of what's possible and I'm blown away by the power of SVG image manipulation.
For the LondonParkour project I wrote the SVG data out into a file and then used Imagick and Inkscape to render out to a PNG and JPG. Now I can start generating images however I like with whatever effects and components I like.
You can send me any questions you like over on my twitter account.
š Quick Reference
<feColorMatrix
type="saturate"
in="SourceGraphic"
values="3"
/>
Saturation Filter
<feComponentTransfer in="sourceGraphic">
<feFuncR type="linear" slope="0.5"/>
<feFuncG type="linear" slope="0.5"/>
<feFuncB type="linear" slope="0.5"/>
</feComponentTransfer>
Brightness filter
<feComponentTransfer in="sourceImage">
<feFuncR type="linear" slope="1.2" intercept="-0.1"/>
<feFuncG type="linear" slope="1.2" intercept="-0.1"/>
<feFuncB type="linear" slope="1.2" intercept="-0.1"/>
</feComponentTransfer>
Contrast Filter
<feConvolveMatrix in="SourceGraphic" order="3" preserveAlpha="true" kernelMatrix="-1 0 0 0 4 0 0 0 -1"/>
Sharpening Filter
<feColorMatrix type="hueRotate" in="SourceGraphic" values="45"/>
Hue Rotate Filter
<filter id="blendingTest">
<feFlood
flood-color="#FF0000"
flood-opacity="0.0"
result="floodOut"
></feFlood>
<feBlend mode="multiply" in="SourceGraphic" in2="floodOut"/>
</filter>
Flood & Blend Mode Filter
<svg viewBox="0 0 1500 1000">
<defs>
<filter id="blackAndWhite">
<feColorMatrix
type="saturate"
in="SourceGraphic"
values="0"
></feColorMatrix>
</filter>
</defs>
<image xlink:href="bigben.jpg" width="1500" height="1000" filter="url(#blackAndWhite)"></image>
</svg>
Simple Black & White Filter
<svg viewBox="0 0 1500 1000">
<defs>
<filter id="blackAndWhiteGreen">
<feColorMatrix type="matrix" values="
0 .33 0 0 0
0 .33 0 0 0
0 .33 0 0 0
0 0 0 1 0">
</feColorMatrix>
</filter>
</defs>
<image xlink:href="bigben.jpg" width="1500" height="1000" filter="url(#blackAndWhiteGreen)"></image>
</svg>
Green Channel B&W
<svg viewBox="0 0 1500 1000">
<defs>
<filter id="blackAndWhiteBlue">
<feColorMatrix type="matrix" values="
0 0 .33 0 0
0 0 .33 0 0
0 0 .33 0 0
0 0 0 1 0">
</feColorMatrix>
</filter>
</defs>
<image xlink:href="bigben.jpg" width="1500" height="1000" filter="url(#blackAndWhiteBlue)"></image>
</svg>
Blue Channel B&W
Finish
As you can see, SVGs are wonderful at post-processing. Being able to add non-destructive filters onto an image will definitely be the future of image processing on the web.