Ma question est la suivante: étant donné une couleur RVB cible, quelle est la formule pour recolorer le noir ( #000
) dans cette couleur en utilisant uniquement des filtres CSS ?
Pour qu'une réponse soit acceptée, il faudrait fournir une fonction (dans n'importe quelle langue) qui accepterait la couleur cible comme argument et renverrait la filter
chaîne CSS correspondante .
Le contexte pour cela est la nécessité de recolorer un SVG dans un fichier background-image
. Dans ce cas, il s'agit de prendre en charge certaines fonctionnalités mathématiques TeX dans KaTeX: https://github.com/Khan/KaTeX/issues/587 .
Exemple
Si la couleur cible est #ffff00
(jaune), une solution correcte est:
filter: invert(100%) sepia() saturate(10000%) hue-rotate(0deg)
( démo )
Non-buts
- Animation.
- Solutions sans filtre CSS.
- À partir d'une couleur autre que le noir.
- Se soucier de ce qui arrive aux couleurs autres que le noir.
Résultats à ce jour
Recherche par force brute pour les paramètres d'une liste de filtres fixes: https://stackoverflow.com/a/43959856/181228
Inconvénients: inefficace, ne génère que certaines des 16 777 216 couleurs possibles (676 248 avechueRotateStep=1
).Une solution de recherche plus rapide utilisant SPSA : https://stackoverflow.com/a/43960991/181228 Bounty attribué
Une
drop-shadow
solution: https://stackoverflow.com/a/43959853/181228
Inconvénients: ne fonctionne pas sur Edge. Nécessite desfilter
modifications non- CSS et des modifications HTML mineures.
Vous pouvez toujours obtenir une réponse acceptée en soumettant une solution sans force brute!
Ressources
Comment
hue-rotate
etsepia
sont calculés: https://stackoverflow.com/a/29521147/181228 Exemple d'implémentation 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
Notez que ce qui
clamp
précède rend lahue-rotate
fonction non linéaire.Démo: obtenir une couleur sans niveaux de gris à partir d'une couleur en niveaux de gris: https://stackoverflow.com/a/25524145/181228
Une formule qui fonctionne presque (à partir d'une question similaire ):
https://stackoverflow.com/a/29958459/181228Une explication détaillée des raisons pour lesquelles la formule ci-dessus est fausse (CSS
hue-rotate
n'est pas une vraie rotation de teinte mais une approximation linéaire):
https://stackoverflow.com/a/19325417/2441511
la source
Réponses:
@Dave a été le premier à poster une réponse à cela (avec un code de travail), et sa réponse a été une source inestimable d' inspiration pour
copier et coller sans vergogne. Cet article a commencé comme une tentative d'expliquer et d'affiner la réponse de @ Dave, mais il a depuis évolué pour devenir une réponse à part entière.Ma méthode est nettement plus rapide. Selon un benchmark jsPerf sur les couleurs RVB générées aléatoirement, l'algorithme de @ Dave s'exécute en 600 ms , tandis que le mien s'exécute en 30 ms . Cela peut certainement avoir une importance, par exemple en temps de chargement, où la vitesse est essentielle.
De plus, pour certaines couleurs, mon algorithme fonctionne mieux:
rgb(0,255,0)
, @ Dave's produitrgb(29,218,34)
et produitrgb(1,255,0)
rgb(0,0,255)
, @ Dave's produitrgb(37,39,255)
et le mien produitrgb(5,6,255)
rgb(19,11,118)
, @ Dave's produitrgb(36,27,102)
et le mien produitrgb(20,11,112)
Démo
Usage
Explication
Nous allons commencer par écrire du Javascript.
Explication:
Color
classe représente une couleur RVB.toString()
fonction renvoie la couleur dans unergb(...)
chaîne de couleurs CSS .hsl()
fonction renvoie la couleur, convertie en TSL .clamp()
fonction garantit qu'une valeur de couleur donnée est dans les limites (0-255).Solver
classe tentera de trouver une couleur cible.css()
fonction renvoie un filtre donné dans une chaîne de filtre CSS.La mise en œuvre
grayscale()
,sepia()
etsaturate()
Le cœur des filtres CSS / SVG sont les primitives de filtre , qui représentent des modifications de bas niveau d'une image.
Les filtres
grayscale()
,sepia()
etsaturate()
sont implémentés par la primative de filtre<feColorMatrix>
, qui effectue une multiplication de matrice entre une matrice spécifiée par le filtre (souvent générée dynamiquement) et une matrice créée à partir de la couleur. Diagramme:Il y a quelques optimisations que nous pouvons faire ici:
1
. Il ne sert à rien de le calculer ou de le stocker.A
) non plus, puisque nous avons affaire à RVB, pas à RVBA.<feColorMatrix>
filtres laissent les colonnes 4 et 5 sous forme de zéros. Par conséquent, nous pouvons réduire davantage la matrice de filtre à 3x3 .La mise en oeuvre:
(Nous utilisons des variables temporaires pour conserver les résultats de chaque multiplication de lignes, car nous ne voulons pas que des modifications
this.r
, etc. affectent les calculs ultérieurs.)Maintenant que nous avons mis en place
<feColorMatrix>
, nous pouvons mettre en œuvregrayscale()
,sepia()
etsaturate()
qui tout simplement l' invoquons avec une matrice de filtre donné:Exécution
hue-rotate()
Le
hue-rotate()
filtre est implémenté par<feColorMatrix type="hueRotate" />
.La matrice de filtre est calculée comme indiqué ci-dessous:
Par exemple, l'élément a 00 serait calculé comme ceci:
Quelques notes:
Math.sin()
ouMath.cos()
.Math.sin(angle)
etMath.cos(angle)
doit être calculé une fois, puis mis en cache.La mise en oeuvre:
Mettre en œuvre
brightness()
etcontrast()
Les filtres
brightness()
etcontrast()
sont implémentés par<feComponentTransfer>
avec<feFuncX type="linear" />
.Chaque
<feFuncX type="linear" />
élément accepte un attribut de pente et d' interception . Il calcule ensuite chaque nouvelle valeur de couleur via une formule simple:C'est facile à mettre en œuvre:
Une fois que cela est implémenté,
brightness()
etcontrast()
peut également être implémenté:Exécution
invert()
Le
invert()
filtre est implémenté par<feComponentTransfer>
avec<feFuncX type="table" />
.La spécification stipule:
Une explication de cette formule:
invert()
filtre définit cette table: [valeur, 1 - valeur]. C'est tableValues ou v .Ainsi, nous pouvons simplifier la formule pour:
En incorporant les valeurs de la table, nous nous retrouvons avec:
Encore une simplification:
La spécification définit C et C ' comme étant des valeurs RVB, dans les limites 0-1 (par opposition à 0-255). En conséquence, nous devons réduire les valeurs avant le calcul et les redimensionner après.
Nous arrivons ainsi à notre implémentation:
Interlude: l'algorithme de force brute de @ Dave
@ Le code de Dave génère 176 660 combinaisons de filtres, dont:
invert()
filtres (0%, 10%, 20%, ..., 100%)sepia()
filtres (0%, 10%, 20%, ..., 100%)saturate()
filtres (5%, 10%, 15%, ..., 100%)hue-rotate()
filtres (0deg, 5deg, 10deg, ..., 360deg)Il calcule les filtres dans l'ordre suivant:
Il parcourt ensuite toutes les couleurs calculées. Il s'arrête une fois qu'il a trouvé une couleur générée dans la tolérance (toutes les valeurs RVB sont à moins de 5 unités de la couleur cible).
Cependant, cela est lent et inefficace. Ainsi, je présente ma propre réponse.
Mise en œuvre de SPSA
Tout d'abord, nous devons définir une fonction de perte , qui renvoie la différence entre la couleur produite par une combinaison de filtres et la couleur cible. Si les filtres sont parfaits, la fonction de perte doit renvoyer 0.
Nous mesurerons la différence de couleur comme la somme de deux métriques:
hue-rotate()
, la saturation est corrélée avecsaturate()
, etc.) Ceci guide l'algorithme.La fonction de perte prendra un argument - un tableau de pourcentages de filtre.
Nous utiliserons l'ordre de filtrage suivant:
La mise en oeuvre:
Nous allons essayer de minimiser la fonction de perte, de sorte que:
L' algorithme SPSA ( site Web , plus d'infos , papier , document de mise en œuvre , code de référence ) est très bon à cela. Il a été conçu pour optimiser des systèmes complexes avec des minima locaux, des fonctions de perte bruyante / non linéaire / multivariée, etc. Il a été utilisé pour régler les moteurs d'échecs . Et contrairement à de nombreux autres algorithmes, les articles qui le décrivent sont en fait compréhensibles (bien qu'avec beaucoup d'efforts).
La mise en oeuvre:
J'ai apporté quelques modifications / optimisations à SPSA:
deltas
,highArgs
,lowArgs
), au lieu de les recréer à chaque itération.fix
fonction après chaque itération. Il fixe toutes les valeurs entre 0% et 100%, saufsaturate
(où le maximum est 7500%),brightness
etcontrast
(où le maximum est 200%), ethueRotate
(où les valeurs sont enroulées au lieu de serrées).J'utilise SPSA dans un processus en deux étapes:
La mise en oeuvre:
Réglage SPSA
Attention: ne jouez pas avec le code SPSA, en particulier avec ses constantes, sauf si vous êtes sûr de savoir ce que vous faites.
Les constantes importantes sont A , a , c , les valeurs initiales, les seuils de nouvelle tentative, les valeurs de
max
infix()
et le nombre d'itérations de chaque étape. Toutes ces valeurs ont été soigneusement ajustées pour produire de bons résultats, et les visser aléatoirement avec elles réduira presque certainement l'utilité de l'algorithme.Si vous insistez pour le modifier, vous devez mesurer avant de «l'optimiser».
Tout d'abord, appliquez ce patch .
Ensuite, exécutez le code dans Node.js. Après un certain temps, le résultat devrait ressembler à ceci:
Réglez maintenant les constantes à votre guise.
Quelques conseils:
--debug
indicateur si vous souhaitez voir le résultat de chaque itération.TL; DR
la source
<filter>
contenant un<feColorMatrix>
avec les valeurs appropriées (tous les zéros sauf la dernière colonne, qui contient le RVB cible valeurs, 0 et 1), insérez le SVG dans le DOM et référencez le filtre à partir de CSS. Veuillez rédiger votre solution en tant que réponse (avec une démo) et je voterai pour.Ce fut tout un voyage dans le terrier du lapin mais le voici!
EDIT: Cette solution n'est pas destinée à une utilisation en production et illustre uniquement une approche qui peut être adoptée pour atteindre ce que OP demande. En l'état, il est faible dans certaines zones du spectre de couleurs. De meilleurs résultats peuvent être obtenus par plus de granularité dans les itérations d'étape ou en implémentant plus de fonctions de filtrage pour les raisons décrites en détail dans la réponse de @ MultiplyByZer0 .
EDIT2: OP recherche une solution sans force brute. Dans ce cas, c'est assez simple, résolvez simplement cette équation:
où
la source
255,0,255
, mon colorimètre numérique rapporte le résultat#d619d9
plutôt que#ff00ff
.clamp
?Remarque: OP m'a demandé d'annuler la suppression , mais la prime ira à la réponse de Dave.
Je sais que ce n'est pas ce qui a été demandé dans le corps de la question, et certainement pas ce que nous attendions tous, mais il y a un filtre CSS qui fait exactement cela:
drop-shadow()
Mises en garde:
la source
background-color: black;
de.icon>span
rend ce travail pour FF 69b. Cependant, n'affiche pas l'icône.Vous pouvez rendre tout cela très simple en utilisant simplement un filtre SVG référencé à partir de CSS. Vous n'avez besoin que d'une seule feColorMatrix pour effectuer une recoloration. Celui-ci se recolore au jaune. La cinquième colonne de feColorMatrix contient les valeurs cibles RVB sur l'échelle des unités. (pour le jaune - c'est 1,1,0)
la source
hue-rotate
dans les navigateurs serait bien ouais.url
fonction caniuse.com/#search=svg%20filterJ'ai remarqué que l'exemple du traitement via un filtre SVG était incomplet, j'ai écrit le mien (qui fonctionne parfaitement): (voir la réponse de Michael Mullany) alors voici le moyen d'obtenir la couleur que vous voulez:
Afficher l'extrait de code
Voici une deuxième solution, en utilisant le filtre SVG uniquement dans code => URL.createObjectURL
Afficher l'extrait de code
la source
juste utiliser
La
fill
propriété en CSS est de remplir la couleur d'une forme SVG. Lafill
propriété peut accepter n'importe quelle valeur de couleur CSS.la source
img
élément par le navigateur.J'ai commencé avec cette réponse en utilisant un filtre svg et j'ai apporté les modifications suivantes:
Filtre SVG à partir de l'URL des données
Si vous ne souhaitez pas définir le filtre SVG quelque part dans votre balisage, vous pouvez utiliser une URL de données à la place (remplacez R , V , B et A par la couleur souhaitée):
Repli en niveaux de gris
Si la version ci-dessus ne fonctionne pas, vous pouvez également ajouter une solution de secours en niveaux de gris.
Les fonctions
saturate
etbrightness
transforment n'importe quelle couleur en noir (vous n'avez pas à l'inclure si la couleur est déjà noire),invert
puis l'éclaircit avec la luminosité souhaitée ( L ) et vous pouvez également spécifier l'opacité ( A ).Mixin SCSS
Si vous souhaitez spécifier la couleur de manière dynamique, vous pouvez utiliser le mixin SCSS suivant:
Exemple d'utilisation:
Avantages:
hue-rotate
.Mises en garde:
la source