Convertir RGB a RGBA sobre blanco

196

Tengo un color hexadecimal, por ejemplo #F4F8FB(o rgb(244, 248, 251)) que quiero convertir en un color rgba tan transparente como sea posible (cuando se muestra en blanco). ¿Tener sentido? Estoy buscando un algoritmo, o al menos una idea de un algoritmo sobre cómo hacerlo.

Por ejemplo:

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

Ideas?


Solución para su información basada en la respuesta 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
fuente
Interseting! ¿Aún quieres que sea visible, no completamente transparente?
Mrchief
66
¡Interesante pregunta! Hay una pregunta exactamente lo contrario a stackoverflow.com/questions/2049230/convert-rgba-color-to-rgb , podría ayudar
apscience
@Mrchief: Sí, quiero que el color se vea igual cuando se muestra sobre blanco, pero que sea lo más transparente posible.
Mark Kahn el
1
Gran pregunta, siempre me he preguntado cómo hacer esto, pero generalmente evito eso;) +1
Dominic K
1
@ e100: la pregunta específica que provocó esto fue stackoverflow.com/questions/6658350/… . Básicamente, quería poder superponer sombras CSS en la parte superior de los elementos con un color de fondo sin bloquear el clic colocando un elemento de sombra en la parte superior de todo. También he querido hacer esto por un tiempo para poder hacer cosas al azar como: jsfiddle.net/qwySW/2 . Además, me pareció una pregunta fascinante :)
Mark Kahn

Respuestas:

185

Tome el componente de color más bajo y conviértalo a un valor alfa. Luego, escale los componentes de color restando el más bajo y dividiendo por el valor alfa.

Ejemplo:

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

Entonces, se rgb(152, 177, 202)muestra como rgba(0, 62, 123, .404).

Verifiqué en Photoshop que los colores realmente coinciden perfectamente.

Guffa
fuente
1
¡Gracias! Estaba haciendo algo similar (por ejemplo, obtener .404) pero no pude conseguir que los números funcionen.
Mark Kahn el
3
FYI, publicó la solución final basada en su respuesta en cuestión
Mark Kahn
2
@templatetypedef: la conversión del componente más bajo a alfa es escalar del rango 0..255a 0..1e invertir. Usar 1.0 - 152 / 255también funcionaría. La conversión de los componentes de color es simplemente escalando a partir n..255de 0..255donde nes el componente más bajo.
Guffa
1
@ Christoph: El principio sería el mismo, pero más complicado. En lugar de usar el componente más bajo para calcular el alfa, calcularía el alfa más bajo posible para cada componente (es decir, qué valor alfa usar para obtener el color correcto cuando usa 0 o 255 como valor del componente), luego use el más alto de esos valores alfa. A partir de eso, puede calcular qué valor de color usar para cada componente con ese valor alfa para obtener el color correcto. Tenga en cuenta que algunas combinaciones de colores (por ejemplo, blanco sobre fondo negro) darán 1.0 como el valor alfa más bajo posible de usar.
Guffa
2
Escribí un pequeño violín que implementa esta solución. Aqui esta el link. jsfiddle.net/wb5fwLoc/1 . Quizás uno de ustedes pueda usarlo. Es solo un script rápido, no libre de errores ... debería ser lo suficientemente bueno para jugar.
chsymann
23

Sean r, g y b los valores de entrada y r ', g', b 'y a' sean los valores de salida, todos escalados (por ahora, ya que hace que las matemáticas sean más bonitas) entre 1 y 0. Luego, por La fórmula para superponer colores:

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

Los términos 1 - a 'representan la contribución de fondo, y los otros términos representan el primer plano. Haz algo de á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 el valor mínimo de color será el factor limitante en el problema, por lo que debe asociarlo a m:

m = min(r, g, b)

Establezca el valor de salida correspondiente, m ', en cero, ya que queremos maximizar la transparencia:

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

Entonces, en javascript (traduciendo de 1 a 255 en el camino):

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];
}

Tenga en cuenta que estoy asumiendo que a 'es opacidad aquí. Es trivial cambiarlo a transparencia: simplemente elimine el "1 -" de la fórmula para un '. Otra cosa a tener en cuenta es que esto no parece producir resultados exactos: dice que la opacidad fue de 0.498 para el ejemplo que proporcionó anteriormente (128, 128, 255). Sin embargo, esto está extremadamente cerca.

gereeter
fuente
Si distribuye el 255 multiplicado en su ecuación, obtiene el resultado correcto -[255 * (r/255 - 1) / a + 1 * 255, ...] == [(255*r/255 - 255 * 1) / a + 255, ...] == [(r - 255) / a + 255, ...]
gereeter
6

Me gustaría ver la conversión RGB <-> HSL. Es decir, luminosidad == cantidad de blanco == cantidad de transparencia.

Para su ejemplo rgb( 128, 128, 255 ), necesitamos cambiar 0primero los valores RGB a la cantidad máxima, es decir, a rgb( 0, 0, 128 )ese sería nuestro color con la menor cantidad de blanco posible. Y después de eso, usando la fórmula para la luminancia, calculamos la cantidad de blanco que necesitamos agregar a nuestro color oscuro para obtener el color original; ese sería nuestro 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 tenga sentido. :)

lxa
fuente
6

Para aquellos de ustedes que usan SASS / SCSS, he escrito una pequeña función SCSS para que puedan usar fácilmente el 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
fuente
3

Solo estoy describiendo una idea para el algoritmo, no hay una solución completa:

Básicamente, usted tiene tres números x, y, zy que busca tres nuevos números x', y', z'y un multiplicador aen el rango [0,1] tal que:

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

Esto se escribe en unidades donde los canales también toman valores en el rango [0,1]. En valores discretos de 8 bits, sería algo como esto:

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

Además, desea el mayor valor posible a. Puedes resolver:

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

Etc. En valores reales esto tiene infinitas soluciones, simplemente conecte cualquier número real a, pero el problema es encontrar un número para el cual el error de discretización sea mínimo.

Kerrek SB
fuente
0

Esto debería hacerlo:

let x = min(r,g,b)
a = 1 - x/255                    # Correction 1
r,g,b = ( (r,g,b) - x ) / a      # Correction 2
la verdad
fuente
De hecho, me acabo de dar cuenta de que tenía un error en el cálculo alfa (solucionado ahora). Entonces eso podría haber tenido algo que ver con eso también.
trutheality
Todavía hay un problema introducido por tu yy (y-x). La 255 / (y-x)obliga incorrectamente el número más grande de 255, por lo que siempre iba a terminar con una sola 0, una sola 255y luego otro número: 0, 22, 255, 255, 0, 55, etc ...
Marcos Kahn
Ya veo, por lo que no permitirá colores más oscuros ... Debería hacerlo /ay luego coincidir con la respuesta correcta.
trutheality
-1

La respuesta principal no me funcionó con componentes de bajo color. Por ejemplo, no calcula el alfa correcto si el color es # 80000. Técnicamente, debería convertirse en # ff0000 con alpha 0.5. Para resolver esto, debe usar la conversión RGB -> HSL -> RGBA. Este es un pseudocódigo para obtener los valores correctos:

//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" contendrá el valor del nuevo color ajustado y usará la variable "alfa" para controlar la transparencia.

Arvydas Juskevicius
fuente
#800000y rgba( 255, 0, 0, .5 )no están cerca del mismo color. El primero sería rojo oscuro, el segundo salmón. A menos que se muestre en negro, creo que serían lo mismo.
Mark Kahn