Minha pergunta é: dada uma cor RGB de destino, qual é a fórmula para recolorir preto ( #000
) nessa cor usando apenas filtros CSS ?
Para uma resposta ser aceita, seria necessário fornecer uma função (em qualquer idioma) que aceitaria a cor de destino como um argumento e retornaria a filter
string CSS correspondente .
O contexto para isso é a necessidade de recolorir um SVG dentro de um background-image
. Nesse caso, é para oferecer suporte a certos recursos matemáticos do TeX no KaTeX: https://github.com/Khan/KaTeX/issues/587 .
Exemplo
Se a cor alvo for #ffff00
(amarelo), uma solução correta é:
filter: invert(100%) sepia() saturate(10000%) hue-rotate(0deg)
( demonstração )
Sem objetivos
- Animação.
- Soluções sem filtro CSS.
- Começando com uma cor diferente do preto.
- Preocupar-se com o que acontece com outras cores além do preto.
Resultados até agora
Pesquisa de força bruta para parâmetros de uma lista de filtros fixos: https://stackoverflow.com/a/43959856/181228
Contras: ineficiente, gera apenas algumas das 16.777.216 cores possíveis (676.248 comhueRotateStep=1
).Uma solução de pesquisa mais rápida usando SPSA : https://stackoverflow.com/a/43960991/181228 Bounty concedido
Uma
drop-shadow
solução: https://stackoverflow.com/a/43959853/181228
Contras: não funciona no Edge. Requerfilter
alterações não CSS e pequenas alterações de HTML.
Você ainda pode obter uma resposta Aceito enviando uma solução sem força bruta!
Recursos
Como
hue-rotate
esepia
são calculados: https://stackoverflow.com/a/29521147/181228 Exemplo de implementação de Ruby: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
Observe que o
clamp
acima torna ahue-rotate
função não linear.Demonstração: Como obter uma cor sem tons de cinza a partir de uma cor em tons de cinza: https://stackoverflow.com/a/25524145/181228
Uma fórmula que quase funciona (de uma pergunta semelhante ):
https://stackoverflow.com/a/29958459/181228Uma explicação detalhada de por que a fórmula acima está errada (CSS
hue-rotate
não é uma rotação de matiz verdadeira, mas uma aproximação linear):
https://stackoverflow.com/a/19325417/2441511
fonte
Respostas:
@Dave foi o primeiro a postar uma resposta para isso (com código funcional), e sua resposta foi uma fonte inestimável de
cópia sem vergonha einspiraçãocoladapara mim. Esta postagem começou como uma tentativa de explicar e refinar a resposta de @ Dave, mas desde então evoluiu para uma resposta própria.Meu método é significativamente mais rápido. De acordo com um benchmark jsPerf em cores RGB geradas aleatoriamente, o algoritmo de @ Dave é executado em 600 ms , enquanto o meu é executado em 30 ms . Isso pode ser importante, por exemplo, no tempo de carregamento, onde a velocidade é crítica.
Além disso, para algumas cores, meu algoritmo tem melhor desempenho:
rgb(0,255,0)
, @ Dave's produzrgb(29,218,34)
e produzrgb(1,255,0)
rgb(0,0,255)
, @ Dave's produzrgb(37,39,255)
e mina produzrgb(5,6,255)
rgb(19,11,118)
, @ Dave's produzrgb(36,27,102)
e mina produzrgb(20,11,112)
Demo
Uso
Explicação
Começaremos escrevendo algum Javascript.
Explicação:
Color
classe representa uma cor RGB.toString()
função retorna a cor em umargb(...)
string de cores CSS .hsl()
função retorna a cor, convertida em HSL .clamp()
função garante que um determinado valor de cor esteja dentro dos limites (0-255).Solver
classe tentará encontrar uma cor-alvo.css()
função retorna um determinado filtro em uma string de filtro CSS.Implementação
grayscale()
,sepia()
esaturate()
O coração dos filtros CSS / SVG são os primitivos de filtro , que representam modificações de baixo nível em uma imagem.
Os filtros
grayscale()
,sepia()
esaturate()
são implementados pelo filtro primário<feColorMatrix>
, que realiza a multiplicação da matriz entre uma matriz especificada pelo filtro (geralmente gerada dinamicamente) e uma matriz criada a partir da cor. Diagrama:Existem algumas otimizações que podemos fazer aqui:
1
. Não adianta calcular ou armazená-lo.A
), já que estamos lidando com RGB, não RGBA.<feColorMatrix>
filtros deixam as colunas 4 e 5 como zeros. Portanto, podemos reduzir ainda mais a matriz do filtro para 3x3 .Implementação:
(Usamos variáveis temporárias para manter os resultados de cada multiplicação de linha, porque não queremos mudanças para
this.r
etc. afetando os cálculos subsequentes.)Agora que temos implementado
<feColorMatrix>
, podemos implementargrayscale()
,sepia()
esaturate()
, que simplesmente invocá-lo com uma dada matriz filtro:Implementando
hue-rotate()
O
hue-rotate()
filtro é implementado por<feColorMatrix type="hueRotate" />
.A matriz do filtro é calculada conforme mostrado abaixo:
Por exemplo, o elemento a 00 seria calculado assim:
Algumas notas:
Math.sin()
ouMath.cos()
.Math.sin(angle)
eMath.cos(angle)
deve ser computado uma vez e depois armazenado em cache.Implementação:
Implementando
brightness()
econtrast()
Os filtros
brightness()
econtrast()
são implementados por<feComponentTransfer>
com<feFuncX type="linear" />
.Cada
<feFuncX type="linear" />
elemento aceita um atributo de inclinação e interceptação . Em seguida, calcula cada novo valor de cor por meio de uma fórmula simples:Isso é fácil de implementar:
Depois de implementado,
brightness()
e tambémcontrast()
pode ser implementado:Implementando
invert()
O
invert()
filtro é implementado por<feComponentTransfer>
com<feFuncX type="table" />
.A especificação afirma:
Uma explicação desta fórmula:
invert()
filtro define esta tabela: [valor, 1 - valor]. Este é tableValues ou v .Assim, podemos simplificar a fórmula para:
Incorporando os valores da tabela, ficamos com:
Mais uma simplificação:
A especificação define C e C ' como valores RGB, dentro dos limites 0-1 (em oposição a 0-255). Como resultado, devemos reduzir os valores antes do cálculo e aumentá-los novamente depois.
Assim, chegamos à nossa implementação:
Interlúdio: algoritmo de força bruta de @ Dave
O código de @ Dave gera 176.660 combinações de filtros, incluindo:
invert()
filtros (0%, 10%, 20%, ..., 100%)sepia()
filtros (0%, 10%, 20%, ..., 100%)saturate()
filtros (5%, 10%, 15%, ..., 100%)hue-rotate()
filtros (0deg, 5deg, 10deg, ..., 360deg)Ele calcula os filtros na seguinte ordem:
Em seguida, itera por todas as cores calculadas. Ele para quando encontra uma cor gerada dentro da tolerância (todos os valores RGB estão dentro de 5 unidades da cor de destino).
No entanto, isso é lento e ineficiente. Assim, apresento minha própria resposta.
Implementando SPSA
Primeiro, devemos definir uma função de perda , que retorna a diferença entre a cor produzida por uma combinação de filtros e a cor alvo. Se os filtros forem perfeitos, a função de perda deve retornar 0.
Mediremos a diferença de cores como a soma de duas métricas:
hue-rotate()
, saturação se correlaciona comsaturate()
etc.) Isso orienta o algoritmo.A função de perda terá um argumento - uma matriz de porcentagens de filtro.
Usaremos a seguinte ordem de filtro:
Implementação:
Tentaremos minimizar a função de perda, de modo que:
O algoritmo SPSA ( website , mais informações , artigo , documento de implementação , código de referência ) é muito bom nisso. Ele foi projetado para otimizar sistemas complexos com mínimos locais, funções de perda com ruído / não linear / multivariada, etc. Ele tem sido usado para ajustar motores de xadrez . E, ao contrário de muitos outros algoritmos, os documentos que o descrevem são realmente compreensíveis (embora com grande esforço).
Implementação:
Fiz algumas modificações / otimizações no SPSA:
deltas
,highArgs
,lowArgs
), em vez de criá-los com cada iteração.fix
função após cada iteração. Ele fixa todos os valores entre 0% e 100%, excetosaturate
(onde o máximo é 7500%),brightness
econtrast
(onde o máximo é 200%) ehueRotate
(onde os valores são agrupados em vez de fixados).Eu uso o SPSA em um processo de duas etapas:
Implementação:
Ajustando SPSA
Aviso: não mexa com o código SPSA, especialmente com suas constantes, a menos que tenha certeza de que sabe o que está fazendo.
As constantes importantes são A , a , c , os valores iniciais, os limites de novas tentativas, os valores de
max
infix()
e o número de iterações de cada estágio. Todos esses valores foram cuidadosamente ajustados para produzir bons resultados, e misturá-los aleatoriamente quase definitivamente reduzirá a utilidade do algoritmo.Se você insiste em alterá-lo, deve medir antes de "otimizar".
Primeiro, aplique este patch .
Em seguida, execute o código em Node.js. Depois de algum tempo, o resultado deve ser mais ou menos assim:
Agora sintonize as constantes de acordo com o seu conteúdo.
Algumas dicas:
--debug
sinalizador se quiser ver o resultado de cada iteração.TL; DR
fonte
<filter>
contendo a<feColorMatrix>
com os valores adequados (todos os zeros exceto a última coluna, que contém o RGB de destino valores, 0 e 1), insira o SVG no DOM e faça referência ao filtro do CSS. Escreva sua solução como uma resposta (com uma demonstração) e eu irei votar a favor.Esta foi uma viagem e tanto pela toca do coelho, mas aqui está!
EDITAR: Esta solução não se destina ao uso em produção e apenas ilustra uma abordagem que pode ser adotada para alcançar o que o OP está pedindo. Como está, é fraco em algumas áreas do espectro de cores. Melhores resultados podem ser alcançados com mais granularidade nas iterações das etapas ou com a implementação de mais funções de filtro pelos motivos descritos em detalhes na resposta de @ MultiplyByZer0 .
EDIT2: OP está procurando uma solução sem força bruta. Nesse caso, é muito simples, basta resolver esta equação:
Onde
fonte
255,0,255
meu medidor de cor digitais relata o resultado como#d619d9
em vez de#ff00ff
.clamp
?Nota: OP me pediu para desfazer a exclusão , mas a recompensa irá para a resposta de Dave.
Eu sei que não é o que foi perguntado no corpo da pergunta e certamente não é o que todos estávamos esperando, mas há um filtro CSS que faz exatamente isso:
drop-shadow()
Ressalvas :
fonte
background-color: black;
a.icon>span
torna esse trabalho para FF 69b. No entanto, não mostra o ícone.Você pode tornar tudo isso muito simples usando apenas um filtro SVG referenciado em CSS. Você só precisa de um único feColorMatrix para fazer uma recoloração. Este muda de cor para amarelo. A quinta coluna em feColorMatrix contém os valores alvo RGB na escala unitária. (para amarelo - é 1,1,0)
fonte
hue-rotate
em navegadores seria bom sim.url
função caniuse.com/#search=svg%20filterPercebi que o exemplo do tratamento por meio de um filtro SVG estava incompleto, escrevi o meu (que funciona perfeitamente): (ver a resposta de Michael Mullany) então aqui está a maneira de obter a cor que você quiser:
Exibir trecho de código
Aqui está uma segunda solução, usando o filtro SVG apenas em code => URL.createObjectURL
Exibir trecho de código
fonte
Apenas use
A
fill
propriedade em CSS é para preencher a cor de uma forma SVG. Afill
propriedade pode aceitar qualquer valor de cor CSS.fonte
img
elemento pelo navegador.Comecei com esta resposta usando um filtro SVG e fiz as seguintes modificações:
Filtro SVG do url de dados
Se não quiser definir o filtro SVG em algum lugar da marcação, você pode usar um URL de dados (substitua R , G , B e A pela cor desejada):
Fallback em tons de cinza
Se a versão acima não funcionar, você também pode adicionar um substituto da escala de cinza.
As funções
saturate
ebrightness
transformam qualquer cor em preto (você não precisa incluir isso se a cor já for preta),invert
então a ilumina com a claridade desejada ( L ) e, opcionalmente, você também pode especificar a opacidade ( A ).SCSS mixin
Se você deseja especificar a cor dinamicamente, pode usar o seguinte mixin SCSS:
Exemplo de uso:
Vantagens:
hue-rotate
.Ressalvas:
fonte