Many of the SVG filters are already available as canvas filters. The notable exception is the convolution filter. Convolution sounds like a very complex operation, but in fact it is very simple. You simply specify an array of numbers. For example:
Imagine that the pixel of interest is the one in the middle of the array. Now take each of the pixels that surround it and multiply by the corresponding number and replace the pixel by the sum. In other words, the convolution mask given above replaces every pixel in the image by the difference between the pixel to its top left and top right.
As another example consider the mask:
This replaces each pixel by the sum of the pixels surrounding it. You can try this out by defining the following SVG filter:
The order attribute sets the size of the matrix, 3 by 3 in this case. The preserveAlpha attribute when set to "true" removes the alpha channel from the convolution. To keep the values within the normal range the convolution is divided by the sum of the matrix elements. Thus, in this case, the sum is divided by 8 and so the center pixel is replaced by the average of the eight surrounding pixels. You can change the divisor by setting the divisor attribute.
There is a small problem about what to do when processing a pixel right at the edge of the image. The problem is that the pixel will lack some of the neighbors used in the mask. You can set the value of the edge attribute so that the missing values are either treated as 0 (none), assumed to the be same as the values actually on the edge (duplicate) or taken from the edge on the other side of the image (wrap).
You can implement your own convolution filter using direct pixel manipulation and of course you can go beyond what convolution offers.
As a final example, the mask:
kernelMatrix="1 1 1
0 0 0
-1 -1 -1"
replaces every pixel by the average difference between the three pixels above and the three below. It is a horizontal edge finder:
Notice that it only shows horizontal edges that have a positive difference. Negative values are mapped to 0 and values larger than 255 are mapped to 255.
Custom Convolution
You can implement your own convolution in JavaScript. It is slower, but you can customize it more than the SVG convolution filter.
For example, to implement the edge finder filter in the previous section:
async function draw() {
var ctx = document.body.appendChild( createCanvas(400, 400)).
getContext("2d");
var img = new Image();
var url = new URL("jeep.jpg", "http://server/");
img.src = url;
await imgLoaded(img);
ctx.drawImage(img, 0, 0, 400, 300);
var ImDat1 = ctx.getImageData(0, 0, 400, 300);
augmentImageData(ImDat1);
var ImDat2 = ctx.createImageData(400, 300);
augmentImageData(ImDat2);
var mask = [[-1, -1, -1], [0, 0, 0], [1, 1, 1]];
var m = 3;
var n = 3;
for (var x = m; x < 400 - m; x++) {
for (var y = n; y < 300 - n; y++) {
var pixel = {R: 0, G: 0, B: 0, A: 0};
for (var i = 0; i < m; i++) {
for (var j = 0; j < n; j++) {
var c1 = ImDat1.getPixel(x + Math.floor(i-m/2),
y + Math.floor(j - n / 2));
pixel.R += mask[j][i] * c1.R;
pixel.G += mask[j][i] * c1.G;
pixel.B += mask[j][i] * c1.B;
}
}
pixel.A = ImDat1.getPixel(x, y).A;
pixel.R = Math.abs(pixel.R);
pixel.G = Math.abs(pixel.G);
pixel.B = Math.abs(pixel.B);
ImDat2.setPixel(x, y, pixel);
}
}
ctx.putImageData(ImDat2, 0, 0);
}
The first two for loops step through the image file x,y and for each pixel the two inner loops form the sum of the product with each pixel and the mask. Notice that we need two ImageData objects as we cannot modify the image while working on it because the old values are needed after the new values are calculated. When the inner loops finish the mask has been convolved at a single x,y location. We next take the absolute value to convert negative gradients into positive values and store the pixel value in the second ImageData object. When all of the loops complete we show the result. Notice that in this case we avoid processing the edge of the image.
Included in book but not in this extract
Reading a PCX File
Listing - Read a PCX File
Summary
The ImageData object allows direct access to the pixel data within a bitmap.
The pixel data is stored in the data array property which is a Uint8ClampedArray in RGBA order.
With direct access to the pixel data you can write filters which change the bitmap in controlled ways using functions formed from the surrounding pixels.
The new Filter API provides a range of predefined filters that you can use.
You can also use any of the SVG filters via the url filter.
The SVG filters include a general convolution filter which can be used to implement many standard and custom linear filters.
A convolution filter replaces the current pixel value with a weighted average of it and the pixels that surround it. The weighted average is specified as a convolution matrix or mask.
It is also fairly easy to implement a convolution filter directly using ImageData and direct pixel manipulation.
You can also arrange to read and process any graphics file format that you have the specification for using the standard techniques of reading files, bit manipulation and ImageData.
Now available as a paperback or ebook from Amazon.