Canvas Image Filters

A filter changes the colour of a pixel based on a manipulation of the original colour of the pixel. Each pixel is processed independently of all other pixels. Therefore, the new colour of any pixel is only dependent on the original colour of that pixel. Common filters include:

Brightness

To make an image brighter, we need to increase each of the red, green and blue values. We do this by multiplying by a brightness factor, where:

Example of a brightness filter (Run Example)

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Worked example from lecture notes</title>
<style>
img
{
    width:400px;
}

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

<script>
let canvas = null;
let ctx = null;
let width = null;
let height = null;
let originalImage = 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');
    width = originalImage.clientWidth;
    height = originalImage.clientHeight;
    canvas.width = width;
    canvas.height = height;

    renderCanvas();
}


let imageData = null;
let data = null;
let brightnessFactor = 2; /* A higher brightnessFactor value will make the image brighter */
function renderCanvas()
{
    ctx.drawImage(originalImage, 0, 0, canvas.width, canvas.height);
    imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    data = imageData.data;

    // Loop through the pixels, turning them grayscale
    for (let i = 0; i < data.length; i += 4)
    {
        data[i] *= brightnessFactor;
        data[i + 1] *= brightnessFactor;
        data[i + 2] *= brightnessFactor;
    }

    ctx.putImageData(imageData, 0, 0);
}
</script>
</head>

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

Modify the code above so that the user can use a slider to adjust the brightness of an image.

Modify the code above so that we can independently adjust the brightness of the red, green and blue colour.

Greyscale

By setting the red, green and blue of a pixel to be the same value, we produce a greyscale pixel. A good value to set as the greyscale is the average of the red, green and blue values, as shown in the example below.

Example of a greyscale filter (Run Example).

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Worked example from lecture notes</title>
<style>
img
{
    width:400px;
}

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

<script>
let canvas = null;
let ctx = null;
let width = null;
let height = null;
let originalImage = null;
let imageData = null;
let data = 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');
    width = originalImage.clientWidth;
    height = originalImage.clientHeight;
    canvas.width = width;
    canvas.height = height;

    renderCanvas();
}


function renderCanvas()
{
    ctx.drawImage(originalImage, 0, 0, canvas.width, canvas.height);
    imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    data = imageData.data;

    // convert to greyscale
    let brightnessFactor = 1;  // we can add a brightness factor to the greyscale
    // Loop through the pixels, turning them into grayscale
    for (let i = 0; i < data.length; i += 4)
    {
        // get the average value
        grayScale = ((data[i] * brightnessFactor) + (data[i + 1] * brightnessFactor) + (data[i + 2] * brightnessFactor)) / 3;
        // assign the same value to red, green and blue to create grayScale
        data[i] = grayScale;
        data[i + 1] = grayScale;
        data[i + 2] = grayScale;
    }

    ctx.putImageData(imageData, 0, 0);
}
</script>
</head>

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

Modify the code above so that the image is in a blue-scale (Example solution).

The code above uses the average of the red, green and blue values. What other values could be used to produce the greyscale? What would be the effect of using these other values?

Sepia

Sepia is a reddish-brown colour. It is commonly used to make images look older.

Example of a sepia filter (Run example)

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Worked example from lecture notes</title>
<style>
img
{
    width:400px;
}

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

<script>
let canvas = null;
let ctx = null;
let width = null;
let height = null;
let originalImage = null;
let imageData = null;
let data = 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');
    width = originalImage.clientWidth;
    height = originalImage.clientHeight;
    canvas.width = width;
    canvas.height = height;

    renderCanvas();
}


function renderCanvas()
{
    ctx.drawImage(originalImage, 0, 0, canvas.width, canvas.height);
    imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    data = imageData.data;

    // Loop through the pixels, turning them into sepia
    for (let i = 0; i < data.length; i += 4)
    {
        red = data[i];
        green = data[i + 1];
        blue = data[i + 2];

        data[i] = (red * 0.393) + (green * 0.769) + (blue * 0.189);
        data[i + 1] = (red * 0.349) + (green * 0.686) + (blue * 0.168);
        data[i + 2] = (red * 0.272) + (green * 0.534) + (blue * 0.131);
    }

    ctx.putImageData(imageData, 0, 0);
}
</script>
</head>

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

Adjust the scale values in the code above to create other filters.

Invert

Example of an invert filter (Run Example)

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Worked example from lecture notes</title>
<style>
img
{
    width:400px;
}

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

<script>
let canvas = null;
let ctx = null;
let width = null;
let height = null;
let originalImage = null;
let imageData = null;
let data = 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');
    width = originalImage.clientWidth;
    height = originalImage.clientHeight;
    canvas.width = width;
    canvas.height = height;

    renderCanvas();
}


function renderCanvas()
{
    ctx.drawImage(originalImage, 0, 0, canvas.width, canvas.height);
    imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    data = imageData.data;

    // Loop through the pixels, inverting them
    for (let i = 0; i < data.length; i += 4)
    {
        data[i + 0] = 255 - data[i + 0];
        data[i + 1] = 255 - data[i + 1];
        data[i + 2] = 255 - data[i + 2];
        data[i + 3] = 255;
    }

    ctx.putImageData(imageData, 0, 0);
}
</script>
</head>

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

Modify the code above to invert only on red (Example solution).

Modify the code above to invert only on yellow (Example solution).

Posterise

A posterised image only uses some of the available colour set. The example below only uses red, green and blue values have a step size of 64.

Example of a posterise filter (Run Example)

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Worked example from lecture notes</title>
<style>
img
{
    width:400px;
}

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

<script>
let canvas = null;
let ctx = null;
let width = null;
let height = null;
let originalImage = null;
let imageData = null;
let data = 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');
    width = originalImage.clientWidth;
    height = originalImage.clientHeight;
    canvas.width = width;
    canvas.height = height;

    renderCanvas();
}


function renderCanvas()
{
    ctx.drawImage(originalImage, 0, 0, canvas.width, canvas.height);
    imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    data = imageData.data;

    // Loop through the pixels, posterising them 
    for (let i = 0; i < data.length; i += 4)
    {
        data[i + 0] = data[i + 0] - data[i + 0] % 64;
        data[i + 1] = data[i + 1] - data[i + 1] % 64;
        data[i + 2] = data[i + 2] - data[i + 2] % 64;
        data[i + 3] = 255;
    }

    ctx.putImageData(imageData, 0, 0);
}
</script>
</head>

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

Modify the code above so that the posterised colours have a step size of 128.

Threshold

A threshold filter maps each red, green and blue value to be either have zero or full saturation (ie values of 0 or 255).

Example of a threshold lookup filter (Run Example)

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Worked example from lecture notes</title>
<style>
img
{
    width:400px;
}

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

<script>
let canvas = null;
let ctx = null;
let width = null;
let height = null;
let originalImage = null;
let imageData = null;
let data = 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');
    width = originalImage.clientWidth;
    height = originalImage.clientHeight;
    canvas.width = width;
    canvas.height = height;

    renderCanvas();
}


function renderCanvas()
{

    ctx.drawImage(originalImage, 0, 0, canvas.width, canvas.height);
    imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    data = imageData.data;


    // Loop through the pixels, performing a threshold on them
    for (let i = 0; i < data.length; i += 4)
    {
        for (let rgb = 0; rgb < 3; rgb++)
        {
            if (data[i + rgb] < 128)
            {
                data[i + rgb] = 0;
            }
            else
            {
                data[i + rgb] = 255;
            }
        }
        data[i + 3] = 255;
    }

    ctx.putImageData(imageData, 0, 0);
}
</script>
</head>

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

Modify the above code to only have a threshold in blue (Example solution).

 
<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>