714 votes

Éclaircir ou assombrir de manière programmée une couleur hexagonale (ou rgb, et couleurs de mélange)

Voici une fonction sur laquelle j'ai travaillé pour éclaircir ou assombrir de manière programmatique une couleur hexagonale par une quantité spécifique. Il suffit de passer une chaîne comme "3F6D2A" pour la couleur (col) et un nombre entier en base10 (amt) pour la quantité à éclaircir ou à assombrir. Pour assombrir, entrez un nombre négatif (par exemple -20).

Je l'ai fait parce que toutes les solutions que j'ai trouvées jusqu'à présent semblaient compliquer le problème à l'excès. Et j'avais le sentiment que cela pouvait être fait avec juste quelques lignes de code. Faites-moi savoir si vous trouvez des problèmes, ou si vous avez des ajustements à faire pour accélérer le processus.

function LightenDarkenColor(col,amt) {
    col = parseInt(col,16);
    return (((col & 0x0000FF) + amt) | ((((col>> 8) & 0x00FF) + amt) << 8) | (((col >> 16) + amt) << 16)).toString(16);
}

Pour le développement, voici une version plus facile à lire :

function LightenDarkenColor(col,amt) {
    var num = parseInt(col,16);
    var r = (num >> 16) + amt;
    var b = ((num >> 8) & 0x00FF) + amt;
    var g = (num & 0x0000FF) + amt;
    var newColor = g | (b << 8) | (r << 16);
    return newColor.toString(16);
}

Et enfin une version pour gérer les couleurs qui peuvent (ou non) avoir le "#" au début. Plus l'ajustement des valeurs de couleur incorrectes :

function LightenDarkenColor(col,amt) {
    var usePound = false;
    if ( col[0] == "#" ) {
        col = col.slice(1);
        usePound = true;
    }

    var num = parseInt(col,16);

    var r = (num >> 16) + amt;

    if ( r > 255 ) r = 255;
    else if  (r < 0) r = 0;

    var b = ((num >> 8) & 0x00FF) + amt;

    if ( b > 255 ) b = 255;
    else if  (b < 0) b = 0;

    var g = (num & 0x0000FF) + amt;

    if ( g > 255 ) g = 255;
    else if  ( g < 0 ) g = 0;

    return (usePound?"#":"") + (g | (b << 8) | (r << 16)).toString(16);
}

Ok, alors maintenant ce n'est pas seulement quelques lignes, mais cela semble beaucoup plus simple et si vous n'utilisez pas le "#" et n'avez pas besoin de vérifier les couleurs hors de la gamme, c'est seulement quelques lignes.

Si vous n'utilisez pas le "#", vous pouvez simplement l'ajouter dans le code comme :

var myColor = "3F6D2A";
myColor = LightenDarkenColor(myColor,10);
thePlaceTheColorIsUsed = ("#" + myColor);

Je suppose que ma principale question est la suivante : ai-je raison ? Est-ce que cela n'englobe pas certaines situations (normales) ? Existe-t-il un moyen plus rapide ? C'est toujours KISS ?

Pimp Trizkit

1 votes

Si vous n'obtenez pas les résultats escomptés en modifiant les couleurs, je vous suggère de vous pencher sur l'espace couleur LAB, qui est plus proche de la vision humaine. De nombreux langages disposent de bibliothèques pour la conversion. D'après mon expérience, les nuances d'orange en particulier peuvent poser problème lors de l'assombrissement ou de l'éclaircissement.

0 votes

Très bon point. Cependant, le but principal de cette question était de trouver, premièrement, la formule la plus rapide et la plus petite taille... et deuxièmement, sa précision. C'est pourquoi je ne me suis pas occupé de la conversion en HSL ou autre. Ici, la vitesse et la taille sont plus importantes. Mais, comme vous pouvez le voir avec ma version 2 de la formule. L'utilisation du LERP pour nuancer donnera des oranges agréables dans toute la gamme de nuances. Jetez un coup d'œil au nuancier ci-dessous et dites-moi si cette gamme de nuances n'est pas assez proche de la réalité.

0 votes

Je me suis un peu embrouillé avec la structure ici, mais vous avez raison, les niveaux d'orange pour shadeColor1 semblent être très bons.

1069voto

Pimp Trizkit Points 1357

Après avoir réfléchi... j'ai décidé de répondre à ma propre question. Un an et demi plus tard. C'était vraiment une aventure avec les idées de plusieurs utilisateurs utiles, et je vous remercie tous ! Celle-ci est pour l'équipe ! Bien que ce ne soit pas nécessairement la réponse que je cherchais. Parce que si ce que James Khoury dit est vrai, alors il n'y a pas de véritables maths hexagonales en javascript, je dois utiliser des décimales, cette double conversion est nécessaire. Si nous faisons cette hypothèse, alors c'est probablement le moyen le plus rapide que j'ai vu (ou auquel je peux penser) pour éclaircir (ajouter du blanc) ou assombrir (ajouter du noir) une couleur RBG arbitraire en pourcentage. Cela tient également compte des problèmes mentionnés par Cool Acid dans sa réponse à cette question (il remplit les 0). Mais cette version appelle toString une seule fois. Cela tient également compte du dépassement des limites (0 et 255 seront appliqués comme limites).

Mais attention, la couleur saisie doit comporter EXACTEMENT 7 caractères, comme par exemple #08a35c . (ou 6 si vous utilisez la version supérieure)

Merci à Pablo pour l'inspiration et l'idée d'utiliser le pourcentage. Pour cela, je garderai le même nom de fonction ! lol ! Cependant, celle-ci est différente, car elle normalise le pourcentage à 255 et ajoute donc la même quantité à chaque couleur (plus de blanc). Si vous passez 100 pour percent cela rendra votre couleur blanche pure. Si vous passez 0 pour percent il ne se passera rien. Si vous passez en 1 pour percent il ajoutera 3 teintes à toutes les couleurs (2,55 teintes par 1%, arrondi). Vous passez donc réellement dans un pourcentage de blanc (ou de noir, utilisez le négatif). Cette version vous permet donc d'éclaircir le rouge pur (FF0000), par exemple.

J'ai également utilisé des éléments de la réponse de Keith Mashinter à cette question : Comment convertir des décimales en hexadécimales en JavaScript ?

J'ai supprimé certaines parenthèses, apparemment inutiles. (comme dans la déclaration ternaire double et dans l'artisanat B) Je ne sais pas si cela va perturber la précédence des opérateurs dans certains environnements. Le test est bon dans FireFox.

function shadeColor1(color, percent) {  
    var num = parseInt(color,16),
    amt = Math.round(2.55 * percent),
    R = (num >> 16) + amt,
    G = (num >> 8 & 0x00FF) + amt,
    B = (num & 0x0000FF) + amt;
    return (0x1000000 + (R<255?R<1?0:R:255)*0x10000 + (G<255?G<1?0:G:255)*0x100 + (B<255?B<1?0:B:255)).toString(16).slice(1);
}

Ou, si vous voulez qu'il gère le "#" :

function shadeColor1(color, percent) {  
    var num = parseInt(color.slice(1),16), amt = Math.round(2.55 * percent), R = (num >> 16) + amt, G = (num >> 8 & 0x00FF) + amt, B = (num & 0x0000FF) + amt;
    return "#" + (0x1000000 + (R<255?R<1?0:R:255)*0x10000 + (G<255?G<1?0:G:255)*0x100 + (B<255?B<1?0:B:255)).toString(16).slice(1);
}

Comment cela se passe-t-il pour deux lignes de code ?

EDIT : Correction de la gaffe de l'échange B<->G. Merci svachalek !

-- UPDATE - Version 2 avec Blending --

Un peu plus d'un an plus tard, à nouveau, et ça continue. Mais cette fois, je pense que c'est fini. Je note les problèmes mentionnés à propos de la non-utilisation de HSL pour éclaircir correctement la couleur. Il existe une technique qui élimine la plupart de ces imprécisions sans avoir à convertir en HSL. Le problème principal est qu'un canal de couleur sera complètement saturé avant le reste de la couleur. Ce qui provoque un décalage de la teinte après ce point. J'ai trouvé ces questions ici , ici et ici ce qui m'a mis sur la voie. Le message de Mark Ransom m'a montré la différence, et Le message de Keith m'a montré le chemin. Le Lerp est le sauveur. C'est la même chose que de mélanger les couleurs, donc j'ai créé un blendColors également la fonction.

Donc, sans plus attendre :

function shadeColor2(color, percent) {   
    var f=parseInt(color.slice(1),16),t=percent<0?0:255,p=percent<0?percent*-1:percent,R=f>>16,G=f>>8&0x00FF,B=f&0x0000FF;
    return "#"+(0x1000000+(Math.round((t-R)*p)+R)*0x10000+(Math.round((t-G)*p)+G)*0x100+(Math.round((t-B)*p)+B)).toString(16).slice(1);
}

function blendColors(c0, c1, p) {
    var f=parseInt(c0.slice(1),16),t=parseInt(c1.slice(1),16),R1=f>>16,G1=f>>8&0x00FF,B1=f&0x0000FF,R2=t>>16,G2=t>>8&0x00FF,B2=t&0x0000FF;
    return "#"+(0x1000000+(Math.round((R2-R1)*p)+R1)*0x10000+(Math.round((G2-G1)*p)+G1)*0x100+(Math.round((B2-B1)*p)+B1)).toString(16).slice(1);
}

Il n'y a pas de contrôle d'erreur, donc les valeurs hors limites transmises provoqueront des résultats inattendus. De plus, la couleur saisie doit comporter EXACTEMENT 7 caractères, par exemple #08a35c . Mais tous les autres avantages sont toujours là, comme le plafonnement de la plage de sortie (sorties 00-FF), le remplissage (0A), les poignées, etc. # et utilisable sur des couleurs unies, comme #FF0000 .

Cette nouvelle version de shadeColor prend un flottant pour son deuxième paramètre. Pour shadeColor2 la plage valable pour le deuxième paramètre (pourcentage) est la suivante -1.0 à 1.0 .

Et pour blendColors la plage valable pour le troisième paramètre (pourcentage) est la suivante 0.0 à 1.0 Les négatifs ne sont pas autorisés ici.

Cette nouvelle version ne prend plus un pourcentage de blanc pur, comme l'ancienne version. Elle prend en compte un pourcentage de la DISTANCE entre la couleur donnée et le blanc pur. Dans l'ancienne version, il était facile de saturer la couleur, et par conséquent, de nombreuses couleurs étaient calculées en blanc pur en utilisant un pourcentage important. Avec la nouvelle version, le calcul du blanc pur n'est effectué que si vous indiquez 1.0 ou du noir pur, utilisez -1.0 .

Appel à blendColors(color, "#FFFFFF", 0.5) est la même chose que shadeColor2(color,0.5) . Ainsi que, blendColors(color,"#000000", 0.5) est la même chose que shadeColor2(color,-0.5) . Juste un peu plus lentement.

shadeColor2 est plus lent que shadeColor1 mais pas de manière significative. (Attends, c'est une déclaration contradictoire !)

La précision obtenue peut être vue ici :

-- Version RGB --

function shadeRGBColor(color, percent) {
    var f=color.split(","),t=percent<0?0:255,p=percent<0?percent*-1:percent,R=parseInt(f[0].slice(4)),G=parseInt(f[1]),B=parseInt(f[2]);
    return "rgb("+(Math.round((t-R)*p)+R)+","+(Math.round((t-G)*p)+G)+","+(Math.round((t-B)*p)+B)+")";
}

function blendRGBColors(c0, c1, p) {
    var f=c0.split(","),t=c1.split(","),R=parseInt(f[0].slice(4)),G=parseInt(f[1]),B=parseInt(f[2]);
    return "rgb("+(Math.round((parseInt(t[0].slice(4))-R)*p)+R)+","+(Math.round((parseInt(t[1])-G)*p)+G)+","+(Math.round((parseInt(t[2])-B)*p)+B)+")";
}

Utilisations :

var color1 = "rbg(63,131,163)";
var lighter-color = shadeRGBColor(color1, 0.5);  //  rgb(159,193,209)
var darker-color = shadeRGBColor(color1, -0.25); //  rgb(47,98,122)

var color2 = "rbg(244,128,0)";
var blend1 = blendRGBColors(color1, color2, 0.75);  //  rgb(199,129,41)
var blend2 = blendRGBColors(color2, color1, 0.62);  //  rgb(132,130,101)

-- Version Uni --

function shade(color, percent){
    if (color.length > 7 ) return shadeRGBColor(color,percent);
    else return shadeColor2(color,percent);
}

function blend(color1, color2, percent){
    if (color1.length > 7) return blendRGBColors(color1,color2,percent);
    else return blendColors(color1,color2,percent);
}

Utilisation :

var color1 = shade("rbg(63,131,163)", 0.5);
var color2 = shade("#3f83a3", 0.5);
var color3 = blend("rbg(63,131,163)", "rbg(244,128,0)", 0.5);
var color4 = blend("#3f83a3", "#f48000" 0.5);

PT

0 votes

ShadeColor('#000000',100) = #ffffff, shadeColor('#000000',1) = #030303... on dirait que vous multipliez simplement "pourcentage" par 2,55 et que vous l'ajoutez à la couleur, ce qui n'a aucun sens pour moi. De plus, G et B semblent être inversés dans les deux formes.

0 votes

Oui, c'est ce que j'essayais de décrire dans mon texte ci-dessus. Ici, percent est le pourcentage de blanc que vous voulez ajouter. Si vous ajoutez 255, vous ajouterez du blanc complet. Si percent est de 100, c'est le blanc complet ( 2.55*100 ). Cette opération permet d'ajouter du blanc à n'importe quelle couleur. Pour ajouter du blanc à une couleur, vous devez ajouter des quantités égales de toutes les couleurs. Donc, shadeColor("#000000",1) = "#030303" ... en anglais c'est : "nuancer le noir de 1% du chemin vers le blanc". Ce qui le rend légèrement moins noir. Ça l'a "éclairci".

10 votes

Une version PHP pour ceux qui en ont besoin : gist.github.com/chaoszcat/5325115#file-gistfile1-php

255voto

Pablo Points 211

J'ai trouvé une solution qui fonctionne très bien pour moi :

function shadeColor(color, percent) {

    var R = parseInt(color.substring(1,3),16);
    var G = parseInt(color.substring(3,5),16);
    var B = parseInt(color.substring(5,7),16);

    R = parseInt(R * (100 + percent) / 100);
    G = parseInt(G * (100 + percent) / 100);
    B = parseInt(B * (100 + percent) / 100);

    R = (R<255)?R:255;  
    G = (G<255)?G:255;  
    B = (B<255)?B:255;  

    var RR = ((R.toString(16).length==1)?"0"+R.toString(16):R.toString(16));
    var GG = ((G.toString(16).length==1)?"0"+G.toString(16):G.toString(16));
    var BB = ((B.toString(16).length==1)?"0"+B.toString(16):B.toString(16));

    return "#"+RR+GG+BB;
}

Exemple Éclaircir :

shadeColor("#63C6FF",40);

Exemple Darken :

shadeColor("#63C6FF",-40);

6 votes

Joli, j'aime le pourcentage ! +1 Tho, je pourrais faire R = ((R<255)?R:255).toString(16); puis R = R.length==1 ? "0"+R : R pour la vitesse. Et je ne suis pas sûr de l'utilité du toUpperCase ?

0 votes

C'est inutile. Je ne l'ajoute que pour avoir une belle impression pendant le test. Je vais le modifier.

0 votes

Très bien. Cependant, le 100% plus clair ne devrait-il pas devenir complètement blanc et le 100% plus foncé toujours noir, quelle que soit la couleur ? Il semble que -100 rende toute couleur noire, mais que 100 (positif) ne la rende pas complètement blanche.

6voto

Cool Acid Points 45

J'ai essayé votre fonction et il y a un petit bug : Si la valeur finale de 'r' n'a qu'un seul chiffre, le résultat ressemble à : 'a0a0a' alors que la bonne valeur est '0a0a0a', par exemple. Je l'ai corrigé rapidement en ajoutant ceci à la place de votre retour :

var rStr = (r.toString(16).length < 2)?'0'+r.toString(16):r.toString(16);
var gStr = (g.toString(16).length < 2)?'0'+g.toString(16):g.toString(16);
var bStr = (b.toString(16).length < 2)?'0'+b.toString(16):b.toString(16);

return (usePound?"#":"") + rStr + gStr + bStr;

Ce n'est peut-être pas très joli, mais ça fait le travail. Super fonction, BTW. Juste ce dont j'avais besoin :)

1 votes

Merci pour le débogage et le compliment ! Dommage qu'il n'y ait pas de réponse à la question de savoir s'il existe ou non une méthode plus rapide, ce qui est ma principale question. Comme par exemple une méthode utilisant uniquement l'hexagone et sans conversion de base. Cependant, je suppose que vous m'avez dit si mon code était correct (+1). Malheureusement, la correction a ajouté considérablement plus d'overhead (maintenant vous appelez toString 6 fois), et un peu moins de KISS. Peut-être serait-il plus rapide de vérifier si le nombre en base 10 est inférieur ou égal à 15, avant la conversion en base 16. Mais, j'aime bien !

5voto

James Khoury Points 5433

Vous avez pensé à une conversion rgb > hsl ? Il suffit ensuite de déplacer la luminosité vers le haut ou vers le bas ? c'est la solution que je choisirais.

Une recherche rapide de quelques algorithmes m'a permis de trouver les sites suivants.

PHP : http://serennu.com/colour/rgbtohsl.php

Javascript : http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript

EDIT le lien ci-dessus n'est plus valide. Vous pouvez consulter le hub git pour le source de la page ou le Gist

Ou encore, un autre StackOverflow question pourrait être un bon endroit à regarder.


Même si ce n'est pas le bon choix pour le PO, ce qui suit est une approximation du code que je suggérais à l'origine. (En supposant que vous avez des fonctions de conversion rgb/hsl)

var SHADE_SHIFT_AMOUNT = 0.1; 

function lightenShade(colorValue)
{
    if(colorValue && colorValue.length >= 6)
    {
        var redValue = parseInt(colorValue.slice(-6,-4), 16);
        var greenValue = parseInt(colorValue.slice(-4,-2), 16);
        var blueValue = parseInt(colorValue.slice(-2), 16);

        var hsl = rgbToHsl(redValue, greenValue, blueValue);
        hsl[2]= Math.min(hsl[2] + SHADE_SHIFT_AMOUNT, 1);
        var rgb = hslToRgb(hsl[0], hsl[1], hsl[2]);
        return "#" + rgb[0].toString(16) + rgb[1].toString(16) + rgb[2].toString(16);
    }
    return null;
}

function darkenShade(colorValue)
{
    if(colorValue && colorValue.length >= 6)
    {
        var redValue = parseInt(colorValue.slice(-6,-4), 16);
        var greenValue = parseInt(colorValue.slice(-4,-2), 16);
        var blueValue = parseInt(colorValue.slice(-2), 16);

        var hsl = rgbToHsl(redValue, greenValue, blueValue);
        hsl[2]= Math.max(hsl[2] - SHADE_SHIFT_AMOUNT, 0);
        var rgb = hslToRgb(hsl[0], hsl[1], hsl[2]);
        return "#" + rgb[0].toString(16) + rgb[1].toString(16) + rgb[2].toString(16);
    }
    return null;
}

Cela suppose :

  1. Vous avez des fonctions hslToRgb et rgbToHsl .
  2. Le paramètre colorValue est une chaîne de caractères de la forme #RRGGBB

Bien que si nous discutons de css, il existe une syntaxe pour spécifier hsl/hsla pour IE9/Chrome/Firefox.

0 votes

Intéressant, mais alors ne devrais-je pas convertir la chaîne hexagonale en rgb et en hsl ? Ça me semble plus compliqué. Peut-être que quelque chose m'échappe. Mais, je cherche une façon KISS de le faire, aussi rapide que possible (temps d'exécution). Je pense qu'idéalement, si je pouvais faire tout cela en hexagone, ce serait le plus rapide. Mais la solution que j'ai développée ici implique de passer en rgb pour pouvoir ajouter une quantité incrémentale.

0 votes

Oui, je suppose que ce serait plus lent, plus compliqué et si vous n'utilisez pas de conversion rgb vers hsl ailleurs, ce ne serait probablement pas la solution la plus simple. Ce serait cependant plus précis que d'ajouter des valeurs rgb, bien que je ne sois pas moi-même un grand amateur de couleurs. Tout dépend de la précision que vous souhaitez obtenir, je suppose.

0 votes

Quelle est la perte de précision que vous mentionnez ? Je suppose que vous voulez dire que toutes les couleurs [web] ne sont pas atteignables avec rgb ou quelque chose comme ça ?

4voto

user1618171 Points 1

Version C#... Notez que j'obtiens des chaînes de couleurs dans ce format #FF12AE34, et que je dois couper le #FF.

    private string GetSmartShadeColorByBase(string s, float percent)
    {
        if (string.IsNullOrEmpty(s))
            return "";
        var r = s.Substring(3, 2);
        int rInt = int.Parse(r, NumberStyles.HexNumber);
        var g = s.Substring(5, 2);
        int gInt = int.Parse(g, NumberStyles.HexNumber);
        var b = s.Substring(7, 2);
        int bInt = int.Parse(b, NumberStyles.HexNumber);

        var t = percent < 0 ? 0 : 255;
        var p = percent < 0 ? percent*-1 : percent;

        int newR = Convert.ToInt32(Math.Round((t - rInt) * p) + rInt);
        var newG = Convert.ToInt32(Math.Round((t - gInt) * p) + gInt);
        var newB = Convert.ToInt32(Math.Round((t - bInt) * p) + bInt);

        return String.Format("#{0:X2}{1:X2}{2:X2}", newR, newG, newB);
    }

6 votes

Je n'ai jamais utilisé C# avant, mais il semble que les trois dernières déclarations de variables sont bizarres. Un site int et deux vars pour le même type de données.

4 votes

Le mot-clé var en C# permet au compilateur de déduire le type au moment de la compilation. Ainsi, dans l'exemple ci-dessus, int et var définissent une variable du même type - int. Ceci est utile si vous avez un long nom de type, ou si vous voulez faire référence à un type anonyme. C'est bizarre car l'utilisateur1618171 a mélangé deux styles de déclaration de variable - probablement une faute de frappe.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X