Convert RGB to RGBA over white
Asked Answered
G

7

211

I have a hex color, e.g. #F4F8FB (or rgb(244, 248, 251)) that I want converted into an as-transparent-as-possible rgba color (when displayed over white). Make sense? I'm looking for an algorithm, or at least idea of an algorithm for how to do so.

For Example:

rgb( 128, 128, 255 ) --> rgba(   0,   0, 255,  .5 )
rgb( 152, 177, 202 ) --> rgba(  50, 100, 150,  .5 ) // can be better(lower alpha)

Ideas?


FYI solution based on Guffa's answer:

function RGBtoRGBA(r, g, b){
    if((g == null) && (typeof r === 'string')){
        var hex = r.replace(/^\s*#|\s*$/g, '');
        if(hex.length === 3){
            hex = hex.replace(/(.)/g, '$1$1');
        }
        r = parseInt(hex.substr(0, 2), 16);
        g = parseInt(hex.substr(2, 2), 16);
        b = parseInt(hex.substr(4, 2), 16);
    }

    var min, a = (255 - (min = Math.min(r, g, b))) / 255;

    return {
        r    : r = 0|(r - min) / a,
        g    : g = 0|(g - min) / a,
        b    : b = 0|(b - min) / a,
        a    : a = (0|1000*a)/1000,
        rgba : 'rgba(' + r + ', ' + g + ', ' + b + ', ' + a + ')'
    };
}

RGBtoRGBA(204, 153, 102) == RGBtoRGBA('#CC9966') == RGBtoRGBA('C96') == 
    {
       r    : 170,
       g    : 85 ,
       b    : 0  ,
       a    : 0.6,
       rgba : 'rgba(170, 85, 0, 0.6)' 
    }
Gorse answered 12/7, 2011 at 23:20 Comment(6)
Interseting! You still want it to be visible right, not fully transparent?Wei
Interesting question! There's an exact opposite question at https://mcmap.net/q/122707/-convert-rgba-color-to-rgb , it might helpWandering
@Wei - Yes, I want the color to look the same when displayed over white, but be as transparent as possible.Gorse
@gladoscc - yeah, the conversion from rgba-->rgb is easy (r = r + (255-r) * (1-a)), and actually how I generated the example numbers. The conversion going the other way is giving me a headache :)Gorse
@e100 - The specific question that triggered this was #6658850. Basically I wanted to be able to overlay CSS shadows on top of elements with a background-color without blocking the click-through by putting a shadow element on top of everything. I've also wanted to do this for a while so I can do random things like: jsfiddle.net/qwySW/2. Plus I just found it to be a fascinating question :)Gorse
It'd be great if you could also specify the background color. Black or gray instead of just white, for example.Vial
P
196

Take the lowest color component, and convert that to an alpha value. Then scale the color components by subtracting the lowest, and dividing by the alpha value.

Example:

152 converts to an alpha value of (255 - 152) / 255 ~ 0.404

152 scales using (152 - 152) / 0.404 = 0
177 scales using (177 - 152) / 0.404 ~ 62
202 scales using (202 - 152) / 0.404 ~ 123

So, rgb(152, 177, 202) displays as rgba(0, 62, 123, .404).

I have verified in Photoshop that the colors actually match perfectly.

Pinch answered 12/7, 2011 at 23:44 Comment(8)
Thank you! I was doing something similar (e.g. get .404) but couldn't quite get the numbers to work out.Gorse
FYI, posted final solution based on your answer in questionGorse
@templatetypedef: Converting te lowest component to alpha is scaling from the range 0..255 to 0..1, and inverting. Using 1.0 - 152 / 255 would also work. Converting the color components is simply scaling from n..255 to 0..255 where n is the lowest component.Pinch
@Pinch could you please explain how to do that on a non-white background?Lithomarge
@Christoph: The principle would be the same, but more complicated. Instead of just using the lowest component to calculate the alpha, you would calculate the lowest possible alpha for each component (i.e. what alpha value to use to get the right color when using either 0 or 255 as the component value), then use the highest of those alpha values. From that you can calculate what color value to use for each component with that alpha value to get the right color. Note that some color combinations (for example white on black background) will give 1.0 as the lowest possible alpha value to use.Pinch
I didn't fully understand that. For even colors rgb(x,x,x) that's simple, I just compute var alpha = (x - lowest(r,g,b)) / x, but what with "uneven" colors?Lithomarge
@Christoph: For each component you have to determine if you should use a darker or brighter value against the background, then determine what lowest alpha value you can use for each component. The highest of those is the lowest alpha that you can use all over.Pinch
I wrote a small fiddle that implements this solution. Here is the link. jsfiddle.net/wb5fwLoc/1. Maybe one of you can use it. It's just a quick, not bug-free script.. it should be good enough for play around.Marron
G
24

Let r, g, and b be the input values and r', g', b', and a' be the output values, all scaled (for now, as it makes the math prettier) between 1 and 0. Then, by the formula for overlaying colors:

r = a' * r' + 1 - a'
g = a' * g' + 1 - a'
b = a' * b' + 1 - a'

The 1 - a' terms represent the background contribution, and the other terms represent the foreground. Do some algebra:

r = a' * (r' - 1) + 1
r - 1 = a' * (r' - 1)
(r - 1) / (r' - 1) = a'
(r' - 1) / (r - 1) = 1 / a'
r' - 1 = (r - 1) / a'
r' = (r - 1) / a' + 1

Intuitively, it seems that the minimum color value will be the limiting factor in the problem, so bind this to m:

m = min(r, g, b)

Set the corresponding output value, m', to zero, as we want to maximize transparency:

0 = (m - 1) / a' + 1
-1 = (m - 1) / a'
-a' = m - 1
a' = 1 - m

So, in javascript (translating from 1 to 255 along the way):

function rgba(r, g, b) {
    var a = 1 - Math.min(r, Math.min(g, b)) / 255;
    return [255 + (r - 255) / a, 255 + (g - 255) / a, 255 + (b - 255) / a, a];
}

Note that I'm assuming that a' is opacity here. It is trivial to change it to transparency - just remove the "1 -" from the formula for a'. Anothing thing to note is that this does not seem to produce exact results - it said that the opacity was 0.498 for the example you gave above (128, 128, 255). However, this is extremely close.

Giguere answered 13/7, 2011 at 0:4 Comment(1)
If you distribute the multiplied 255 in your equation you get the right result - [255 * (r/255 - 1) / a + 1 * 255, ...] == [(255*r/255 - 255 * 1) / a + 255, ...] == [(r - 255) / a + 255, ...]Giguere
L
6

I'd look to RGB<->HSL conversion. I.e. luminosity == amount of white == amount of transparency.

For your example rgb( 128, 128, 255 ), we need to shift RGB values to 0 first by maximum amount, i.e. to rgb( 0, 0, 128 ) - that would be our color with as few of white as possible. And after that, using formula for luminance, we calculate amount of white we need to add to our dark color to get original color - that would be our alpha:

L = (MAX(R,G,B) + MIN(R,G,B))/2
L1 = (255 + 128) / 2 = 191.5
L2 = (128 + 0) /2 = 64
A = (191,5 - 64) / 255 = 0,5;

Hope that makes sense. :)

Llewellyn answered 12/7, 2011 at 23:44 Comment(0)
E
6

For those of you using SASS/SCSS, I've wrote a small SCSS function so you can easily use the algorithm described by @Guffa

@function transparentize-on-white($color)
{
    $red: red($color);
    $green: green($color);
    $blue: blue($color);
    $lowestColorComponent: min($red, $green, $blue);
    $alpha: (255 - $lowestColorComponent) / 255;

    @return rgba(
        ($red - $lowestColorComponent) / $alpha,
        ($green - $lowestColorComponent) / $alpha,
        ($blue - $lowestColorComponent) / $alpha,
        $alpha
    );
}
Eradis answered 12/2, 2016 at 13:3 Comment(0)
P
3

I'm just describing an idea for the algorithm, no full solution:

Basically, you have three numbers x, y, z and you are looking for three new numbers x', y', z' and a multiplier a in the range [0,1] such that:

x = a + (1 - a) x'
y = a + (1 - a) y'
z = a + (1 - a) z'

This is written in units where the channels also take values in the range [0,1]. In 8bit discrete values, it'd be something like this:

x = 255 a + (1 - a) x'
y = 255 a + (1 - a) y'
z = 255 a + (1 - a) z'

Moreover, you want the largest possible value a. You can solve:

a  = (x - x')/(255 - x')          x' = (x - 255 a)/(1 - a)

Etc. In real values this has infinitely many solutions, just plug in any real number a, but the problem is to find a number for which the discretization error is minimal.

Piling answered 12/7, 2011 at 23:41 Comment(0)
O
0

This should do it:

let x = min(r,g,b)
a = 1 - x/255                    # Correction 1
r,g,b = ( (r,g,b) - x ) / a      # Correction 2
Osmund answered 12/7, 2011 at 23:42 Comment(3)
I actually just noticed that I had an error in the alpha calc (fixed now). So that might have had something to do with it too.Osmund
There's still a problem introduced by your y and (y-x). The 255 / (y-x) incorrectly forces the largest number to 255, so you'd always end up with a single 0, a single 255 and then another number: 0, 22, 255, 255, 0, 55, etc...Gorse
I see, so it won't allow for darker colors... I should just make it /a and then it matches the correct answer.Osmund
C
0

The top answer didn't work for me with low color components. For example it does not calculate correct alpha if color is #80000. Technically it should make it into #ff0000 with alpha 0.5. To resolve this, you need to use RGB -> HSL -> RGBA conversion. This is pseudo code to get the correct values:

//Convert RGB to HSL
hsl = new HSL(rgb)

//Use lightness as alpha
alpha = hsl.Lightness

//For #80000 lightness is 0.5, so we have to take this into account.
//Lightness more than 0.5 converts to alpha 1 and below is a linear ratio
if (alpha > 0.5)
{
    alpha = 1;
}
else
{
    alpha = alpha / 0.5;
    //We need to drop the lightness of the color to 0.5 to get the actual color
    //that needs to display. Alpha blending will take care of that.
    hsl.Lightness = 0.5;
}

newRgb = hsl.convertToRgb()

"newRgb" will contain the value of the new adjusted color and use "alpha" variable to control the transparency.

Chkalov answered 8/10, 2014 at 16:5 Comment(1)
#800000 and rgba( 255, 0, 0, .5 ) are nowhere near the same color. The first would be dark red, the second salmon-ish. Unless displaying over black, then I think they'd be the same.Gorse

© 2022 - 2024 — McMap. All rights reserved.