DEV Community

Michael Lip
Michael Lip

Posted on • Originally published at zovo.one

Browser-Based Photo Editing Is Canvas Pixel Manipulation

Every photo editor from Photoshop to Instagram filters does the same thing at the lowest level: read pixel values modify them and write them back. The HTML5 Canvas API gives you direct access to pixel data making browser-based photo editing genuinely powerful.

Reading pixel data

const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const img = new Image();

img.onload = () => {
  canvas.width = img.width;
  canvas.height = img.height;
  ctx.drawImage(img, 0, 0);

  const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
  const pixels = imageData.data;
  // pixels is a Uint8ClampedArray: [R, G, B, A, R, G, B, A, ...]
};
Enter fullscreen mode Exit fullscreen mode

Every four consecutive values represent one pixel: red, green, blue, and alpha (transparency). A 1920x1080 image has 2,073,600 pixels and 8,294,400 values in the array.

Brightness adjustment

function adjustBrightness(imageData, amount) {
  const data = imageData.data;
  for (let i = 0; i < data.length; i += 4) {
    data[i] = Math.min(255, Math.max(0, data[i] + amount));     // R
    data[i+1] = Math.min(255, Math.max(0, data[i+1] + amount)); // G
    data[i+2] = Math.min(255, Math.max(0, data[i+2] + amount)); // B
    // Alpha unchanged
  }
  return imageData;
}
Enter fullscreen mode Exit fullscreen mode

Contrast adjustment

Contrast moves values away from (or toward) the midpoint (128):

function adjustContrast(imageData, factor) {
  // factor > 1 increases contrast, < 1 decreases
  const data = imageData.data;
  for (let i = 0; i < data.length; i += 4) {
    data[i] = Math.min(255, Math.max(0, factor * (data[i] - 128) + 128));
    data[i+1] = Math.min(255, Math.max(0, factor * (data[i+1] - 128) + 128));
    data[i+2] = Math.min(255, Math.max(0, factor * (data[i+2] - 128) + 128));
  }
  return imageData;
}
Enter fullscreen mode Exit fullscreen mode

Grayscale conversion

The perceptually correct grayscale formula weights channels by human eye sensitivity:

function grayscale(imageData) {
  const data = imageData.data;
  for (let i = 0; i < data.length; i += 4) {
    const gray = 0.299 * data[i] + 0.587 * data[i+1] + 0.114 * data[i+2];
    data[i] = data[i+1] = data[i+2] = gray;
  }
  return imageData;
}
Enter fullscreen mode Exit fullscreen mode

The weights (0.299, 0.587, 0.114) reflect that human eyes are most sensitive to green, then red, then blue. A simple average (1/3 each) produces visibly wrong results.

Crop, rotate, resize

These operations use canvas transformations rather than pixel manipulation:

// Crop: draw a portion of the image
ctx.drawImage(img, sx, sy, sWidth, sHeight, 0, 0, dWidth, dHeight);

// Rotate: transform the canvas context
ctx.translate(canvas.width/2, canvas.height/2);
ctx.rotate(angle * Math.PI / 180);
ctx.drawImage(img, -img.width/2, -img.height/2);
Enter fullscreen mode Exit fullscreen mode

For editing photos directly in the browser with crop, rotate, brightness, contrast, and filters, I built an editor at zovo.one/free-tools/photo-editor. It uses Canvas API pixel manipulation and runs entirely client-side.


I'm Michael Lip. I build free developer tools at zovo.one. 500+ tools, all private, all free.

Top comments (0)