Modify The Color Values Of An Image With The feColorMatrix Filter Primitive

Sometimes you can completely change the emotional impact of an image through color changes alone. You can make the subject pop or you can change a cool image into a warmer one. SVG provides a couple of primitives that allow for the non-destructive modifications of color in any image or graphic.

Before the holidays I was talking about the details of SVG filter primitives as part of a larger series about SVG filter effects. I began with an overview and then walked through the filter element before talking about filter primitives and their input and output. I covered some simple filter primitives, some for working with external images, and different ways to combine the results of multiple primitives using feMerge, feComposite, or feBlend.

Today I want to pick up where I left off and continue talking about different filter primitives. Earlier in the series I showed you the feFlood filter primitive, which lets you set a fill color over the filter primitive subregion. There are two more primitives that deal with color that I’d like to cover next. Today I’ll talk about feColorMatrix and next week I’ll cover feComponentTransfer, both of which allow you to make more changes to different aspects of color.

I’m going to assume you’ve read the earlier series or have worked with SVG enough to understand the basics of working with filters and filter primitives. If you find what’s here a little confusing or would like a review, check the links a couple paragraphs back.

The feColorMatrix Filter Primitive

The feColorMatrix filter primitive offers a general way to change color values. It can be used to modify the color or alpha values of every pixel in the image or graphic being transformed.

As you might guess from the name, this primitive applies a matrix transformation to an image to adjust different aspects of color. If the thought of working with matrices scares you, join the club. I admit it’s easy to look at the following matrix math from the spec and think you’re never going to use this filter.

1
2
3
4
5
| R' |   | a00 a01 a02 a03 a04 |   | R |
| G' |   | a10 a11 a12 a13 a14 |   | G |
| B' | = | a20 a21 a22 a23 a24 | * | B |
| A' |   | a30 a31 a32 a33 a34 |   | A |
| 1  |   | 0   0   0   0   1   |   | 1 |

Don’t run away. I promise it’s not as complicated as it looks and I’ll do my best to make it understandable with an example. For the moment just note the values represent the RGBA channels of color and the last column and row are multipliers.

Here’s an example where I used an <image> element to display the image of the Strawberry Fields Memorial in Central Park that I used earlier in the series. I set a width a height on the image and referenced a filter which includes only the feColorMatrix primitive inside.

You’ll notice there are two attributes, type and values (plural). The type is matrix and the values are shown as a matrix of values.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<svg width="100%" height="495" style="outline: 1px solid red">
 <defs>
   <filter id="color"> 
     <feColorMatrix
       type="matrix"
       values="1 0 0 0 0
               0 1 0 0 0
               0 0 1 0 0
               0 0 0 1 0"/>
   </filter>
 </defs>

 <image xlink:href="http://www.vanseodesign.com/blog/wp-content/uploads/2013/09/strawberry-fields.jpg" width="660" height="495" filter=url(#color) />
</svg>

The specific values I’ve chosen don’t alter the image at all so the result below displays an unfiltered image despite the filter. I wanted you to see these “unfiltered” values first. I’ll change them momentarily so you can see the effects, but first I want to very briefly talk about matrix math.

Don’t worry if you’ve never done any matrix math or have forgotten everything you learned about it. Here’s how it works. To the left of the arrows are a row of values from the feColorMatrix primitive and to the right of the arrow is how the row is used to multiply each of the RGBA components.

1
2
3
4
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

To find the final value for R you multiply the first value in the first row of the matrix (1) by the value of R in the image. Then you add the next value in the row (0) multiplied by the value of G in the image and so on.

You do the same for the second row to find the final value for G, the third row for B, and the fourth row for A. I don’t know why the spec shows a fifth row for the multiplier. Maybe for completeness.

Here’s the general form of the matrix using the color component associated with each value. To the right of the arrow you can see which component each row is used to calculate.

1
2
3
4
R G B A M --> R
R G B A M --> G
R G B A M --> B
R G B A M --> A

The reason the matrix from the previous results in an “unfiltered” image is because each row multiplies only the resulting color channel by 1 and the other color channels by 0, effectively removing them from the equation.

Let’s say we wanted to use the filter to remove the red and blue from the image. We can do that with the following matrix.

1
2
3
4
0 0 0 0 0
0 1 0 0 0 
0 0 0 0 0
0 0 0 1 0

The red and blue channels drop out because all of the values in their rows are 0. Here’s how the previous example looks using these values for the matrix.

Removing the red and blue channels adds a green cast to the image. Another way to add a green cast is to increase the values in the green row. For example this next matrix of values increases the multiplier in the green row to 0.5, even as it leaves the red and blue channels “unfiltered.”

1
2
3
4
1 0 0 0 0
0 1 0 0 0.5 
0 0 1 0 0
0 0 0 1 0

The result again adds a green cast to the image, though not quite the same one as the previous example.

Of course you can adjust any of the values in the matrix and achieve different results. Here’s a more subtle change using the matrix.

1
2
3
4
1     0.75  0     0  0
0     1     0.75  0  0 
0.75  0     1     0  0
0     0     0     1  0

I added a little (0.75) more blue to the red and green channels and I added a little more red to the blue channel. The result changes the red and yellow flowers in the original image to purple and pinky peach followers.

It took me a little while to really understand how the matrix transformation works and I found that experimenting with different values proved helped the most.

Take my example and play around with it. Replace my image with one of your own and adjust the values in each row of the matrix one at a time to see what happens and to gain a feel for what will happen when you increase or decrease the numbers. Then change the values in another row.

Also take a look at this article by Una Cravats from A List Apart. Una presents a lot more examples than I have here and her article is the one that helped me figure out how these matrices work.

The type Attribute

I mentioned that matrix is only one possible value for the type attribute. The others are saturate, hueRotate, and luminanceToAlpha.

1
type = "matrix | saturate | hueRotate | luminanceToAlpha"

While these other types do use matrices behind the scenes to make changes, you don’t need to work with the matrix. Instead these three types work as shortcuts that alter the matrix that does the work. If you’d like to see the matrices that perform the operations you can have a look in the spec.

type=“saturate”

The value for saturate is a number between 0.0 and 1.0 with a higher number leading to a greater saturation. The default, if not set, is 1.0 or full saturation.

Here’s an example that uses saturate as the type. I set the value of the values attribute (note it’s still plural) to 0.25.

1
2
3
4
5
6
7
8
9
<svg width="100%" height="495" style="outline: 1px solid red">
 <defs>
   <filter id="saturate" filterUnits="objectBoundingBox" x="0%" y="0%" width="100%" height="100%"> 
     <feColorMatrix type="saturate" values="0.25"/>
   </filter>
 </defs>

 <image xlink:href="http://www.vanseodesign.com/blog/wp-content/uploads/2013/09/strawberry-fields.jpg" width="660" height="495" filter="url(#saturate)" />
</svg>

The result is a washed out or desaturated version of the image.

type=“hueRotate”

The type hueRotate will change the hue by the amount you set. The values will be a number of degrees, though you don’t need to include the units. The default is 0 or no change.

Here I set type to hueRotate and values to 225.

1
2
3
4
5
6
7
8
9
<svg width="100%" height="495" style="outline: 1px solid red">
 <defs>
   <filter id="hue" filterUnits="objectBoundingBox" x="0%" y="0%" width="100%" height="100%"> 
     <feColorMatrix type="hueRotate" values="225"/>
   </filter>
 </defs>

 <image xlink:href="http://www.vanseodesign.com/blog/wp-content/uploads/2013/09/strawberry-fields.jpg" width="660" height="495" filter="url(#hue)" />
</svg>

Negative numbers are fine by the way and instead of 225 I could have used –135 and achieved the same result. Notice the grays haven’t changed, but the yellows and reds have become shades of purple and blue.

type=“luminanceToAlpha”

The final type, luminanceToAlpha, takes no value. It creates an alpha channel based on the luminance (brightness, lightness) of the color.

1
2
3
4
5
6
7
8
9
<svg width="100%" height="495" style="outline: 1px solid red">
 <defs>
   <filter id="luminance" filterUnits="objectBoundingBox" x="0%" y="0%" width="100%" height="100%"> 
     <feColorMatrix type="luminanceToAlpha" />
   </filter>
 </defs>

 <image xlink:href="http://www.vanseodesign.com/blog/wp-content/uploads/2013/09/strawberry-fields.jpg" width="660" height="495" filter="url(#luminance)" />
</svg>

Compare the result of the luminanceToAlpha type with the original image. Notice that where the original image is darker the result of the luminanceToAlpha conversion is lighter and vice versa. That’s probably the opposite of what you might have expected. I know it was for me.

What’s actually happening is the lighter the color the less transparency is given to the filtered image. It kind of looks like a film negative.

Closing Thoughts

The feColorMatrix primitive offers a number of ways to modify the color of an image. You can work with the matrix itself for the most control or you can choose one of the other types to adjust the hue or saturation or to produce an alpha channel based on the luminance of the image.

You may have noticed there wasn’t a type to adjust the third fundamental of color, lightness. Next week I’ll talk about feComponentTransfer, which provides additional ways to modify the color of an image, including its lightness.

Download a free sample from my book, Design Fundamentals.

Leave a Reply

Your email address will not be published. Required fields are marked *