mirror of
https://github.com/danieldemus/openhab-core.git
synced 2026-07-29 12:34:22 +02:00
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:
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
+7
-6
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user