Moje pytanie brzmi: biorąc pod uwagę docelowy kolor RGB, jaka jest formuła ponownego pokolorowania czerni ( #000
) na ten kolor przy użyciu tylko filtrów CSS ?
Aby odpowiedź została zaakceptowana, musiałaby dostarczyć funkcję (w dowolnym języku), która zaakceptowałaby kolor docelowy jako argument i zwróciłaby odpowiedni filter
ciąg CSS .
Kontekstem tego jest potrzeba ponownego kolorowania SVG w pliku background-image
. W tym przypadku ma obsługiwać pewne funkcje matematyczne TeX w KaTeX: https://github.com/Khan/KaTeX/issues/587 .
Przykład
Jeśli docelowy kolor to #ffff00
(żółty), jednym poprawnym rozwiązaniem jest:
filter: invert(100%) sepia() saturate(10000%) hue-rotate(0deg)
Nie-cele
- Animacja.
- Rozwiązania bez filtrów CSS.
- Począwszy od koloru innego niż czarny.
- Dbanie o to, co stanie się z kolorami innymi niż czarny.
Dotychczasowe wyniki
Brute-force wyszukiwanie parametrów stałej listy filtrów: https://stackoverflow.com/a/43959856/181228
Wady: nieefektywne, generuje tylko część z 16777216 możliwych kolorów (676248 zhueRotateStep=1
).Szybsze rozwiązanie wyszukiwania przy użyciu SPSA : https://stackoverflow.com/a/43960991/181228 Nagroda przyznana
drop-shadow
Rozwiązanie: https://stackoverflow.com/a/43959853/181228
Wady: nie działa na krawędzi. Wymagafilter
zmian innych niż CSS i drobnych zmian HTML.
Nadal możesz uzyskać zaakceptowaną odpowiedź, przesyłając rozwiązanie inne niż brutalne!
Zasoby
Jak
hue-rotate
isepia
są obliczane: https://stackoverflow.com/a/29521147/181228 Przykładowa implementacja Rubiego:LUM_R = 0.2126; LUM_G = 0.7152; LUM_B = 0.0722 HUE_R = 0.1430; HUE_G = 0.1400; HUE_B = 0.2830 def clamp(num) [0, [255, num].min].max.round end def hue_rotate(r, g, b, angle) angle = (angle % 360 + 360) % 360 cos = Math.cos(angle * Math::PI / 180) sin = Math.sin(angle * Math::PI / 180) [clamp( r * ( LUM_R + (1 - LUM_R) * cos - LUM_R * sin ) + g * ( LUM_G - LUM_G * cos - LUM_G * sin ) + b * ( LUM_B - LUM_B * cos + (1 - LUM_B) * sin )), clamp( r * ( LUM_R - LUM_R * cos + HUE_R * sin ) + g * ( LUM_G + (1 - LUM_G) * cos + HUE_G * sin ) + b * ( LUM_B - LUM_B * cos - HUE_B * sin )), clamp( r * ( LUM_R - LUM_R * cos - (1 - LUM_R) * sin ) + g * ( LUM_G - LUM_G * cos + LUM_G * sin ) + b * ( LUM_B + (1 - LUM_B) * cos + LUM_B * sin ))] end def sepia(r, g, b) [r * 0.393 + g * 0.769 + b * 0.189, r * 0.349 + g * 0.686 + b * 0.168, r * 0.272 + g * 0.534 + b * 0.131] end
Zauważ, że
clamp
powyższe sprawia, żehue-rotate
funkcja jest nieliniowa.Demo: przejście do koloru innego niż w skali szarości z koloru w skali szarości: https://stackoverflow.com/a/25524145/181228
Formuła, która prawie działa (z podobnego pytania ):
https://stackoverflow.com/a/29958459/181228Szczegółowe wyjaśnienie, dlaczego powyższa formuła jest błędna (CSS
hue-rotate
nie jest prawdziwą rotacją odcieni, ale liniowym przybliżeniem):
https://stackoverflow.com/a/19325417/2441511
źródło
Odpowiedzi:
@Dave był pierwszym, który opublikował odpowiedź na to pytanie (z działającym kodem), a jego odpowiedź była nieocenionym źródłem
bezwstydnego kopiowania i wklejaniadla mnie inspiracji. Ten post rozpoczął się jako próba wyjaśnienia i udoskonalenia odpowiedzi @ Dave, ale od tego czasu przekształcił się w własną odpowiedź.Moja metoda jest znacznie szybsza. Zgodnie z benchmarkiem jsPerf dotyczącym losowo generowanych kolorów RGB, algorytm @ Dave działa w 600 ms , podczas gdy mój działa w 30 ms . Może to z pewnością mieć znaczenie, na przykład w czasie ładowania, gdzie szybkość ma kluczowe znaczenie.
Ponadto w przypadku niektórych kolorów mój algorytm działa lepiej:
rgb(0,255,0)
@ Dave's produkujergb(29,218,34)
i produkujergb(1,255,0)
rgb(0,0,255)
@ Dave's produkuje,rgb(37,39,255)
a mój produkujergb(5,6,255)
rgb(19,11,118)
@ Dave's produkuje,rgb(36,27,102)
a mój produkujergb(20,11,112)
Próbny
Stosowanie
Wyjaśnienie
Zaczniemy od napisania Javascript.
Wyjaśnienie:
Color
Klasa reprezentuje barwy RGB.toString()
funkcja zwraca kolor wrgb(...)
łańcuchu kolorów CSS .hsl()
funkcja zwraca kolor, przekonwertowany na HSL .clamp()
funkcja zapewnia, że dana wartość koloru mieści się w granicach (0-255).Solver
Klasa będzie próbował rozwiązać za pomocą koloru docelowego.css()
funkcja zwraca podany filtr w ciągu filtru CSS.Wykonawczych
grayscale()
,sepia()
orazsaturate()
Sercem filtrów CSS / SVG są prymitywy filtrów , które reprezentują niskopoziomowe modyfikacje obrazu.
Filtry
grayscale()
,sepia()
isaturate()
są implementowane przez element pierwotny filtru<feColorMatrix>
, który wykonuje mnożenie macierzy między macierzą określoną przez filtr (często generowaną dynamicznie) a macierzą utworzoną z koloru. Diagram:Istnieje kilka optymalizacji, które możemy tutaj wprowadzić:
1
. Nie ma sensu go obliczać ani przechowywać.A
), ponieważ mamy do czynienia z RGB, a nie RGBA.<feColorMatrix>
filtry pozostawiają kolumny 4 i 5 jako zera. Dlatego możemy dodatkowo zmniejszyć matrycę filtra do 3x3 .Realizacja:
(Używamy zmiennych tymczasowych do przechowywania wyników mnożenia każdego wiersza, ponieważ nie chcemy, aby zmiany
this.r
itp. Miały wpływ na kolejne obliczenia).Teraz, wdrożyliśmy
<feColorMatrix>
możemy wdrożyćgrayscale()
,sepia()
isaturate()
, co po prostu wywołać ją z danej matrycy filtra:Realizowanie
hue-rotate()
hue-rotate()
Filtr jest realizowany przez<feColorMatrix type="hueRotate" />
.Macierz filtrów jest obliczana w sposób pokazany poniżej:
Na przykład element a 00 zostałby obliczony w następujący sposób:
Kilka uwag:
Math.sin()
lubMath.cos()
.Math.sin(angle)
iMath.cos(angle)
powinno być obliczone raz, a następnie zapisane w pamięci podręcznej.Realizacja:
Wdrażanie
brightness()
icontrast()
brightness()
Icontrast()
filtry są realizowane przez<feComponentTransfer>
z<feFuncX type="linear" />
.Każdy
<feFuncX type="linear" />
element przyjmuje atrybut nachylenia i przecięcia . Następnie oblicza każdą nową wartość koloru za pomocą prostej formuły:Jest to łatwe do wdrożenia:
Po wdrożeniu
brightness()
icontrast()
można to również zaimplementować:Realizowanie
invert()
invert()
Filtr jest realizowany przez<feComponentTransfer>
z<feFuncX type="table" />
.Specyfikacja stwierdza:
Wyjaśnienie tego wzoru:
invert()
Filtr wyznacza poniższej tabeli: [wartość, 1 - wartość]. To jest tableValues lub v .W ten sposób możemy uprościć wzór do:
Podkreślając wartości tabeli, pozostaje:
Jeszcze jedno uproszczenie:
Specyfikacja definiuje C i C ' jako wartości RGB, w granicach 0-1 (w przeciwieństwie do 0-255). W rezultacie musimy zmniejszyć wartości przed obliczeniem, a następnie przeskalować je ponownie.
W ten sposób dochodzimy do naszej realizacji:
Interludium: algorytm brutalnej siły @ Dave'a
Kod @ Dave'a generuje 176,660 kombinacji filtrów, w tym:
invert()
filtrów (0%, 10%, 20%, ..., 100%)sepia()
filtrów (0%, 10%, 20%, ..., 100%)saturate()
filtrów (5%, 10%, 15%, ..., 100%)hue-rotate()
filtry (0 stopni, 5 stopni, 10 stopni, ..., 360 stopni)Oblicza filtry w następującej kolejności:
Następnie wykonuje iterację przez wszystkie obliczone kolory. Zatrzymuje się, gdy znajdzie wygenerowany kolor z tolerancją (wszystkie wartości RGB mieszczą się w granicach 5 jednostek od koloru docelowego).
Jest to jednak powolne i nieefektywne. Dlatego przedstawiam własną odpowiedź.
Wdrażanie SPSA
Najpierw musimy zdefiniować funkcję strat , która zwraca różnicę między kolorem wytworzonym przez kombinację filtrów a kolorem docelowym. Jeśli filtry są doskonałe, funkcja utraty powinna zwrócić 0.
Różnicę kolorów zmierzymy jako sumę dwóch wskaźników:
hue-rotate()
, nasycenie jest skorelowane zsaturate()
itp.) To prowadzi algorytm.Funkcja straty przyjmie jeden argument - tablicę wartości procentowych filtrów.
Użyjemy następującej kolejności filtrów:
Realizacja:
Postaramy się zminimalizować funkcję straty, tak aby:
SpsA algorytm ( strona internetowa , więcej informacji , papier , papier realizacja , kod referencyjny ) jest bardzo dobry w tym. Został zaprojektowany w celu optymalizacji złożonych systemów z lokalnymi minimami, zaszumionymi / nieliniowymi / wielowymiarowymi funkcjami strat itp. Został użyty do strojenia silników szachowych . I w przeciwieństwie do wielu innych algorytmów, opisujące go artykuły są w rzeczywistości zrozumiałe (aczkolwiek z dużym wysiłkiem).
Realizacja:
Dokonałem pewnych modyfikacji / optymalizacji w SPSA:
deltas
,highArgs
,lowArgs
), zamiast odtworzyć je z każdej iteracji.fix
funkcji po każdej iteracji. Obcina wszystkie wartości od 0% do 100%, z wyjątkiemsaturate
(gdzie maksimum to 7500%)brightness
icontrast
(gdzie maksimum to 200%) ihueRotate
(gdzie wartości są zawijane zamiast zaciśniętych).Używam SPSA w dwuetapowym procesie:
Realizacja:
Tuning SPSA
Ostrzeżenie: nie mieszaj kodu SPSA, zwłaszcza jego stałych, chyba że jesteś pewien, że wiesz, co robisz.
Ważnymi stałymi są A , a , c , wartości początkowe, progi ponownych prób, wartości
max
infix()
oraz liczba iteracji każdego etapu. Wszystkie te wartości zostały starannie dostrojone, aby uzyskać dobre wyniki, a przypadkowe wkręcanie się z nimi prawie na pewno zmniejszy użyteczność algorytmu.Jeśli nalegasz na jego zmianę, musisz dokonać pomiaru przed „optymalizacją”.
Najpierw zastosuj tę poprawkę .
Następnie uruchom kod w Node.js. Po pewnym czasie wynik powinien wyglądać mniej więcej tak:
Teraz dostrój stałe do zadowolenia twojego serca.
Kilka porad:
--debug
flagi, jeśli chcesz zobaczyć wynik każdej iteracji.TL; DR
źródło
<filter>
zawierający a<feColorMatrix>
z odpowiednimi wartościami (wszystkie zera z wyjątkiem ostatniej kolumny, która zawiera docelowe RGB wartości, 0 i 1), wstaw SVG do DOM i odwołaj się do filtra z CSS. Napisz swoje rozwiązanie jako odpowiedź (z demo), a ja zagłosuję za.To była niezła wycieczka w dół króliczej nory, ale oto jest!
EDYCJA: To rozwiązanie nie jest przeznaczone do użytku produkcyjnego i ilustruje jedynie podejście, które można zastosować, aby osiągnąć to, o co prosi OP. W rzeczywistości jest słaby w niektórych obszarach spektrum kolorów. Lepsze wyniki można osiągnąć dzięki większej szczegółowości w iteracjach krokowych lub implementacji większej liczby funkcji filtrujących z powodów opisanych szczegółowo w odpowiedzi @ MultiplyByZer0 .
EDIT2: OP szuka rozwiązania innego niż brutalna siła. W takim przypadku jest to całkiem proste, po prostu rozwiąż to równanie:
gdzie
źródło
255,0,255
, mój cyfrowy kolorowy miernik zgłasza wynik jako#d619d9
zamiast#ff00ff
.clamp
?Uwaga: OP poprosił mnie o cofnięcie usunięcia , ale nagroda zostanie przyznana za odpowiedź Dave'a.
Wiem, że nie o to pytano w treści pytania, a już na pewno nie o to, na co wszyscy czekaliśmy, ale jest jeden filtr CSS, który robi dokładnie to:
drop-shadow()
Ostrzeżenia:
źródło
background-color: black;
do.icon>span
powoduje, że działa to dla FF 69b. Jednak nie wyświetla ikony.Możesz to wszystko bardzo uprościć, używając po prostu filtra SVG, do którego odwołuje się CSS. Potrzebujesz tylko jednego feColorMatrix, aby wykonać ponowne kolorowanie. Ten zmienia kolor na żółty. Piąta kolumna w feColorMatrix zawiera docelowe wartości RGB w skali jednostkowej. (dla żółtego - 1,1,0)
źródło
hue-rotate
w przeglądarkach byłaby fajna.url
funkcji caniuse.com/#search=svg%20filterZauważyłem, że przykład obróbki przez filtr SVG był niekompletny, napisałem swój (który działa idealnie): (patrz odpowiedź Michaela Mullany'ego), więc oto sposób na uzyskanie dowolnego koloru:
Pokaż fragment kodu
Oto drugie rozwiązanie, używając filtru SVG tylko w code => URL.createObjectURL
Pokaż fragment kodu
źródło
po prostu użyj
fill
Nieruchomość w CSS jest do wypełnienia w kolorze kształcie SVG.fill
Nieruchomość może przyjąć dowolną wartość koloru CSS.źródło
img
elementu przez przeglądarkę.Zacząłem od tej odpowiedzi używając filtru svg i wprowadziłem następujące modyfikacje:
Filtr SVG z adresu URL danych
Jeśli nie chcesz definiować filtra SVG gdzieś w swoim znaczniku, możesz zamiast tego użyć adresu URL danych (zamień R , G , B i A na żądany kolor):
Powrót do skali szarości
Jeśli powyższa wersja nie działa, możesz również dodać rezerwę w skali szarości.
Funkcje
saturate
ibrightness
zmieniają dowolny kolor na czarny (nie trzeba tego uwzględniać, jeśli kolor jest już czarny),invert
a następnie rozjaśnia go żądaną jasnością ( L ) i opcjonalnie można również określić krycie ( A ).Mieszanie SCSS
Jeśli chcesz dynamicznie określić kolor, możesz użyć następującej mieszanki SCSS:
Przykładowe użycie:
Zalety:
hue-rotate
.Ostrzeżenia:
źródło