I’m sure you’ve used your graphic editor to add an emboss or a bevel to some graphic element. If so you’ll probably like working with feConvolveMatrix, which can be used to do both.
For a couple of months, I’ve been walking through different filter primitives. I began with feColorMatrix and feComponentTransfer for working with color and then moved on to lighting effects with feDiffuseLighting and feSpecularLighting. The last couple of weeks I’ve talked about feMorphology, feTurbulence, and feDisplacementMap, which were as easily categorized.
Today marks the end of our filter primitive walkthrough with a post on the feConvolveMatrix primitive.
The feConvolveMatrix Filter Primitive
The feConvolveMatrix filter primitive alters pixels in an input image by combining them with neighboring pixels. This altering of pixels is referred to as a convolution and some of the effects you can achieve through the feConvolveMatrix primitive include blurring, edge detection, sharpening, embossing and beveling.
A matrix convolution is based on an n-by-m matrix called the convolution kernel and it uses the following formula to alter the pixels of the image.
[code type=html]
COLOR X,Y = (
SUM I=0 to [orderY-1] {
SUM J=0 to [orderX-1] {
SOURCE X-targetX+J, Y-targetY+I * kernelMatrixorderX-J-1, orderY-I-1
}
}
) / divisor + bias * ALPHAX,Y
[/code]
Don’t worry if the formula looks confusing. You won’t work directly with it. I’m presenting it more for the sake of completeness and curiosity than anything else. Here’s how it works.
Say we’re altering the pixel P (in the center) using its neighboring pixels A-H (the pixels surrounding P).
[code type=html]
A B C
D P E
F G H
[/code]
We then define a convolution matrix as:
[code type=html]
1 2 3
4 5 6
7 8 9
[/code]
The new value for pixel P would be:
[code type=html]
p’ = ( (9*A) + (8*B) + (7*C) + (6*D) + (5*P) + (4*E) + (3*F) + (2*G) + (1*H) ) / (1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9)
[/code]
Note: I’ve seen articles that show the reverse of this calculation where it’s 1*A + 2*B and so on instead of what I’ve shown above. I’m not sure which is right and decided to go with what’s listed in the spec. In practical use it shouldn’t matter as you won’t be performing the calculation.
Attributes of the feConvolveMatrix Filter Primitive
What does matter are the attributes that feConvolveMatrix accepts and there are quite a few, more than many of the other primitives. Bear with me as I walk through each of them.
- order = "<number-optional-number>”—This sets the size of the kernelMatrix. By default it’s 3×3, but you can change that using this attribute. The first number is orderX and the second, if included is orderY. Just remember if you set the order to something other than 3, you’ll need to adjust the how many values are in the kernelMatrix to match.
- kernelMatrix = “<list of numbers>”—The list of numbers that make up the kernel matrix for the convolution. Values are separated by space characters and/or a comma. The number of entries in the list must equal <orderX> by <orderY>.
- divisor = “<number>”—By default the divisor will be the sum of all the values in the kernelMatrix (1 if the sum of the values is 0), but you can change this. Using the default leads to a smoothing effect of the overall color intensity.
- bias = “<number>”—This shifts the range of the filter. The default is 0.
- targetX = “<integer>”—Determines the positioning in X of the convolution matrix relative to a given target pixel in the input image. This shifts the convolution matrix horizontally. targetX must be greater than 0 (the leftmost column) and less than orderX (one greater than the rightmost column).
- targetY = “<integer>”—Determines the positioning in Y of the convolution matrix relative to a given target pixel in the input image. This shifts the convolution matrix vertically. targetY must be greater than 0 (the top row) and less than orderY (one greater than the bottom row).
- edgeMode = “duplicate | wrap | none”—Determines how to extend the input image as necessary with color values so that the matrix operations can be applied when the kernel is positioned at or near the edge of the input image.
- duplicate – (default )indicates that the input image is extended along each of its borders as necessary by duplicating the color values at the given edge of the input image.
- wrap – indicates that the input image is extended by taking the color values from the opposite edge of the image.
- none – indicates that the input image is extended with pixel values of zero for R, G, B and A.
- kernelUnitLength = “<number-optional-number>”—Indicates the intended distance in current filter units between successive columns and rows in the kernelMatrix. It allows the kernel to become defined in a scalable, abstract coordinate system. The default is 1 and 0 is not permitted as a value.
- preserveAlpha = “false | true”—The default is false and it indicates that the convolution will apply to all channels, including the alpha channel. If true it indicates that the convolution will only apply to the color channels.
At this point you might be thinking this primitive is going to be a pain in the you know what to use. It’s actually easier than it looks, since many of the attributes you won’t bother setting.
Emboss and Bevel Examples
In this example I set a couple of lines of SVG text. The word on top is unfiltered and the word on the bottom is run through the feConvolveMatrix filter. The only attribute I set is the kernelMatrix. Since I went with the default I needed a 3×3 matrix or 9 values.
[code type=html]
<svg width="100%" height="220" style="outline: 1px solid red">
<defs>
<filter id="convolve">
<feConvolveMatrix kernelMatrix="1 0 0 0 0 0 0 0 -1" />
</filter>
</defs>
<g font-size="3em">
<text x="225" y="75"">Convolve</text>
<text x="225" y="150" filter="url(#convolve)">Convolve</text>
</g>
</svg>
[/code]
Here’s the result. This kernelMatrix produced an embossing effect.
If you’re wondering how I knew the kernelMatrix I used would lead to an embossing effect. I didn’t. I found the kernalMatrix in an example online and used the same values in my example.
Here’s another example and once again I’m using a convolution matrix I found elsewhere. This time the matrix is 8×8 so the order attribute is set to 8,8. I also set the divisor to 4 to smoother out the result a little. The matrix values are the long string you see, which again are values I found and copied.
In order to show the original text on top of the result from the feConvolveMatrix filter, I used feMerge. I placed the SourceGraphic second so it appears on top. Like the previous example I also created a word of unfiltered text for comparison.
[code type=html]
<svg width="100%" height="220" style="outline: 1px solid red">
<defs>
<filter id="convolve2">
<feConvolveMatrix order="8,8" divisor="4" kernelMatrix="1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 " in="SourceAlpha" result="bevel"></feConvolveMatrix>
<feMerge>
<feMergeNode in="bevel"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
<g font-size="3em">
<text x="225" y="75"">Convolve</text>
<text x="225" y="150" fill="red" filter="url(#convolve2)">Convolve</text>
</g>
</svg>
[/code]
Here’s the result, which has a somewhat beveled look to it.
Victor Powell built a demo that creates kernelMatrix values based on a few different effects. You can see Victor’s values and use them to experiment with your own. Beyond Victor’s demo, I searched Google for feConvolveMatrix (or convolution matrix or kernel) and various effects to see what I could find. There’s plenty out there if you want more.
Closing Thoughts
That brings me to the end of the filter primitives. As I worked my way through the spec and some additional articles to figure out how all the filter primitives worked, I found some to be much easier to understand than others and I suspect the same happened as you read through what I’ve written.
Some trial and error helps. Take my examples, find some others online, and experiment. Change the value of a single attribute until you have a better feel for how those changes impact the final image.
The main thing to take away from this series is that SVG filters can do a lot of things you might think are only possible using an image editor like Photoshop.
I may be finished with the primitives, but I’m not yet done with this series. I mentioned at the very start of the earlier series that you can add filters using CSS. They don’t have the same level of support as SVG, but I imagine they will in the future. Next week I’ll talk about filters in CSS, which are generally easier to work with, but offer less control over what you can do.
Download a free sample from my book, Design Fundamentals.