Comment générer automatiquement N couleurs “distinctes”?

J’ai écrit les deux méthodes ci-dessous pour sélectionner automatiquement N couleurs distinctes. Cela fonctionne en définissant une fonction linéaire par morceaux sur le cube RVB. L’avantage de ceci est que vous pouvez également obtenir une échelle progressive si c’est ce que vous voulez, mais quand N devient grand, les couleurs peuvent commencer à ressembler. Je peux également imaginer subdiviser le cube RVB en un treillis et dessiner des points. Est-ce que quelqu’un connaît d’autres méthodes? Je rejette la définition d’une liste et je passe ensuite à la vitesse supérieure. Je devrais aussi dire que je ne me soucie généralement pas de savoir si elles se heurtent ou ne sont pas belles, elles doivent simplement être visuellement distinctes.

public static List pick(int num) { List colors = new ArrayList(); if (num < 2) return colors; float dx = 1.0f / (float) (num - 1); for (int i = 0; i = 0.0f && x = 0.2f && x = 0.4f && x = 0.6f && x = 0.8f && x <= 1.0f) { x = (x - 0.8f) / 0.2f; r = 1.0f; g = 0.0f; b = x; } return new Color(r, g, b); } 

Vous pouvez utiliser le modèle de couleur HSL pour créer vos couleurs.

Si tout ce que vous voulez, ce sont des teintes différentes (probables) et de légères variations de luminosité ou de saturation, vous pouvez répartir les teintes comme suit:

 // assumes hue [0, 360), saturation [0, 100), lightness [0, 100) for(i = 0; i < 360; i += 360 / num_colors) { HSLColor c; c.hue = i; c.saturation = 90 + randf() * 10; c.lightness = 50 + randf() * 10; addColor(c); } 

Cette question apparaît dans plusieurs discussions SO:

  • Algorithme pour générer des couleurs uniques
  • Générer des couleurs uniques
  • Générer des couleurs RVB distinctes dans les graphiques
  • Comment générer n couleurs différentes pour un nombre naturel n?

Différentes solutions sont proposées, mais aucune n’est optimale. Heureusement, la science vient à la rescousse

N arbitraire

  • Affichages couleur pour les images catégorielles (téléchargement gratuit)
  • UN SERVICE WEB POUR PERSONNALISER LA COLORATION DES CARTES (téléchargement gratuit, une solution de service Web devrait être disponible d’ici le mois prochain)
  • Un algorithme pour la sélection de jeux de couleurs à contraste élevé (les auteurs proposent une implémentation C ++ gratuite)
  • Jeux de couleurs à contraste élevé (Le premier algorithme pour le problème)

Les deux derniers seront gratuits via la plupart des bibliothèques / proxies universitaires.

N est fini et relativement petit

Dans ce cas, on pourrait aller chercher une solution de liste. Un article très intéressant sur le sujet est disponible gratuitement:

  • Un alphabet de couleur et les limites du codage couleur

Il existe plusieurs listes de couleurs à prendre en compte:

  • La liste de Boynton de 11 couleurs qui ne sont presque jamais confondues (disponible dans le premier article de la section précédente)
  • Les 22 couleurs de Kelly au contraste maximum (disponibles dans l’article ci-dessus)

J’ai également rencontré cette palette par un étudiant du MIT. Enfin, les liens suivants peuvent être utiles pour la conversion entre différents systèmes de couleur / coordonnées (certaines couleurs des articles ne sont pas spécifiées dans RVB, par exemple):

Pour la liste de Kelly et Boynton, j’ai déjà effectué la conversion en RVB (à l’exception du blanc et du noir, ce qui devrait être évident). Un code C #:

 public static ReadOnlyCollection KellysMaxContrastSet { get { return _kellysMaxContrastSet.AsReadOnly(); } } private static readonly List _kellysMaxContrastSet = new List { UIntToColor(0xFFFFB300), //Vivid Yellow UIntToColor(0xFF803E75), //Strong Purple UIntToColor(0xFFFF6800), //Vivid Orange UIntToColor(0xFFA6BDD7), //Very Light Blue UIntToColor(0xFFC10020), //Vivid Red UIntToColor(0xFFCEA262), //Grayish Yellow UIntToColor(0xFF817066), //Medium Gray //The following will not be good for people with defective color vision UIntToColor(0xFF007D34), //Vivid Green UIntToColor(0xFFF6768E), //Strong Purplish Pink UIntToColor(0xFF00538A), //Strong Blue UIntToColor(0xFFFF7A5C), //Strong Yellowish Pink UIntToColor(0xFF53377A), //Strong Violet UIntToColor(0xFFFF8E00), //Vivid Orange Yellow UIntToColor(0xFFB32851), //Strong Purplish Red UIntToColor(0xFFF4C800), //Vivid Greenish Yellow UIntToColor(0xFF7F180D), //Strong Reddish Brown UIntToColor(0xFF93AA00), //Vivid Yellowish Green UIntToColor(0xFF593315), //Deep Yellowish Brown UIntToColor(0xFFF13A13), //Vivid Reddish Orange UIntToColor(0xFF232C16), //Dark Olive Green }; public static ReadOnlyCollection BoyntonOptimized { get { return _boyntonOptimized.AsReadOnly(); } } private static readonly List _boyntonOptimized = new List { Color.FromArgb(0, 0, 255), //Blue Color.FromArgb(255, 0, 0), //Red Color.FromArgb(0, 255, 0), //Green Color.FromArgb(255, 255, 0), //Yellow Color.FromArgb(255, 0, 255), //Magenta Color.FromArgb(255, 128, 128), //Pink Color.FromArgb(128, 128, 128), //Gray Color.FromArgb(128, 0, 0), //Brown Color.FromArgb(255, 128, 0), //Orange }; static public Color UIntToColor(uint color) { var a = (byte)(color >> 24); var r = (byte)(color >> 16); var g = (byte)(color >> 8); var b = (byte)(color >> 0); return Color.FromArgb(a, r, g, b); } 

Et voici les valeurs RVB dans les représentations hexadécimales et 8 bits par canal:

 kelly_colors_hex = [ 0xFFB300, # Vivid Yellow 0x803E75, # Strong Purple 0xFF6800, # Vivid Orange 0xA6BDD7, # Very Light Blue 0xC10020, # Vivid Red 0xCEA262, # Grayish Yellow 0x817066, # Medium Gray # The following don't work well for people with defective color vision 0x007D34, # Vivid Green 0xF6768E, # Strong Purplish Pink 0x00538A, # Strong Blue 0xFF7A5C, # Strong Yellowish Pink 0x53377A, # Strong Violet 0xFF8E00, # Vivid Orange Yellow 0xB32851, # Strong Purplish Red 0xF4C800, # Vivid Greenish Yellow 0x7F180D, # Strong Reddish Brown 0x93AA00, # Vivid Yellowish Green 0x593315, # Deep Yellowish Brown 0xF13A13, # Vivid Reddish Orange 0x232C16, # Dark Olive Green ] kelly_colors = dict(vivid_yellow=(255, 179, 0), strong_purple=(128, 62, 117), vivid_orange=(255, 104, 0), very_light_blue=(166, 189, 215), vivid_red=(193, 0, 32), grayish_yellow=(206, 162, 98), medium_gray=(129, 112, 102), # these aren't good for people with defective color vision: vivid_green=(0, 125, 52), strong_purplish_pink=(246, 118, 142), strong_blue=(0, 83, 138), strong_yellowish_pink=(255, 122, 92), strong_violet=(83, 55, 122), vivid_orange_yellow=(255, 142, 0), strong_purplish_red=(179, 40, 81), vivid_greenish_yellow=(244, 200, 0), strong_reddish_brown=(127, 24, 13), vivid_yellowish_green=(147, 170, 0), deep_yellowish_brown=(89, 51, 21), vivid_reddish_orange=(241, 58, 19), dark_olive_green=(35, 44, 22)) 

Pour tous les développeurs Java, voici les couleurs JavaFX:

 // Don't forget to import javafx.scene.paint.Color; private static final Color[] KELLY_COLORS = { Color.web("0xFFB300"), // Vivid Yellow Color.web("0x803E75"), // Strong Purple Color.web("0xFF6800"), // Vivid Orange Color.web("0xA6BDD7"), // Very Light Blue Color.web("0xC10020"), // Vivid Red Color.web("0xCEA262"), // Grayish Yellow Color.web("0x817066"), // Medium Gray Color.web("0x007D34"), // Vivid Green Color.web("0xF6768E"), // Strong Purplish Pink Color.web("0x00538A"), // Strong Blue Color.web("0xFF7A5C"), // Strong Yellowish Pink Color.web("0x53377A"), // Strong Violet Color.web("0xFF8E00"), // Vivid Orange Yellow Color.web("0xB32851"), // Strong Purplish Red Color.web("0xF4C800"), // Vivid Greenish Yellow Color.web("0x7F180D"), // Strong Reddish Brown Color.web("0x93AA00"), // Vivid Yellowish Green Color.web("0x593315"), // Deep Yellowish Brown Color.web("0xF13A13"), // Vivid Reddish Orange Color.web("0x232C16"), // Dark Olive Green }; 

Ce qui suit sont les couleurs de kelly non sortingées selon l’ordre ci-dessus.

couleurs de kelly non triées

les couleurs de kelly sortingées selon les teintes sont les suivantes (notez que certains jaunes ne sont pas très contrastés)

couleurs de kelly triées

Voici une idée. Imaginez un cylindre HSV

Définissez les limites supérieure et inférieure souhaitées pour la luminosité et la saturation. Ceci définit un anneau de section carrée dans l’espace.

Maintenant, dispersez N points au hasard dans cet espace.

Appliquez ensuite un algorithme de répulsion itérative, soit pour un nombre fixe d’itérations, soit jusqu’à stabilisation des points.

Vous devez maintenant avoir N points représentant N couleurs qui sont à peu près aussi différentes que possible de l’espace de couleur qui vous intéresse.

Hugo

Comme la réponse d’Uri Cohen, mais est plutôt un générateur. Commencera par utiliser des couleurs très éloignées. Déterministe

Échantillon, couleurs laissées en premier: échantillon

 #!/usr/bin/env python3.3 import colorsys import itertools from fractions import Fraction def zenos_dichotomy(): """ http://en.wikipedia.org/wiki/1/2_%2B_1/4_%2B_1/8_%2B_1/16_%2B_%C2%B7_%C2%B7_%C2%B7 """ for k in itertools.count(): yield Fraction(1,2**k) def getfracs(): """ [Fraction(0, 1), Fraction(1, 2), Fraction(1, 4), Fraction(3, 4), Fraction(1, 8), Fraction(3, 8), Fraction(5, 8), Fraction(7, 8), Fraction(1, 16), Fraction(3, 16), ...] [0.0, 0.5, 0.25, 0.75, 0.125, 0.375, 0.625, 0.875, 0.0625, 0.1875, ...] """ yield 0 for k in zenos_dichotomy(): i = k.denominator # [1,2,4,8,16,...] for j in range(1,i,2): yield Fraction(j,i) bias = lambda x: (math.sqrt(x/3)/Fraction(2,3)+Fraction(1,3))/Fraction(6,5) # can be used for the v in hsv to map linear values 0..1 to something that looks equidistant def genhsv(h): for s in [Fraction(6,10)]: # optionally use range for v in [Fraction(8,10),Fraction(5,10)]: # could use range too yield (h, s, v) # use bias for v here if you use range genrgb = lambda x: colorsys.hsv_to_rgb(*x) flatten = itertools.chain.from_iterable gethsvs = lambda: flatten(map(genhsv,getfracs())) getrgbs = lambda: map(genrgb, gethsvs()) def genhtml(x): uint8tuple = map(lambda y: int(y*255), x) return "rgb({},{},{})".format(*uint8tuple) gethtmlcolors = lambda: map(genhtml, getrgbs()) if __name__ == "__main__": print(list(itertools.islice(gethtmlcolors(), 100))) 

Pour les générations à venir, j’ajoute ici la réponse acceptée en Python.

 import numpy as np import colorsys def _get_colors(num_colors): colors=[] for i in np.arange(0., 360., 360. / num_colors): hue = i/360. lightness = (50 + np.random.rand() * 10)/100. saturation = (90 + np.random.rand() * 10)/100. colors.append(colorsys.hls_to_rgb(hue, lightness, saturation)) return colors 

Tout le monde semble avoir manqué l’existence de l’espace de couleurs YUV très utile, conçu pour représenter les différences de couleur perçues dans le système visuel humain. Les distances en YUV représentent des différences dans la perception humaine. J’avais besoin de cette fonctionnalité pour MagicCube4D qui implémente des cubes de Rubik en 4 dimensions et un nombre illimité d’autres puzzles twistés en 4D ayant un nombre arbitraire de faces.

Ma solution commence en sélectionnant des points aléatoires dans YUV, puis en décomposant de manière itérative les deux points les plus proches, puis en convertissant uniquement en RVB lorsque vous retournez le résultat. La méthode est O (n ^ 3) mais cela n’a pas d’importance pour les petits nombres ou ceux qui peuvent être mis en cache. Cela peut certainement être rendu plus efficace mais les résultats semblent excellents.

La fonction permet une spécification facultative des seuils de luminosité afin de ne pas produire de couleurs dans lesquelles aucun composant n’est plus clair ou plus sombre que les quantités données. IE, vous ne souhaitez peut-être pas que les valeurs soient proches du noir ou du blanc. Ceci est utile lorsque les couleurs résultantes seront utilisées comme couleurs de base qui seront ensuite ombrées par le biais de l’éclairage, de la superposition, de la transparence, etc.

 import java.awt.Color; import java.util.Random; /** * Contains a method to generate N visually distinct colors and helper methods. * * @author Melinda Green */ public class ColorUtils { private ColorUtils() {} // To disallow instantiation. private final static float U_OFF = .436f, V_OFF = .615f; private static final long RAND_SEED = 0; private static Random rand = new Random(RAND_SEED); /* * Returns an array of ncolors RGB sortingplets such that each is as unique from the rest as possible * and each color has at least one component greater than minComponent and one less than maxComponent. * Use min == 1 and max == 0 to include the full RGB color range. * * Warning: ON^2 algorithm blows up fast for more than 100 colors. */ public static Color[] generateVisuallyDistinctColors(int ncolors, float minComponent, float maxComponent) { rand.setSeed(RAND_SEED); // So that we get consistent results for each combination of inputs float[][] yuv = new float[ncolors][3]; // initialize array with random colors for(int got = 0; got < ncolors;) { System.arraycopy(randYUVinRGBRange(minComponent, maxComponent), 0, yuv[got++], 0, 3); } // continually break up the worst-fit color pair until we get tired of searching for(int c = 0; c < ncolors * 1000; c++) { float worst = 8888; int worstID = 0; for(int i = 1; i < yuv.length; i++) { for(int j = 0; j < i; j++) { float dist = sqrdist(yuv[i], yuv[j]); if(dist < worst) { worst = dist; worstID = i; } } } float[] best = randYUVBetterThan(worst, minComponent, maxComponent, yuv); if(best == null) break; else yuv[worstID] = best; } Color[] rgbs = new Color[yuv.length]; for(int i = 0; i < yuv.length; i++) { float[] rgb = new float[3]; yuv2rgb(yuv[i][0], yuv[i][1], yuv[i][2], rgb); rgbs[i] = new Color(rgb[0], rgb[1], rgb[2]); //System.out.println(rgb[i][0] + "\t" + rgb[i][1] + "\t" + rgb[i][2]); } return rgbs; } public static void hsv2rgb(float h, float s, float v, float[] rgb) { // H is given on [0->6] or -1. S and V are given on [0->1]. // RGB are each returned on [0->1]. float m, n, f; int i; float[] hsv = new float[3]; hsv[0] = h; hsv[1] = s; hsv[2] = v; System.out.println("H: " + h + " S: " + s + " V:" + v); if(hsv[0] == -1) { rgb[0] = rgb[1] = rgb[2] = hsv[2]; return; } i = (int) (Math.floor(hsv[0])); f = hsv[0] - i; if(i % 2 == 0) f = 1 - f; // if i is even m = hsv[2] * (1 - hsv[1]); n = hsv[2] * (1 - hsv[1] * f); switch(i) { case 6: case 0: rgb[0] = hsv[2]; rgb[1] = n; rgb[2] = m; break; case 1: rgb[0] = n; rgb[1] = hsv[2]; rgb[2] = m; break; case 2: rgb[0] = m; rgb[1] = hsv[2]; rgb[2] = n; break; case 3: rgb[0] = m; rgb[1] = n; rgb[2] = hsv[2]; break; case 4: rgb[0] = n; rgb[1] = m; rgb[2] = hsv[2]; break; case 5: rgb[0] = hsv[2]; rgb[1] = m; rgb[2] = n; break; } } // From http://en.wikipedia.org/wiki/YUV#Mathematical_derivations_and_formulas public static void yuv2rgb(float y, float u, float v, float[] rgb) { rgb[0] = 1 * y + 0 * u + 1.13983f * v; rgb[1] = 1 * y + -.39465f * u + -.58060f * v; rgb[2] = 1 * y + 2.03211f * u + 0 * v; } public static void rgb2yuv(float r, float g, float b, float[] yuv) { yuv[0] = .299f * r + .587f * g + .114f * b; yuv[1] = -.14713f * r + -.28886f * g + .436f * b; yuv[2] = .615f * r + -.51499f * g + -.10001f * b; } private static float[] randYUVinRGBRange(float minComponent, float maxComponent) { while(true) { float y = rand.nextFloat(); // * YFRAC + 1-YFRAC); float u = rand.nextFloat() * 2 * U_OFF - U_OFF; float v = rand.nextFloat() * 2 * V_OFF - V_OFF; float[] rgb = new float[3]; yuv2rgb(y, u, v, rgb); float r = rgb[0], g = rgb[1], b = rgb[2]; if(0 <= r && r <= 1 && 0 <= g && g <= 1 && 0 <= b && b <= 1 && (r > minComponent || g > minComponent || b > minComponent) && // don't want all dark components (r < maxComponent || g < maxComponent || b < maxComponent)) // don't want all light components return new float[]{y, u, v}; } } private static float sqrdist(float[] a, float[] b) { float sum = 0; for(int i = 0; i < a.length; i++) { float diff = a[i] - b[i]; sum += diff * diff; } return sum; } private static double worstFit(Color[] colors) { float worst = 8888; float[] a = new float[3], b = new float[3]; for(int i = 1; i < colors.length; i++) { colors[i].getColorComponents(a); for(int j = 0; j < i; j++) { colors[j].getColorComponents(b); float dist = sqrdist(a, b); if(dist < worst) { worst = dist; } } } return Math.sqrt(worst); } private static float[] randYUVBetterThan(float bestDistSqrd, float minComponent, float maxComponent, float[][] in) { for(int attempt = 1; attempt < 100 * in.length; attempt++) { float[] candidate = randYUVinRGBRange(minComponent, maxComponent); boolean good = true; for(int i = 0; i < in.length; i++) if(sqrdist(candidate, in[i]) < bestDistSqrd) good = false; if(good) return candidate; } return null; // after a bunch of passes, couldn't find a candidate that beat the best. } /** * Simple example program. */ public static void main(String[] args) { final int ncolors = 10; Color[] colors = generateVisuallyDistinctColors(ncolors, .8f, .3f); for(int i = 0; i < colors.length; i++) { System.out.println(colors[i].toString()); } System.out.println("Worst fit color = " + worstFit(colors)); } } 

Voici une solution pour gérer votre problème “distinct”, qui est entièrement exagéré:

Créez une sphère unitaire et déposez des points avec des charges repoussantes. Exécutez un système de particules jusqu’à ce qu’ils ne bougent plus (ou que le delta soit suffisamment petit). À ce stade, chacun des points est le plus éloigné possible l’un de l’autre. Convertir (x, y, z) en rgb.

Je le mentionne car pour certaines classes de problèmes, ce type de solution peut fonctionner mieux que la force brute.

J’ai d’abord vu cette approche ici pour tesseler une sphère.

Encore une fois, les solutions les plus évidentes pour traverser l’espace HSL ou l’espace RVB fonctionneront probablement très bien.

J’essaierais de fixer la saturation et la lumination au maximum et de me concentrer uniquement sur la teinte. Comme je le vois, H peut aller de 0 à 255 et ensuite boucler. Maintenant, si vous vouliez deux couleurs contrastées, vous prendriez les côtés opposés de cet anneau, à savoir 0 et 128. Si vous vouliez 4 couleurs, vous en prendriez quelques-unes séparées par 1/4 de la longueur du cercle, soit 0, 64,128,192. Et bien sûr, comme d’autres l’ont suggéré lorsque vous avez besoin de N couleurs, vous pourriez simplement les séparer par 256 / N.

Ce que je voudrais append à cette idée est d’utiliser une représentation inversée d’un nombre binary pour former cette séquence. Regarde ça:

 0 = 00000000 after reversal is 00000000 = 0 1 = 00000001 after reversal is 10000000 = 128 2 = 00000010 after reversal is 01000000 = 64 3 = 00000011 after reversal is 11000000 = 192 

… de cette façon, si vous avez besoin de N couleurs différentes, vous pouvez simplement prendre les N premiers chiffres, les inverser et obtenir autant de points distants que possible (pour N puissance de deux) tout en préservant chaque préfixe du la séquence diffère beaucoup.

C’était un objective important dans mon cas d’utilisation, car j’avais un tableau où les couleurs étaient sortingées par zone couverte par cette couleur. Je voulais que les plus grandes zones de la carte aient un grand contraste et que certaines petites zones avaient des couleurs semblables à celles du top 10, car il était évident pour le lecteur de choisir celle qui consiste à observer la zone.

Si N est assez grand, vous obtiendrez des couleurs similaires. Il n’y en a que beaucoup dans le monde.

Pourquoi ne pas simplement les dissortingbuer également à travers le spectre, comme ceci:

 IEnumerable CreateUniqueColors(int nColors) { int subdivision = (int)Math.Floor(Math.Pow(nColors, 1/3d)); for(int r = 0; r < 255; r += subdivision) for(int g = 0; g < 255; g += subdivision) for(int b = 0; b < 255; b += subdivision) yield return Color.FromArgb(r, g, b); } 

Si vous voulez mélanger la séquence pour que des couleurs similaires ne soient pas côte à côte, vous pouvez peut-être mélanger la liste résultante.

Suis-je en train de penser cela?

Ceci est sortingvial dans MATLAB (il y a une commande hsv):

 cmap = hsv(number_of_colors) 

J’ai écrit un paquet pour R appelé qualpalr qui est spécialement conçu à cet effet. Je vous recommande de regarder la vignette pour savoir comment cela fonctionne, mais je vais essayer de résumer les points principaux.

qualpalr prend une spécification de couleurs dans l’ espace colorimésortingque HSL (décrit précédemment dans ce fil), le projette dans l’espace colorimésortingque DIN99d (uniformément perceptible) et trouve le n qui maximise la distance minimale entre eux.

 # Create a palette of 4 colors of hues from 0 to 360, saturations between # 0.1 and 0.5, and lightness from 0.6 to 0.85 pal <- qualpal(n = 4, list(h = c(0, 360), s = c(0.1, 0.5), l = c(0.6, 0.85))) # Look at the colors in hex format pal$hex #> [1] "#6F75CE" "#CC6B76" "#CAC16A" "#76D0D0" # Create a palette using one of the predefined color subspaces pal2 <- qualpal(n = 4, colorspace = "pretty") # Distance matrix of the DIN99d color differences pal2$de_DIN99d #> #69A3CC #6ECC6E #CA6BC4 #> 6ECC6E 22 #> CA6BC4 21 30 #> CD976B 24 21 21 plot(pal2) 

entrer la description de l'image ici

Je pense que cet algorithme récursif simple complète la réponse acceptée, afin de générer des valeurs de teinte distinctes. Je l’ai fait pour le hsv, mais je peux aussi l’utiliser pour d’autres espaces de couleur.

Il génère des teintes en cycles, aussi distinctes que possible les unes des autres dans chaque cycle.

 /** * 1st cycle: 0, 120, 240 * 2nd cycle (+60): 60, 180, 300 * 3th cycle (+30): 30, 150, 270, 90, 210, 330 * 4th cycle (+15): 15, 135, 255, 75, 195, 315, 45, 165, 285, 105, 225, 345 */ public static float recursiveHue(int n) { // if 3: alternates red, green, blue variations float firstCycle = 3; // First cycle if (n < firstCycle) { return n * 360f / firstCycle; } // Each cycle has as much values as all previous cycles summed (powers of 2) else { // floor of log base 2 int numCycles = (int)Math.floor(Math.log(n / firstCycle) / Math.log(2)); // divDown stores the larger power of 2 that is still lower than n int divDown = (int)(firstCycle * Math.pow(2, numCycles)); // same hues than previous cycle, but summing an offset (half than previous cycle) return recursiveHue(n % divDown) + 180f / divDown; } } 

Je n'ai pas pu trouver ce type d'algorithme ici. J'espère que ça aide, c'est mon premier post ici.