Have you seen Spotify’s end-of-year campaign? They’ve created a compelling visual aesthetic through image-color manipulation.

Article Continues Below





Image manipulation is a powerful mechanism for making a project stand out from the crowd, or just adding a little sparkle—and web filters offer a dynamic and cascadable way of doing it in the browser.

CSS vs. SVG#section2

Earlier this year, I launched CSSgram, a pure CSS library that uses filters and blend modes to recreate Instagram filters.





Now, this could be done with tinkering and blend modes—but one key feature CSS filters lack is the ability to do per-channel manipulation. This is a huge downside. While CSS filters are convenient, they are merely shortcuts derived from SVG and therefore provide no control over RGBA channels. SVG (particularly the feColorMatrix map) gives us much more power and lets us take CSS filters to the next level, granting significantly more control over image manipulation and special effects.

SVG filters#section3

In the SVG world, filter effects are prefixed with fe-. (Get it? For “filter effect.”) They can produce a wide variety of color effects, ranging from blur to generated 3-D textures. The term fe– is a bit loose, though; see the end of this article for a summary of each of the SVG filter effects’ methods.

SVG filters are currently supported in the following browsers:



Screenshot from caniuse.com.

So yeah, you should be good to go for the most part, unless you need to support IE9 or older. SVG filter support is relatively stable, and is more widespread than CSS filters and blend modes. There are also few major weird bugs, unlike with CSS blend modes (where only Chrome 46 has issues rendering the multiply, difference, and exclusion blend modes).

Note: Some of the 3-D filters, such as feConvolveMatrix , do have known bugs in certain browsers, though feColorMatrix , which this article focuses on, does not. Also, keep in mind that performance will inevitably take a tiny hit when it comes to applying any action in-browser (as opposed to rendering a pre-edited image on the page).

Using filters#section4

The basic layout of an SVG filter goes like this:

<svg> <filter id="filterName"> // filter definition here can include // multiple of the above items </filter> </svg>

Within an SVG, you can declare a filter. Most of the time, you’ll want to declare filters within defs of an SVG and can apply them in CSS like so:

.filter-me { filter: url('#filterName'); }

The filter URL is relative, so both filter: url('../img/filter.svg#filterName') and filter: url('http://una.im/filters.svg#filterName') are valid.

When it comes to color manipulation, feColorMatrix is your best option. feColorMatrix is a filter type that uses a matrix to affect color values on a per-channel (RGBA) basis. Think of it like editing the channels in Photoshop.

This is what the feColorMatrix looks like (with each RGBA value as 1 by default in the original image):

<filter id="linear"> <feColorMatrix type="matrix" values="R 0 0 0 0 0 G 0 0 0 0 0 B 0 0 0 0 0 A 0 "/> </filter> </feColorMatrix>

The matrix here is actually calculating a final RGBA value in its rows, giving each RGBA channel its own RGBA channel. The last number is a multiplier. The final RGBA value can be read from top to bottom like a column:

/* R G B A 1 */ 1 0 0 0 0 // R = 1*R + 0*G + 0*B + 0*A + 0 0 1 0 0 0 // G = 0*R + 1*G + 0*B + 0*A + 0 0 0 1 0 0 // B = 0*R + 0*G + 1*B + 0*A + 0 0 0 0 1 0 // A = 0*R + 0*G + 0*B + 1*A + 0

Here’s a better visualization:





RGB values#section6

You can colorize images by omitting and mixing color channels like so:

<!-- lacking the B & G channels (only R at 1) --> <filter id="red"> <feColorMatrix type="matrix" values="1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 "/> </filter> <!-- lacking the R & G channels (only B at 1) --> <filter id="blue"> <feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0 "/> </filter> <!-- lacking the R & B channels (only G at 1) --> <filter id="green"> <feColorMatrix type="matrix" values="0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 1 0 "/> </filter>

Here’s what adding the “green” filter to an image looks like:





Channel mixing#section8

You can also mix RGB channels to get solid colorizing results:

<!-- lacking the B channel (mix of R & G) Red + Green = Yellow This is saying there is no yellow channel --> <filter id="yellow"> <feColorMatrix type="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 1 0 "/> </filter> <!-- lacking the G channels (mix of R & B) Red + Blue = Magenta --> <filter id="magenta"> <feColorMatrix type="matrix" values="1 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0 "/> </filter> <!-- lacking the R channel (mix of G & B) Green + Blue = Cyan --> <filter id="cyan"> <feColorMatrix type="matrix" values="0 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 "/> </filter>

In each of the previous examples, we mixed colors in CMYK mode, so removing the red channel would mean that green and blue remain. When green and blue mix, they create cyan. Red and blue make magenta. We still retain some of the red and blue values where they are most prominent, but in areas that lack the two (light areas of white, where all colors are present in the RGB schema, or areas of green), the RGBA values of the other two channels replace them.

Justin McDowell has written an excellent article that explains HSL (hue, saturation, lightness) color theory. With SVG, the lightness value is the luminosity, which we also need to keep in mind. Here, each luminosity level is retained in each channel, so for magenta, we get an image that looks like this:





Why is there so much magenta in the clouds and lightest values? Consider the RGB chart:





When one value is missing, the other two take its place. So now, without the green channel, there is no white, cyan, or yellow. These colors don’t actually disappear, however, because their luminosity (or alpha) values have not yet been touched. Let’s see what happens when we manipulate those alpha channels next.

Alpha values#section9

We can play with the shadow and highlight tones via the alpha channels (fourth column). The fourth row affects overall alpha channels, while the fourth column affects luminosity on a per-channel basis.

<!-- Acts like an opacity filter at .5 --> <filter id="alpha"> <feColorMatrix type="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 .5 0 "/> </filter> <!-- increases green opacity to be on the same level as overall opacity --> <filter id="hard-green"> <feColorMatrix type="matrix" values="1 0 0 0 0 0 1 0 1 0 0 0 1 0 0 0 0 0 1 0 "/> </filter> <filter id="hard-yellow"> <feColorMatrix type="matrix" values="1 0 0 1 0 0 1 0 1 0 0 0 1 0 0 0 0 0 1 0 "/> </filter>

In the following example, we’re reusing the matrix from the magenta example and adding a 100% alpha channel on the blue level. We retain the red values, yet override any red in the shadows so the shadow colors all become blue, while the lightest values that have red in them become a mix of blue and red (magenta).

<filter id="blue-shadow-magenta-highlight"> <feColorMatrix type="matrix" values="1 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 1 0 "/> </filter>





If this last value was less than 0 (up to -1), the opposite would happen. The shadows would turn red instead of blue. At -1, these create identical effects:

<filter id="red-overlay"> <feColorMatrix type="matrix" values="1 0 0 0 0 0 0 0 0 0 0 0 1 -1 0 0 0 0 1 0 "/> </filter> <filter id="identical-red-overlay"> <feColorMatrix type="matrix" values="1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 "/> </filter>





Making this value .5 instead of -1, however, allows us to see the mixture of color in the shadow:

<filter id="blue-magenta-2"> <feColorMatrix type="matrix" values="1 0 0 0 0 0 0 0 0 0 0 0 1 .5 0 0 0 0 1 0 "/> </filter>





Blowing out channels#section10

We can affect the overall alpha of individual channels via the fourth row. Since our example has a blue sky, we can get rid of the sky and the blue values by converting blue values to white, like this:

<filter id="elim-blue"> <feColorMatrix type="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 -2 1 0 "/> </filter>





Here are a few more examples of channel mixing:

<!-- No G channel, Red is at 100% on the G Channel, so the G channel looks Red (luminosity of G channel lost) --> <filter id="no-g-red"> <feColorMatrix type="matrix" values="1 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0 "/> </filter> <!-- No G channel, Red and Green is at 100% on the G Channel, so the G Channel looks Magenta (luminosity of G channel lost) --> <filter id="no-g-magenta"> <feColorMatrix type="matrix" values="1 1 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 1 0 "/> </filter> <!-- G channel being shared by red and blue values. This is a colorized magenta effect (luminosity maintained) --> <filter id="yes-g-colorized-magenta"> <feColorMatrix type="matrix" values="1 1 0 0 0 0 1 0 0 0 0 1 1 0 0 0 0 0 1 0 "/> </filter>

Lighten and darken#section11

You can create a darken effect by setting the RGB values at each channel to a value less than 1 (which is the full natural strength). To lighten, increase the values to greater than 1. You can think of this as expanding or decreasing the RGB color circle shown earlier. The wider the radius of the circle, the lighter the tones created and the more white is “blown out”. The opposite happens when the radius is decreased.





Here’s what the matrix looks like:

<filter id="darken"> <feColorMatrix type="matrix" values=".5 0 0 0 0 0 .5 0 0 0 0 0 .5 0 0 0 0 0 1 0 "/> </filter>





<filter id="lighten"> <feColorMatrix type="matrix" values="1.5 0 0 0 0 0 1.5 0 0 0 0 0 1.5 0 0 0 0 0 1 0 "/> </filter>





You can create a grayscale effect by accepting only one shade’s pixel values in a column. There are different grayscale effects, however, based on which active levels one applies. Here we’re doing a channel manipulation, since we’re grayscaling the image. Consider these examples:

<filter id="gray-on-light"> <feColorMatrix type="matrix" values="1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 1 0 "/> </filter>





<filter id="gray-on-mid"> <feColorMatrix type="matrix" values="0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 1 0 "/> </filter>





<filter id="gray-on-dark"> <feColorMatrix type="matrix" values="0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 1 0 "/> </filter>





Pulling it all together#section13

The real power of feColorMatrix lies in its ability to mix channels and combine many of these concepts into new image effects. Can you read what’s going on in this filter?

<filter id="peachy"> <feColorMatrix type="matrix" values="1 0 0 0 0 0 .5 0 0 0 0 0 0 .5 0 0 0 0 1 0 "/> </filter>

We’re using the red channel at its normal alpha channel, applying green at half strength, and applying blue on the darker alpha channels but not at its original color location. The effect gives us dark blue in the shadows, and a mix of red and half-green for the highlights and midtones. If we recall red + green = yellow, red + (green/2) would be more of a coral color:





Here’s another example:

<filter id="lime"> <feColorMatrix type="matrix" values="1 0 0 0 0 0 2 0 0 0 0 0 0 .5 0 0 0 0 1 0 "/> </filter>

In that segment, we’re using the normal pixel hue of red, a blown-out green, and blue devoid of its original hue pixels, but applied in the shadows. Again, we see that dark blue in the shadows, and since red + green = yellow, red + (green*2) would be more of a yellow-green in the highlights:





So much can be explored by playing with these values. An excellent example of such exploration is Rachel Nabors’ Dev Tools Challenger, where she filters out the longer wavelengths (i.e., the red and orange channels) from the fish in the sea, explaining why “Orange Roughy” actually appears black in the water. (Note: requires Firefox.)

How cool! Science! And color filters! Now that you have a basic grasp of the situation, you, too, have the tools you need to create your own effects.

For some of those really rad Spotify duotone effects, I recommend you check out an article by Amelia Bellamy-Royds, who goes into even more detail about feColorMatrix . Sara Soueidan also wrote an excellent post on image effects where she recreates CSS blend modes with SVG.

Filter effects reference#section14

Once you understand what’s going on with the feColorMatrix , you have the basic tools to create detailed filters within a single contained filter definition, but there are other options out there that will let you take it even further. Here’s a handy guide to all of the fe- * options currently out there for further exploration:

feBlend : similar to CSS blend modes, this function describes how images interact via a blend mode

: similar to CSS blend modes, this function describes how images interact via a blend mode feComponentTransfer : an umbrella term for a function that alters individual RGBA channels (i.e. , feFuncG )

: an umbrella term for a function that alters individual RGBA channels (i.e. , ) feComposite : a filter primitive that defines pixel-level image interactions

: a filter primitive that defines pixel-level image interactions feConvolveMatrix : this filter dictates how pixels interact with their close neighbors (i.e., blurring or sharpening)

: this filter dictates how pixels interact with their close neighbors (i.e., blurring or sharpening) feDiffuseLighting : defines a light source

: defines a light source feDisplacementMap : displaces an image ( in ) using the pixel values of another input ( in2 )

: displaces an image ( ) using the pixel values of another input ( ) feFlood : complete fill of the filter subregion with a specified color and alpha level

: complete fill of the filter subregion with a specified color and alpha level feGaussianBlur : blurs input pixels using an input standard deviation

: blurs input pixels using an input standard deviation feImage : for use within other filters (like feBlend or feComposite )

: for use within other filters (like or ) feMerge : allows for asynchronous application of filter effects, instead of layering them

: allows for asynchronous application of filter effects, instead of layering them feMorphology : erodes or dilates lines of source graphic (think strokes on text)

: erodes or dilates lines of source graphic (think strokes on text) feOffset : used for creating drop shadows

: used for creating drop shadows feSpecularLighting : source for the alpha component as a bump map, a.k.a. the “specular” portion of the Phong Reflection Model

: source for the alpha component as a bump map, a.k.a. the “specular” portion of the Phong Reflection Model feTile : refers to how an image is repeated to fill a space

: refers to how an image is repeated to fill a space feTurbulence : allows the creation of synthetic textures using Perlin Noise

Additional resources#section15