From our monthly sponsor: Automate manual QA and catch visual bugs with Percy’s all-in-one visual testing and review platform.
In the previous article in this series I introduced you to the
<feComponentTransfer>, and we used it to limit the number of colors in an image to create a poster effect. In this article, we will take a look at how it can be used to create a Photoshop-like duotone effect. We’ll also learn how to use it to control the intensity and contrast of an image’s colors.
A Quick Recap
To quickly recap, the
feComponentTransfer primitive allows you to modify each of the R, G, B and A components present in a pixel. In other words,
feComponentTransfer allows the independent manipulation of each color channel, as well as the alpha channel, in the input element.
The RGBA components are modified by running different kinds of functions on these components. To do that, each component has its own element. These component elements are nested within
feComponentTransfer. The RGBA component elements are:
type attribute is used on a component element to define the type of function you want to use to modify this component. There are currently five available function types:
gamma. These function types are used to modify the RGBA components (the colors and alpha channel) of a source graphic. We mentioned that you can modify one or more component at a time and that you can modify channels independently, applying a different function to each component element.
<feComponentTransfer> <!-- The RED component --> <feFuncR type="identity | table | discrete | linear | gamma"></feFuncR> <!-- The GREEN component --> <feFuncG type="identity | table | discrete | linear | gamma"></feFuncG> <!-- The BLUE component --> <feFuncB type="identity | table | discrete | linear | gamma"></feFuncB> <!-- The ALPHA component --> <feFuncA type="identity | table | discrete | linear | gamma"></feFuncA> </feComponentTransfer>">
In the previous article, we demystified the
discrete function and saw how it can be used to posterize images. In this article, we will start by using the
table function to create a duotone effect similar to what you can create in Photoshop.
Creating a Duotone Effect in Photoshop
I’m not a designer, and I don’t know my way around graphics editors like Photoshop. When I wanted to create a duotone effect in SVG, I looked for the way to create this effect in graphics editor first to see if I can replicate it using the filter operations available in SVG. As it turns out, the steps to create duotone images in SVG turned out to be same as those used in Photoshop.
The following video is a sped-up version of this tutorial I found on YouTube.
In the video, the designer creates the duotone effect following these steps:
- Desaturate the image, making it grayscale.
- Map the grayscale range into a new range that, instead of having black and white on either end, it has two different colors that you want to use in the duotone effect. In other words, you will need to create and use a gradient map that the grayscale is mapped to.
Let’s see how these steps can be replicated in SVG.
Creating a Duotone Effect in SVG
To recreate this effect in SVG, we will need to desaturate the image first. This is possible using the
<feColorMatrix> filter primitive.
Then, we need to be able to create and provide a gradient map for the browser to map the new grayscale image to.
Converting an image to grayscale using
feColorMatrix you can provide a color matrix that specifies the amount of red, green, and blue in your image. By providing equal amounts of these three components, we are creating a matrix that converts our image into a grayscale version of itself:
<svg viewBox="0 0 266 400"> <filter id="duotone"> <feColorMatrix type="matrix" values=".33 .33 .33 0 0 .33 .33 .33 0 0 .33 .33 .33 0 0 0 0 0 1 0"> </feColorMatrix> <!-- ... --> </filter> <image xlink:href="..." width="100%" x="0" y="0" height="100%" filter="url(#duotone)"></image> </svg>
In the following image, the image on the right is the result of applying the above filter to the image on the left:
You can learn all about
feColorMatrix and how to use it in this article by Una Kravets.
Now that our image is essentially made of a gray gradient, we need to create a duotone gradient map to map the gray gradient to.
Creating a Gradient Map using the
table component transfer function
In SVG, to create the gradient map, we can use
feComponentTransfer primitive with the type
In the previous article, we saw how you can map the colors in an image to a list of colors that you provide in the
tableValues attribute using the
discrete function. The browser used our list of tableValues to generate ranges that are then used to map the colors to the values we provided.
When using the
table function, we will also provide color values in the
tableValues attribute. Once again, the browser will use the values we provide to map the colors in the image to them. How the browser will map the colors, though, is different. Instead of mapping color ranges to discrete color values, it will create a color range from the values we provide and then map the input range to this new range.
Suppose we want to use the following two colors for our duotone effect:
These two colors will be used to create a gradient map:
..that we are going to map our grayscale map to.
In order to use these colors in
feComponentTransfer, we need to get the values of the R, G, and B channels of each color. Since
tableValues are provided in fractions, we’ll need to convert the RGB values to fractions. Color values are usually in the range [0, 255]. To convert these values to fractions, we need to divide them by 255.
For example, the pink color has the following RGB values:
R: 254 G: 32 B: 141
Converted to fractions, these values are now equal to:
R: 254/255 = .996078431 G: 32/255 = .125490196 B: 141/255 = .552941176
Similarly, the yellow color values resolve to:
R: .984313725 G: .941176471 B: .478431373
Now that we have our color values handy, it’s time to create our gradient map. We mentioned earlier that when we provide values to
tableValues with the
table function in use, the browser will use the tableValues to create a range. So we start by providing the RGB values of the two colors as values for the RGB component elements:
<feComponentTransfer color-interpolation-filters="sRGB"> <feFuncR type="table" tableValues=".996078431 .984313725"></feFuncR> <feFuncG type="table" tableValues=".125490196 .941176471"></feFuncG> <feFuncB type="table" tableValues=".552941176 .478431373"></feFuncB> </feComponentTransfer>
We saw in the previous article that when using the
discrete function, the browser creates n ranges for n values in
tableValues. When we use the
table function, the browser creates n-1 ranges for n values; and since we provided two tableValues for each component, this means that we will get one range ([pink, yellow]) for each.
feComponentTransfer will do its thing: The browser will go over each and every pixel in the source image. For each pixel, it will get the value of the Red, Green, and Blue components. Since our image is grayscale, the R/G/B values will be in the range [0, 1] = [black, white] (0 being fully black, 1 being fully white, and shades of gray in between). Then, the value of each component will be mapped to the new range we provided in
- The red component value will be mapped to the range [.996078431, .984313725]
- The blue component value will be mapped to the range [.125490196, .941176471]
- The green component value will be mapped to the range [.552941176, .478431373]
So by the time the browser goes over all the pixels in the image, you will have replaced all RGB values in the grayscale gradient with the RGB values of the duotone gradient map. As a result, the image becomes duotone.
Our full code now looks like this:
<svg viewBox="0 0 266 400"> <filter id="duotone"> <!-- Grab the SourceGraphic (implicit) and convert it to grayscale --> <feColorMatrix type="matrix" values=".33 .33 .33 0 0 .33 .33 .33 0 0 .33 .33 .33 0 0 0 0 0 1 0"> </feColorMatrix> <!-- Map the grayscale result to the gradient map provided in tableValues --> <feComponentTransfer color-interpolation-filters="sRGB"> <feFuncR type="table" tableValues=".996078431 .984313725"></feFuncR> <feFuncG type="table" tableValues=".125490196 .941176471"></feFuncG> <feFuncB type="table" tableValues=".552941176 .478431373"></feFuncB> </feComponentTransfer> </filter> <image xlink:href=".." width="100%" x="0" y="0" height="100%" filter="url(#duotone)"></image> </svg>
And you can play with the live demo here:
You can take this further and instead of providing only two color values for the gradient map you can provide three color values in
tableValues, creating a gradient map that has three colors instead of two.
Controling color contrast and intensity with the
gamma transfer function
gamma component transfer function we are able to perform gamma correction on our source graphic. Gamma correction is the function of controlling an image’s luminance levels.
gamma function has three attributes that allow you to control the gamma correction function that will be used to control the luminance:
offset. Combined, they make up the following transfer function:
C' = amplitude * pow(C, exponent) + offset
gamma can be used to control the overall contrast in an image. Increasing the exponent makes the darker areas darker while increasing the amplitude makes the lighter areas shine more. And this, in turn, increases the overall contrast of the image. The offset is used to increase the intensity of each component, and also affects the overall image: both highlights and dark areas.
Tweaking the contrast and dark and light areas of an image can sometimes be useful if you’re not getting the amount of “shine” that you’d like to see in an image.
For example, if I apply the duotone filter from the previous section to the following image, the result is not as “lively” as I’d want it to be:
The duotone image on the right looks a little pale and the colors slightly washed out. I want to add some contrast to it to make it look more lively. By increasing the
amplitude and the
exponent a little bit:
<feComponentTransfer color-interpolation-filters="sRGB"> <feFuncR type="gamma" exponent="1.5" amplitude="1.3" offset="0"></feFuncR> <feFuncG type="gamma" exponent="1.5" amplitude="1.3" offset="0"></feFuncG> <feFuncB type="gamma" exponent="1.5" amplitude="1.3" offset="0"></feFuncB> </feComponentTransfer>
I am able to make the light areas shine more and the darker areas look more intense:
Of course, this is just an example. You may prefer the paler version of the image, especially that it might look better with text on top. I think gamma correction is most useful in controlling the contrast of black-and-white images. If I apply the same gamma correction operation to the grayscale version of the image, I get a more favorable version:
Of course, you may want to do the opposite: instead of increasing contrast, you may want to lighten the dark areas a bit, in which case you’d decrease the
exponent values instead of increasing them. The default value for both the
exponent is 1. The default
offset value is 0.
Play with the
gamma function values in the following live demo to get a better feel of how it affects the brightness and contrast of an image:
The SVG Gradient Map Tool
Yoksel has been playing with SVG Filters for a while and has recently created a fantastic visual tool that allows you to upload an image and apply different duotone and even tritone effects, and that generates the SVG filter code for you ready to copy-paste anywhere you need it. It is a great tool to play with to learn more about
The tool even allows you to tweak the grayscale effect created using
feColorMatrix. In our code, we used equal amounts of the R, G, and B channels to get a grayscale effect. This is one way to make an image grayscale. But there are other ways, too. For example, you could create a grayscale effect per channel, which would result in a different grayscale result for each:
I recommend playing with the tool a little bit and checking how your choice of effect changes the underlying code, as this is one of the best ways to learn more about SVG filters.
feComponentTransfer primitive gives us a lot of control over the color and alpha components of images and enables us to create Photoshop-grade effects in the comfort of our code editors.
In the next article in this series, we will look at a couple more primitives that allow us to replicate yet another Photoshop effect using almost exactly the same steps as those you’d take in Photoshop, showing once again how SVG can bring the power of graphic editors into the Web platform. We’ll learn how to blend text with both the color and the texture of a background image, to create some eye-catching results. Stay tuned.