ThisCalculator

Everyday

How to Tint an Image with Blend Modes: Pixel Math & Code

Updated · Standard formula, shown below

How to Tint an Image with Blend Modes: Pixel Math & Code

This guide explains how to tint an image using blend modes, covering the pixel math behind common modes like multiply, screen, and overlay, and providing practical JavaScript (Canvas API) code to implement it. You’ll learn what each blend mode does mathematically, how to apply a solid color tint to an image, and how to control the intensity. By the end, you’ll be able to write a function that tints any image with any color, and you’ll understand why the results look the way they do.

What You’ll Need

  • A basic understanding of HTML and JavaScript.
  • An image file (PNG, JPEG, etc.) or a canvas element with an image loaded.
  • A modern web browser (Chrome, Firefox, Safari, Edge) that supports the Canvas API.
  • Optional: a code editor and a local server (or just open the HTML file directly).

Step-by-Step

  1. Set up the HTML and canvas.
    Create an HTML file with a <canvas> element and a script. Load your image into the canvas using the Image object. For example:

    <canvas id="myCanvas" width="400" height="300"></canvas>
    <script>
      const canvas = document.getElementById('myCanvas');
      const ctx = canvas.getContext('2d');
      const img = new Image();
      img.onload = function() {
        ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
        // ... tinting code goes here
      };
      img.src = 'your-image.jpg';
    </script>
  2. Get the pixel data.
    Use ctx.getImageData() to retrieve an ImageData object containing the RGBA values of every pixel. The data is stored as a flat array: [red, green, blue, alpha, red, green, blue, alpha, …].

    const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    const data = imageData.data; // Uint8ClampedArray
  3. Define your tint color and blend mode.
    Choose a tint color as RGB values (0-255) and pick a blend mode. For this example, we’ll use ‘multiply’ with a blue tint (0, 100, 200).

    const tintR = 0, tintG = 100, tintB = 200;
    const blendMode = 'multiply'; // or 'screen', 'overlay', etc.
  4. Implement the blend math.
    For each pixel, extract the red, green, blue channels (ignore alpha for now). Apply the blend formula. Here are the core formulas (all values normalized to 0-1):

    • Multiply: result = base * blend
    • Screen: result = 1 - (1 - base) * (1 - blend)
    • Overlay: if base < 0.5, result = 2 * base * blend; else result = 1 – 2 * (1 – base) * (1 – blend)
    • Soft Light: result = (1 – 2*blend) * base^2 + 2*blend*base (simplified)

    For a tint, the blend color is constant for all pixels. Store the tint as normalized values (0-1).

  5. Loop through pixels and apply the blend.
    Iterate over the data array in steps of 4. For each channel (R, G, B), normalize the original pixel value (divide by 255), apply the blend formula with the tint value, then multiply by 255 and assign back. Example for multiply:

    for (let i = 0; i < data.length; i += 4) {
      // Normalize base pixel
      let r = data[i] / 255;
      let g = data[i+1] / 255;
      let b = data[i+2] / 255;
    
      // Multiply blend
      data[i]   = r * (tintR/255) * 255;
      data[i+1] = g * (tintG/255) * 255;
      data[i+2] = b * (tintB/255) * 255;
    }
  6. Put the pixel data back.
    After the loop, use ctx.putImageData(imageData, 0, 0) to draw the modified pixels onto the canvas.
  7. Add opacity control (optional).
    To control tint intensity, blend the tinted result with the original pixel using a factor alpha (0-1): final = original * (1 - alpha) + tinted * alpha. This is a linear interpolation.

Common Mistakes

  • Forgetting to normalize values: If you don’t divide by 255, the math will be off (e.g., multiply will produce huge numbers). Always work in 0-1 range.
  • Ignoring alpha channel: The data array includes alpha. If you accidentally modify the alpha values, the image may become transparent. Only modify indices 0,1,2 (and 3 if you want to change transparency).
  • Using integer division: In JavaScript, division always yields a float, but be careful when using Math.round() or bitwise operations that truncate. Use Math.round() when writing back to the array to avoid color banding.
  • Applying blend to alpha channel: Blend modes typically don’t apply to alpha. Keep alpha unchanged unless you specifically want to alter it.
  • Not handling image load errors: If the image fails to load, the canvas will be blank. Always include an onerror handler.

Tips

  • Precompute tint values: Store the tint as normalized constants (e.g., const tR = tintR/255) outside the loop to avoid division each iteration.
  • Use a lookup table for performance: For large images, precompute the result for every possible base value (0-255) and tint combination. This reduces the math to a simple array lookup.
  • Test with a small image first: Use a small canvas (e.g., 100×100) to verify the math before processing a large image.
  • Combine with CSS filters: For simple tints, you can use CSS filter: sepia() hue-rotate() but the pixel math gives you full control and works in canvas contexts.
  • Consider using globalCompositeOperation: The canvas API has built-in blend modes via ctx.globalCompositeOperation. For example, set it to ‘multiply’ and draw a solid color rectangle over the image. This is much simpler for static tints, but the pixel math approach is needed for dynamic or per-pixel effects.

Troubleshooting

  • Image appears black after multiply: This happens if the tint color is black (0,0,0) or if you forgot to normalize and used values >1. Check your tint values and ensure you divide by 255.
  • Colors look washed out: This is common with ‘screen’ mode if the tint is too light. Reduce the tint intensity or use a different blend mode.
  • Canvas is blank: Make sure the image is loaded before calling getImageData. Put the tinting code inside the img.onload callback.
  • SecurityError when calling getImageData: This happens if the image is from a different origin (e.g., a CDN) and the server doesn’t allow cross-origin access. Use a local image or set the crossOrigin attribute to ‘anonymous’ if the server supports CORS.
  • Performance is slow: The loop is O(n) and can be slow for large images. Use a Web Worker or reduce the image size before processing.

Frequently Asked Questions

What is the difference between multiply and screen blend modes?

Multiply darkens the image because it multiplies two values together (result is always ≤ each input). Screen brightens because it inverts, multiplies, then inverts again (result is always ≥ each input).

Can I use these blend modes on any image format?

Yes, the pixel data is format-agnostic. Once the image is drawn to canvas, it’s converted to RGBA, so the blend math works on any format.

How do I tint only part of an image?

You can modify the loop to check a condition (e.g., pixel coordinates or alpha value) and only apply the blend to those pixels. Alternatively, use an offscreen canvas to draw a mask.

What is the overlay blend mode used for?

Overlay combines multiply and screen depending on the base pixel’s brightness. It increases contrast and is often used for photo effects, like adding a warm tint while preserving highlights and shadows.

Is there a built-in way to tint in canvas without pixel math?

Yes, you can use ctx.globalCompositeOperation = 'multiply' and then fill the canvas with a solid color. This is much simpler, but it applies the tint uniformly without per-pixel control.

Why do my colors look different after saving?

Canvas is in sRGB color space, but some image viewers might interpret colors differently. Also, if you use toDataURL(), the compression (e.g., JPEG) can alter colors slightly.

Related

Method: the standard formula for this calculation, rounded as each result states. See how the calculators work or report an error.