Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Principles of 10 Basic Canvas Filters

Tech 1

While learning Canvas, you'll likely encounter filters, which are quite fascinating. Developing Canvas filters requires a foundational understanding of geometry, mathematics, and color theory. However, don't close this page just yet—if you lack the basics, you can always copy and paste the code (much like dealing with regular expressions). I've tried writing and collecting various simple filters. Since it's been a while, I've lost the original sources. If I've offended any original authors, please contact me to make corrections.

The cat image used in this article comes from The Cat API.

Development Environment

The examples listed in this article are written in .html files. Note that although the methods used are native Canvas pixel manipulation APIs, they must be run on a local server to work properly, otherwise they will not take effect due to CORS restrictions.

You can set up a local server in various ways. For instance, projects scaffolded with Vue or React will work out of the box, or you can use http-server. I use a simpler tool: the VS Code Live Server extension, which starts a local service with hot-reloading capabilities.

Setting up the environment is not the focus of this article, so I'll leave it at that.

Filter Principles

As is well known, a bitmap is composed of pixels, which are the smallest units of information in a bitmap. You can understand most everyday images as graphics made up of tiny dots.

Filters work by using specific rules to modify the pixels in an image according to a calculated formula and then re-rendering them.

For instance, inverting the colors of a black cat photo turns originally white pixels black and originally black pixels white. This color inversion is the specific rule.

Filters essentially manipulate pixels. In Canvas, there are 3 commonly used APIs for pixel manipulation:

  • getImageData()
  • putImageData()
  • createImageData()

getImageData()

The getImageData() method retrieves image data, such as the pixel set composing the image and its dimensions.

Syntax:

javascript context.getImageData(x, y, width, height)

  • x: The x-coordinate of the top-left corner to start copying.
  • y: The y-coordinate of the top-left corner to start copying.
  • width: The width of the rectangular area to copy.
  • height: The height of the rectangular area to copy.

For example, using a cat image rendered on a Canvas, we can use getImageData() to retrieve its data.

javascript

Looking at the printed information:

  • data: The pixel data set, stored as an array. This is the key focus of this article!
  • colorSpace: The color standard used by the image (browser dependent).
  • height: The image height.
  • width: The image width.

The data property is a one-dimensional array where values range from 0 to 255. It records the rgba values for each pixel:

  • r: Red
  • g: Green
  • b: Blue
  • a: Alpha (Opacity)

In the data array, 4 elements represent 1 pixel (r, g, b, a). Each element is an integer between 0 and 255.

javascript data: [r1, g1, b1, a1, r2, g2, b2, a2, ...]

Pixel Value Color Channel
imageData.data[0] 49 Red (r)
imageData.data[1] 47 Green (g)
imageData.data[2] 51 Blue (b)
imageData.data[3] 255 Alpha (a)
... ... ...
imageData.data[n-4] 206 Red (r)
imageData.data[n-3] 200 Green (g)
imageData.data[n-2] 200 Blue (b)
imageData.data[n-1] 255 Alpha (a)

If an image has only 10 pixels, its data array will contain 40 elements.

Therefore, developing Canvas filters means manipulating pixels, which means manipulating the data array. For example, to set an image's opacity to 50%, you iterate through data, change the value of every 4th element (where (index + 1) % 4 === 0) to 128, and then use putImageData() to render the modified data back to the canvas.

putImageData()

The putImageData() method draws the data from an ImageData object onto the canvas.

Syntax:

javascript context.putImageData(imageData, x, y) // or context.putImageData(imageData, x, y, dirtyX, dirtyY, dirtyWidth, dirtyHeight)

  • imageData: The ImageData object to place back on the canvas.
  • x: The x-coordinate of the top-left corner of the ImageData object.
  • y: The y-coordinate of the top-left corner of the ImageData object.
  • dirtyX, dirtyY, dirtyWidth, dirtyHeight: Optional parameters defining the dirty rectangle.

javascript

putImageData() is typically used in conjunction with getImageData().

createImageData()

The createImageData() method creates a new, blank ImageData object.

Syntax:

javascript // Creates a new ImageData object with the specified dimensions context.createImageData(width, height) // Creates a new ImageData object with the same dimensions as another ImageData object context.createImageData(imageData)


The APIs above might seem abstract on their own, so let's look at some practical examples, starting with the simplest ones.


Invert Colors

A pixel consists of 4 elements (rgba). For an invert effect, you don't need to modify a (opacity). If rgb are all 255, the color is white; if all 0, it's black. The principle of color inversion is subtracting the original value from 255 for each of the red, green, and blue channels.

For instance, the inverse of rgb(10, 200, 100) is rgb(245, 55, 155).

javascript

By only operating on indices idx, idx + 1, and idx + 2, we modify the rgb channels without affecting the alpha channel, preserving the original opacity.

Grayscale

Using a weighted average approach can produce a visually pleasing grayscale photo.

javascript

Black and White

Sum the rgb values of each pixel and divide by 3. Check if the result is greater than 128; if so, set it to white (255), otherwise set it to black (0). Since the channel range is 0-255, the midpoint 128 serves as the threshold.

javascript

Brightness Adjustment

To brighten an image, increase the rgb values; to darken it, decrease them.

Brighten

javascript

Darken

javascript

Adjust RGB Channels

Similar to the previous example, define an adjustment parameter. If you only want to adjust the red channel, add or subtract this parameter from the red channel values.

javascript

Adjust Opacity

With the previous examples, adjusting opacity is straightforward. Simply subtract an adjustment value from the a (alpha) channel.

javascript

RGB Mask

The logic of a mask is somewhat similar to adjusting RGB channels. The formula used here for a red mask is: find the average of the rgb channels, assign this average to the red channel, and set the green and blue channels to 0.

javascript

Vintage Photo Filter

A vintage photo effect is essentially a grayscale image with a yellowish tint (Red + Green = Yellow). By adjusting the weights, we get the following algorithm:

javascript

Blur

The method to blur an image involves setting the current pixel's value to the average of its surrounding 8 pixels (fewer if the pixel is on the edge). Assigning this average creates a smooth transition color between adjacent pixels. The underlying principle is complex, so I will cover it in a separate article.

javascript

Mosaic

The principle of a mosaic effect is: define a block size parameter (the larger it is, the bigger the mosaic tiles). For the current pixel, find its surrounding pixels within the block, calculate their average color value, and then apply this average color to all pixels within that block. The underlying principle is also quite complex, so I will write another article specifically about it.

javascript

Summary

Developing Canvas filters is essentially about defining filter rules and deriving algorithms to manipulate pixels. In the examples above, the blur and mosaic effects involve many nested loops. Processing large images might cause the application to lag. To resolve this, you can either optimize the algorithm to reduce traversals or offload image processing tasks to Web Workers. Since optimizing algorithms can be quite challenging, I prefer using Web Workers, which I will cover in a future article.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.