Convertir RGB en RGBA sur blanc

196

J'ai une couleur hexadécimale, par exemple #F4F8FB(ou rgb(244, 248, 251)) que je veux convertir en une couleur rgba aussi transparente que possible (lorsqu'elle est affichée sur du blanc). Ça a du sens? Je cherche un algorithme, ou du moins une idée d'un algorithme pour savoir comment le faire.

Par exemple:

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

Des idées?


Solution FYI basée sur la réponse 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
la source
Interset! Vous voulez toujours qu'il soit visible à droite, pas totalement transparent?
Mrchief
6
Question interessante! Il y a une question opposée exacte à stackoverflow.com/questions/2049230/convert-rgba-color-to-rgb , cela pourrait aider
apscience
@Mrchief - Oui, je veux que la couleur soit la même lorsqu'elle est affichée sur du blanc, mais aussi transparente que possible.
Mark Kahn
1
Excellente question, je me suis toujours demandé comment faire, mais je m'en éloignais généralement;) +1
Dominic K
1
@ e100 - La question spécifique qui a déclenché cela était stackoverflow.com/questions/6658350/… . Fondamentalement, je voulais pouvoir superposer les ombres CSS au-dessus des éléments avec une couleur d'arrière-plan sans bloquer le clic en mettant un élément d'ombre au-dessus de tout. J'ai également voulu faire cela pendant un certain temps pour pouvoir faire des choses aléatoires comme: jsfiddle.net/qwySW/2 . De plus, je viens de trouver que c'était une question fascinante :)
Mark Kahn

Réponses:

185

Prenez le composant de couleur le plus bas et convertissez-le en une valeur alpha. Mettez ensuite à l'échelle les composants de couleur en soustrayant le plus bas et en divisant par la valeur alpha.

Exemple:

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

Ainsi, rgb(152, 177, 202)s'affiche comme rgba(0, 62, 123, .404).

J'ai vérifié dans Photoshop que les couleurs correspondent parfaitement.

Guffa
la source
1
Je vous remercie! Je faisais quelque chose de similaire (par exemple, obtenir .404), mais je n'arrivais pas à comprendre les chiffres.
Mark Kahn
3
FYI, a publié la solution finale basée sur votre réponse en question
Mark Kahn
2
@templatetypedef: La conversion du composant le plus bas en alpha consiste à passer de la plage 0..255à 0..1et inverser. L'utilisation 1.0 - 152 / 255fonctionnerait également. La conversion des composantes de couleur est mise à l' échelle de simplement n..255à 0..255nest le plus bas des composants.
Guffa
1
@Christoph: Le principe serait le même, mais plus compliqué. Au lieu d'utiliser simplement le composant le plus bas pour calculer l'alpha, vous calculeriez l'alpha le plus bas possible pour chaque composant (c'est-à-dire la valeur alpha à utiliser pour obtenir la bonne couleur lorsque vous utilisez 0 ou 255 comme valeur de composant), puis utilisez le plus élevé de ces valeurs alpha. À partir de cela, vous pouvez calculer la valeur de couleur à utiliser pour chaque composant avec cette valeur alpha pour obtenir la bonne couleur. Notez que certaines combinaisons de couleurs (par exemple blanc sur fond noir) donneront 1.0 comme valeur alpha la plus basse possible à utiliser.
Guffa
2
J'ai écrit un petit violon qui implémente cette solution. Voici le lien. jsfiddle.net/wb5fwLoc/1 . Peut-être que l'un de vous peut l'utiliser. C'est juste un script rapide et non exempt de bogues .. il devrait être assez bon pour jouer.
chsymann
23

Soit r, g et b les valeurs d'entrée et r ', g', b 'et a' les valeurs de sortie, toutes mises à l'échelle (pour l'instant, car cela rend les mathématiques plus jolies) entre 1 et 0. Ensuite, en la formule de superposition des couleurs:

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

Les termes 1 - a 'représentent la contribution de fond et les autres termes représentent le premier plan. Faites de l'algèbre:

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

Intuitivement, il semble que la valeur de couleur minimale soit le facteur limitant du problème, alors liez-le à m:

m = min(r, g, b)

Réglez la valeur de sortie correspondante, m ', sur zéro, car nous voulons maximiser la transparence:

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

Donc, en javascript (traduisant de 1 à 255 en cours de route):

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

Notez que je suppose qu'un 'est l'opacité ici. Il est trivial de le changer en transparence - il suffit de supprimer le "1 -" de la formule pour un '. Une autre chose à noter est que cela ne semble pas produire de résultats exacts - il a déclaré que l'opacité était de 0,498 pour l'exemple que vous avez donné ci-dessus (128, 128, 255). Cependant, c'est extrêmement proche.

gereeter
la source
Si vous distribuez le 255 multiplié dans votre équation, vous obtenez le bon résultat -[255 * (r/255 - 1) / a + 1 * 255, ...] == [(255*r/255 - 255 * 1) / a + 255, ...] == [(r - 255) / a + 255, ...]
gereeter
6

Je regarderais la conversion RVB <-> HSL. C'est-à-dire luminosité == quantité de blanc == quantité de transparence.

Pour votre exemple rgb( 128, 128, 255 ), nous devons déplacer les valeurs RVB en 0premier par quantité maximale, c'est-à-dire rgb( 0, 0, 128 )- ce serait notre couleur avec le moins de blanc possible. Et après cela, en utilisant la formule de luminance, nous calculons la quantité de blanc que nous devons ajouter à notre couleur sombre pour obtenir la couleur d'origine - ce serait notre 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;

J'espère que cela a du sens. :)

lxa
la source
6

Pour ceux d'entre vous qui utilisent SASS / SCSS, j'ai écrit une petite fonction SCSS pour que vous puissiez facilement utiliser l'algorithme décrit par @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
la source
3

Je viens de décrire une idée pour l'algorithme, pas de solution complète:

En gros, vous avez trois numéros x, y, zet vous êtes à la recherche de trois nouveaux numéros x', y', z'et un multiplicateur adans l'intervalle [0,1] tel que:

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

Ceci est écrit en unités où les canaux prennent également des valeurs dans la plage [0,1]. En valeurs discrètes 8 bits, ce serait quelque chose comme ceci:

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

De plus, vous voulez la plus grande valeur possible a. Vous pouvez résoudre:

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

Etc. En valeurs réelles, cela a une infinité de solutions, il suffit de brancher n'importe quel nombre réel a, mais le problème est de trouver un nombre pour lequel l'erreur de discrétisation est minime.

Kerrek SB
la source
0

Cela devrait le faire:

let x = min(r,g,b)
a = 1 - x/255                    # Correction 1
r,g,b = ( (r,g,b) - x ) / a      # Correction 2
vérité
la source
En fait, je viens de remarquer que j'avais une erreur dans l'alpha calc (corrigé maintenant). Cela pourrait donc aussi avoir quelque chose à voir avec cela.
realityeality
Il y a toujours un problème introduit par votre yet (y-x). L' 255 / (y-x)oblige à tort le plus grand nombre à 255, de sorte que vous souhaitez toujours se retrouver avec un seul 0, un seul 255, puis un autre numéro: 0, 22, 255, 255, 0, 55, etc ...
Mark Kahn
Je vois, donc cela ne permettra pas de couleurs plus foncées ... Je devrais juste le faire /aet ensuite cela correspond à la bonne réponse.
trueeality
-1

La meilleure réponse n'a pas fonctionné pour moi avec des composants de faible couleur. Par exemple, il ne calcule pas l'alpha correct si la couleur est # 80000. Techniquement, il devrait devenir # ff0000 avec alpha 0.5. Pour résoudre ce problème, vous devez utiliser RGB -> HSL -> RGBA conversion. Ceci est un pseudo code pour obtenir les valeurs correctes:

//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" contiendra la valeur de la nouvelle couleur ajustée et utilisera la variable "alpha" pour contrôler la transparence.

Arvydas Juskevicius
la source
#800000et rgba( 255, 0, 0, .5 )sont loin de la même couleur. Le premier serait rouge foncé, le second saumon. À moins qu'ils ne s'affichent sur du noir, je pense qu'ils seraient les mêmes.
Mark Kahn