JavaScript: RGBA pixel arrays and Uint8ClampedArray
Convert pixels stored the way a canvas stores them to grayscale and see how clamping works.
const pixels = new Uint8ClampedArray([
255, 0, 0, 255,
0, 255, 0, 255,
0, 0, 255, 255,
255, 255, 255, 255,
]);
for (let i = 0; i < pixels.length; i += 4) {
const gray = Math.round(0.299 * pixels[i] + 0.587 * pixels[i + 1] + 0.114 * pixels[i + 2]);
pixels[i] = gray;
pixels[i + 1] = gray;
pixels[i + 2] = gray;
}
console.log(pixels.join(","));
const clamped = new Uint8ClampedArray([300, -20, 127.6]);
console.log(clamped.join(","));76,76,76,255,150,150,150,255,29,29,29,255,255,255,255,255 255,0,128
Explanation
In the browser, canvas ImageData stores an image as one flat Uint8ClampedArray with four bytes per pixel in the order red, green, blue, alpha. Pixel number p starts at index 4·p. Image-processing code walks this array in steps of four.
A Uint8ClampedArray forces every stored value into 0 to 255: values above 255 become 255, values below 0 become 0, and fractions are rounded to the nearest integer. That is convenient for image math, because overshoot from a filter cannot wrap around. The program converts four pixels to grayscale with luma weights and shows the clamping.
Explore the dry run
const pixels = new Uint8ClampedArray([ 255, 0, 0, 255, 0, 255, 0, 255, 0, 0, 255, 255, 255, 255, 255, 255,]); for (let i = 0; i < pixels.length; i += 4) { const gray = Math.round(0.299 * pixels[i] + 0.587 * pixels[i + 1] + 0.114 * pixels[i + 2]); pixels[i] = gray; pixels[i + 1] = gray; pixels[i + 2] = gray;}console.log(pixels.join(",")); const clamped = new Uint8ClampedArray([300, -20, 127.6]);console.log(clamped.join(","));Follow the example
Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.
View the complete dry-run table
| Step / line | What happens | State after step | Output so far |
|---|---|---|---|
| Step through the array four bytes (one pixel) at a time. | — | — | |
| Luma weights: the red pixel gives 76, green 150, blue 29, and white 255. | gray = 76, 150, 29, 255 | — | |
| Write the same gray value to red, green, and blue. Alpha stays unchanged. | gray = 76, 150, 29, 255 | — | |
| Four gray pixels, each with alpha 255. | gray = 76, 150, 29, 255 | 76,76,76,255,150,150,150,255,29,29,29,255,255,255,255,255 | |
| 300 becomes 255, −20 becomes 0, and 127.6 rounds to 128. | gray = 76, 150, 29, 255 | 76,76,76,255,150,150,150,255,29,29,29,255,255,255,255,255 255,0,128 |
Cost and common mistakes
O(p) for p pixels.
- A plain Array or Uint8Array does not clamp. In a Uint8Array, 300 wraps around to 44.
- Remember the stride of four. Treating the array as one value per pixel mixes the channels.
- Alpha is separate from color. Premultiplied alpha, used in some canvases, changes the arithmetic.
Try it yourself
At which index does the third pixel start?
Reveal answer and reasoning
At index 8, because pixel number 2 (counting from 0) starts at 4 × 2.