Canvas Image Convolutions

A convolution changes the colour of a pixel based on a manipulation of the original colour of the pixel and the original colour of the sourrounding pixels. Therefore, a convolution is dependent on the values of the sourrounding pixels. A 3x3 convolution replaces the value of a pixel with a value that is a weighted average of the original pixel plus the eight sourrounding pixels. A 3x3 convolution matrix (known as a kernal) contains the contribution amounts for the pixel and the eight sourrounding pixels. Four common 3x3 convolution matrices are shown below:

Emboss
[0,  0,  0, 
 0,  2, -1,
 0, -1,  0]
Blur
[1, 2, 1,
 2, 4, 2,
 1, 2, 1]
Sharpen
[ 0, -2,  0,
 -2, 11, -2,
  0, -2,  0]
Edge Detection
[1,  1, 1,
 1, -7, 1,
 1,  1, 1]

What is the matrix for the convolution that does not change the original image?

Example of an emboss convolution (Run Example)
<!DOCTYPE html>
<html>
    <head>
        <title>Course notes example code</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">

        <style>
            img,
            canvas
            {
                width:500px;
                height:500px;
                border:thin solid black;
            }


            #loadingMessage
            {
                position:absolute;
                top:100px;
                left:100px;
                z-index:100;
                font-size:50px;
            }
        </style>

        <script>
            // set up the convolution matrix
            let embossConvolutionMatrix = [0,  0,  0,
                                           0,  2, -1,
                                           0, -1,  0]

            let blurConvolutionMatrix = [1, 2, 1,
                                         2, 4, 2,
                                         1, 2, 1]

            let sharpenConvolutionMatrix = [0,  -2,  0,
                                            -2, 11, -2,
                                            0,  -2,  0]

            let edgeDetectionConvolutionMatrix = [1,  1, 1,
                                                  1, -7, 1,
                                                  1,  1, 1]

            let noConvolutionMatrix = [0, 0, 0,
                                       0, 1, 0,
                                       0, 0, 0]

            let convolutionMatrix = embossConvolutionMatrix /* select which convolution to use */ 
            
            
            
            let canvas = null
            let ctx = null
            let offscreenCanvas = null
            let offscreenCanvasCtx = null
            let originalImage = null
            let imageData = null
            let data = null
            let originalImageData = null
            let originalData = null


            window.onload = onAllAssetsLoaded
            document.write("<div id='loadingMessage'>Loading...</div>")
            function onAllAssetsLoaded()
            {
                // hide the webpage loading message
                document.getElementById('loadingMessage').style.visibility = "hidden"

                originalImage = document.getElementById('originalImage')
                canvas = document.getElementById('canvas')
                ctx = canvas.getContext('2d')
                offscreenCanvas = document.createElement('canvas')
                offscreenCanvasCtx = offscreenCanvas.getContext('2d')
               
                canvas.width = canvas.clientWidth
                canvas.height = canvas.clientHeight
                offscreenCanvas.width = canvas.width
                offscreenCanvas.height = canvas.height

               
                renderCanvas()
            }


            function renderCanvas()
            {
                // draw the original image into the double buffer
                offscreenCanvasCtx.drawImage(originalImage, 0, 0, canvas.width, canvas.height)


                // do the convolution
                let totalConvolutionSum = 0  
                for (let j = 0; j < 9; j++)
                {
                    totalConvolutionSum += convolutionMatrix[j]
                }
                
                // get the image data (i.e. the pixels) from the double buffer
                imageData = offscreenCanvasCtx.getImageData(0, 0, canvas.width, canvas.height)
                data = imageData.data
                
                // keep a copy of the original data, as we need to look at the pixels around the current pixel when doing the convolution
                originalImageData = offscreenCanvasCtx.getImageData(0, 0, canvas.width, canvas.height)
                originalData = originalImageData.data

                for (let i = 0; i < data.length; i += 4)
                {
                    data[ i + 3] = 255 // alpha

                    // apply the convolution for each of red, green and blue
                    for (let rgbOffset = 0; rgbOffset < 3; rgbOffset++)
                    {
                        // get the pixel and its eight sourrounding pixel values from the original image 
                        let convolutionPixels = [originalData[i + rgbOffset - canvas.width * 4 - 4],
                            originalData[i + rgbOffset - canvas.width * 4],
                            originalData[i + rgbOffset - canvas.width * 4 + 4],
                            originalData[i + rgbOffset - 4],
                            originalData[i + rgbOffset],
                            originalData[i + rgbOffset + 4],
                            originalData[i + rgbOffset + canvas.width * 4 - 4],
                            originalData[i + rgbOffset + canvas.width * 4],
                            originalData[i + rgbOffset + canvas.width * 4 + 4]]

                        // do the convolution
                        let convolvedPixel = 0
                        for (let j = 0; j < 9; j++)
                        {
                            convolvedPixel += convolutionPixels[j] * convolutionMatrix[j]
                        }

                        // place the convolved pixel in the double buffer		 
                        if (convolutionMatrix === embossConvolutionMatrix) // embossed is treated differently
                        {
                            data[i + rgbOffset] = convolvedPixel + 127
                        }
                        else
                        {
                            convolvedPixel /= totalConvolutionSum
                            data[i + rgbOffset] = convolvedPixel
                        }
                    }
                } 
                offscreenCanvasCtx.putImageData(imageData, 0, 0)
                
                
                
                ctx.drawImage(offscreenCanvas, 0, 0, canvas.width, canvas.height)
            }
        </script>
    </head>

    <body>
        <img id = 'originalImage' src = 'images/dancing.png'>
        <canvas id = 'canvas'></canvas>
    </body>
</html>

Modify the code above to produce a blurred image.

Modify your code to make the image more blurred.

Modify the code above to produce a sharpened image.

Modify the code above to produce an edge detection image.

Find another 3x3 convolution matrix on the www and modify the code above to implement this.

The emboss in the example above is in the direction of the bottom left corner. Modify the code to show four convolves, each one directed toward a different corner.

Modify the code above to create a convolution that produces a brighter image. Hint: Increasing the value of the convolution matrix centre value increases the weighting of the original pixel on the final pixel's colour.

By changing the various values of the convolution matrix, you can produce other interesting convolutions. Modify the code above to allow a user to modify each of the nine convolution matrix values. Note that it is common, but not necessary,for the sum of the nine matrix values to equal 1. The image should update in real time to reflect any changes in the matrix values.

Convolution matricies can be 5x5 or bigger. What are the advantages of having a bigger convolution matrix? What are the disadvantages of having a bigger convolution matrix?

Modify the code above so that it can take any sized matrix as it input. Using your amended code, implement a 5x5 blur matrix.

 
<div align="center"><a href="../versionC/index.html" title="DKIT Lecture notes homepage for Derek O&#39; Reilly, Dundalk Institute of Technology (DKIT), Dundalk, County Louth, Ireland. Copyright Derek O&#39; Reilly, DKIT." target="_parent" style='font-size:0;color:white;background-color:white'>&nbsp;</a></div>