DEV Community

Michael Lip
Michael Lip

Posted on • Originally published at zovo.one

Photo Filters Are Just Matrix Operations on Pixel Arrays

Every Instagram filter is a combination of brightness adjustment contrast modification color channel manipulation and selective blurring. None of these operations are complex individually. The art is in combining them.

Anatomy of a filter

A typical "vintage" filter combines:

  1. Reduce contrast slightly (factor 0.9)
  2. Add warmth (increase red +10, decrease blue -10)
  3. Slight vignette (darken corners)
  4. Reduce saturation to 80%
function vintageFilter(imageData) {
  const data = imageData.data;
  for (let i = 0; i < data.length; i += 4) {
    let r = data[i], g = data[i+1], b = data[i+2];

    // Reduce contrast
    r = 0.9 * (r - 128) + 128;
    g = 0.9 * (g - 128) + 128;
    b = 0.9 * (b - 128) + 128;

    // Add warmth
    r = Math.min(255, r + 10);
    b = Math.max(0, b - 10);

    // Reduce saturation
    const gray = 0.299 * r + 0.587 * g + 0.114 * b;
    r = gray + 0.8 * (r - gray);
    g = gray + 0.8 * (g - gray);
    b = gray + 0.8 * (b - gray);

    data[i] = Math.min(255, Math.max(0, r));
    data[i+1] = Math.min(255, Math.max(0, g));
    data[i+2] = Math.min(255, Math.max(0, b));
  }
  return imageData;
}
Enter fullscreen mode Exit fullscreen mode

Sepia tone

The sepia effect uses a color matrix transformation:

function sepiaFilter(imageData) {
  const data = imageData.data;
  for (let i = 0; i < data.length; i += 4) {
    const r = data[i], g = data[i+1], b = data[i+2];
    data[i]   = Math.min(255, 0.393*r + 0.769*g + 0.189*b);
    data[i+1] = Math.min(255, 0.349*r + 0.686*g + 0.168*b);
    data[i+2] = Math.min(255, 0.272*r + 0.534*g + 0.131*b);
  }
  return imageData;
}
Enter fullscreen mode Exit fullscreen mode

The matrix coefficients create the characteristic warm brown tone by boosting red/green relative to blue.

Blur

Blur is a convolution operation using a kernel matrix. A 3x3 box blur:

function boxBlur(imageData, width) {
  const data = new Uint8ClampedArray(imageData.data);
  const kernel = [1/9, 1/9, 1/9, 1/9, 1/9, 1/9, 1/9, 1/9, 1/9];
  // Apply convolution...
}
Enter fullscreen mode Exit fullscreen mode

Gaussian blur uses a weighted kernel that produces smoother results than a box blur. The kernel weights follow a Gaussian distribution, giving more weight to the center pixel and less to surrounding pixels.

For applying photo filters with live preview, I built a filter tool at zovo.one/free-tools/photo-filter. It includes preset filters plus adjustable parameters for creating custom effects.


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

Top comments (0)