Mi pregunta es: dado un color RGB de destino, ¿cuál es la fórmula para cambiar el color del negro ( #000
) a ese color usando solo filtros CSS ?
Para que se acepte una respuesta, debería proporcionar una función (en cualquier idioma) que acepte el color de destino como argumento y devuelva la filter
cadena CSS correspondiente .
El contexto para esto es la necesidad de cambiar el color de un SVG dentro de un archivo background-image
. En este caso, es para admitir ciertas funciones matemáticas de TeX en KaTeX: https://github.com/Khan/KaTeX/issues/587 .
Ejemplo
Si el color de destino es #ffff00
(amarillo), una solución correcta es:
filter: invert(100%) sepia() saturate(10000%) hue-rotate(0deg)
( demo )
No goles
- Animación.
- Soluciones sin filtro CSS.
- A partir de un color que no sea el negro.
- Preocuparse por lo que sucede con los colores distintos del negro.
Resultados hasta ahora
Búsqueda por fuerza bruta de parámetros de una lista de filtros fija: https://stackoverflow.com/a/43959856/181228
Contras: ineficiente, solo genera algunos de los 16.777.216 colores posibles (676.248 conhueRotateStep=1
).Una solución de búsqueda más rápida usando SPSA : https://stackoverflow.com/a/43960991/181228 Recompensa otorgada
Una
drop-shadow
solución: https://stackoverflow.com/a/43959853/181228
Contras: no funciona en Edge. Requierefilter
cambios no CSS y cambios HTML menores.
¡Aún puede obtener una respuesta Aceptada enviando una solución sin fuerza bruta!
Recursos
Cómo
hue-rotate
ysepia
se calculan: https://stackoverflow.com/a/29521147/181228 Ejemplo de implementación 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
Tenga en cuenta que lo
clamp
anterior hace que lahue-rotate
función no sea lineal.Demostración: Cómo obtener un color sin escala de grises desde un color en escala de grises: https://stackoverflow.com/a/25524145/181228
Una fórmula que casi funciona (de una pregunta similar ):
https://stackoverflow.com/a/29958459/181228Una explicación detallada de por qué la fórmula anterior es incorrecta (CSS
hue-rotate
no es una verdadera rotación de tono, sino una aproximación lineal):
https://stackoverflow.com/a/19325417/2441511
fuente
Respuestas:
@Dave fue el primero en publicar una respuesta a esto (con código de trabajo), y su respuesta ha sido una fuente invaluable de
copia descarada einspiración parapegarpara mí. Esta publicación comenzó como un intento de explicar y refinar la respuesta de @ Dave, pero desde entonces se ha convertido en una respuesta propia.Mi método es significativamente más rápido. De acuerdo con un punto de referencia de jsPerf sobre colores RGB generados aleatoriamente, el algoritmo de @ Dave se ejecuta en 600 ms , mientras que el mío se ejecuta en 30 ms . Esto definitivamente puede importar, por ejemplo, en el tiempo de carga, donde la velocidad es crítica.
Además, para algunos colores, mi algoritmo funciona mejor:
rgb(0,255,0)
@ Dave's producergb(29,218,34)
y producergb(1,255,0)
rgb(0,0,255)
@ Dave's producergb(37,39,255)
y el mío producergb(5,6,255)
rgb(19,11,118)
@ Dave's producergb(36,27,102)
y el mío producergb(20,11,112)
Manifestación
Uso
Explicación
Comenzaremos escribiendo algo de Javascript.
Explicación:
Color
clase representa un color RGB.toString()
función devuelve el color en unargb(...)
cadena de colores CSS .hsl()
función devuelve el color, convertido a HSL .clamp()
función asegura que un valor de color dado esté dentro de los límites (0-255).Solver
clase intentará encontrar un color objetivo.css()
función devuelve un filtro dado en una cadena de filtro CSS.Implementando
grayscale()
,sepia()
ysaturate()
El corazón de los filtros CSS / SVG son los filtros primitivos , que representan modificaciones de bajo nivel en una imagen.
Los filtros
grayscale()
,sepia()
ysaturate()
son implementados por el filtro primativo<feColorMatrix>
, que realiza la multiplicación de matrices entre una matriz especificada por el filtro (a menudo generada dinámicamente) y una matriz creada a partir del color. Diagrama:Hay algunas optimizaciones que podemos hacer aquí:
1
. No tiene sentido calcularlo o almacenarlo.A
), ya que estamos tratando con RGB, no con RGBA.<feColorMatrix>
filtros dejan las columnas 4 y 5 como ceros. Por lo tanto, podemos reducir aún más la matriz del filtro a 3x3 .Implementación:
(Usamos variables temporales para contener los resultados de cada multiplicación de filas, porque no queremos cambios en
this.r
, etc., afecten los cálculos posteriores).Ahora que lo hemos implementado
<feColorMatrix>
, podemos implementargrayscale()
,sepia()
ysaturate()
, que simplemente lo invocan con una matriz de filtro dada:Implementar
hue-rotate()
El
hue-rotate()
filtro es implementado por<feColorMatrix type="hueRotate" />
.La matriz de filtro se calcula como se muestra a continuación:
Por ejemplo, el elemento a 00 se calcularía así:
Algunas notas:
Math.sin()
oMath.cos()
.Math.sin(angle)
yMath.cos(angle)
debe calcularse una vez y luego almacenarse en caché.Implementación:
Implementar
brightness()
ycontrast()
Los filtros
brightness()
ycontrast()
se implementan con<feComponentTransfer>
con<feFuncX type="linear" />
.Cada
<feFuncX type="linear" />
elemento acepta un atributo de pendiente e intersección . Luego calcula cada nuevo valor de color mediante una fórmula simple:Esto es fácil de implementar:
Una vez que esto se implementa,
brightness()
y tambiéncontrast()
se puede implementar:Implementar
invert()
El
invert()
filtro se implementa con<feComponentTransfer>
con<feFuncX type="table" />
.La especificación dice:
Una explicación de esta fórmula:
invert()
filtro define esta tabla: [valor, 1 - valor]. Esto es tableValues o v .Por tanto, podemos simplificar la fórmula a:
Al incluir los valores de la tabla, nos queda:
Una simplificación más:
La especificación define C y C ' como valores RGB, dentro de los límites 0-1 (a diferencia de 0-255). Como resultado, debemos reducir la escala de los valores antes del cálculo y volver a escalarlos después.
Así llegamos a nuestra implementación:
Interludio: algoritmo de fuerza bruta de @ Dave
El código de @ Dave genera 176,660 combinaciones de filtros, que incluyen:
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)Calcula los filtros en el siguiente orden:
Luego, recorre todos los colores calculados. Se detiene una vez que ha encontrado un color generado dentro de la tolerancia (todos los valores RGB están dentro de las 5 unidades del color de destino).
Sin embargo, esto es lento e ineficaz. Por lo tanto, presento mi propia respuesta.
Implementación de SPSA
Primero, debemos definir una función de pérdida , que devuelva la diferencia entre el color producido por una combinación de filtros y el color de destino. Si los filtros son perfectos, la función de pérdida debería devolver 0.
Mediremos la diferencia de color como la suma de dos métricas:
hue-rotate()
, la saturación se correlaciona consaturate()
, etc.) Esto guía el algoritmo.La función de pérdida tomará un argumento: una matriz de porcentajes de filtro.
Usaremos el siguiente orden de filtro:
Implementación:
Intentaremos minimizar la función de pérdida, de manera que:
El algoritmo SPSA ( sitio web , más información , documento , documento de implementación , código de referencia ) es muy bueno en esto. Fue diseñado para optimizar sistemas complejos con mínimos locales, funciones de pérdida ruidosas / no lineales / multivariantes, etc. Se ha utilizado para ajustar motores de ajedrez . Y a diferencia de muchos otros algoritmos, los artículos que lo describen son realmente comprensibles (aunque con gran esfuerzo).
Implementación:
Hice algunas modificaciones / optimizaciones a SPSA:
deltas
,highArgs
,lowArgs
), en lugar de volver a crear con cada iteración.fix
función después de cada iteración. Fija todos los valores entre 0% y 100%, exceptosaturate
(donde el máximo es 7500%),brightness
ycontrast
(donde el máximo es 200%) yhueRotate
(donde los valores se envuelven en lugar de fijar).Utilizo SPSA en un proceso de dos etapas:
Implementación:
Tuning SPSA
Advertencia: No se meta con el código SPSA, especialmente con sus constantes, a menos que esté seguro de saber lo que está haciendo.
Las constantes importantes son A , a , c , los valores iniciales, los umbrales de reintento, los valores de
max
infix()
y el número de iteraciones de cada etapa. Todos estos valores se ajustaron cuidadosamente para producir buenos resultados, y usarlos al azar reducirá casi definitivamente la utilidad del algoritmo.Si insiste en modificarlo, debe medir antes de "optimizar".
Primero, aplique este parche .
Luego ejecute el código en Node.js. Después de bastante tiempo, el resultado debería ser algo como esto:
Ahora sintonice las constantes al contenido de su corazón.
Algunos consejos:
--debug
bandera si desea ver el resultado de cada iteración.TL; DR
fuente
<filter>
contiene a<feColorMatrix>
con los valores adecuados (todos los ceros excepto la última columna, que contiene el RGB de destino valores, 0 y 1), inserte el SVG en el DOM y haga referencia al filtro de CSS. Escriba su solución como respuesta (con una demostración) y votaré a favor.Este fue un gran viaje por la madriguera del conejo, pero aquí está!
EDITAR: Esta solución no está diseñada para uso de producción y solo ilustra un enfoque que se puede tomar para lograr lo que OP está pidiendo. Tal como está, es débil en algunas áreas del espectro de colores. Se pueden lograr mejores resultados mediante una mayor granularidad en las iteraciones de los pasos o implementando más funciones de filtro por las razones descritas en detalle en la respuesta de @ MultiplyByZer0 .
EDIT2: OP está buscando una solución sin fuerza bruta. En ese caso, es bastante simple, solo resuelve esta ecuación:
dónde
fuente
255,0,255
, mi medidor de color digital informa el resultado como en#d619d9
lugar de#ff00ff
.clamp
.Nota: OP me pidió que recuperara , pero la recompensa irá a la respuesta de Dave.
Sé que no es lo que se preguntó en el cuerpo de la pregunta, y ciertamente no es lo que todos estábamos esperando, pero hay un filtro CSS que hace exactamente esto:
drop-shadow()
Advertencias:
fuente
background-color: black;
de.icon>span
marcas este trabajo para FF 69b. Sin embargo, no muestra el icono.Puede hacer que todo esto sea muy simple con solo usar un filtro SVG referenciado desde CSS. Solo necesita una única feColorMatrix para cambiar el color. Éste se vuelve amarillo. La quinta columna de feColorMatrix contiene los valores de destino RGB en la escala unitaria. (para amarillo - es 1,1,0)
fuente
hue-rotate
en los navegadores estaría bien, sí.url
función caniuse.com/#search=svg%20filterNoté que el ejemplo del tratamiento a través de un filtro SVG estaba incompleto, escribí el mío (que funciona perfectamente): (ver la respuesta de Michael Mullany), así que aquí está la forma de obtener el color que desee:
Mostrar fragmento de código
Aquí hay una segunda solución, usando SVG Filter solo en code => URL.createObjectURL
Mostrar fragmento de código
fuente
Solo usa
La
fill
propiedad en CSS es para rellenar el color de una forma SVG. Lafill
propiedad puede aceptar cualquier valor de color CSS.fuente
img
elemento por el navegador.Comencé con esta respuesta usando un filtro svg e hice las siguientes modificaciones:
Filtro SVG de URL de datos
Si no desea definir el filtro SVG en algún lugar del marcado, puede usar una URL de datos en su lugar (reemplace R , G , B y A con el color deseado):
Retroceso en escala de grises
Si la versión anterior no funciona, también puede agregar una alternativa en escala de grises.
Las funciones
saturate
ybrightness
cambian cualquier color a negro (no tiene que incluir eso si el color ya es negro),invert
luego lo ilumina con la luminosidad deseada ( L ) y, opcionalmente, también puede especificar la opacidad ( A ).Mezcla de SCSS
Si desea especificar el color de forma dinámica, puede utilizar la siguiente mezcla SCSS:
Uso de ejemplo:
Ventajas:
hue-rotate
.Advertencias:
fuente