From 503c83212e9779b412c999dc1d64d4e6c5e41cae Mon Sep 17 00:00:00 2001 From: Ben Soares Date: Wed, 6 Nov 2024 20:43:49 +0000 Subject: [PATCH] JAL-4386 Some methods to create consistent colours from a string id, with applicable colour schemes --- src/jalview/util/ColorUtils.java | 125 ++++++++++++++++++++++++++++++++++++- src/jalview/util/StringUtils.java | 40 ++++++++++++ 2 files changed, 163 insertions(+), 2 deletions(-) diff --git a/src/jalview/util/ColorUtils.java b/src/jalview/util/ColorUtils.java index 24128ea..ee519a7 100644 --- a/src/jalview/util/ColorUtils.java +++ b/src/jalview/util/ColorUtils.java @@ -30,6 +30,9 @@ import java.util.Locale; import java.util.Map; import java.util.Random; +import jalview.bin.Cache; +import jalview.bin.Console; + public class ColorUtils { private static final int MAX_CACHE_SIZE = 1729; @@ -37,7 +40,16 @@ public class ColorUtils /* * a cache for colours generated from text strings */ - static Map myColours = new HashMap<>(); + private static Map myColours = new HashMap<>(); + + private static Map myHSBSpacedColours = new HashMap<>(); + + public static final String ID_COLOUR_SCHEME; + + static + { + ID_COLOUR_SCHEME = Cache.getDefault("ID_COLOUR_SCHEME", "NONE"); + } /** * Generates a random color, will mix with input color. Code taken from @@ -393,4 +405,113 @@ public class ColorUtils return col; } -} + + public static Color getDefaultColourFromName(String name) + { + return getColourFromNameAndScheme(name, ID_COLOUR_SCHEME); + } + + public static Color getColourFromNameAndScheme(String name, + String colourSchemes) + { + String cacheKey = name + "::" + colourSchemes; + if (myHSBSpacedColours.containsKey(cacheKey)) + { + return myHSBSpacedColours.get(cacheKey); + } + float Hmin = 0.0f; + float Hmax = 1.0f; + float Smin = 0.7f; + float Smax = 1.0f; + float Bmin = 0.7f; + float Bmax = 1.0f; + for (String scheme : colourSchemes.split(",")) + { + switch (scheme) + { + case "AVOID_RED": + Hmin = 0.15f; + Hmax = 0.85f; + break; + case "AVOID_GREEN": + Hmin = 0.48f; + Hmax = 0.18f; + break; + case "AVOID_BLUE": + Hmin = 0.51f; + Hmax = 0.81f; + break; + case "SATURATED": + Smin = 1.0f; + Smax = 1.0f; + break; + case "PALE": + Smin = 0.2f; + Smax = 0.7f; + break; + case "GREYSCALE": + Smin = 0.0f; + Smax = 0.0f; + case "BRIGHT": + Bmin = 1.0f; + Bmax = 0.9f; + break; + case "DARK": + Bmin = 0.2f; + Bmax = 0.6f; + break; + } + } + Color col = getHSBColourspacedColourFromName(name, Hmin, Hmax, Smin, + Smax, Bmin, Bmax); + myHSBSpacedColours.put(cacheKey, col); + return col; + } + + public static Color getHSBColourspacedColourFromName(String name, + float Hmin, float Hmax, float Smin, float Smax, float Bmin, + float Bmax) + { + if (name == null) + { + return Color.white; + } + + // use first three bytes from MD5 rather than String.hashCode() as short + // strings all low number hashCodes + byte[] hash = StringUtils.getMD5Bytes(name); + int bl = hash.length > 0 ? hash[0] : 0; + int g = hash.length > 1 ? hash[1] : 0; + int r = hash.length > 2 ? hash[2] : 0; + + float[] hsbf = Color.RGBtoHSB(r, g, bl, null); + + if (hsbf.length < 3) + { + // This really shouldn't happen + Console.warn("Unexpected short length of HSB float array"); + } + float h0 = hsbf.length > 0 ? hsbf[0] : 0f; + float s0 = hsbf.length > 1 ? hsbf[1] : 0f; + float b0 = hsbf.length > 2 ? hsbf[2] : 0f; + + // Now map these HSB values into the given ranges + + // hue wraps around 1.0->0.0 so deal with a Hmin-Hmax range that goes across + // this + float h1 = 0f; + if (Hmin > Hmax) + { + Hmax += 1f; + } + h1 = Hmin + (Hmax - Hmin) * h0; + if (h1 > 1f) + { + h1 -= 1f; + } + float s1 = Smin + (Smax - Smin) * s0; + float b1 = Bmin + (Bmax - Bmin) * b0; + + return Color.getHSBColor(h1, s1, b1); + } +} \ No newline at end of file diff --git a/src/jalview/util/StringUtils.java b/src/jalview/util/StringUtils.java index 42c52a7..3b3cb47 100644 --- a/src/jalview/util/StringUtils.java +++ b/src/jalview/util/StringUtils.java @@ -21,13 +21,18 @@ package jalview.util; import java.io.UnsupportedEncodingException; +import java.math.BigInteger; import java.net.URLEncoder; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; +import jalview.bin.Console; + public class StringUtils { private static final Pattern DELIMITERS_PATTERN = Pattern @@ -644,4 +649,39 @@ public class StringUtils } return max; } + + /* + * return an MD5 hash of the given String + */ + public static byte[] getMD5Bytes(String name) + { + final String alg = "MD5"; + try + { + MessageDigest md5 = MessageDigest.getInstance(alg); + return md5.digest(name.getBytes()); + } catch (NoSuchAlgorithmException e) + { + Console.warn("Could not find hashing algorithm '" + alg + "'"); + return null; + } + } + + public static String getMD5String(String name) + { + byte[] hashBytes = getMD5Bytes(name); + if (hashBytes == null) + { + return null; + } + BigInteger hashNum = new BigInteger(1, hashBytes); + String hashString = hashNum.toString(16); + StringBuilder sb = new StringBuilder(32); + for (int i = 0; i + hashString.length() < 32; i++) + { + sb.append('0'); + } + sb.append(hashString); + return sb.toString(); + } } -- 1.7.10.2