Improve precision of ColorUtil (#3542)

* [ColorUtil] add fine precision methods and tests
* [ColorUtil] set precision to 5

Signed-off-by: Andrew Fiddian-Green <software@whitebear.ch>
This commit is contained in:
Andrew Fiddian-Green
2023-04-21 21:31:16 +02:00
committed by GitHub
parent bc922022c3
commit da5ac36a68
3 changed files with 168 additions and 113 deletions
@@ -13,6 +13,7 @@
package org.openhab.core.util;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import org.eclipse.jdt.annotation.NonNullByDefault;
@@ -36,6 +37,7 @@ import org.slf4j.LoggerFactory;
@NonNullByDefault
public class ColorUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(ColorUtil.class);
private static final MathContext COLOR_MATH_CONTEXT = new MathContext(5, RoundingMode.HALF_UP);
protected static final BigDecimal BIG_DECIMAL_HUNDRED = BigDecimal.valueOf(100);
public static final Gamut DEFAULT_GAMUT = new Gamut(new double[] { 0.9961, 0.0001 }, new double[] { 0, 0.9961 },
new double[] { 0, 0.0001 });
@@ -50,7 +52,10 @@ public class ColorUtil {
*
* This function does rounding to integer valued components. It is the preferred way of doing HSB to RGB conversion.
*
* See also: {@link hsbToRgbPercent(HSBType)}, {@link hsbTosRGB(HSBType)}
* See also: {@link #hsbToRgbPercent(HSBType)}, {@link #hsbTosRgb(HSBType)}
*
* @param hsb an {@link HSBType} value.
* @return array of three int with the RGB values in the range 0 to 255.
*/
public static int[] hsbToRgb(HSBType hsb) {
final PercentType[] rgbPercent = hsbToRgbPercent(hsb);
@@ -62,10 +67,13 @@ public class ColorUtil {
* Transform <a href="https://en.wikipedia.org/wiki/HSL_and_HSV">HSV</a> based {@link HSBType} to
* <a href="https://en.wikipedia.org/wiki/SRGB">sRGB</a>.
*
* This function not round the components to integer values. Please consider consider
* using {@link hsbToRgb(HSBType)} whenever integer values are required.
* This function does not round the components. For conversion to integer values in the range 0 to 255 use
* {@link #hsbToRgb(HSBType)}.
*
* See also: {@link hsbToRgb(HSBType)}, {@link hsbTosRgb(HSBType)}
* See also: {@link #hsbToRgb(HSBType)}, {@link #hsbTosRgb(HSBType)}
*
* @param hsb an {@link HSBType} value.
* @return array of three {@link PercentType} with the RGB values in the range 0 to 100 percent.
*/
public static PercentType[] hsbToRgbPercent(HSBType hsb) {
PercentType red = null;
@@ -79,6 +87,7 @@ public class ColorUtil {
final BigDecimal f = h.multiply(BigDecimal.valueOf(5)).divide(BigDecimal.valueOf(3), 10, RoundingMode.HALF_UP)
.remainder(BigDecimal.ONE);
final BigDecimal value = hsb.getBrightness().toBigDecimal();
PercentType a = new PercentType(value.multiply(BigDecimal.ONE.subtract(s)));
PercentType b = new PercentType(value.multiply(BigDecimal.ONE.subtract(s.multiply(f))));
PercentType c = new PercentType(
@@ -128,9 +137,10 @@ public class ColorUtil {
* <a href="https://en.wikipedia.org/wiki/SRGB">sRGB</a> color model.
* (Bits 24-31 are alpha, 16-23 are red, 8-15 are green, 0-7 are blue).
*
* See also: {@link hsbToRgb(HSBType)}, {@link hsbToRgbPercent(HSBType)}
* See also: {@link #hsbToRgb(HSBType)}, {@link #hsbToRgbPercent(HSBType)}
*
* @return the RGB value of the color in the default sRGB color model
* @param hsb an {@link HSBType} value.
* @return the RGB value of the color in the default sRGB color model.
*/
public static int hsbTosRgb(HSBType hsb) {
final int[] rgb = hsbToRgb(hsb);
@@ -145,11 +155,11 @@ public class ColorUtil {
* "https://developers.meethue.com/develop/application-design-guidance/color-conversion-formulas-rgb-to-xy-and-back/">Hue
* developerportal</a>.
*
* @param hsbType a {@link HSBType} value
* @return double array with the closest matching CIE 1931 color, x, y between 0.0000 and 1.0000.
* @param hsb an {@link HSBType} value.
* @return array of three double with the closest matching CIE 1931 x,y,Y in the range 0.0000 to 1.0000
*/
public static double[] hsbToXY(HSBType hsbType) {
return hsbToXY(hsbType, DEFAULT_GAMUT);
public static double[] hsbToXY(HSBType hsb) {
return hsbToXY(hsb, DEFAULT_GAMUT);
}
/**
@@ -160,12 +170,12 @@ public class ColorUtil {
* "https://developers.meethue.com/develop/application-design-guidance/color-conversion-formulas-rgb-to-xy-and-back/">Hue
* developer portal</a>.
*
* @param hsbType a {@link HSBType} value
* @param gamut the gamut supported by the light.
* @return double array with the closest matching CIE 1931 color, x, y, Y between 0.0000 and 1.0000.
* @param hsb an {@link HSBType} value.
* @param gamut the color Gamut supported by the light.
* @return array of three double with the closest matching CIE 1931 x,y,Y in the range 0.0000 to 1.0000
*/
public static double[] hsbToXY(HSBType hsbType, Gamut gamut) {
PercentType[] rgb = hsbToRgbPercent(hsbType);
public static double[] hsbToXY(HSBType hsb, Gamut gamut) {
PercentType[] rgb = hsbToRgbPercent(hsb);
double r = inverseCompand(rgb[0].doubleValue() / PercentType.HUNDRED.doubleValue());
double g = inverseCompand(rgb[1].doubleValue() / PercentType.HUNDRED.doubleValue());
double b = inverseCompand(rgb[2].doubleValue() / PercentType.HUNDRED.doubleValue());
@@ -181,8 +191,10 @@ public class ColorUtil {
double[] xyY = new double[] { ((int) (q.x * 10000.0)) / 10000.0, ((int) (q.y * 10000.0)) / 10000.0,
((int) (Y * 10000.0)) / 10000.0 };
LOGGER.trace("HSB: {} - RGB: {} - XYZ: {} {} {} - xyY: {}", hsbType, hsbType.toRGB(), X, Y, Z, xyY);
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("HSB: {} - RGB: {} - XYZ: {} {} {} - xyY: {}", hsb, ColorUtil.hsbToRgbPercent(hsb), X, Y, Z,
xyY);
}
return xyY;
}
@@ -190,56 +202,67 @@ public class ColorUtil {
* Transform <a href="https://en.wikipedia.org/wiki/SRGB">sRGB</a> color format to
* <a href="https://en.wikipedia.org/wiki/HSL_and_HSV">HSV</a> based {@link HSBType}.
*
* Note: Conversion result is rounded and HSBType is created with integer valued components.
*
* @param rgb int array of length 3, all values are constrained to 0-255
* @param rgb array of three int with the RGB values in the range 0 to 255.
* @return the corresponding {@link HSBType}.
* @throws IllegalArgumentException when input array has wrong size or exceeds allowed value range
* @throws IllegalArgumentException when input array has wrong size or exceeds allowed value range.
*/
public static HSBType rgbToHsb(int[] rgb) throws IllegalArgumentException {
if (rgb.length != 3 || !inByteRange(rgb[0]) || !inByteRange(rgb[1]) || !inByteRange(rgb[2])) {
throw new IllegalArgumentException("RGB array only allows values between 0 and 255");
}
final int r = rgb[0];
final int g = rgb[1];
final int b = rgb[2];
return rgbToHsb(new PercentType[] { convertByteToColorPercent(rgb[0]), convertByteToColorPercent(rgb[1]),
convertByteToColorPercent(rgb[2]) });
}
int max = (r > g) ? r : g;
if (b > max) {
max = b;
/**
* Transform <a href="https://en.wikipedia.org/wiki/SRGB">sRGB</a> color format to
* <a href="https://en.wikipedia.org/wiki/HSL_and_HSV">HSV</a> based {@link HSBType}.
*
* @param rgb array of three {@link PercentType] with the RGB values in the range 0 to 100 percent.
* @return the corresponding {@link HSBType}.
* @throws IllegalArgumentException when input array has wrong size or exceeds allowed value range.
*/
public static HSBType rgbToHsb(PercentType[] rgb) throws IllegalArgumentException {
if (rgb.length != 3) {
throw new IllegalArgumentException("RGB array needs exactly three values!");
}
int min = (r < g) ? r : g;
if (b < min) {
min = b;
BigDecimal r = rgb[0].toBigDecimal();
BigDecimal g = rgb[1].toBigDecimal();
BigDecimal b = rgb[2].toBigDecimal();
BigDecimal max = r.max(g).max(b);
BigDecimal min = r.min(g).min(b);
BigDecimal span = max.subtract(min);
if (max.compareTo(BigDecimal.ZERO) == 0) { // all values are 0, return black
return new HSBType();
} else if (span.compareTo(BigDecimal.ZERO) == 0) { // all values are equal, return dimmed white
return new HSBType(new DecimalType(), new PercentType(), new PercentType(max));
}
float tmpHue;
final float tmpBrightness = max / 2.55f;
final float tmpSaturation = (max != 0 ? ((float) (max - min)) / ((float) max) : 0) * 100.0f;
// smallest possible saturation: 0 (max=0 or max-min=0), other value closest to 0 is 100/255 (max=255, min=254)
// -> avoid float comparision to 0
// if (tmpSaturation == 0) {
if (max == 0 || (max - min) == 0) {
tmpHue = 0;
PercentType saturation = new PercentType(span.divide(max, COLOR_MATH_CONTEXT).multiply(BIG_DECIMAL_HUNDRED));
PercentType brightness = new PercentType(max);
BigDecimal scale = span.divide(BigDecimal.valueOf(60), COLOR_MATH_CONTEXT);
BigDecimal redAngle = max.subtract(r).divide(scale, COLOR_MATH_CONTEXT);
BigDecimal greenAngle = max.subtract(g).divide(scale, COLOR_MATH_CONTEXT);
BigDecimal blueAngle = max.subtract(b).divide(scale, COLOR_MATH_CONTEXT);
BigDecimal hue;
if (r.compareTo(max) == 0) {
hue = blueAngle.subtract(greenAngle);
} else if (g.compareTo(max) == 0) {
hue = BigDecimal.valueOf(120).add(redAngle).subtract(blueAngle);
} else {
float red = ((float) (max - r)) / ((float) (max - min));
float green = ((float) (max - g)) / ((float) (max - min));
float blue = ((float) (max - b)) / ((float) (max - min));
if (r == max) {
tmpHue = blue - green;
} else if (g == max) {
tmpHue = 2.0f + red - blue;
} else {
tmpHue = 4.0f + green - red;
}
tmpHue = tmpHue / 6.0f * 360;
if (tmpHue < 0) {
tmpHue = tmpHue + 360.0f;
}
hue = BigDecimal.valueOf(240).add(greenAngle).subtract(redAngle);
}
if (hue.compareTo(BigDecimal.ZERO) < 0) {
hue = hue.add(BigDecimal.valueOf(360));
}
// adding 0.5 and casting to int approximates rounding
return new HSBType(new DecimalType((int) (tmpHue + .5f)), new PercentType((int) (tmpSaturation + .5f)),
new PercentType((int) (tmpBrightness + .5f)));
return new HSBType(new DecimalType(hue), saturation, brightness);
}
/**
@@ -250,9 +273,9 @@ public class ColorUtil {
* "https://developers.meethue.com/develop/application-design-guidance/color-conversion-formulas-rgb-to-xy-and-back/">Hue
* developer portal</a>.
*
* @param xy the CIE 1931 xy color, x,y between 0.0000 and 1.0000.
* @param xy array of double with CIE 1931 x,y[,Y] in the range 0.0000 to 1.0000 <code>Y</code> value is optional.
* @return the corresponding {@link HSBType}.
* @throws IllegalArgumentException when input array has wrong size or exceeds allowed value range
* @throws IllegalArgumentException when input array has wrong size or exceeds allowed value range.
*/
public static HSBType xyToHsb(double[] xy) throws IllegalArgumentException {
return xyToHsb(xy, DEFAULT_GAMUT);
@@ -266,15 +289,15 @@ public class ColorUtil {
* "https://developers.meethue.com/develop/application-design-guidance/color-conversion-formulas-rgb-to-xy-and-back/">Hue
* developer portal</a>.
*
* @param xy the CIE 1931 xy color, x,y[,Y] between 0.0000 and 1.0000. <code>Y</code> value is optional.
* @param gamut the gamut supported by the light.
* @param xy array of double with CIE 1931 x,y[,Y] in the range 0.0000 to 1.0000 <code>Y</code> value is optional.
* @param gamut the color Gamut supported by the light.
* @return the corresponding {@link HSBType}.
* @throws IllegalArgumentException when input array has wrong size or exceeds allowed value range
*/
public static HSBType xyToHsb(double[] xy, Gamut gamut) throws IllegalArgumentException {
if (xy.length < 2 || xy.length > 3 || !inRange(xy[0]) || !inRange(xy[1])
|| (xy.length == 3 && !inRange(xy[2]))) {
throw new IllegalArgumentException("xy array only allowes two or three values between 0.0 and 1.0.");
throw new IllegalArgumentException("xy array only allows two or three values between 0.0 and 1.0.");
}
Point p = gamut.closest(new Point(xy[0], xy[1]));
double x = p.x;
@@ -315,11 +338,10 @@ public class ColorUtil {
b /= max;
}
HSBType hsb = rgbToHsb(
new int[] { (int) Math.round(255.0 * r), (int) Math.round(255.0 * g), (int) Math.round(255.0 * b) });
LOGGER.trace("xy: {} - XYZ: {} {} {} - RGB: {} {} {} - HSB: {} ", xy, X, Y, Z, r, g, b, hsb);
LOGGER.trace("xy: {} - XYZ: {} {} {} - RGB: {} {} {}", xy, X, Y, Z, r, g, b);
return hsb;
return rgbToHsb(new PercentType[] { convertDoubleToColorPercent(r), convertDoubleToColorPercent(g),
convertDoubleToColorPercent(b) });
}
/**
@@ -466,4 +488,12 @@ public class ColorUtil {
return percent.toBigDecimal().multiply(BigDecimal.valueOf(255))
.divide(BIG_DECIMAL_HUNDRED, 0, RoundingMode.HALF_UP).intValue();
}
private static PercentType convertByteToColorPercent(int b) {
return new PercentType(new BigDecimal(b).divide(new BigDecimal("2.55"), COLOR_MATH_CONTEXT));
}
private static PercentType convertDoubleToColorPercent(double d) {
return new PercentType(new BigDecimal(d).multiply(BIG_DECIMAL_HUNDRED, COLOR_MATH_CONTEXT));
}
}
@@ -70,10 +70,11 @@ public class HSBTypeTest {
private void compareHsbToRgbValues(String hsbValues, int red, int green, int blue) {
HSBType hsb = new HSBType(hsbValues);
HSBType hsbRgb = HSBType.fromRGB(red, green, blue);
assertEquals(red, convertPercentToByte(hsb.getRed()));
assertEquals(green, convertPercentToByte(hsb.getGreen()));
assertEquals(blue, convertPercentToByte(hsb.getBlue()));
assertEquals(hsb.getHue().doubleValue(), hsbRgb.getHue().doubleValue(), 0.5);
assertEquals(hsb.getSaturation().doubleValue(), hsbRgb.getSaturation().doubleValue(), 0.5);
assertEquals(hsb.getBrightness().doubleValue(), hsbRgb.getBrightness().doubleValue(), 0.5);
}
@Test
@@ -93,9 +94,9 @@ public class HSBTypeTest {
HSBType hsb = new HSBType(hsbValues);
HSBType hsbRgb = HSBType.fromRGB(red, green, blue);
assertEquals(hsb.getHue(), hsbRgb.getHue());
assertEquals(hsb.getSaturation(), hsbRgb.getSaturation());
assertEquals(hsb.getBrightness(), hsbRgb.getBrightness());
assertEquals(hsb.getHue().doubleValue(), hsbRgb.getHue().doubleValue(), 0.5);
assertEquals(hsb.getSaturation().doubleValue(), hsbRgb.getSaturation().doubleValue(), 0.5);
assertEquals(hsb.getBrightness().doubleValue(), hsbRgb.getBrightness().doubleValue(), 0.5);
}
@Test
@@ -13,9 +13,7 @@
package org.openhab.core.util;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.lessThan;
import static org.hamcrest.Matchers.lessThanOrEqualTo;
import static org.hamcrest.Matchers.*;
import static org.junit.jupiter.api.Assertions.*;
import java.util.stream.Stream;
@@ -32,6 +30,7 @@ import org.junit.jupiter.params.provider.ArgumentsSource;
import org.junit.jupiter.params.provider.MethodSource;
import org.openhab.core.library.types.HSBType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.util.ColorUtil.Gamut;
/**
* The {@link ColorUtilTest} is a test class for the color conversion
@@ -52,8 +51,10 @@ public class ColorUtilTest {
deltaHue = deltaHue > 180.0 ? Math.abs(deltaHue - 360) : deltaHue;
double deltaSat = Math.abs(hsb.getSaturation().doubleValue() - hsb2.getSaturation().doubleValue());
double deltaBri = Math.abs(hsb.getBrightness().doubleValue() - hsb2.getBrightness().doubleValue());
assertThat(deltaHue, is(lessThan(5.0)));
// hue is meaningless when saturation is zero
if (hsb.getSaturation().doubleValue() > 0) {
assertThat(deltaHue, is(lessThan(5.0)));
}
assertThat(deltaSat, is(lessThanOrEqualTo(1.0)));
assertThat(deltaBri, is(lessThanOrEqualTo(1.0)));
}
@@ -75,12 +76,12 @@ public class ColorUtilTest {
// test RGB -> HSB -> RGB conversion for different values, including the ones known to cause rounding error
@ParameterizedTest
@ArgumentsSource(RgbValueProvider.class)
public void testConversionRgbToHsbToRgb(int[] rgb, int maxSquaredSum) {
public void testConversionRgbToHsbToRgb(int[] rgb) {
HSBType hsb = ColorUtil.rgbToHsb(rgb);
Assertions.assertNotNull(hsb);
final int[] convertedRgb = ColorUtil.hsbToRgb(hsb);
assertRgbEquals(rgb, convertedRgb, maxSquaredSum);
assertRgbEquals(rgb, convertedRgb);
}
@ParameterizedTest
@@ -90,7 +91,7 @@ public class ColorUtilTest {
final HSBType hsbType = new HSBType(hsbString);
final int[] converted = ColorUtil.hsbToRgb(hsbType);
assertRgbEquals(rgb, converted, 0);
assertRgbEquals(rgb, converted);
}
@ParameterizedTest
@@ -99,7 +100,7 @@ public class ColorUtilTest {
final HSBType hsbType = ColorUtil.rgbToHsb(rgb);
final int[] rgbConverted = ColorUtil.hsbToRgb(hsbType);
assertRgbEquals(rgb, rgbConverted, 0);
assertRgbEquals(rgb, rgbConverted);
}
@ParameterizedTest
@@ -113,11 +114,47 @@ public class ColorUtilTest {
assertTrue(hsbType.closeTo(new HSBType(expected), 0.01));
}
private void xyToXY(double[] xy, Gamut gamut) {
assertTrue(xy.length > 1);
HSBType hsb = ColorUtil.xyToHsb(xy, gamut);
double[] xy2 = ColorUtil.hsbToXY(hsb, gamut);
assertTrue(xy2.length > 1);
for (int i = 0; i < xy.length; i++) {
assertEquals(xy[i], xy2[i], 0.02);
}
}
/**
* Test XY -> RGB -> HSB - RGB - XY round trips.
* Use ColorUtil fine precision methods for conversions.
* Test on Hue standard Gamuts 'A', 'B', and 'C'.
*/
@Test
public void testXyHsbRoundTrips() {
Gamut[] gamuts = new Gamut[] {
new Gamut(new double[] { 0.704, 0.296 }, new double[] { 0.2151, 0.7106 }, new double[] { 0.138, 0.08 }),
new Gamut(new double[] { 0.675, 0.322 }, new double[] { 0.409, 0.518 }, new double[] { 0.167, 0.04 }),
new Gamut(new double[] { 0.6915, 0.3038 }, new double[] { 0.17, 0.7 }, new double[] { 0.1532, 0.0475 }) //
};
for (Gamut g : gamuts) {
xyToXY(g.r(), g);
xyToXY(g.g(), g);
xyToXY(g.b(), g);
xyToXY(new double[] { (g.r()[0] + g.g()[0]) / 2f, (g.r()[1] + g.g()[1]) / 2f }, g);
xyToXY(new double[] { (g.g()[0] + g.b()[0]) / 2f, (g.g()[1] + g.b()[1]) / 2f }, g);
xyToXY(new double[] { (g.b()[0] + g.r()[0]) / 2f, (g.b()[1] + g.r()[1]) / 2f }, g);
xyToXY(new double[] { (g.r()[0] + g.g()[0] + g.b()[0]) / 3f, (g.r()[1] + g.g()[1] + g.b()[1]) / 3f }, g);
xyToXY(ColorUtil.hsbToXY(HSBType.WHITE), g);
}
}
/* Providers for parameterized tests */
private static Stream<Arguments> colors() {
return Stream.of(HSBType.BLACK, HSBType.BLUE, HSBType.GREEN, HSBType.RED, HSBType.WHITE,
ColorUtil.rgbToHsb(new int[] { 127, 94, 19 })).map(Arguments::of);
return Stream
.of(HSBType.BLACK, HSBType.BLUE, HSBType.GREEN, HSBType.RED, HSBType.WHITE,
ColorUtil.rgbToHsb(new int[] { 127, 94, 19 }), new HSBType("0,0.1,0"), new HSBType("0,0.1,100"))
.map(Arguments::of);
}
private static Stream<Arguments> invalids() {
@@ -152,55 +189,42 @@ public class ColorUtilTest {
}
/*
* Return a stream RGB values together with allowed deviation (sum of squared differences).
* Differences in conversion are due to rounding errors as HSBType is created with integer numbers.
* Return a stream RGB values.
*/
static class RgbValueProvider implements ArgumentsProvider {
@Override
public Stream<? extends Arguments> provideArguments(@Nullable ExtensionContext context) throws Exception {
return Stream.of(Arguments.of(new int[] { 0, 0, 0 }, 0), Arguments.of(new int[] { 255, 255, 255 }, 0),
Arguments.of(new int[] { 255, 0, 0 }, 0), Arguments.of(new int[] { 0, 255, 0 }, 0),
Arguments.of(new int[] { 0, 0, 255 }, 0), Arguments.of(new int[] { 255, 255, 0 }, 0),
Arguments.of(new int[] { 255, 0, 255 }, 0), Arguments.of(new int[] { 0, 255, 255 }, 0),
Arguments.of(new int[] { 191, 191, 191 }, 0), Arguments.of(new int[] { 128, 128, 128 }, 0),
Arguments.of(new int[] { 128, 0, 0 }, 0), Arguments.of(new int[] { 128, 128, 0 }, 0),
Arguments.of(new int[] { 0, 128, 0 }, 0), Arguments.of(new int[] { 128, 0, 128 }, 0),
Arguments.of(new int[] { 0, 128, 128 }, 0), Arguments.of(new int[] { 0, 0, 128 }, 0),
Arguments.of(new int[] { 0, 132, 255 }, 0), Arguments.of(new int[] { 1, 131, 254 }, 3),
Arguments.of(new int[] { 2, 130, 253 }, 6), Arguments.of(new int[] { 3, 129, 252 }, 4),
Arguments.of(new int[] { 4, 128, 251 }, 3), Arguments.of(new int[] { 5, 127, 250 }, 0));
return Stream.of(Arguments.of(new int[] { 0, 0, 0 }), Arguments.of(new int[] { 255, 255, 255 }),
Arguments.of(new int[] { 255, 0, 0 }), Arguments.of(new int[] { 0, 255, 0 }),
Arguments.of(new int[] { 0, 0, 255 }), Arguments.of(new int[] { 255, 255, 0 }),
Arguments.of(new int[] { 255, 0, 255 }), Arguments.of(new int[] { 0, 255, 255 }),
Arguments.of(new int[] { 191, 191, 191 }), Arguments.of(new int[] { 128, 128, 128 }),
Arguments.of(new int[] { 128, 0, 0 }), Arguments.of(new int[] { 128, 128, 0 }),
Arguments.of(new int[] { 0, 128, 0 }), Arguments.of(new int[] { 128, 0, 128 }),
Arguments.of(new int[] { 0, 128, 128 }), Arguments.of(new int[] { 0, 0, 128 }),
Arguments.of(new int[] { 0, 132, 255 }), Arguments.of(new int[] { 1, 131, 254 }),
Arguments.of(new int[] { 2, 130, 253 }), Arguments.of(new int[] { 3, 129, 252 }),
Arguments.of(new int[] { 4, 128, 251 }), Arguments.of(new int[] { 5, 127, 250 }));
}
}
/* Helper functions */
/**
* Helper method for checking if expected and actual RGB color parameters (int[3], 0..255) lie within a given
* percentage of each other. This method is required in order to eliminate integer rounding artifacts in JUnit tests
* when comparing RGB values. Asserts that the color parameters of expected and actual do not have a squared sum
* of differences which exceeds maxSquaredSum.
* Helper method for checking if expected and actual RGB color parameters (int[3], 0..255) match.
*
* When the test fails, both colors are printed.
*
* @param expected an HSBType containing the expected color.
* @param actual an HSBType containing the actual color.
* @param maxSquaredSum the maximum allowed squared sum of differences.
*/
private void assertRgbEquals(final int[] expected, final int[] actual, int maxSquaredSum) {
int squaredSum = 0;
private void assertRgbEquals(final int[] expected, final int[] actual) {
if (expected[0] != actual[0] || expected[1] != actual[1] || expected[2] != actual[2]) {
// only proceed if both RGB colors are not idential
for (int i = 0; i < 3; i++) {
int diff = expected[i] - actual[i];
squaredSum = squaredSum + diff * diff;
}
if (squaredSum > maxSquaredSum) {
// deviation too high, just prepare readable string compare and let it fail
final String expectedS = expected[0] + ", " + expected[1] + ", " + expected[2];
final String actualS = actual[0] + ", " + actual[1] + ", " + actual[2];
assertEquals(expectedS, actualS);
}
// only proceed if both RGB colors are not idential,
// just prepare readable string compare and let it fail
final String expectedS = expected[0] + ", " + expected[1] + ", " + expected[2];
final String actualS = actual[0] + ", " + actual[1] + ", " + actual[2];
assertEquals(expectedS, actualS);
}
}
}