Conversão RGB para RGBA sobre branco

196

Eu tenho uma cor hexadecimal, por exemplo, #F4F8FB(ou rgb(244, 248, 251)) que desejo converter em uma cor rgba o mais transparente possível (quando exibida em branco). Faz sentido? Estou procurando um algoritmo, ou pelo menos a idéia de um algoritmo para saber como fazê-lo.

Por exemplo:

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

Ideias?


Solução FYI baseada na resposta de Guffa:

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)' 
    }
Mark Kahn
fonte
Interseting! Você ainda quer que seja visível certo, não totalmente transparente?
Mrchief
6
Pergunta interessante! Há uma questão exato oposto a stackoverflow.com/questions/2049230/convert-rgba-color-to-rgb , pode ajudar
apscience
@ Mrchief - Sim, quero que a cor tenha a mesma aparência quando exibida em branco, mas seja o mais transparente possível.
21411 Mark Kahn
1
Ótima pergunta, eu sempre quis saber como fazer isso, mas geralmente evitava;) +1
Dominic K
1
@ e100 - A pergunta específica que desencadeou isso foi stackoverflow.com/questions/6658350/… . Basicamente, eu queria poder sobrepor sombras CSS em cima de elementos com uma cor de plano de fundo sem bloquear o clique, colocando um elemento sombra em cima de tudo. Eu também queria fazer isso por um tempo, para que eu pudesse fazer coisas aleatórias como: jsfiddle.net/qwySW/2 . Além disso, eu simplesmente achei que fosse uma pergunta fascinante :)
Mark Kahn

Respostas:

185

Pegue o componente de cor mais baixo e converta-o em um valor alfa. Em seguida, redimensione os componentes de cor subtraindo o menor e dividindo pelo valor alfa.

Exemplo:

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

Então, rgb(152, 177, 202)exibe como rgba(0, 62, 123, .404).

Eu verifiquei no Photoshop que as cores realmente combinam perfeitamente.

Guffa
fonte
1
Obrigado! Eu estava fazendo algo semelhante (por exemplo, get .404), mas não conseguia entender os números.
21411 Mark Kahn
3
FYI, postou solução final com base em sua resposta em questão
Mark Kahn
2
@templatetypedef: a conversão do componente mais baixo em alfa está escalando do intervalo 0..255para 0..1e invertendo. Usar 1.0 - 152 / 255também funcionaria. Convertendo os componentes de cor é simplesmente escalar a partir n..255de 0..255onde né o menor componente.
Guffa
1
@Christoph: O princípio seria o mesmo, mas mais complicado. Em vez de usar apenas o componente mais baixo para calcular o alfa, você calcularia o menor alfa possível para cada componente (ou seja, qual valor alfa usar para obter a cor certa ao usar 0 ou 255 como valor do componente) e, em seguida, use o valor mais alto desses valores alfa. A partir disso, você pode calcular qual valor de cor usar para cada componente com esse valor alfa para obter a cor certa. Observe que algumas combinações de cores (por exemplo, branco sobre fundo preto) fornecerão 1,0 como o menor valor alfa possível de usar.
Guffa
2
Eu escrevi um pequeno violino que implementa esta solução. Aqui está o link. jsfiddle.net/wb5fwLoc/1 . Talvez um de vocês possa usá-lo. É apenas um script rápido, sem erros. Deve ser bom o suficiente para brincar.
chsymann
23

Seja r, g e b os valores de entrada e r ', g', b 'e a' sejam os valores de saída, todos redimensionados (por enquanto, pois torna a matemática mais bonita) entre 1 e 0. Então, por a fórmula para sobrepor cores:

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

Os termos 1 - a 'representam a contribuição em segundo plano e os outros termos representam o primeiro plano. Faça alguma álgebra:

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

Intuitivamente, parece que o valor mínimo da cor será o fator limitante do problema, portanto, ligue-o a m:

m = min(r, g, b)

Defina o valor de saída correspondente, m ', como zero, pois queremos maximizar a transparência:

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

Então, em javascript (traduzindo de 1 para 255 ao longo do caminho):

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 que estou assumindo que a 'é opacidade aqui. É trivial alterá-lo para transparência - basta remover o "1 -" da fórmula para a '. Outra coisa a se notar é que isso não parece produzir resultados exatos - dizia que a opacidade era de 0,498 para o exemplo que você deu acima (128, 128, 255). No entanto, isso é extremamente próximo.

gereeter
fonte
Se você distribuir o multiplicada 255 em sua equação de obter o resultado certo -[255 * (r/255 - 1) / a + 1 * 255, ...] == [(255*r/255 - 255 * 1) / a + 255, ...] == [(r - 255) / a + 255, ...]
gereeter
6

Gostaria de olhar para RGB <-> HSL conversão. Ou seja, luminosidade == quantidade de branco == quantidade de transparência.

Para o seu exemplo rgb( 128, 128, 255 ), precisamos alterar os valores RGB para o 0primeiro pela quantidade máxima, ou seja, para rgb( 0, 0, 128 )- essa seria a nossa cor com o mínimo de branco possível. Depois disso, usando a fórmula da luminância, calculamos a quantidade de branco que precisamos adicionar à nossa cor escura para obter a cor original - esse seria o nosso alfa:

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;

Espero que isso faça sentido. :)

lxa
fonte
6

Para aqueles que usam SASS / SCSS, escrevi uma pequena função SCSS para que você possa usar facilmente o algoritmo descrito por @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
    );
}
Stefan
fonte
3

Estou apenas descrevendo uma idéia para o algoritmo, sem solução completa:

Basicamente, você tem três números x, y, ze está à procura de três novos números x', y', z'e um multiplicador ana faixa [0,1] de tal forma que:

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

Isso está escrito em unidades em que os canais também recebem valores no intervalo [0,1]. Em valores discretos de 8 bits, seria algo como isto:

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

Além disso, você deseja o maior valor possível a. Você pode resolver:

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

Etc. Em valores reais, existem infinitas soluções, basta conectar qualquer número real a, mas o problema é encontrar um número para o qual o erro de discretização é mínimo.

Kerrek SB
fonte
0

Isso deve servir:

let x = min(r,g,b)
a = 1 - x/255                    # Correction 1
r,g,b = ( (r,g,b) - x ) / a      # Correction 2
verdade
fonte
Na verdade, acabei de perceber que havia um erro no alfa calc (corrigido agora). Então isso também pode ter algo a ver com isso.
trutheality
Ainda existe um problema introduzido por você ye (y-x). O 255 / (y-x)incorretamente força o maior número para 255, então você sempre acabar com um único 0, um único 255e, em seguida, outro número: 0, 22, 255, 255, 0, 55, etc ...
Mark Kahn
Entendo, para que não permita cores mais escuras ... eu deveria fazê-lo /ae, em seguida, corresponde à resposta correta.
trutheality
-1

A resposta principal não funcionou para mim com componentes de baixa cor. Por exemplo, ele não calcula o alfa correto se a cor for # 80000. Tecnicamente, ele deve ser convertido em # ff0000 com alfa 0.5. Para resolver isso, você precisa usar a conversão RGB -> HSL -> RGBA. Este é um pseudocódigo para obter os valores corretos:

//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" conterá o valor da nova cor ajustada e usará a variável "alpha" para controlar a transparência.

Arvydas Juskevicius
fonte
#800000e rgba( 255, 0, 0, .5 )não estão nem perto da mesma cor. O primeiro seria vermelho escuro, o segundo salmão-ish. A menos que seja exibido sobre preto, acho que serão os mesmos.
Mark Kahn