Search code examples
javascriptmathcolorsgraphics

How can I convert a color to a semi-transparent version of the same color assuming a white background?


enter image description here

I'm looking for a formula which would take any opaque color (shown on the left in each scenario above) and output a semi-transparent color which is essentially the equivalent given a white background. It seems to be straightforward for any grayscale color (eg. the first gray: 1-(32/255) -> 0.87), but not sure what to do with other colors.

Note how they appear to be the exact same colors anywhere there is a white background even though they are clearly different colors.

I found a wikipedia page on alpha compositing but I don't know how to use it to do what I want. I saw this question but it's not exactly the same. Finally, I think this "Color to alpha" gimp filter is essentially what I want to do, always using white as the color - but there is no documentation on how it's being done. Any help is appreciated.

Essentially looking for a function like this...

function whiteToAlpha(r, g, b) {
  // ...
  return [newR, newG, newB, newAlpha]
}

Solution

  • Multiple Solutions

    As you might guess with having three known values and four unknowns, there are multiple solutions, that can have multiple different alpha values. Here I'll assume you want the lowest possible alpha value (and where one of the output color values is 0), since that's what you have in your examples.

    Reworking the Alpha Compositing formula

    Your linked formula on Wikipedia for alpha compositing is all we need to calculate new colors:

    formula

    This formula is used per color channel (so there's a separate, independent formula for red, green, and blue input and output values). In your particular case, where the color is overlaid on a white background:

    • Ca is the color channel value we want to calculate
    • alphaa is the alpha channel value we want to calculate
    • Cb is the color channel value for white (255 or 1 depending on your units)
    • alphab is the alpha channel value for solid white (in this case, 1)
    • C0 is the input color channel value
    • alpha0 is the input alpha channel value (in this case, 1)

    Plugging in the constraints, and using 255 for Cb yields this simplified formula:

    formula

    Calculating the Alpha

    For the constraint to have the lowest alpha value possible, this happens when the converted color channel value (Ca) is zero. In this case, that would yield this formula:

    formula

    Since there are three colors, how do you figure out which channel to use? Thankfully each channel uses the same linear equation and the lowest value in each of the input channels will always correspond to the lowest one after conversion. So you can just take the minimum of the input values to calculate the alpha.

    formula

    Calculating each color

    From there, you can plug in alphaa into our simplified formula:

    formula

    Where you'd do this for each color channel (red, green, and blue)

    Sample Implementation:

    Here's an example that uses the above formulas:

    function minimumAlpha(color) {
      return (255 - color) / 255
    }
    
    function convert(color, alpha) {
      return Math.floor(255 - (255 - color) / alpha);
    }
    
    function opaqueToAlpha(r, g, b) {
      var alpha = minimumAlpha(Math.min(r, g, b));
      // may want to enforce more constraints on alpha
      return [
        convert(r, alpha),
        convert(g, alpha),
        convert(b, alpha),
        alpha
      ];
    }
    
    // just used for logging
    function convertColor(r, g, b) {
      console.log(`[${r}, ${g}, ${b}] converts to [${opaqueToAlpha(r, g, b).join(', ')}]`);
    }
    
    convertColor(54, 201, 85);
    convertColor(77, 141, 177);
    convertColor(255, 244, 149);