DEV Community

toolzip
toolzip

Posted on Originally published at toolzip.app

Image Adjustment in the Browser — Brightness, Contrast, Saturation Without Libraries

Image Adjustment in the Browser — Brightness, Contrast, Saturation Without Libraries

CSS filter handles simple visual effects. For pixel-level control — downloadable output, real adjustment values — you need Canvas and pixel manipulation.

Getting Pixel Data

async function getImageData(file) {
  const img = await loadImage(file);
  const canvas = document.createElement('canvas');
  canvas.width = img.naturalWidth;
  canvas.height = img.naturalHeight;
  canvas.getContext('2d').drawImage(img, 0, 0);
  return canvas.getContext('2d').getImageData(0, 0, canvas.width, canvas.height);
}
Enter fullscreen mode Exit fullscreen mode

Brightness

Brightness adds or subtracts from each channel:

function applyBrightness(data, value) {
  // value: -100 to +100
  const factor = value * 2.55;
  for (let i = 0; i < data.length; i += 4) {
    data[i]     = Math.min(255, Math.max(0, data[i]     + factor));
    data[i + 1] = Math.min(255, Math.max(0, data[i + 1] + factor));
    data[i + 2] = Math.min(255, Math.max(0, data[i + 2] + factor));
  }
}
Enter fullscreen mode Exit fullscreen mode

Contrast

Contrast expands or compresses the range around the midpoint (128):

function applyContrast(data, value) {
  // value: -100 to +100
  const factor = (259 * (value + 255)) / (255 * (259 - value));
  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));
  }
}
Enter fullscreen mode Exit fullscreen mode

Saturation

Saturation requires converting to HSL, adjusting S, and converting back:

function rgbToHsl(r, g, b) {
  r /= 255; g /= 255; b /= 255;
  const max = Math.max(r, g, b), min = Math.min(r, g, b);
  let h, s, l = (max + min) / 2;

  if (max === min) { h = s = 0; }
  else {
    const d = max - min;
    s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
    switch (max) {
      case r: h = ((g - b) / d + (g < b ? 6 : 0)) / 6; break;
      case g: h = ((b - r) / d + 2) / 6; break;
      case b: h = ((r - g) / d + 4) / 6; break;
    }
  }
  return [h, s, l];
}
Enter fullscreen mode Exit fullscreen mode

Sharpness (Unsharp Mask)

Sharpness isn't a simple per-pixel operation — it requires a convolution:

function applySharpness(imageData, strength) {
  const kernel = [
     0, -strength,  0,
    -strength, 1 + 4 * strength, -strength,
     0, -strength,  0
  ];
  return convolve(imageData, kernel);
}
Enter fullscreen mode Exit fullscreen mode

Performance

Processing a 4000×3000 image (48MB of raw pixel data) takes ~200ms in JavaScript. For a preview, work at 1/4 resolution and only process full resolution on download.


Adjust images at toolzip.app/tools/image-adjust.

Top comments (0)