Konwertuj RGB na RGBA na białym

198

Mam kolor szesnastkowy, np. #F4F8FB(Lub rgb(244, 248, 251)), który chcę przekształcić w tak przezroczysty-jak to możliwe kolor rgba (gdy jest wyświetlany na białym). Ma sens? Szukam algorytmu, a przynajmniej pomysłu na algorytm, jak to zrobić.

Na przykład:

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

Pomysły?


Rozwiązanie FYI oparte na odpowiedzi 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
źródło
Interseting! Nadal chcesz, aby był widoczny, prawda, a nie w pełni przezroczysty?
Mrchief,
6
Interesujące pytanie! Jest dokładnie odwrotne pytanie na stackoverflow.com/questions/2049230/convert-rgba-color-to-rgb , może pomóc
apscience
@Mrchief - Tak, chcę, aby kolor wyglądał tak samo po wyświetleniu na białym tle, ale był jak najbardziej przezroczysty.
Mark Kahn,
1
Świetne pytanie, zawsze się zastanawiałem, jak to zrobić, ale zwykle unikałem tego;) +1
Dominic K
1
@ e100 - Konkretnym pytaniem, które to spowodowało, było stackoverflow.com/questions/6658350/… . Zasadniczo chciałem mieć możliwość nakładania cieni CSS na elementy w kolorze tła bez blokowania klikalności, umieszczając element cienia na wszystkim. Chciałem też to zrobić przez jakiś czas, aby móc robić losowe rzeczy, takie jak: jsfiddle.net/qwySW/2 . Poza tym po prostu uznałem to za fascynujące pytanie :)
Mark Kahn,

Odpowiedzi:

187

Weź najniższy składnik koloru i zamień go na wartość alfa. Następnie przeskaluj komponenty koloru, odejmując najniższą i dzieląc przez wartość alfa.

Przykład:

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

Tak więc rgb(152, 177, 202)wyświetla się jako rgba(0, 62, 123, .404).

W Photoshopie sprawdziłem, że kolory rzeczywiście pasują idealnie.

Guffa
źródło
1
Dziękuję Ci! Robiłem coś podobnego (np. Get .404), ale nie mogłem znaleźć liczb do obliczenia.
Mark Kahn,
3
Do Twojej wiadomości, opublikowałem ostateczne rozwiązanie w oparciu o twoją odpowiedź
Mark Kahn,
2
@templatetypedef: Konwersja najniższego komponentu na alfa to skalowanie z zakresu 0..255do 0..1i odwracanie. Używanie 1.0 - 152 / 255też by działało. Konwersja składników koloru to po prostu skalowanie od miejsca, n..255w 0..255którym nznajduje się najniższy składnik.
Guffa,
1
@Christoph: Zasada byłaby taka sama, ale bardziej skomplikowana. Zamiast używać tylko najniższego składnika do obliczenia współczynnika alfa, obliczysz najniższą możliwą wartość alfa dla każdego składnika (tj. Jakiej wartości alfa użyć, aby uzyskać właściwy kolor, używając 0 lub 255 jako wartości składnika), a następnie użyj najwyższej tych wartości alfa. Na tej podstawie możesz obliczyć, jakiej wartości koloru użyć dla każdego komponentu z tą wartością alfa, aby uzyskać właściwy kolor. Zauważ, że niektóre kombinacje kolorów (na przykład biały na czarnym tle) dadzą 1,0 jako najniższą możliwą do użycia wartość alfa.
Guffa
2
Napisałem małe skrzypce, które implementują to rozwiązanie. Tutaj jest link. jsfiddle.net/wb5fwLoc/1 . Może ktoś z was może to wykorzystać. To tylko szybki, niezawierający błędów skrypt ... powinien wystarczyć do zabawy.
chsymann
25

Niech r, g i b będą wartościami wejściowymi, a r ', g', b 'i a' będą wartościami wyjściowymi, wszystkie przeskalowane (na razie, ponieważ dzięki temu matematyka jest ładniejsza) między 1 a 0. Następnie, przez wzór nakładania kolorów:

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

Wyrazy 1 - a 'reprezentują wkład tła, a pozostałe terminy reprezentują pierwszy plan. Zrób trochę algebry:

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

Intuicyjnie wydaje się, że minimalna wartość koloru będzie czynnikiem ograniczającym problem, więc powiąż to z m:

m = min(r, g, b)

Ustaw odpowiednią wartość wyjściową, m ', na zero, ponieważ chcemy zmaksymalizować przezroczystość:

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

Tak więc w javascript (tłumacząc od 1 do 255 po drodze):

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

Zauważ, że zakładam, że 'jest tutaj nieprzezroczystością. Zmiana na przezroczystość jest trywialna - wystarczy usunąć „1 -” ze wzoru na a. Nic, na co należy zwrócić uwagę, to fakt, że nie wydaje się to dawać dokładnych wyników - stwierdzono, że krycie wynosi 0,498 dla przykładu, który podałeś powyżej (128, 128, 255). Jednak jest to bardzo blisko.

gereeter
źródło
Jeśli [255 * (r/255 - 1) / a + 1 * 255, ...] == [(255*r/255 - 255 * 1) / a + 255, ...] == [(r - 255) / a + 255, ...]
podzielisz
6

Spojrzałbym na konwersję RGB <-> HSL. Tj. Jasność == ilość bieli == stopień przezroczystości.

Na przykład rgb( 128, 128, 255 )musimy przesunąć wartości RGB do 0pierwszej o wartość maksymalną, tj. Do rgb( 0, 0, 128 )- to byłby nasz kolor z jak najmniejszą ilością bieli. Następnie, używając wzoru na luminancję, obliczamy ilość bieli, którą musimy dodać do naszego ciemnego koloru, aby uzyskać oryginalny kolor - to byłaby nasza 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;

Mam nadzieję, że to ma sens. :)

lxa
źródło
6

Dla tych z Was, którzy używają SASS / SCSS, napisałem małą funkcję SCSS, dzięki czemu można łatwo korzystać z algorytmu opisanego przez @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
źródło
3

Właśnie opisuję pomysł na algorytm, brak pełnego rozwiązania:

Zasadniczo masz trzy numery x, y, zi szukasz trzech nowych numerów x', y', z'i mnożnik aw przedziale [0,1] taka, że:

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

Jest to zapisane w jednostkach, w których kanały również przyjmują wartości z zakresu [0,1]. W 8-bitowych wartościach dyskretnych wyglądałoby to mniej więcej tak:

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

Ponadto chcesz mieć jak największą wartość a. Możesz rozwiązać:

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

Itd. W rzeczywistych wartościach ma to nieskończenie wiele rozwiązań, wystarczy podłączyć dowolną liczbę rzeczywistą a, ale problem polega na znalezieniu liczby, dla której błąd dyskretyzacji jest minimalny.

Kerrek SB
źródło
0

To powinno wystarczyć:

let x = min(r,g,b)
a = 1 - x/255                    # Correction 1
r,g,b = ( (r,g,b) - x ) / a      # Correction 2
prawdomówność
źródło
Właściwie właśnie zauważyłem, że mam błąd w kalkulatorze alfa (teraz naprawiony). Więc to też mogło mieć z tym coś wspólnego.
trutheality
Nadal występuje problem wprowadzony przez yi (y-x). 255 / (y-x)Nieprawidłowo wymusza największą liczbę do 255, tak że zawsze skończyć z jednym 0, pojedynczym 255, a następnie inny numer: 0, 22, 255, 255, 0, 55itd ...
Mark Kahn
Rozumiem, więc nie pozwoli na ciemniejsze kolory ... Powinienem to zrobić, /aa wtedy pasuje do poprawnej odpowiedzi.
prawdomówność
-1

Najlepsza odpowiedź nie zadziałała dla mnie z niskimi kolorami. Na przykład nie oblicza prawidłowego współczynnika alfa, jeśli kolor to # 80000. Technicznie powinien trafić do # ff0000 z alfa 0.5. Aby rozwiązać ten problem, musisz użyć konwersji RGB -> HSL -> RGBA. To jest pseudo kod do uzyskania poprawnych wartości:

//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” będzie zawierało wartość nowego dopasowanego koloru i użyje zmiennej „alpha” do sterowania przezroczystością.

Arvydas Juskevicius
źródło
#800000i rgba( 255, 0, 0, .5 )nigdzie nie są w tym samym kolorze. Pierwsza byłaby ciemnoczerwona, druga łososiowa. Chyba że wyświetlają się na czarno, wtedy myślę, że będą takie same.
Mark Kahn