Back

Images & Pixels

So far you've drawn everything from scratch — rectangles, arcs, paths. But canvas can also draw existing images, and more importantly, it lets you read and write individual pixels. This is where things get powerful: filters, effects, procedural art, and understanding what colour actually looks like as raw data.

Drawing images

ctx.drawImage(source, x, y) draws an image onto the canvas. The source can be an <img> element, another <canvas>, or even a video frame. Here we'll create a source image using an offscreen canvas — this keeps everything self-contained and avoids loading external files.

JavaScript

drawImage also works with new Image() for loading URLs — you'd set img.src and wait for onload to fire. Using an offscreen canvas as the source is handy for dynamic textures, thumbnail generation, or compositing multiple layers.

Scaling and cropping

The 3-argument version draws at original size. Add width and height for scaling: drawImage(src, x, y, w, h). For cropping, use the full 9-argument version: drawImage(src, sx, sy, sw, sh, dx, dy, dw, dh) — the first four define a rectangle in the source, the last four define where it goes on canvas.

JavaScript

The 9-arg form is how spritesheets work in games: one big image contains many sprites, and you crop out the one you need each frame. Try cropping different cells from the grid, or scaling the original up to 200%.

Reading pixel data

ctx.getImageData(x, y, w, h) returns an ImageData object with a .data property — a flat Uint8ClampedArray of RGBA values. Every pixel is 4 consecutive numbers: red, green, blue, alpha, each 0–255. To find pixel (x, y) in the data:

JavaScript

The data array is huge — a 400×100 canvas has 160,000 bytes (40,000 pixels × 4 channels). That's fine, JavaScript handles it easily. The Uint8ClampedArray type means values are automatically clamped to 0–255 — no need to bounds-check yourself.

Pixel manipulation — filters

Once you have the pixel data, you can modify it and write it back with ctx.putImageData(imageData, x, y). This is how image filters work at the lowest level: loop through every pixel, do some maths, put it back.

JavaScript

Grayscale averages the three channels. Invert subtracts each from 255. Try a brightness filter: multiply each channel by 1.5 (brighter) or 0.5 (darker). What about a sepia tone? Set R to avg * 1.2, G to avg * 1.0, B to avg * 0.8.

Creating pixels from scratch

You don't have to read existing pixels — ctx.createImageData(w, h) gives you a blank ImageData you can fill in yourself. This means you can build images entirely from code, pixel by pixel. Here's a plasma pattern generated with nothing but Math.sin.

JavaScript

Every pixel is computed from maths — no images, no drawing commands. Change the frequency multipliers (0.03, 0.04, etc.) to morph the pattern. Try replacing Math.sin with Math.cos in one of the waves, or add Math.random() * 0.3 for noise. Can you make it animate by adding a time offset to the wave functions inside animate()?

Putting it all together

Let's combine pixel reading with mouse interaction: a colour picker. Move your mouse over the scene and see the exact RGB values of the pixel under the cursor, with a magnified preview.

JavaScript

Move your mouse over the top scene to see the RGB values update live, with a magnified pixel grid on the right. This is exactly how colour picker tools work in image editors — read the pixel under the cursor, display its values. Try adding HSL conversion, or copying the hex code to the clipboard on click.