Clover icon

Coverage Report

  1. Project Clover database Thu Dec 4 2025 14:43:25 GMT
  2. Package jalview.util

File ColorUtils.java

 

Coverage histogram

../../img/srcFileCovDistChart8.png
20% of files have more coverage

Code metrics

84
258
20
2
758
461
89
0.34
12.9
10
4.45

Classes

Class Line # Actions
ColorUtils 41 258 89
0.7541436675.4%
ColorUtils.ColourScheme 463 0 0
-1.0 -
 

Contributing tests

This file is covered by 277 tests. .

Source view

1    /*
2    * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3    * Copyright (C) $$Year-Rel$$ The Jalview Authors
4    *
5    * This file is part of Jalview.
6    *
7    * Jalview is free software: you can redistribute it and/or
8    * modify it under the terms of the GNU General Public License
9    * as published by the Free Software Foundation, either version 3
10    * of the License, or (at your option) any later version.
11    *
12    * Jalview is distributed in the hope that it will be useful, but
13    * WITHOUT ANY WARRANTY; without even the implied warranty
14    * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15    * PURPOSE. See the GNU General Public License for more details.
16    *
17    * You should have received a copy of the GNU General Public License
18    * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19    * The Jalview Authors are detailed in the 'AUTHORS' file.
20    */
21    /**
22    * author: Lauren Michelle Lui
23    */
24   
25    package jalview.util;
26   
27    import java.awt.Color;
28    import java.util.ArrayList;
29    import java.util.Collections;
30    import java.util.HashMap;
31    import java.util.List;
32    import java.util.Locale;
33    import java.util.Map;
34    import java.util.Map.Entry;
35    import java.util.Random;
36   
37    import org.jcolorbrewer.ColorBrewer;
38   
39    import jalview.bin.Console;
40   
 
41    public class ColorUtils
42    {
43    private static final int MAX_CACHE_SIZE = 1729;
44   
45    /*
46    * a cache for colours generated from text strings
47    */
48    private static Map<String, Color> myColours = new HashMap<>();
49   
50    private static Map<String, Color> myHSBSpacedColours = new HashMap<>();
51   
52    private static final double GOLDEN_RATIO_CONJUGATE = 0.6180339887;
53   
54    private static final float DEFAULT_HSB_DISTANCE_THRESHOLD = 0.5f; // Minimum HSB
55    // distance
56    // required
57   
58    private static float HSB_DISTANCE_THRESHOLD = DEFAULT_HSB_DISTANCE_THRESHOLD;
59   
60    private static final float DECREMENT_VAL_FOR_HSB_DISTANCE_THRESHOLD = 0.01f; // Value
61    // decremented
62    // from
63    // HSB
64    // distance
65    // threshold
66    // after
67    // reaching
68    // max
69    // iteration
70    // count
71   
72    private static final int MAX_ITERATION_FOR_ADJUSTING_COLOR = 100; // Max
73    // iteration
74    // for
75    // adjusting
76    // the
77    // colour
78    // for a
79    // particular
80    // threshold
81   
82    private static final int POS_HUE = 0;
83   
84    private static final int POS_SATURATION = 1;
85   
86    private static final int POS_BRIGHTNESS = 2;
87   
88    private static final float HSB_HIGH_SATURATION = 0.75f;
89    private static final float HSB_HIGH_BRIGHTNESS = 0.9f;
90   
91   
92    /**
93    * Generates a random color, will mix with input color. Code taken from
94    * http://stackoverflow
95    * .com/questions/43044/algorithm-to-randomly-generate-an-aesthetically
96    * -pleasing-color-palette
97    *
98    * @param mix
99    * @return Random color in RGB
100    */
 
101  3 toggle public static final Color generateRandomColor(Color mix)
102    {
103  3 Random random = new Random();
104  3 int red = random.nextInt(256);
105  3 int green = random.nextInt(256);
106  3 int blue = random.nextInt(256);
107   
108    // mix the color
109  3 if (mix != null)
110    {
111  3 red = (red + mix.getRed()) / 2;
112  3 green = (green + mix.getGreen()) / 2;
113  3 blue = (blue + mix.getBlue()) / 2;
114    }
115   
116  3 Color color = new Color(red, green, blue);
117  3 return color;
118    }
119   
120    /**
121    *
122    * @return random color
123    */
 
124  0 toggle public static final Color getARandomColor()
125    {
126  0 Color col = new Color((int) (Math.random() * 255),
127    (int) (Math.random() * 255), (int) (Math.random() * 255));
128  0 return col;
129    }
130   
131    /**
132    * Generate a particular colour from the HSB colour space for a particular
133    * index.
134    *
135    * @param index
136    * @return colour generated for the index
137    */
 
138  2 toggle public static Color getColorForIndex(int index)
139    {
140   
141  2 float hue = (index * (float) GOLDEN_RATIO_CONJUGATE) % 1.0f;
142  2 return Color.getHSBColor(hue, HSB_HIGH_SATURATION, HSB_HIGH_BRIGHTNESS);
143   
144    }
145   
146    /**
147    * Convert to Tk colour code format
148    *
149    * @param colour
150    * @return
151    * @see http
152    * ://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/colortool.html#
153    * tkcode
154    */
 
155  15 toggle public static final String toTkCode(Color colour)
156    {
157  15 String colstring = "#" + ((colour.getRed() < 16) ? "0" : "")
158    + Integer.toHexString(colour.getRed())
159  15 + ((colour.getGreen() < 16) ? "0" : "")
160    + Integer.toHexString(colour.getGreen())
161  15 + ((colour.getBlue() < 16) ? "0" : "")
162    + Integer.toHexString(colour.getBlue());
163  15 return colstring;
164    }
165   
166    /**
167    * Returns a colour three shades darker. Note you can't guarantee that
168    * brighterThan reverses this, as darkerThan may result in black.
169    *
170    * @param col
171    * @return
172    */
 
173  4 toggle public static Color darkerThan(Color col)
174    {
175  4 return col == null ? null : col.darker().darker().darker();
176    }
177   
178    /**
179    * Returns a colour three shades brighter. Note you can't guarantee that
180    * darkerThan reverses this, as brighterThan may result in white.
181    *
182    * @param col
183    * @return
184    */
 
185  4 toggle public static Color brighterThan(Color col)
186    {
187  4 return col == null ? null : col.brighter().brighter().brighter();
188    }
189   
190    /**
191    * Returns a color between minColour and maxColour; the RGB values are in
192    * proportion to where 'value' lies between minValue and maxValue
193    *
194    * @param value
195    * @param minValue
196    * @param minColour
197    * @param maxValue
198    * @param maxColour
199    * @return
200    */
 
201  234678 toggle public static Color getGraduatedColour(float value, float minValue,
202    Color minColour, float maxValue, Color maxColour)
203    {
204  234678 if (minValue == maxValue)
205    {
206  1 return minColour;
207    }
208  234677 if (value < minValue)
209    {
210  301 value = minValue;
211    }
212  234677 if (value > maxValue)
213    {
214  1466 value = maxValue;
215    }
216   
217    /*
218    * prop = proportion of the way value is from minValue to maxValue
219    */
220  234677 float prop = (value - minValue) / (maxValue - minValue);
221  234677 float r = minColour.getRed()
222    + prop * (maxColour.getRed() - minColour.getRed());
223  234677 float g = minColour.getGreen()
224    + prop * (maxColour.getGreen() - minColour.getGreen());
225  234677 float b = minColour.getBlue()
226    + prop * (maxColour.getBlue() - minColour.getBlue());
227  234677 return new Color(r / 255, g / 255, b / 255);
228    }
229   
230    /**
231    * 'Fades' the given colour towards white by the specified proportion. A
232    * factor of 1 or more results in White, a factor of 0 leaves the colour
233    * unchanged, and a factor between 0 and 1 results in a proportionate change
234    * of RGB values towards (255, 255, 255).
235    * <p>
236    * A negative bleachFactor can be specified to darken the colour towards Black
237    * (0, 0, 0).
238    *
239    * @param colour
240    * @param bleachFactor
241    * @return
242    */
 
243  555313 toggle public static Color bleachColour(Color colour, float bleachFactor)
244    {
245  555313 if (bleachFactor >= 1f)
246    {
247  2791 return Color.WHITE;
248    }
249  552521 if (bleachFactor <= -1f)
250    {
251  2 return Color.BLACK;
252    }
253  552520 if (bleachFactor == 0f)
254    {
255  3832 return colour;
256    }
257   
258  548687 int red = colour.getRed();
259  548687 int green = colour.getGreen();
260  548685 int blue = colour.getBlue();
261   
262  548690 if (bleachFactor > 0)
263    {
264  548688 red += (255 - red) * bleachFactor;
265  548689 green += (255 - green) * bleachFactor;
266  548690 blue += (255 - blue) * bleachFactor;
267  548696 return new Color(red, green, blue);
268    }
269    else
270    {
271  2 float factor = 1 + bleachFactor;
272  2 red *= factor;
273  2 green *= factor;
274  2 blue *= factor;
275  2 return new Color(red, green, blue);
276    }
277    }
278   
279    /**
280    * Parses a string into a Color, where the accepted formats are
281    * <ul>
282    * <li>an AWT colour name e.g. white</li>
283    * <li>a hex colour value (without prefix) e.g. ff0000</li>
284    * <li>an rgb triple e.g. 100,50,150</li>
285    * </ul>
286    *
287    * @param colour
288    * @return the parsed colour, or null if parsing fails
289    */
 
290  22659 toggle public static Color parseColourString(String colour)
291    {
292  22659 if (colour == null)
293    {
294  5 return null;
295    }
296  22654 colour = colour.trim();
297   
298  22654 Color col = null;
299   
300  22654 if ("random".equals(colour))
301    {
302  0 return generateRandomColor(null);
303    }
304   
305  22654 if (StringUtils.isHexString(colour))
306    {
307  22515 try
308    {
309  22515 int value = Integer.parseInt(colour, 16);
310  22514 col = new Color(value);
311    } catch (NumberFormatException ex)
312    {
313    }
314    }
315   
316  22654 if (col == null)
317    {
318  140 col = ColorUtils.getAWTColorFromName(colour);
319    }
320   
321  22654 if (col == null)
322    {
323  47 try
324    {
325  47 String[] tokens = colour.split(",");
326  47 if (tokens.length == 3)
327    {
328  20 int r = Integer.parseInt(tokens[0].trim());
329  20 int g = Integer.parseInt(tokens[1].trim());
330  20 int b = Integer.parseInt(tokens[2].trim());
331  20 col = new Color(r, g, b);
332    }
333    } catch (IllegalArgumentException ex) // IllegalArgumentException includes
334    // NumberFormatException
335    {
336    // non-numeric token or out of 0-255 range
337    }
338    }
339   
340  22654 return col;
341    }
342   
343    /**
344    * Constructs a colour from a text string. The hashcode of the whole string is
345    * scaled to the range 0-135. This is added to RGB values made from the
346    * hashcode of each third of the string, and scaled to the range 20-229.
347    *
348    * @param name
349    * @return
350    */
 
351  97 toggle public static Color createColourFromName(String name)
352    {
353  97 if (name == null)
354    {
355  1 return Color.white;
356    }
357  96 if (myColours.containsKey(name))
358    {
359  56 return myColours.get(name);
360    }
361  40 int lsize = name.length();
362  40 int start = 0;
363  40 int end = lsize / 3;
364   
365  40 int rgbOffset = Math.abs(name.hashCode() % 10) * 15; // 0-135
366   
367    /*
368    * red: first third
369    */
370  40 int r = Math.abs(name.substring(start, end).hashCode() + rgbOffset)
371    % 210 + 20;
372  40 start = end;
373  40 end += lsize / 3;
374  40 if (end > lsize)
375    {
376  0 end = lsize;
377    }
378   
379    /*
380    * green: second third
381    */
382  40 int g = Math.abs(name.substring(start, end).hashCode() + rgbOffset)
383    % 210 + 20;
384   
385    /*
386    * blue: third third
387    */
388  40 int b = Math.abs(name.substring(end).hashCode() + rgbOffset) % 210 + 20;
389   
390  40 Color color = new Color(r, g, b);
391   
392  40 if (myColours.size() < MAX_CACHE_SIZE)
393    {
394  40 myColours.put(name, color);
395    }
396   
397  40 return color;
398    }
399   
400    /**
401    * Returns the Color constant for a given colour name e.g. "pink", or null if
402    * the name is not recognised
403    *
404    * @param name
405    * @return
406    */
 
407  147 toggle public static Color getAWTColorFromName(String name)
408    {
409  147 if (name == null)
410    {
411  1 return null;
412    }
413  146 Color col = null;
414  146 name = name.toLowerCase(Locale.ROOT);
415   
416    // or make a static map; or use reflection on the field name
417  146 switch (name)
418    {
419  12 case "black":
420  12 col = Color.black;
421  12 break;
422  32 case "blue":
423  32 col = Color.blue;
424  32 break;
425  0 case "cyan":
426  0 col = Color.cyan;
427  0 break;
428  0 case "darkgray":
429  0 col = Color.darkGray;
430  0 break;
431  0 case "gray":
432  0 col = Color.gray;
433  0 break;
434  15 case "green":
435  15 col = Color.green;
436  15 break;
437  1 case "lightgray":
438  1 col = Color.lightGray;
439  1 break;
440  0 case "magenta":
441  0 col = Color.magenta;
442  0 break;
443  1 case "orange":
444  1 col = Color.orange;
445  1 break;
446  2 case "pink":
447  2 col = Color.pink;
448  2 break;
449  28 case "red":
450  28 col = Color.red;
451  28 break;
452  5 case "white":
453  5 col = Color.white;
454  5 break;
455  1 case "yellow":
456  1 col = Color.yellow;
457  1 break;
458    }
459   
460  146 return col;
461    }
462   
 
463    public enum ColourScheme
464    {
465    NONE, AVOID_RED, AVOID_GREEN, AVOID_BLUE, SATURATED, MEDIUM_SATURATION, DESATURATED,
466    GREYISH, GREYSCALE, BRIGHT, MEDIUM, DARK
467    }
468   
 
469  61 toggle public static float[] getHSBRanges(String colourScheme)
470    {
471  61 float Hmin = 0.0f;
472  61 float Hmax = 1.0f;
473  61 float Smin = 0.6f;
474  61 float Smax = 1.0f;
475  61 float Bmin = 0.6f;
476  61 float Bmax = 1.0f;
477  61 if (!colourScheme.contains(ColourScheme.NONE.name()))
478    {
479  0 for (String scheme : colourScheme.split(","))
480    {
481  0 Console.debug("Applying colourScheme component " + scheme);
482  0 ColourScheme cs;
483  0 try
484    {
485  0 cs = ColourScheme.valueOf(scheme);
486    } catch (IllegalArgumentException | NullPointerException e)
487    {
488  0 Console.warn("Did not recognise something in the colour scheme '"
489    + colourScheme + "'");
490  0 return new float[] { Hmin, Hmax, Smin, Smax, Bmin, Bmax };
491    }
492  0 switch (cs)
493    {
494  0 case AVOID_RED:
495  0 Hmin = 0.15f;
496  0 Hmax = 0.85f;
497  0 break;
498  0 case AVOID_GREEN:
499  0 Hmin = 0.48f;
500  0 Hmax = 0.18f;
501  0 break;
502  0 case AVOID_BLUE:
503  0 Hmin = 0.81f;
504  0 Hmax = 0.51f;
505  0 break;
506  0 case SATURATED:
507  0 Smin = 1.0f;
508  0 Smax = 1.0f;
509  0 break;
510  0 case DESATURATED:
511  0 Smin = 0.2f;
512  0 Smax = 0.6f;
513  0 break;
514  0 case GREYISH:
515  0 Smin = 0.0f;
516  0 Smax = 0.2f;
517  0 case GREYSCALE:
518  0 Smin = 0.0f;
519  0 Smax = 0.0f;
520  0 Bmin = 0.1f;
521  0 Bmax = 0.9f;
522  0 break;
523  0 case BRIGHT:
524  0 Bmin = 1.0f;
525  0 Bmax = 1.0f;
526  0 break;
527  0 case MEDIUM:
528  0 Bmin = 0.6f;
529  0 Bmax = 0.8f;
530  0 case DARK:
531  0 Bmin = 0.1f;
532  0 Bmax = 0.4f;
533  0 break;
534  0 case NONE:
535  0 break;
536    }
537    }
538    }
539  61 return new float[] { Hmin, Hmax, Smin, Smax, Bmin, Bmax };
540    }
541   
542   
 
543  556 toggle private static String getHSBColourSpacedCacheKey(String name,
544    String colourScheme)
545    {
546  556 return name.hashCode() + "::" + colourScheme;
547    }
548   
549    /**
550    * This method restores the colour map from the loaded file.
551    * It replaces the current colour for the labels with colour
552    * present in the loaded file.
553    * @param colorMap
554    */
 
555  3 toggle public static void restoreMyHSBSpacedColours(
556    Map<String, Color> colorMap)
557    {
558  3 for(Entry<String, Color> entry : colorMap.entrySet()) {
559  12 String cacheKey = getHSBColourSpacedCacheKey(entry.getKey(), "NONE");
560  12 ColorUtils.myHSBSpacedColours.put(cacheKey, entry.getValue());
561    }
562    }
563   
564   
 
565  544 toggle public static Color getColourFromNameAndScheme(String name,
566    String colourScheme)
567    {
568  544 String cacheKey = getHSBColourSpacedCacheKey(name, colourScheme);
569  544 if (myHSBSpacedColours.containsKey(cacheKey))
570    {
571  483 return myHSBSpacedColours.get(cacheKey);
572    }
573  61 float[] vals = getHSBRanges(colourScheme);
574  61 Color col = null;
575  61 if (vals.length > 5)
576    {
577  61 col = getHSBColourspacedColourFromName(name, vals[0], vals[1],
578    vals[2], vals[3], vals[4], vals[5]);
579   
580    // col = getHSBColourFromName(name);
581   
582  61 int iterationCounter = 0; // reset counter
583  61 HSB_DISTANCE_THRESHOLD = DEFAULT_HSB_DISTANCE_THRESHOLD; // reset
584    // threshold
585   
586    // Adjust colour until it is distinct
587  16300 while (!isColorDistinct(col))
588    {
589  16239 col = adjustColor(col);
590   
591  16239 iterationCounter++;
592   
593    // If too many iterations occur, reduce the threshold to allow closer
594    // colours
595  16239 if (iterationCounter > MAX_ITERATION_FOR_ADJUSTING_COLOR)
596    {
597  158 HSB_DISTANCE_THRESHOLD = HSB_DISTANCE_THRESHOLD
598    - DECREMENT_VAL_FOR_HSB_DISTANCE_THRESHOLD;
599  158 iterationCounter = 0; // reset counter
600    }
601    }
602   
603  61 myHSBSpacedColours.put(cacheKey, col);
604    }
605  61 return col;
606    }
607   
 
608  61 toggle public static Color getHSBColourspacedColourFromName(String name,
609    float Hmin, float Hmax, float Smin, float Smax, float Bmin,
610    float Bmax)
611    {
612  61 if (name == null)
613    {
614  0 return Color.white;
615    }
616   
617    // use first three bytes from MD5 rather than String.hashCode() as short
618    // strings all low number hashCodes
619  61 byte[] hash = StringUtils.getHashedBytes(name);
620  61 int b = hash.length > 0 ? Byte.toUnsignedInt(hash[0]) : 0;
621  61 int g = hash.length > 1 ? Byte.toUnsignedInt(hash[1]) : 0;
622  61 int r = hash.length > 2 ? Byte.toUnsignedInt(hash[2]) : 0;
623   
624  61 float[] hsbf = Color.RGBtoHSB(r, g, b, null);
625   
626  61 if (hsbf.length < 3)
627    {
628    // This really shouldn't happen
629  0 Console.warn("Unexpected short length of HSB float array");
630    }
631  61 float h0 = hsbf.length > 0 ? hsbf[0] : 0f;
632  61 float s0 = hsbf.length > 1 ? hsbf[1] : 0f;
633  61 float b0 = hsbf.length > 2 ? hsbf[2] : 0f;
634   
635    // Now map these HSB values into the given ranges
636   
637    // hue wraps around 1.0->0.0 so deal with a Hmin-Hmax range that goes across
638    // this
639  61 float h1 = 0f;
640  61 if (Hmin > Hmax)
641    {
642  0 Hmax += 1f;
643    }
644  61 h1 = Hmin + (Hmax - Hmin) * h0;
645  61 if (h1 > 1f)
646    {
647  0 h1 -= 1f;
648    }
649  61 float s1 = Smin + (Smax - Smin) * s0;
650  61 float b1 = Bmin + (Bmax - Bmin) * b0;
651   
652  61 Console.debug("Setting new colour for '" + name + "' with H=" + h1
653    + ", S=" + s1 + ", B=" + b1);
654   
655  61 return Color.getHSBColor(h1, s1, b1);
656    }
657   
658   
659    /**
660    * Checks if the colour is distinct from existing colours in the cache.
661    *
662    * @param newColor
663    * Colour to be checked.
664    * @return {@code true} if the colour is distinct; {@code false} otherwise.
665    */
 
666  16300 toggle private static boolean isColorDistinct(Color newColor)
667    {
668  16300 float[] newHSB = convertRGBColorToHSB(newColor); // Convert new color to HSB
669   
670  16300 for (Color existingColor : myHSBSpacedColours.values())
671    {
672  31812 float[] existingHSB = convertRGBColorToHSB(existingColor);
673  31812 float distanceFromExistingColor = hsbDistance(newHSB, existingHSB);
674    //float distanceFromExistingColor = CIEDE2000ColorDistance(newColor, existingColor);
675  31812 if (distanceFromExistingColor < HSB_DISTANCE_THRESHOLD)
676    {
677  16239 return false; // Colours are not distinct
678    }
679    }
680  61 return true; // Colours are distinct
681    }
682   
683   
684    /**
685    * Adjusts the colour using golden ratio conjugate.
686    *
687    * @param col
688    * Colour to be adjusted.
689    * @return adjusted color
690    */
 
691  16239 toggle private static Color adjustColor(Color col) {
692  16239 float[] hsbComponents = convertRGBColorToHSB(col); // Convert RGB to HSB
693  16239 hsbComponents[POS_HUE] = (float) ((hsbComponents[POS_HUE]
694    + GOLDEN_RATIO_CONJUGATE) % 1.0); // Adjust the hue using the
695    // golden ratio
696   
697  16239 if (hsbComponents[POS_HUE] > 1f)
698    {
699  0 hsbComponents[POS_HUE] -= 1f;
700    }
701    // slightly tweak saturation and brightness
702   
703    // range: 0.4 - 0.9
704  16239 hsbComponents[POS_SATURATION] = 0.4f
705    + (hsbComponents[POS_HUE] * 0.5f);
706   
707    // range: 0.5 - 0.9
708  16239 hsbComponents[POS_BRIGHTNESS] = 0.5f
709    + (hsbComponents[POS_HUE] * 0.4f);
710   
711  16239 Color adjustedColor = Color.getHSBColor((float) hsbComponents[POS_HUE],
712    hsbComponents[POS_SATURATION],
713    hsbComponents[POS_BRIGHTNESS]);
714   
715  16239 return adjustedColor;
716    }
717   
718   
719    /**
720    * Converts RGB colour into HSB.
721    *
722    * @param color
723    * RGB colour to be converted.
724    * @return Array containing HSB values.
725    */
 
726  64351 toggle private static float[] convertRGBColorToHSB(Color color)
727    {
728  64351 float[] hsb = new float[3];
729  64351 Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), hsb);
730  64351 return hsb;
731    }
732   
733   
734    /**
735    * Calculates the Euclidean distance between two colours in the HSB space.
736    *
737    * @param hsbX
738    * HSB values of the first colour.
739    * @param hsbY
740    * HSB values of the second colour.
741    * @return Euclidean distance between the two colours in HSB space.
742    */
 
743  31812 toggle private static float hsbDistance(float[] hsbX, float[] hsbY)
744    {
745    // Compute differences in HSB components
746  31812 float diffH = Math.abs(hsbX[POS_HUE] - hsbY[POS_HUE]);
747  31812 if (diffH > 0.5)
748    {
749  8374 diffH = 1.0f - diffH; // Adjust as hue has circular nature
750    }
751  31812 float diffS = hsbX[POS_SATURATION] - hsbY[POS_SATURATION];
752  31812 float diffB = hsbX[POS_BRIGHTNESS] - hsbY[POS_BRIGHTNESS];
753   
754    // Calculate and return Euclidean distance
755  31812 return (float) Math.sqrt(diffH * diffH + diffS * diffS + diffB * diffB);
756    }
757   
758    }