Clover icon

Coverage Report

  1. Project Clover database Wed Nov 12 2025 13:01:44 GMT
  2. Package jalview.util

File ColorUtils.java

 

Coverage histogram

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

Code metrics

82
257
20
2
755
458
88
0.34
12.85
10
4.4

Classes

Class Line # Actions
ColorUtils 41 257 88
0.7520891475.2%
ColorUtils.ColourScheme 460 0 0
-1.0 -
 

Contributing tests

This file is covered by 265 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  193331 toggle public static Color getGraduatedColour(float value, float minValue,
202    Color minColour, float maxValue, Color maxColour)
203    {
204  193331 if (minValue == maxValue)
205    {
206  1 return minColour;
207    }
208  193330 if (value < minValue)
209    {
210  1 value = minValue;
211    }
212  193330 if (value > maxValue)
213    {
214  926 value = maxValue;
215    }
216   
217    /*
218    * prop = proportion of the way value is from minValue to maxValue
219    */
220  193330 float prop = (value - minValue) / (maxValue - minValue);
221  193330 float r = minColour.getRed()
222    + prop * (maxColour.getRed() - minColour.getRed());
223  193330 float g = minColour.getGreen()
224    + prop * (maxColour.getGreen() - minColour.getGreen());
225  193330 float b = minColour.getBlue()
226    + prop * (maxColour.getBlue() - minColour.getBlue());
227  193330 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  160675 toggle public static Color bleachColour(Color colour, float bleachFactor)
244    {
245  160675 if (bleachFactor >= 1f)
246    {
247  1306 return Color.WHITE;
248    }
249  159369 if (bleachFactor <= -1f)
250    {
251  2 return Color.BLACK;
252    }
253  159367 if (bleachFactor == 0f)
254    {
255  13470 return colour;
256    }
257   
258  145897 int red = colour.getRed();
259  145897 int green = colour.getGreen();
260  145897 int blue = colour.getBlue();
261   
262  145897 if (bleachFactor > 0)
263    {
264  145895 red += (255 - red) * bleachFactor;
265  145895 green += (255 - green) * bleachFactor;
266  145895 blue += (255 - blue) * bleachFactor;
267  145895 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  22647 toggle public static Color parseColourString(String colour)
291    {
292  22647 if (colour == null)
293    {
294  5 return null;
295    }
296  22642 colour = colour.trim();
297   
298  22642 Color col = null;
299   
300  22642 if ("random".equals(colour))
301    {
302  0 return generateRandomColor(null);
303    }
304   
305  22642 try
306    {
307  22642 int value = Integer.parseInt(colour, 16);
308  22503 col = new Color(value);
309    } catch (NumberFormatException ex)
310    {
311    }
312   
313  22642 if (col == null)
314    {
315  139 col = ColorUtils.getAWTColorFromName(colour);
316    }
317   
318  22642 if (col == null)
319    {
320  46 try
321    {
322  46 String[] tokens = colour.split(",");
323  46 if (tokens.length == 3)
324    {
325  20 int r = Integer.parseInt(tokens[0].trim());
326  20 int g = Integer.parseInt(tokens[1].trim());
327  20 int b = Integer.parseInt(tokens[2].trim());
328  20 col = new Color(r, g, b);
329    }
330    } catch (IllegalArgumentException ex) // IllegalArgumentException includes
331    // NumberFormatException
332    {
333    // non-numeric token or out of 0-255 range
334    }
335    }
336   
337  22642 return col;
338    }
339   
340    /**
341    * Constructs a colour from a text string. The hashcode of the whole string is
342    * scaled to the range 0-135. This is added to RGB values made from the
343    * hashcode of each third of the string, and scaled to the range 20-229.
344    *
345    * @param name
346    * @return
347    */
 
348  110 toggle public static Color createColourFromName(String name)
349    {
350  110 if (name == null)
351    {
352  1 return Color.white;
353    }
354  109 if (myColours.containsKey(name))
355    {
356  70 return myColours.get(name);
357    }
358  39 int lsize = name.length();
359  39 int start = 0;
360  39 int end = lsize / 3;
361   
362  39 int rgbOffset = Math.abs(name.hashCode() % 10) * 15; // 0-135
363   
364    /*
365    * red: first third
366    */
367  39 int r = Math.abs(name.substring(start, end).hashCode() + rgbOffset)
368    % 210 + 20;
369  39 start = end;
370  39 end += lsize / 3;
371  39 if (end > lsize)
372    {
373  0 end = lsize;
374    }
375   
376    /*
377    * green: second third
378    */
379  39 int g = Math.abs(name.substring(start, end).hashCode() + rgbOffset)
380    % 210 + 20;
381   
382    /*
383    * blue: third third
384    */
385  39 int b = Math.abs(name.substring(end).hashCode() + rgbOffset) % 210 + 20;
386   
387  39 Color color = new Color(r, g, b);
388   
389  39 if (myColours.size() < MAX_CACHE_SIZE)
390    {
391  39 myColours.put(name, color);
392    }
393   
394  39 return color;
395    }
396   
397    /**
398    * Returns the Color constant for a given colour name e.g. "pink", or null if
399    * the name is not recognised
400    *
401    * @param name
402    * @return
403    */
 
404  146 toggle public static Color getAWTColorFromName(String name)
405    {
406  146 if (name == null)
407    {
408  1 return null;
409    }
410  145 Color col = null;
411  145 name = name.toLowerCase(Locale.ROOT);
412   
413    // or make a static map; or use reflection on the field name
414  145 switch (name)
415    {
416  12 case "black":
417  12 col = Color.black;
418  12 break;
419  32 case "blue":
420  32 col = Color.blue;
421  32 break;
422  0 case "cyan":
423  0 col = Color.cyan;
424  0 break;
425  0 case "darkgray":
426  0 col = Color.darkGray;
427  0 break;
428  0 case "gray":
429  0 col = Color.gray;
430  0 break;
431  15 case "green":
432  15 col = Color.green;
433  15 break;
434  1 case "lightgray":
435  1 col = Color.lightGray;
436  1 break;
437  0 case "magenta":
438  0 col = Color.magenta;
439  0 break;
440  1 case "orange":
441  1 col = Color.orange;
442  1 break;
443  2 case "pink":
444  2 col = Color.pink;
445  2 break;
446  28 case "red":
447  28 col = Color.red;
448  28 break;
449  5 case "white":
450  5 col = Color.white;
451  5 break;
452  1 case "yellow":
453  1 col = Color.yellow;
454  1 break;
455    }
456   
457  145 return col;
458    }
459   
 
460    public enum ColourScheme
461    {
462    NONE, AVOID_RED, AVOID_GREEN, AVOID_BLUE, SATURATED, MEDIUM_SATURATION, DESATURATED,
463    GREYISH, GREYSCALE, BRIGHT, MEDIUM, DARK
464    }
465   
 
466  62 toggle public static float[] getHSBRanges(String colourScheme)
467    {
468  62 float Hmin = 0.0f;
469  62 float Hmax = 1.0f;
470  62 float Smin = 0.6f;
471  62 float Smax = 1.0f;
472  62 float Bmin = 0.6f;
473  62 float Bmax = 1.0f;
474  62 if (!colourScheme.contains(ColourScheme.NONE.name()))
475    {
476  0 for (String scheme : colourScheme.split(","))
477    {
478  0 Console.debug("Applying colourScheme component " + scheme);
479  0 ColourScheme cs;
480  0 try
481    {
482  0 cs = ColourScheme.valueOf(scheme);
483    } catch (IllegalArgumentException | NullPointerException e)
484    {
485  0 Console.warn("Did not recognise something in the colour scheme '"
486    + colourScheme + "'");
487  0 return new float[] { Hmin, Hmax, Smin, Smax, Bmin, Bmax };
488    }
489  0 switch (cs)
490    {
491  0 case AVOID_RED:
492  0 Hmin = 0.15f;
493  0 Hmax = 0.85f;
494  0 break;
495  0 case AVOID_GREEN:
496  0 Hmin = 0.48f;
497  0 Hmax = 0.18f;
498  0 break;
499  0 case AVOID_BLUE:
500  0 Hmin = 0.81f;
501  0 Hmax = 0.51f;
502  0 break;
503  0 case SATURATED:
504  0 Smin = 1.0f;
505  0 Smax = 1.0f;
506  0 break;
507  0 case DESATURATED:
508  0 Smin = 0.2f;
509  0 Smax = 0.6f;
510  0 break;
511  0 case GREYISH:
512  0 Smin = 0.0f;
513  0 Smax = 0.2f;
514  0 case GREYSCALE:
515  0 Smin = 0.0f;
516  0 Smax = 0.0f;
517  0 Bmin = 0.1f;
518  0 Bmax = 0.9f;
519  0 break;
520  0 case BRIGHT:
521  0 Bmin = 1.0f;
522  0 Bmax = 1.0f;
523  0 break;
524  0 case MEDIUM:
525  0 Bmin = 0.6f;
526  0 Bmax = 0.8f;
527  0 case DARK:
528  0 Bmin = 0.1f;
529  0 Bmax = 0.4f;
530  0 break;
531  0 case NONE:
532  0 break;
533    }
534    }
535    }
536  62 return new float[] { Hmin, Hmax, Smin, Smax, Bmin, Bmax };
537    }
538   
539   
 
540  507 toggle private static String getHSBColourSpacedCacheKey(String name,
541    String colourScheme)
542    {
543  507 return name.hashCode() + "::" + colourScheme;
544    }
545   
546    /**
547    * This method restores the colour map from the loaded file.
548    * It replaces the current colour for the labels with colour
549    * present in the loaded file.
550    * @param colorMap
551    */
 
552  3 toggle public static void restoreMyHSBSpacedColours(
553    Map<String, Color> colorMap)
554    {
555  3 for(Entry<String, Color> entry : colorMap.entrySet()) {
556  12 String cacheKey = getHSBColourSpacedCacheKey(entry.getKey(), "NONE");
557  12 ColorUtils.myHSBSpacedColours.put(cacheKey, entry.getValue());
558    }
559    }
560   
561   
 
562  495 toggle public static Color getColourFromNameAndScheme(String name,
563    String colourScheme)
564    {
565  495 String cacheKey = getHSBColourSpacedCacheKey(name, colourScheme);
566  495 if (myHSBSpacedColours.containsKey(cacheKey))
567    {
568  433 return myHSBSpacedColours.get(cacheKey);
569    }
570  62 float[] vals = getHSBRanges(colourScheme);
571  62 Color col = null;
572  62 if (vals.length > 5)
573    {
574  62 col = getHSBColourspacedColourFromName(name, vals[0], vals[1],
575    vals[2], vals[3], vals[4], vals[5]);
576   
577    // col = getHSBColourFromName(name);
578   
579  62 int iterationCounter = 0; // reset counter
580  62 HSB_DISTANCE_THRESHOLD = DEFAULT_HSB_DISTANCE_THRESHOLD; // reset
581    // threshold
582   
583    // Adjust colour until it is distinct
584  16302 while (!isColorDistinct(col))
585    {
586  16240 col = adjustColor(col);
587   
588  16240 iterationCounter++;
589   
590    // If too many iterations occur, reduce the threshold to allow closer
591    // colours
592  16240 if (iterationCounter > MAX_ITERATION_FOR_ADJUSTING_COLOR)
593    {
594  158 HSB_DISTANCE_THRESHOLD = HSB_DISTANCE_THRESHOLD
595    - DECREMENT_VAL_FOR_HSB_DISTANCE_THRESHOLD;
596  158 iterationCounter = 0; // reset counter
597    }
598    }
599   
600  62 myHSBSpacedColours.put(cacheKey, col);
601    }
602  62 return col;
603    }
604   
 
605  62 toggle public static Color getHSBColourspacedColourFromName(String name,
606    float Hmin, float Hmax, float Smin, float Smax, float Bmin,
607    float Bmax)
608    {
609  62 if (name == null)
610    {
611  0 return Color.white;
612    }
613   
614    // use first three bytes from MD5 rather than String.hashCode() as short
615    // strings all low number hashCodes
616  62 byte[] hash = StringUtils.getHashedBytes(name);
617  62 int b = hash.length > 0 ? Byte.toUnsignedInt(hash[0]) : 0;
618  62 int g = hash.length > 1 ? Byte.toUnsignedInt(hash[1]) : 0;
619  62 int r = hash.length > 2 ? Byte.toUnsignedInt(hash[2]) : 0;
620   
621  62 float[] hsbf = Color.RGBtoHSB(r, g, b, null);
622   
623  62 if (hsbf.length < 3)
624    {
625    // This really shouldn't happen
626  0 Console.warn("Unexpected short length of HSB float array");
627    }
628  62 float h0 = hsbf.length > 0 ? hsbf[0] : 0f;
629  62 float s0 = hsbf.length > 1 ? hsbf[1] : 0f;
630  62 float b0 = hsbf.length > 2 ? hsbf[2] : 0f;
631   
632    // Now map these HSB values into the given ranges
633   
634    // hue wraps around 1.0->0.0 so deal with a Hmin-Hmax range that goes across
635    // this
636  62 float h1 = 0f;
637  62 if (Hmin > Hmax)
638    {
639  0 Hmax += 1f;
640    }
641  62 h1 = Hmin + (Hmax - Hmin) * h0;
642  62 if (h1 > 1f)
643    {
644  0 h1 -= 1f;
645    }
646  62 float s1 = Smin + (Smax - Smin) * s0;
647  62 float b1 = Bmin + (Bmax - Bmin) * b0;
648   
649  62 Console.debug("Setting new colour for '" + name + "' with H=" + h1
650    + ", S=" + s1 + ", B=" + b1);
651   
652  62 return Color.getHSBColor(h1, s1, b1);
653    }
654   
655   
656    /**
657    * Checks if the colour is distinct from existing colours in the cache.
658    *
659    * @param newColor
660    * Colour to be checked.
661    * @return {@code true} if the colour is distinct; {@code false} otherwise.
662    */
 
663  16302 toggle private static boolean isColorDistinct(Color newColor)
664    {
665  16302 float[] newHSB = convertRGBColorToHSB(newColor); // Convert new color to HSB
666   
667  16302 for (Color existingColor : myHSBSpacedColours.values())
668    {
669  31814 float[] existingHSB = convertRGBColorToHSB(existingColor);
670  31814 float distanceFromExistingColor = hsbDistance(newHSB, existingHSB);
671    //float distanceFromExistingColor = CIEDE2000ColorDistance(newColor, existingColor);
672  31814 if (distanceFromExistingColor < HSB_DISTANCE_THRESHOLD)
673    {
674  16240 return false; // Colours are not distinct
675    }
676    }
677  62 return true; // Colours are distinct
678    }
679   
680   
681    /**
682    * Adjusts the colour using golden ratio conjugate.
683    *
684    * @param col
685    * Colour to be adjusted.
686    * @return adjusted color
687    */
 
688  16240 toggle private static Color adjustColor(Color col) {
689  16240 float[] hsbComponents = convertRGBColorToHSB(col); // Convert RGB to HSB
690  16240 hsbComponents[POS_HUE] = (float) ((hsbComponents[POS_HUE]
691    + GOLDEN_RATIO_CONJUGATE) % 1.0); // Adjust the hue using the
692    // golden ratio
693   
694  16240 if (hsbComponents[POS_HUE] > 1f)
695    {
696  0 hsbComponents[POS_HUE] -= 1f;
697    }
698    // slightly tweak saturation and brightness
699   
700    // range: 0.4 - 0.9
701  16240 hsbComponents[POS_SATURATION] = 0.4f
702    + (hsbComponents[POS_HUE] * 0.5f);
703   
704    // range: 0.5 - 0.9
705  16240 hsbComponents[POS_BRIGHTNESS] = 0.5f
706    + (hsbComponents[POS_HUE] * 0.4f);
707   
708  16240 Color adjustedColor = Color.getHSBColor((float) hsbComponents[POS_HUE],
709    hsbComponents[POS_SATURATION],
710    hsbComponents[POS_BRIGHTNESS]);
711   
712  16240 return adjustedColor;
713    }
714   
715   
716    /**
717    * Converts RGB colour into HSB.
718    *
719    * @param color
720    * RGB colour to be converted.
721    * @return Array containing HSB values.
722    */
 
723  64356 toggle private static float[] convertRGBColorToHSB(Color color)
724    {
725  64356 float[] hsb = new float[3];
726  64356 Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), hsb);
727  64356 return hsb;
728    }
729   
730   
731    /**
732    * Calculates the Euclidean distance between two colours in the HSB space.
733    *
734    * @param hsbX
735    * HSB values of the first colour.
736    * @param hsbY
737    * HSB values of the second colour.
738    * @return Euclidean distance between the two colours in HSB space.
739    */
 
740  31814 toggle private static float hsbDistance(float[] hsbX, float[] hsbY)
741    {
742    // Compute differences in HSB components
743  31814 float diffH = Math.abs(hsbX[POS_HUE] - hsbY[POS_HUE]);
744  31814 if (diffH > 0.5)
745    {
746  8375 diffH = 1.0f - diffH; // Adjust as hue has circular nature
747    }
748  31814 float diffS = hsbX[POS_SATURATION] - hsbY[POS_SATURATION];
749  31814 float diffB = hsbX[POS_BRIGHTNESS] - hsbY[POS_BRIGHTNESS];
750   
751    // Calculate and return Euclidean distance
752  31814 return (float) Math.sqrt(diffH * diffH + diffS * diffS + diffB * diffB);
753    }
754   
755    }