Clover icon

Magnolia Imaging Module 3.4.2-SUPPORT-10161

  1. Project Clover database Tue Jul 16 2019 23:33:19 EEST
  2. Package info.magnolia.imaging.util

File ColorConverter.java

 
failsOnNull: Can't decode color null: please provide either an #ffffff...
failsOnBadHexa: Can't decode color #12345G: please provide either an #fff...
failsOnAlphaValueWithAlphaLessFunction: Can't decode color rgb(10,20,255, 0.5): the rgb() functio...
failsOnBadAlpha: Can't decode color rgba(10,20,255, 10): 10 is not in the ...
percentage: Can't decode color blah abc blah: abc should be a percent...
failsOnBadRgb: Can't decode color rgb(10,20,256): 256 is not in the allo...
failsOnUnknownName: Can't decode color foo: please provide either an #ffffff ...
 

Coverage histogram

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

Code metrics

36
88
11
1
250
166
38
0.43
8
11
3.45

Classes

Class Line # Actions
ColorConverter 51 88 0% 38 32
0.7629629476.3%
 

Contributing tests

This file is covered by 16 tests. .

Source view

1    /**
2    * This file Copyright (c) 2009-2018 Magnolia International
3    * Ltd. (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10    * This file is distributed in the hope that it will be
11    * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12    * implied warranty of MERCHANTABILITY or FITNESS FOR A
13    * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14    * Redistribution, except as permitted by whichever of the GPL
15    * or MNA you select, is prohibited.
16    *
17    * 1. For the GPL license (GPL), you can redistribute and/or
18    * modify this file under the terms of the GNU General
19    * Public License, Version 3, as published by the Free Software
20    * Foundation. You should have received a copy of the GNU
21    * General Public License, Version 3 along with this program;
22    * if not, write to the Free Software Foundation, Inc., 51
23    * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24    *
25    * 2. For the Magnolia Network Agreement (MNA), this file
26    * and the accompanying materials are made available under the
27    * terms of the MNA which accompanies this distribution, and
28    * is available at http://www.magnolia-cms.com/mna.html
29    *
30    * Any modifications to this file must keep this entire header
31    * intact.
32    *
33    */
34    package info.magnolia.imaging.util;
35   
36    import java.awt.Color;
37    import java.lang.reflect.Field;
38    import java.lang.reflect.Modifier;
39    import java.util.Map;
40    import java.util.TreeMap;
41    import java.util.regex.Matcher;
42    import java.util.regex.Pattern;
43   
44    import org.apache.commons.lang3.StringUtils;
45   
46    /**
47    * A class that is able to convert java.lang.String instances to java.awt.Color instances.
48    * It recognizes multiple String representations, such as named colors (using the {@link java.awt.Color} constants),
49    * hexadecimal, and css-like values (e.g "rgb(100,100,100)", rgba(100,100,100,0.1)", hsl(100,100,100)", hsl(100,100,100,0.1)")
50    */
 
51    public class ColorConverter {
52    private static final Map<String, Color> namedColors = reflectivelyGetNamedColors();
53    private static final Pattern HEX = Pattern.compile("^#[0-9A-Fa-f]{3,6}$");
54    private static final Pattern FUNC = Pattern.compile("^(rgb|rgba|hsl|hsla)\\(" +
55    "([0-9]{1,3})" +
56    ",([0-9]{1,3}%?)" + // % for S
57    ",([0-9]{1,3}%?)" + // % for L
58    "(?:,([0-9.]{1,4}))?" + // 0.00 to 1.00 -- more precision ?
59    "\\)$");
60   
 
61  23 toggle public static Color toColor(final String input) {
62  22 if (input == null) {
63  0 Test failure here throw cantParse(input, "please provide either an #ffffff or #fff hexadecimal value or a known named color.");
64    }
65  22 final String code = StringUtils.deleteWhitespace(input).toLowerCase();
66  22 if (namedColors.containsKey(code)) {
67  4 return namedColors.get(code);
68  18 } else if (HEX.matcher(code).matches()) {
69  8 if (StringUtils.length(code) == 4) {
70    // yay optimal
71  4 return Color.decode("#" + code.charAt(1) + code.charAt(1) + code.charAt(2) + code.charAt(2) + code.charAt(3) + code.charAt(3));
72  4 } else if (StringUtils.length(code) == 7) {
73  4 return Color.decode(code);
74    } else {
75  0 throw cantParse(input, "please provide a 6 or 3 digit long hexadecimal string, e.g #ffffff or #fff.");
76    }
77   
78    } else {
79  10 final Matcher matcher = FUNC.matcher(code);
80  8 if (matcher.matches()) {
81  8 final String function = matcher.group(1);
82   
83    // only if function is rgba or hsla, otherwise it SHOULD be null
84  8 final String alphaStr = matcher.group(5);
85  8 final int alpha;
86  8 if (StringUtils.endsWith(function, "a")) {
87  3 Test failure here alpha = (int) (rangedFloat(alphaStr, input, 0, 1) * 255);
88  4 } else if (alphaStr != null) {
89  0 Test failure here throw cantParse(input, "the " + function + "() function does not take an <alphavalue> argument.");
90    } else {
91  4 alpha = 255;
92    }
93   
94  6 switch (function) {
95  3 case "rgb":
96  3 return new Color(
97    rangedInt(0, 255, matcher, 2, input),
98    rangedInt(0, 255, matcher, 3, input),
99    Test failure here rangedInt(0, 255, matcher, 4, input)
100    );
101  1 case "rgba":
102  1 return new Color(
103    rangedInt(0, 255, matcher, 2, input),
104    rangedInt(0, 255, matcher, 3, input),
105    rangedInt(0, 255, matcher, 4, input),
106    alpha
107    );
108  1 case "hsl":
109  1 return new Color(hsbToRgb(input, matcher));
110  1 case "hsla":
111  1 int rgb = hsbToRgb(input, matcher);
112  1 int rgba = (alpha & 0xFF) << 24 | rgb;
113  1 final Color color = new Color(rgba);
114  1 return new Color(color.getRed(), color.getGreen(), color.getBlue(), alpha);
115  0 default:
116  0 throw cantParse(input, "'" + function + "()' is not a valid function. Please use rgb(), rgba(), hsl(), hsla(), a #ffffff hexadecimal value or a known named color.");
117    }
118    }
119    }
120  0 Test failure here throw cantParse(input, "please provide either an #ffffff or #fff hexadecimal value or a known named color.");
121    }
122   
 
123  2 toggle private static int hsbToRgb(String input, Matcher matcher) {
124  2 final float[] hsb = hslToHsv(
125    normalizeAngle(Integer.parseInt(matcher.group(2))),
126    percentage(matcher.group(3), input),
127    percentage(matcher.group(4), input));
128  2 return Color.HSBtoRGB(hsb[0], hsb[1], hsb[2]);
129    }
130   
131    /**
132    * @param hslH degrees
133    * @param hslS percentage
134    * @param hslL percentage
135    * @return [degree, 0.0-1.0, 0.0-1.0]
136    */
 
137  0 toggle private static float[] convertHslToHsb(float hslH, float hslS, float hslL) {
138  0 final float h = hslH / 360;
139  0 final float s = ((float) hslS / 100);
140  0 final float l = ((float) hslL / 100);
141  0 final float hsbB =
142    (
143    (2 * l) + (hslS * (1 - Math.abs((2 * l) - 1)))
144    )
145    / 2;
146  0 final float hsbS = (2 * (hsbB - l)) / hsbB;
147   
148  0 return new float[] { h, hsbS, hsbB };
149    }
150   
151    /**
152    * @param hslH degrees
153    * @param hslS percentage
154    * @param hslL percentage
155    * @return [degree, 0.0-1.0, 0.0-1.0]
156    *
157    * https://gist.github.com/peteroupc/4085710
158    */
 
159  2 toggle static float[] hslToHsv(int hslH, int hslS, int hslL) {
160  2 final float h = (float) hslH / 360;
161  2 final float s = ((float) hslS / 100);
162  2 final float l = ((float) hslL / 100);
163   
164  2 float saturation = 0.0f;
165  2 float value = 0.0f;
166  2 if (l > 0) { // Then it's all grey, we just need the hue.
167  2 float lumScale = 1.0f - Math.max(l - 0.5f, 0f) * 2f;
168  2 lumScale = (lumScale == 0f) ? 0f : 1.0f / lumScale;
169  2 float lumStart = Math.max(0f, lumScale - 0.5f);
170  2 float lumDiv = (lumScale - lumStart);
171  2 lumDiv = (lumStart + (s * lumDiv));
172  2 saturation = (lumDiv == 0) ? 0 : (s / lumDiv);
173  2 value = l + (1.0f - l) * s;
174    }
175  2 return new float[] { h, saturation, value };
176    }
177   
 
178  12 toggle private static int rangedInt(int min, int max, Matcher matcher, int groupNr, String wholeInputForReporting) {
179  12 final String value = matcher.group(groupNr);
180  12 Test failure here return rangedInt(min, max, value, wholeInputForReporting);
181    }
182   
 
183  16 toggle private static int rangedInt(int min, int max, String value, String wholeInputForReporting) {
184  16 if (StringUtils.endsWith(value, "%")) {
185  0 throw cantParse(wholeInputForReporting, value + " should not be a percentage value.");
186    }
187  16 final int i = Integer.parseInt(value);
188  15 if (i < min || i > max) {
189  0 Test failure here throw cantParse(wholeInputForReporting, "%s is not in the allowed %d-%d range.", value, min, max);
190    }
191  15 return i;
192    }
193   
 
194  3 toggle private static float rangedFloat(String value, String wholeInputForReporting, float min, float max) {
195  3 final float f = Float.parseFloat(value);
196  2 if (f < min || f > max) {
197  0 Test failure here throw cantParse(wholeInputForReporting, "%s is not in the allowed %.1f-%.1f <alphavalue> range.", value, min, max);
198    }
199  2 return f;
200    }
201   
 
202  5 toggle static int percentage(String value, String wholeInputForReporting) {
203  4 if (!StringUtils.endsWith(value, "%")) {
204  0 Test failure here throw cantParse(wholeInputForReporting, value + " should be a percentage value.");
205    }
206  4 return rangedInt(0, 100, value.substring(0, value.length() - 1), wholeInputForReporting);
207    }
208   
 
209  16 toggle static int normalizeAngle(int angle) {
210  16 final int modulo = angle % 360;
211  16 if (modulo < 0) {
212  5 return modulo + 360;
213    } else {
214  11 return modulo;
215    }
216    }
217   
218    /**
219    * Using reflection, gather all fields of java.awt.Color that are
220    * static, public, final and of type java.awt.Color, and put them
221    * in them in a case-insensitive Map.
222    * Since we've already loaded the java.awt.Color, there is virtually
223    * no hit on performance or memory usage, except for this very small
224    * map.
225    * Yes, this is a bit ugly, since we're relying on how those constants
226    * are declared ... but it is not very likely to change, is it ?
227    */
 
228  1 toggle private static Map<String, Color> reflectivelyGetNamedColors() {
229  1 try {
230  1 final Map<String, Color> namedColors = new TreeMap<String, Color>(String.CASE_INSENSITIVE_ORDER);
231  1 final Field[] fields = Color.class.getFields();
232  1 for (final Field field : fields) {
233  29 int mod = field.getModifiers();
234  29 if (Modifier.isStatic(mod) && Modifier.isPublic(mod) && Modifier.isFinal(mod)) {
235  29 if (Color.class.equals(field.getType())) {
236  26 namedColors.put(field.getName().toLowerCase(), (Color) field.get(null));
237    }
238    }
239    }
240  1 return namedColors;
241    } catch (IllegalAccessException e) {
242  0 throw new RuntimeException("Can't access field values of java.awt.Color, is this system too secure?", e);
243    }
244    }
245   
 
246  0 toggle private static RuntimeException cantParse(String input, String messageFmt, Object... args) {
247  0 Test failure here return new IllegalArgumentException("Can't decode color " + input + ": " + String.format(messageFmt, args));
248    }
249   
250    }