mirror of
https://github.com/danieldemus/openhab-core.git
synced 2025-01-10 13:21:53 +01:00
ColorUtil bug fixes and improvements (#4124)
* ColorUtil improvements Signed-off-by: Andrew Fiddian-Green <software@whitebear.ch>
This commit is contained in:
parent
b563f1577a
commit
a3bfcf5b2f
@ -33,6 +33,7 @@ import org.slf4j.LoggerFactory;
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
* @author Holger Friedrich - Transfer RGB color conversion from HSBType, improve RGB conversion, restructuring
|
||||
* @author Chris Jackson - Added fromRGB (moved from HSBType)
|
||||
* @author Andrew Fiddian-Green - Extensive revamp to fix bugs and improve accuracy
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ColorUtil {
|
||||
@ -69,9 +70,7 @@ public class ColorUtil {
|
||||
* @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);
|
||||
return new int[] { convertColorPercentToByte(rgbPercent[0]), convertColorPercentToByte(rgbPercent[1]),
|
||||
convertColorPercentToByte(rgbPercent[2]) };
|
||||
return getIntArray(hsbToRgbPercent(hsb));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -87,9 +86,7 @@ public class ColorUtil {
|
||||
* @return array of four int with the RGBW values in the range 0 to 255.
|
||||
*/
|
||||
public static int[] hsbToRgbw(HSBType hsb) {
|
||||
final PercentType[] rgbPercent = hsbToRgbwPercent(hsb);
|
||||
return new int[] { convertColorPercentToByte(rgbPercent[0]), convertColorPercentToByte(rgbPercent[1]),
|
||||
convertColorPercentToByte(rgbPercent[2]), convertColorPercentToByte(rgbPercent[3]) };
|
||||
return getIntArray(hsbToRgbwPercent(hsb));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -175,38 +172,36 @@ public class ColorUtil {
|
||||
* @return array of four {@link PercentType} with the RGBW values in the range 0 to 100 percent.
|
||||
*/
|
||||
public static PercentType[] hsbToRgbwPercent(HSBType hsb) {
|
||||
PercentType[] rgb = hsbToRgbPercent(hsb);
|
||||
final BigDecimal inRed = rgb[0].toBigDecimal();
|
||||
final BigDecimal inGreen = rgb[1].toBigDecimal();
|
||||
final BigDecimal inBlue = rgb[2].toBigDecimal();
|
||||
// Get the maximum between R, G, and B
|
||||
final BigDecimal maxColor = inRed.max(inGreen.max(inBlue));
|
||||
PercentType[] rgbPercents = hsbToRgbPercent(hsb);
|
||||
|
||||
// If the maximum value is 0, immediately return pure black.
|
||||
if (BigDecimal.ZERO.equals(maxColor)) {
|
||||
return new PercentType[] { PercentType.ZERO, PercentType.ZERO, PercentType.ZERO, PercentType.ZERO };
|
||||
// convert RGB PercentTypes to RGB doubles
|
||||
double[] rgb = new double[3];
|
||||
for (int i = 0; i < 3; i++) {
|
||||
rgb[i] = rgbPercents[i].doubleValue();
|
||||
}
|
||||
|
||||
// This section serves to figure out what the color with 100% hue is
|
||||
final BigDecimal multiplier = BIG_DECIMAL_100.divide(maxColor, 0, RoundingMode.DOWN);
|
||||
final BigDecimal hR = inRed.multiply(multiplier);
|
||||
final BigDecimal hG = inGreen.multiply(multiplier);
|
||||
final BigDecimal hB = inBlue.multiply(multiplier);
|
||||
// create RGBW array
|
||||
PercentType[] rgbw = new PercentType[4];
|
||||
if (Math.max(rgb[0], Math.max(rgb[1], rgb[2])) > 0) {
|
||||
double whi = Math.min(rgb[0], Math.min(rgb[1], rgb[2]));
|
||||
for (int i = 0; i < 3; i++) {
|
||||
rgbw[i] = new PercentType(BigDecimal.valueOf(rgb[i] - whi));
|
||||
}
|
||||
rgbw[3] = new PercentType(BigDecimal.valueOf(whi));
|
||||
} else {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
rgbw[i] = PercentType.ZERO;
|
||||
}
|
||||
}
|
||||
|
||||
// This calculates the Whiteness (not strictly speaking Luminance) of the color
|
||||
final BigDecimal whitenessMax = hR.max(hG.max(hB));
|
||||
final BigDecimal whitenessMin = hR.min(hG.min(hB));
|
||||
final BigDecimal luminance = ((whitenessMax.add(whitenessMin).divide(BIG_DECIMAL_2).subtract(BIG_DECIMAL_50))
|
||||
.multiply(BIG_DECIMAL_100.divide(BIG_DECIMAL_50))).divide(multiplier);
|
||||
if (LOGGER.isTraceEnabled()) {
|
||||
LOGGER.trace("{}",
|
||||
String.format("RGB:[%.6f,%.6f,%.6f] - RGBW:[%.6f,%.6f,%.6f,%.6f]", rgbPercents[0].doubleValue(),
|
||||
rgbPercents[1].doubleValue(), rgbPercents[2].doubleValue(), rgbw[0].doubleValue(),
|
||||
rgbw[1].doubleValue(), rgbw[2].doubleValue(), rgbw[3].doubleValue()));
|
||||
}
|
||||
|
||||
// check range
|
||||
BigDecimal outRed = inRed.subtract(luminance).max(BigDecimal.ZERO);
|
||||
BigDecimal outGreen = inGreen.subtract(luminance).max(BigDecimal.ZERO);
|
||||
BigDecimal outBlue = inBlue.subtract(luminance).max(BigDecimal.ZERO);
|
||||
BigDecimal outWhite = luminance.max(BigDecimal.ZERO);
|
||||
|
||||
return new PercentType[] { new PercentType(outRed), new PercentType(outGreen), new PercentType(outBlue),
|
||||
new PercentType(outWhite) };
|
||||
return rgbw;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -221,7 +216,7 @@ public class ColorUtil {
|
||||
* @return the RGB value of the color in the default sRGB color model.
|
||||
*/
|
||||
public static int hsbTosRgb(HSBType hsb) {
|
||||
final int[] rgb = hsbToRgb(hsb);
|
||||
final int[] rgb = getIntArray(hsbToRgbPercent(hsb));
|
||||
return (0xFF << 24) | ((rgb[0] & 0xFF) << 16) | ((rgb[1] & 0xFF) << 8) | ((rgb[2] & 0xFF) << 0);
|
||||
}
|
||||
|
||||
@ -250,29 +245,58 @@ public class ColorUtil {
|
||||
*
|
||||
* @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
|
||||
* @return array of three or four double with the closest matching CIE 1931 x,y,Y in the range 0.0000 to 1.0000 -
|
||||
* plus an optional extra empty element to flag if the xyY result has been forced inside the given Gamut.
|
||||
*/
|
||||
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());
|
||||
PercentType[] rgbPercents = hsbToRgbPercent(hsb);
|
||||
|
||||
// convert rgbPercents to doubles
|
||||
double r = rgbPercents[0].doubleValue() / 100.0;
|
||||
double g = rgbPercents[1].doubleValue() / 100.0;
|
||||
double b = rgbPercents[2].doubleValue() / 100.0;
|
||||
|
||||
// prevent divide by zero errors
|
||||
if (Math.max(r, Math.max(g, b)) <= 0.0) {
|
||||
r = 0.000001;
|
||||
g = 0.000001;
|
||||
b = 0.000001;
|
||||
}
|
||||
|
||||
// apply gamma correction
|
||||
r = r > 0.04045 ? Math.pow((r + 0.055) / (1.0 + 0.055), 2.4) : r / 12.92;
|
||||
g = g > 0.04045 ? Math.pow((g + 0.055) / (1.0 + 0.055), 2.4) : g / 12.92;
|
||||
b = b > 0.04045 ? Math.pow((b + 0.055) / (1.0 + 0.055), 2.4) : b / 12.92;
|
||||
|
||||
// convert RGB to XYZ using 'Wide RGB D65' formula
|
||||
double X = r * 0.664511 + g * 0.154324 + b * 0.162028;
|
||||
double Y = r * 0.283881 + g * 0.668433 + b * 0.047685;
|
||||
double Z = r * 0.000088 + g * 0.072310 + b * 0.986039;
|
||||
|
||||
// convert XYZ to xyz
|
||||
double sum = X + Y + Z;
|
||||
Point p = sum == 0.0 ? new Point() : new Point(X / sum, Y / sum);
|
||||
Point q = gamut.closest(p);
|
||||
double x = X / sum;
|
||||
double y = Y / sum;
|
||||
double z = Y;
|
||||
|
||||
double[] xyY = new double[] { ((int) (q.x * 10000.0)) / 10000.0, ((int) (q.y * 10000.0)) / 10000.0,
|
||||
((int) (Y * 10000.0)) / 10000.0 };
|
||||
// force xy point to be inside the gamut
|
||||
Point xy = gamut.closest(new Point(x, y));
|
||||
boolean xyForced = xy.x != x || xy.y != y;
|
||||
|
||||
// create xyY; increment array size to flag if xy was forced
|
||||
double[] xyY = new double[xyForced ? 4 : 3];
|
||||
xyY[0] = xy.x;
|
||||
xyY[1] = xy.y;
|
||||
xyY[2] = Y;
|
||||
|
||||
if (LOGGER.isTraceEnabled()) {
|
||||
LOGGER.trace("HSB: {} - RGB: {} - XYZ: {} {} {} - xyY: {}", hsb, ColorUtil.hsbToRgbPercent(hsb), X, Y, Z,
|
||||
xyY);
|
||||
LOGGER.trace("{}", String.format(
|
||||
"HSB:[%.6f,%.6f,%.6f] - RGB:[%.6f,%.6f,%.6f] - RGB':[%.6f,%.6f,%.6f] - XYZ:[%.6f,%.6f,%.6f] - xyz:[%.6f,%.6f,%.6f] - xyY:[%.6f,%.6f,%.6f] (xyForced:%b)",
|
||||
hsb.getHue().doubleValue(), hsb.getSaturation().doubleValue(), hsb.getBrightness().doubleValue(),
|
||||
rgbPercents[0].doubleValue() / 100.0, rgbPercents[1].doubleValue() / 100.0,
|
||||
rgbPercents[2].doubleValue() / 100.0, r, g, b, X, Y, Z, x, y, z, xyY[0], xyY[1], xyY[2], xyForced));
|
||||
}
|
||||
|
||||
return xyY;
|
||||
}
|
||||
|
||||
@ -285,60 +309,24 @@ public class ColorUtil {
|
||||
* @throws IllegalArgumentException when input array has wrong size or exceeds allowed value range.
|
||||
*/
|
||||
public static HSBType rgbToHsb(int[] rgbw) throws IllegalArgumentException {
|
||||
if (rgbw.length == 4) {
|
||||
if (!inByteRange(rgbw[0]) || !inByteRange(rgbw[1]) || !inByteRange(rgbw[2]) || !inByteRange(rgbw[3])) {
|
||||
throw new IllegalArgumentException("rgbToHsb requires 3 or 4 values between 0 and 255");
|
||||
if (rgbw.length < 3 || rgbw.length > 4) {
|
||||
throw new IllegalArgumentException("rgbToHsb() requires 3 or 4 arguments");
|
||||
}
|
||||
|
||||
for (int i = 0; i < rgbw.length; i++) {
|
||||
if (rgbw[i] < 0 || rgbw[i] > 255) {
|
||||
throw new IllegalArgumentException(
|
||||
String.format("rgbToHsb() argument %d value '%f' out of range [0..255]", i, rgbw[i]));
|
||||
}
|
||||
return rgbwToHsb(new PercentType[] { convertByteToColorPercent(rgbw[0]), convertByteToColorPercent(rgbw[1]),
|
||||
convertByteToColorPercent(rgbw[2]), convertByteToColorPercent(rgbw[3]) });
|
||||
}
|
||||
if (rgbw.length != 3 || !inByteRange(rgbw[0]) || !inByteRange(rgbw[1]) || !inByteRange(rgbw[2])) {
|
||||
throw new IllegalArgumentException("rgbToHsb requires 3 or 4 values between 0 and 255");
|
||||
}
|
||||
return rgbToHsb(new PercentType[] { convertByteToColorPercent(rgbw[0]), convertByteToColorPercent(rgbw[1]),
|
||||
convertByteToColorPercent(rgbw[2]) });
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform RGBW to <a href="https://en.wikipedia.org/wiki/HSL_and_HSV">HSV</a> based {@link HSBType}.
|
||||
*
|
||||
* See <a href=
|
||||
* "https://stackoverflow.com/questions/40312216/converting-rgb-to-rgbw">Converting RGB to RGBW</a>.
|
||||
*
|
||||
* See also: {@link #hsbToRgb(HSBType)}, {@link #hsbTosRgb(HSBType)}, {@link #hsbToRgbPercent(HSBType)}
|
||||
*
|
||||
* @param rgbw array of four int with the RGBW values in the range 0 to 255.
|
||||
* @return hsb an {@link HSBType} value.
|
||||
*
|
||||
*/
|
||||
private static HSBType rgbwToHsb(PercentType[] rgbw) {
|
||||
if (rgbw.length != 4) {
|
||||
throw new IllegalArgumentException("RGBW requires 4 values");
|
||||
}
|
||||
|
||||
BigDecimal luminance = BigDecimal.valueOf(rgbw[3].doubleValue() / PercentType.HUNDRED.doubleValue() * 255.0);
|
||||
BigDecimal inRed = BigDecimal.valueOf(rgbw[0].doubleValue() / PercentType.HUNDRED.doubleValue() * 255.0)
|
||||
.add(luminance);
|
||||
BigDecimal inGreen = BigDecimal.valueOf(rgbw[1].doubleValue() / PercentType.HUNDRED.doubleValue() * 255.0)
|
||||
.add(luminance);
|
||||
BigDecimal inBlue = BigDecimal.valueOf(rgbw[2].doubleValue() / PercentType.HUNDRED.doubleValue() * 255.0)
|
||||
.add(luminance);
|
||||
|
||||
// Get the maximum between R, G, and B
|
||||
final BigDecimal maxColor = BIG_DECIMAL_255.min(inRed.max(inGreen.max(inBlue)).max(BigDecimal.ZERO));
|
||||
|
||||
// If the maximum value is 0, immediately return pure black.
|
||||
if (BigDecimal.ZERO.compareTo(maxColor) == 0) {
|
||||
return HSBType.BLACK;
|
||||
PercentType[] rgbwPercents = new PercentType[rgbw.length];
|
||||
for (int i = 0; i < rgbw.length; i++) {
|
||||
rgbwPercents[i] = new PercentType(
|
||||
new BigDecimal(rgbw[i]).divide(BIG_DECIMAL_2_POINT_55, COLOR_MATH_CONTEXT));
|
||||
}
|
||||
|
||||
final BigDecimal multiplier = BIG_DECIMAL_255.divide(maxColor, 0, RoundingMode.DOWN);
|
||||
|
||||
BigDecimal outRed = inRed.divide(multiplier).min(BIG_DECIMAL_255).max(BigDecimal.ZERO);
|
||||
BigDecimal outGreen = inGreen.divide(multiplier).min(BIG_DECIMAL_255).max(BigDecimal.ZERO);
|
||||
BigDecimal outBlue = inBlue.divide(multiplier).min(BIG_DECIMAL_255).max(BigDecimal.ZERO);
|
||||
|
||||
return HSBType.fromRGB(outRed.intValue(), outGreen.intValue(), outBlue.intValue());
|
||||
return rgbToHsb(rgbwPercents);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -349,17 +337,43 @@ public class ColorUtil {
|
||||
* @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 == 4) {
|
||||
return rgbwToHsb(rgb);
|
||||
}
|
||||
if (rgb.length != 3) {
|
||||
throw new IllegalArgumentException("RGB array needs exactly three values!");
|
||||
public static HSBType rgbToHsb(PercentType[] rgbw) throws IllegalArgumentException {
|
||||
if (rgbw.length < 3 || rgbw.length > 4) {
|
||||
throw new IllegalArgumentException("rgbToHsb() requires 3 or 4 arguments");
|
||||
}
|
||||
|
||||
BigDecimal r = rgb[0].toBigDecimal();
|
||||
BigDecimal g = rgb[1].toBigDecimal();
|
||||
BigDecimal b = rgb[2].toBigDecimal();
|
||||
BigDecimal r;
|
||||
BigDecimal g;
|
||||
BigDecimal b;
|
||||
|
||||
if (rgbw.length == 3) {
|
||||
// use RGB BigDecimal values as-is
|
||||
r = rgbw[0].toBigDecimal();
|
||||
g = rgbw[1].toBigDecimal();
|
||||
b = rgbw[2].toBigDecimal();
|
||||
} else {
|
||||
// convert RGBW BigDecimal values to RGB BigDecimal values
|
||||
double red = rgbw[0].doubleValue();
|
||||
double grn = rgbw[1].doubleValue();
|
||||
double blu = rgbw[2].doubleValue();
|
||||
double max = Math.max(red, Math.max(grn, blu));
|
||||
double whi = Math.min(100 - max, rgbw[3].doubleValue());
|
||||
|
||||
if (max > 0 || whi > 0) {
|
||||
r = BigDecimal.valueOf(red + whi);
|
||||
g = BigDecimal.valueOf(grn + whi);
|
||||
b = BigDecimal.valueOf(blu + whi);
|
||||
} else {
|
||||
r = BigDecimal.ZERO;
|
||||
g = BigDecimal.ZERO;
|
||||
b = BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
if (LOGGER.isTraceEnabled()) {
|
||||
LOGGER.trace("{}", String.format("RGBW:[%.6f,%.6f,%.6f,%.6f] - RGB:[%.6f,%.6f,%.6f]", red, grn, blu,
|
||||
whi, r.doubleValue(), g.doubleValue(), b.doubleValue()));
|
||||
}
|
||||
}
|
||||
|
||||
BigDecimal max = r.max(g).max(b);
|
||||
BigDecimal min = r.min(g).min(b);
|
||||
@ -390,7 +404,7 @@ public class ColorUtil {
|
||||
}
|
||||
if (hue.compareTo(BigDecimal.ZERO) < 0) {
|
||||
hue = hue.add(BIG_DECIMAL_360);
|
||||
} else if (hue.compareTo(BIG_DECIMAL_360) > 0) {
|
||||
} else if (hue.compareTo(BIG_DECIMAL_360) >= 0) {
|
||||
hue = hue.subtract(BIG_DECIMAL_360);
|
||||
}
|
||||
|
||||
@ -421,82 +435,110 @@ 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 array of double with CIE 1931 x,y[,Y] in the range 0.0000 to 1.0000 <code>Y</code> value is optional.
|
||||
* @param xyY 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 allows two or three values between 0.0 and 1.0.");
|
||||
public static HSBType xyToHsb(double[] xyY, Gamut gamut) throws IllegalArgumentException {
|
||||
if (xyY.length < 2 || xyY.length > 4) {
|
||||
throw new IllegalArgumentException("xyToHsb() requires 2, 3 or 4 arguments");
|
||||
}
|
||||
Point p = gamut.closest(new Point(xy[0], xy[1]));
|
||||
double x = p.x;
|
||||
double y = p.y == 0.0 ? 0.000001 : p.y;
|
||||
double z = 1.0 - x - y;
|
||||
double Y = (xy.length == 3) ? xy[2] : 1.0;
|
||||
double X = (Y / y) * x;
|
||||
double Z = (Y / y) * z;
|
||||
double r = X * 1.656492 + Y * -0.354851 + Z * -0.255038;
|
||||
double g = X * -0.707196 + Y * 1.655397 + Z * 0.036152;
|
||||
double b = X * 0.051713 + Y * -0.121364 + Z * 1.011530;
|
||||
|
||||
// Correction for negative values is missing from Philips' documentation.
|
||||
double min = Math.min(r, Math.min(g, b));
|
||||
for (int i = 0; i < xyY.length; i++) {
|
||||
if (xyY[i] < 0 || xyY[i] > 1) {
|
||||
throw new IllegalArgumentException(
|
||||
String.format("xyToHsb() argument %d value '%f' out of range [0..1.0]", i, xyY[i]));
|
||||
}
|
||||
}
|
||||
|
||||
// map xy to the closest point on the gamut
|
||||
final Point xy = gamut.closest(new Point(xyY[0], xyY[1]));
|
||||
|
||||
// convert to xyz
|
||||
final double x = xy.x;
|
||||
final double y = xy.y == 0.0 ? 0.000001 : xy.y;
|
||||
final double z = 1.0 - x - y;
|
||||
|
||||
// convert xy(Y) to XYZ
|
||||
final double Y = xyY.length == 3 && xyY[2] > 0.0 ? xyY[2] : 1.0;
|
||||
final double X = (Y / y) * x;
|
||||
final double Z = (Y / y) * z;
|
||||
|
||||
// convert XYZ to RGB using 'Wide RGB D65' formula
|
||||
final double[] rgb = new double[] {
|
||||
// @formatter:off
|
||||
X * 1.656492 + Y * -0.354851 + Z * -0.255038,
|
||||
X * -0.707196 + Y * 1.655397 + Z * 0.036152,
|
||||
X * 0.051713 + Y * -0.121364 + Z * 1.011530 };
|
||||
// @formatter:on
|
||||
|
||||
final double[] rgbPrime = rgb.clone();
|
||||
|
||||
// correction for negative values is missing from Philips' documentation.
|
||||
double min = Math.min(rgb[0], Math.min(rgb[1], rgb[2]));
|
||||
if (min < 0.0) {
|
||||
r -= min;
|
||||
g -= min;
|
||||
b -= min;
|
||||
for (int i = 0; i < rgb.length; i++) {
|
||||
rgb[i] -= min;
|
||||
}
|
||||
}
|
||||
|
||||
// rescale
|
||||
double max = Math.max(r, Math.max(g, b));
|
||||
double max = Math.max(rgb[0], Math.max(rgb[1], rgb[2]));
|
||||
if (max > 1.0) {
|
||||
r /= max;
|
||||
g /= max;
|
||||
b /= max;
|
||||
for (int i = 0; i < rgb.length; i++) {
|
||||
rgb[i] /= max;
|
||||
}
|
||||
}
|
||||
|
||||
r = compand(r);
|
||||
g = compand(g);
|
||||
b = compand(b);
|
||||
// remove gamma correction
|
||||
for (int i = 0; i < rgb.length; i++) {
|
||||
rgb[i] = rgb[i] <= 0.0031308 ? 12.92 * rgb[i] : (1.0 + 0.055) * Math.pow(rgb[i], (1.0 / 2.4)) - 0.055;
|
||||
}
|
||||
|
||||
// rescale
|
||||
max = Math.max(r, Math.max(g, b));
|
||||
max = Math.max(rgb[0], Math.max(rgb[1], rgb[2]));
|
||||
if (max > 1.0) {
|
||||
r /= max;
|
||||
g /= max;
|
||||
b /= max;
|
||||
for (int i = 0; i < rgb.length; i++) {
|
||||
rgb[i] /= max;
|
||||
}
|
||||
}
|
||||
|
||||
LOGGER.trace("xy: {} - XYZ: {} {} {} - RGB: {} {} {}", xy, X, Y, Z, r, g, b);
|
||||
// convert double[] to PercentType[]
|
||||
PercentType[] rgbPercents = new PercentType[rgb.length];
|
||||
for (int i = 0; i < rgb.length; i++) {
|
||||
rgbPercents[i] = new PercentType(new BigDecimal(rgb[i]).multiply(BIG_DECIMAL_100, COLOR_MATH_CONTEXT));
|
||||
}
|
||||
|
||||
return rgbToHsb(new PercentType[] { convertDoubleToColorPercent(r), convertDoubleToColorPercent(g),
|
||||
convertDoubleToColorPercent(b) });
|
||||
HSBType hsb = rgbToHsb(rgbPercents);
|
||||
|
||||
if (LOGGER.isTraceEnabled()) {
|
||||
LOGGER.trace("{}", String.format(
|
||||
"xyY:[%.6f,%.6f,%.6f] - xyz:[%.6f,%.6f,%.6f] - XYZ:[%.6f,%.6f,%.6f] - RGB':[%.6f,%.6f,%.6f] - RGB:[%.6f,%.6f,%.6f] - HSB:[%.6f,%.6f,%.6f] (xyForced:%b)",
|
||||
xyY[0], xyY[1], Y, x, y, z, X, Y, Z, rgbPrime[0], rgbPrime[1], rgbPrime[2], rgb[0], rgb[1], rgb[2],
|
||||
hsb.getHue().doubleValue(), hsb.getSaturation().doubleValue(), hsb.getBrightness().doubleValue(),
|
||||
xy.x != xyY[0] || xy.y != xyY[1]));
|
||||
}
|
||||
|
||||
return hsb;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gamma correction (inverse sRGB companding)
|
||||
*
|
||||
* @param value the value to process
|
||||
* @return the processed value
|
||||
* Get an array of int from an array of PercentType.
|
||||
*/
|
||||
private static double inverseCompand(double value) {
|
||||
return value > 0.04045 ? Math.pow((value + 0.055) / (1.0 + 0.055), 2.4) : value / 12.92;
|
||||
private static int[] getIntArray(PercentType[] percents) {
|
||||
int[] ints = new int[percents.length];
|
||||
for (int i = 0; i < percents.length; i++) {
|
||||
ints[i] = percents[i].toBigDecimal().multiply(BIG_DECIMAL_255)
|
||||
.divide(BIG_DECIMAL_100, 0, RoundingMode.HALF_UP).intValue();
|
||||
}
|
||||
return ints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inverse Gamma correction (sRGB companding)
|
||||
*
|
||||
* @param value the value to process
|
||||
* @return the processed value
|
||||
* Class for points in the CIE xy color space
|
||||
*/
|
||||
public static double compand(double value) {
|
||||
return value <= 0.0031308 ? 12.92 * value : (1.0 + 0.055) * Math.pow(value, (1.0 / 2.4)) - 0.055;
|
||||
}
|
||||
|
||||
private static class Point {
|
||||
public static class Point {
|
||||
public final double x;
|
||||
public final double y;
|
||||
|
||||
@ -556,14 +598,15 @@ public class ColorUtil {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Color <a href="https://en.wikipedia.org/wiki/Gamut">gamut</a>
|
||||
*
|
||||
* @param r double array with {@code xy} coordinates for red, x, y between 0.0000 and 1.0000.
|
||||
* @param g double array with {@code xy} coordinates for green, x, y between 0.0000 and 1.0000.
|
||||
* @param b double array with {@code xy} coordinates for blue, x, y between 0.0000 and 1.0000.
|
||||
*/
|
||||
public record Gamut(double[] r, double[] g, double[] b) {
|
||||
/**
|
||||
* Color <a href="https://en.wikipedia.org/wiki/Gamut">gamut</a>
|
||||
*
|
||||
* @param r double array with {@code xy} coordinates for red, x, y between 0.0000 and 1.0000.
|
||||
* @param g double array with {@code xy} coordinates for green, x, y between 0.0000 and 1.0000.
|
||||
* @param b double array with {@code xy} coordinates for blue, x, y between 0.0000 and 1.0000.
|
||||
*/
|
||||
|
||||
public Gamut {
|
||||
}
|
||||
|
||||
@ -607,25 +650,4 @@ public class ColorUtil {
|
||||
return retVal;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean inByteRange(int val) {
|
||||
return val >= 0 && val <= 255;
|
||||
}
|
||||
|
||||
private static boolean inRange(double val) {
|
||||
return val >= 0.0 && val <= 1.0;
|
||||
}
|
||||
|
||||
private static int convertColorPercentToByte(PercentType percent) {
|
||||
return percent.toBigDecimal().multiply(BIG_DECIMAL_255).divide(BIG_DECIMAL_100, 0, RoundingMode.HALF_UP)
|
||||
.intValue();
|
||||
}
|
||||
|
||||
private static PercentType convertByteToColorPercent(int b) {
|
||||
return new PercentType(new BigDecimal(b).divide(BIG_DECIMAL_2_POINT_55, COLOR_MATH_CONTEXT));
|
||||
}
|
||||
|
||||
private static PercentType convertDoubleToColorPercent(double d) {
|
||||
return new PercentType(new BigDecimal(d).multiply(BIG_DECIMAL_100, COLOR_MATH_CONTEXT));
|
||||
}
|
||||
}
|
||||
|
@ -13,9 +13,16 @@
|
||||
package org.openhab.core.util;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.lessThan;
|
||||
import static org.hamcrest.Matchers.lessThanOrEqualTo;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
@ -28,15 +35,18 @@ import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.ArgumentsProvider;
|
||||
import org.junit.jupiter.params.provider.ArgumentsSource;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.HSBType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.util.ColorUtil.Gamut;
|
||||
import org.openhab.core.util.ColorUtil.Point;
|
||||
|
||||
/**
|
||||
* The {@link ColorUtilTest} is a test class for the color conversion
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
* @author Holger Friedrich - Parameterized tests for RGB and HSB conversion
|
||||
* @author Andrew Fiddian-Green - Added tests to detect prior bugs and accuracy limitations
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ColorUtilTest {
|
||||
@ -143,6 +153,69 @@ public class ColorUtilTest {
|
||||
new PercentType[] { new PercentType(0), new PercentType(0), new PercentType(0), new PercentType(0) });
|
||||
convertedRgb = ColorUtil.hsbToRgb(hsb);
|
||||
assertRgbEquals(new int[] { 0, 0, 0 }, convertedRgb);
|
||||
|
||||
// Test Over-Drive Red
|
||||
hsb = ColorUtil.rgbToHsb(new int[] { 255, 0, 0, 255 });
|
||||
convertedRgb = ColorUtil.hsbToRgb(hsb);
|
||||
assertRgbEquals(new int[] { 255, 0, 0 }, convertedRgb);
|
||||
hsb = ColorUtil.rgbToHsb(new PercentType[] { new PercentType(100), new PercentType(0), new PercentType(0),
|
||||
new PercentType(100) });
|
||||
convertedRgb = ColorUtil.hsbToRgb(hsb);
|
||||
assertRgbEquals(new int[] { 255, 0, 0 }, convertedRgb);
|
||||
|
||||
// Test Over-Drive Green
|
||||
hsb = ColorUtil.rgbToHsb(new int[] { 0, 255, 0, 255 });
|
||||
convertedRgb = ColorUtil.hsbToRgb(hsb);
|
||||
assertRgbEquals(new int[] { 0, 255, 0 }, convertedRgb);
|
||||
hsb = ColorUtil.rgbToHsb(new PercentType[] { new PercentType(0), new PercentType(100), new PercentType(0),
|
||||
new PercentType(100) });
|
||||
convertedRgb = ColorUtil.hsbToRgb(hsb);
|
||||
assertRgbEquals(new int[] { 0, 255, 0 }, convertedRgb);
|
||||
|
||||
// Test Over-Drive Blue
|
||||
hsb = ColorUtil.rgbToHsb(new int[] { 0, 0, 255, 255 });
|
||||
convertedRgb = ColorUtil.hsbToRgb(hsb);
|
||||
assertRgbEquals(new int[] { 0, 0, 255 }, convertedRgb);
|
||||
hsb = ColorUtil.rgbToHsb(new PercentType[] { new PercentType(0), new PercentType(0), new PercentType(100),
|
||||
new PercentType(100) });
|
||||
convertedRgb = ColorUtil.hsbToRgb(hsb);
|
||||
assertRgbEquals(new int[] { 0, 0, 255 }, convertedRgb);
|
||||
|
||||
// Test White - Alternate B
|
||||
hsb = ColorUtil.rgbToHsb(new int[] { 255, 255, 255, 0 });
|
||||
convertedRgb = ColorUtil.hsbToRgb(hsb);
|
||||
assertRgbEquals(new int[] { 255, 255, 255 }, convertedRgb);
|
||||
hsb = ColorUtil.rgbToHsb(new PercentType[] { new PercentType(100), new PercentType(100), new PercentType(100),
|
||||
new PercentType(0) });
|
||||
convertedRgb = ColorUtil.hsbToRgb(hsb);
|
||||
assertRgbEquals(new int[] { 255, 255, 255 }, convertedRgb);
|
||||
|
||||
// Test Over-Drive White
|
||||
hsb = ColorUtil.rgbToHsb(new int[] { 255, 255, 255, 255 });
|
||||
convertedRgb = ColorUtil.hsbToRgb(hsb);
|
||||
assertRgbEquals(new int[] { 255, 255, 255 }, convertedRgb);
|
||||
hsb = ColorUtil.rgbToHsb(new PercentType[] { new PercentType(100), new PercentType(100), new PercentType(100),
|
||||
new PercentType(100) });
|
||||
convertedRgb = ColorUtil.hsbToRgb(hsb);
|
||||
assertRgbEquals(new int[] { 255, 255, 255 }, convertedRgb);
|
||||
|
||||
// Test Unsaturated Orange-Yellow
|
||||
hsb = ColorUtil.rgbToHsb(new int[] { 255, 191, 128, 0 });
|
||||
convertedRgb = ColorUtil.hsbToRgb(hsb);
|
||||
assertRgbEquals(new int[] { 255, 191, 128 }, convertedRgb);
|
||||
hsb = ColorUtil.rgbToHsb(new PercentType[] { new PercentType(100), new PercentType(75), new PercentType(50),
|
||||
new PercentType(0) });
|
||||
convertedRgb = ColorUtil.hsbToRgb(hsb);
|
||||
assertRgbEquals(new int[] { 255, 191, 128 }, convertedRgb);
|
||||
|
||||
// Test Unsaturated Orange-Yellow - With White
|
||||
hsb = ColorUtil.rgbToHsb(new int[] { 155, 91, 28, 100 });
|
||||
convertedRgb = ColorUtil.hsbToRgb(hsb);
|
||||
assertRgbEquals(new int[] { 255, 191, 128 }, convertedRgb);
|
||||
hsb = ColorUtil.rgbToHsb(new PercentType[] { new PercentType(61), new PercentType(36), new PercentType(11),
|
||||
new PercentType(39) });
|
||||
convertedRgb = ColorUtil.hsbToRgb(hsb);
|
||||
assertRgbEquals(new int[] { 255, 191, 128 }, convertedRgb);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ -215,27 +288,29 @@ public class ColorUtilTest {
|
||||
* 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);
|
||||
}
|
||||
@ParameterizedTest
|
||||
@MethodSource("gamuts")
|
||||
public void testXyHsbRoundTrips(Gamut g) {
|
||||
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> gamuts() {
|
||||
return Stream.of(
|
||||
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 }))
|
||||
.map(Arguments::of);
|
||||
}
|
||||
|
||||
private static Stream<Arguments> colors() {
|
||||
return Stream
|
||||
.of(HSBType.BLACK, HSBType.BLUE, HSBType.GREEN, HSBType.RED, HSBType.WHITE,
|
||||
@ -246,7 +321,7 @@ public class ColorUtilTest {
|
||||
private static Stream<Arguments> invalids() {
|
||||
return Stream.of(new double[] { 0.0 }, new double[] { -1.0, 0.5 }, new double[] { 1.5, 0.5 },
|
||||
new double[] { 0.5, -1.0 }, new double[] { 0.5, 1.5 }, new double[] { 0.5, 0.5, -1.0 },
|
||||
new double[] { 0.5, 0.5, 1.5 }, new double[] { 0.0, 1.0, 0.0, 1.0 }).map(Arguments::of);
|
||||
new double[] { 0.5, 0.5, 1.5 }).map(Arguments::of);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -294,6 +369,70 @@ public class ColorUtilTest {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Return an extended stream of HSB values.
|
||||
*/
|
||||
private static Stream<Arguments> allHSB() {
|
||||
List<Arguments> result = new ArrayList<>();
|
||||
final double step = 5.0;
|
||||
for (double h = 0; h < 360; h = h + step) {
|
||||
for (double s = 0; s <= 100; s = s + step) {
|
||||
for (double b = 0; b <= 100; b = b + step) {
|
||||
result.add(Arguments.of(new double[] { h, s, b }));
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.stream();
|
||||
}
|
||||
|
||||
/*
|
||||
* Return a extended stream of RGB values.
|
||||
*/
|
||||
private static Stream<Arguments> allRGB() {
|
||||
List<Arguments> result = new ArrayList<>();
|
||||
final double step = 5.0;
|
||||
for (double r = 0; r <= 100; r = r + step) {
|
||||
for (double g = 0; g <= 100; g = g + step) {
|
||||
for (double b = 0; b <= 100; b = b + step) {
|
||||
result.add(Arguments.of(new double[] { r, g, b }));
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.stream();
|
||||
}
|
||||
|
||||
/*
|
||||
* Return an extended stream of XY values within Gamut C overall limits.
|
||||
*/
|
||||
private static Stream<Arguments> allXY() {
|
||||
List<Arguments> result = new ArrayList<>();
|
||||
final double step = 0.01;
|
||||
for (double x = 0.1532; x <= 0.6915; x = x + step) {
|
||||
for (double y = 0.0475; y <= 0.7; y = y + step) {
|
||||
result.add(Arguments.of(new double[] { x, y }));
|
||||
}
|
||||
}
|
||||
return result.stream();
|
||||
}
|
||||
|
||||
/*
|
||||
* Return an extended stream of RGBW values.
|
||||
*/
|
||||
private static Stream<Arguments> allRGBW() {
|
||||
List<Arguments> result = new ArrayList<>();
|
||||
final double step = 5.0;
|
||||
for (double r = 0; r <= 100; r = r + step) {
|
||||
for (double g = 0; g <= 100; g = g + step) {
|
||||
for (double b = 0; b <= 100; b = b + step) {
|
||||
for (double w = 0; w <= 100; w = w + step) {
|
||||
result.add(Arguments.of(new double[] { r, g, b, w }));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.stream();
|
||||
}
|
||||
|
||||
/* Helper functions */
|
||||
|
||||
/**
|
||||
@ -313,4 +452,193 @@ public class ColorUtilTest {
|
||||
assertEquals(expectedS, actualS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test round trips HSB => xyY => HSB
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@MethodSource("allHSB")
|
||||
public void hsbToXY2xyToHsb(double[] hsb) {
|
||||
HSBType hsb1 = new HSBType(new DecimalType(hsb[0]), new PercentType(new BigDecimal(hsb[1])),
|
||||
new PercentType(new BigDecimal(hsb[2])));
|
||||
double[] xyY = new double[3];
|
||||
HSBType hsb2 = HSBType.BLACK;
|
||||
try {
|
||||
xyY = ColorUtil.hsbToXY(hsb1);
|
||||
hsb2 = ColorUtil.xyToHsb(xyY);
|
||||
|
||||
// HSB assertions are meaningless if B is zero, or xy was forced into gamut
|
||||
if (hsb[2] == 0 || xyY.length > 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
// assert that S values are within 0.01%
|
||||
assertEquals(hsb1.getSaturation().doubleValue(), hsb2.getSaturation().doubleValue(), 0.01);
|
||||
|
||||
// assert that B values are within 0.01%
|
||||
assertEquals(hsb1.getBrightness().doubleValue(), hsb2.getBrightness().doubleValue(), 0.01);
|
||||
|
||||
// H assertions are meaningless if S is zero
|
||||
if (hsb[1] == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// assert that H values are within 0.05 degrees
|
||||
double h1 = hsb1.getHue().doubleValue();
|
||||
h1 = h1 >= 180.0 ? 360.0 - h1 : h1;
|
||||
double h2 = hsb2.getHue().doubleValue();
|
||||
h2 = h2 >= 180.0 ? 360.0 - h2 : h2;
|
||||
assertEquals(h1, h2, 0.05);
|
||||
} catch (AssertionError e) {
|
||||
throw new AssertionError(
|
||||
String.format("HSB1:[%.6f,%.6f,%.6f] - xyY:[%.6f,%.6f,%.6f] - HSB2:[%.6f,%.6f,%.6f] - %s",
|
||||
hsb1.getHue().doubleValue(), hsb1.getSaturation().doubleValue(),
|
||||
hsb1.getBrightness().doubleValue(), xyY[0], xyY[1], xyY[2], hsb2.getHue().doubleValue(),
|
||||
hsb2.getSaturation().doubleValue(), hsb2.getBrightness().doubleValue(), e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test round trips xyY => HSB => xyY
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@MethodSource("allXY")
|
||||
public void xyToHsb2hsbToXY(double[] xy) {
|
||||
Gamut gamutC = new Gamut(new double[] { 0.6915, 0.3038 }, new double[] { 0.17, 0.7 },
|
||||
new double[] { 0.1532, 0.0475 });
|
||||
HSBType hsb = HSBType.BLACK;
|
||||
double[] xy2 = new double[2];
|
||||
try {
|
||||
Point p = gamutC.closest(new Point(xy[0], xy[1]));
|
||||
|
||||
// XY assertions are meaningless if if xy was forced into gamut
|
||||
if (!(p.x == xy[0] && p.y == xy[1])) {
|
||||
return;
|
||||
}
|
||||
|
||||
double[] xy1 = new double[] { p.x, p.y };
|
||||
hsb = ColorUtil.xyToHsb(xy1, gamutC);
|
||||
xy2 = ColorUtil.hsbToXY(hsb, gamutC);
|
||||
|
||||
// assert that x and y values are within 0.01%
|
||||
assertEquals(xy1[0], xy2[0], 0.01);
|
||||
assertEquals(xy1[1], xy2[1], 0.01);
|
||||
} catch (AssertionError e) {
|
||||
throw new AssertionError(
|
||||
String.format("xy1:[%.6f,%.6f] - HSB:[%.6f,%.6f,%.6f] - xyY2:[%.6f,%.6f,%.6f] - %s", xy[0], xy[1],
|
||||
hsb.getHue().doubleValue(), hsb.getSaturation().doubleValue(),
|
||||
hsb.getBrightness().doubleValue(), xy2[0], xy2[1], xy2[2], e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test round trips HSB => RGB => HSB
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@MethodSource("allHSB")
|
||||
public void hsbToRgb2rgbToHsb(double[] hsb) {
|
||||
HSBType hsb1 = new HSBType(new DecimalType(hsb[0]), new PercentType(new BigDecimal(hsb[1])),
|
||||
new PercentType(new BigDecimal(hsb[2])));
|
||||
PercentType[] rgb = new PercentType[3];
|
||||
HSBType hsb2 = HSBType.BLACK;
|
||||
try {
|
||||
rgb = ColorUtil.hsbToRgbPercent(hsb1);
|
||||
hsb2 = ColorUtil.rgbToHsb(rgb);
|
||||
|
||||
// HSB assertions are meaningless if B is zero
|
||||
if (hsb[2] == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
assertEquals(hsb1.getSaturation().doubleValue(), hsb2.getSaturation().doubleValue(), 0.01);
|
||||
assertEquals(hsb1.getBrightness().doubleValue(), hsb2.getBrightness().doubleValue(), 0.01);
|
||||
|
||||
// H assertions are meaningless if S is zero
|
||||
if (hsb[1] == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
assertEquals(hsb1.getHue().doubleValue(), hsb2.getHue().doubleValue(), 0.05);
|
||||
} catch (AssertionError e) {
|
||||
throw new AssertionError(
|
||||
String.format("HSB1:[%.6f,%.6f,%.6f] - RGB:[%.6f,%.6f,%.6f] - HSB2:[%.6f,%.6f,%.6f] - %s",
|
||||
hsb1.getHue().doubleValue() / 100, hsb1.getSaturation().doubleValue() / 100,
|
||||
hsb1.getBrightness().doubleValue() / 100, rgb[0].doubleValue() / 100,
|
||||
rgb[1].doubleValue() / 100, rgb[2].doubleValue() / 100, hsb2.getHue().doubleValue() / 100,
|
||||
hsb2.getSaturation().doubleValue() / 100, hsb2.getBrightness().doubleValue() / 100,
|
||||
e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test round trips RGB => HSB => RGB
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@MethodSource("allRGB")
|
||||
public void rgbToHsb2hsbToRgb(double[] rgb) {
|
||||
PercentType[] rgb1 = new PercentType[] { new PercentType(new BigDecimal(rgb[0])),
|
||||
new PercentType(new BigDecimal(rgb[1])), new PercentType(new BigDecimal(rgb[2])) };
|
||||
HSBType hsb = HSBType.BLACK;
|
||||
PercentType[] rgb2 = new PercentType[3];
|
||||
try {
|
||||
hsb = ColorUtil.rgbToHsb(rgb1);
|
||||
rgb2 = ColorUtil.hsbToRgbPercent(hsb);
|
||||
|
||||
// RGB assertions are meaningless if B or S are zero
|
||||
if (hsb.getBrightness().doubleValue() == 0 || hsb.getSaturation().doubleValue() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < rgb1.length; i++) {
|
||||
assertEquals(rgb1[i].doubleValue(), rgb2[i].doubleValue(), 0.05);
|
||||
}
|
||||
} catch (AssertionError e) {
|
||||
throw new AssertionError(
|
||||
String.format("RGB1:[%.6f,%.6f,%.6f] - HSB:[%.6f,%.6f,%.6f] - RGB2:[%.6f,%.6f,%.6f] - %s",
|
||||
rgb1[0].doubleValue() / 100, rgb1[1].doubleValue() / 100, rgb1[2].doubleValue() / 100,
|
||||
hsb.getHue().doubleValue() / 100, hsb.getSaturation().doubleValue() / 100,
|
||||
hsb.getBrightness().doubleValue() / 100, rgb2[0].doubleValue() / 100,
|
||||
rgb2[1].doubleValue() / 100, rgb2[2].doubleValue() / 100, e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test round trips RGBW => HSB => RGBW
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@MethodSource("allRGBW")
|
||||
public void rgbwToHsb2hsbToRgbw(double[] rgbw) {
|
||||
PercentType[] rgbw1 = new PercentType[] { new PercentType(new BigDecimal(rgbw[0])),
|
||||
new PercentType(new BigDecimal(rgbw[1])), new PercentType(new BigDecimal(rgbw[2])),
|
||||
new PercentType(new BigDecimal(rgbw[3])) };
|
||||
HSBType hsb = HSBType.BLACK;
|
||||
PercentType[] rgbw2 = new PercentType[4];
|
||||
try {
|
||||
hsb = ColorUtil.rgbToHsb(rgbw1);
|
||||
rgbw2 = ColorUtil.hsbToRgbwPercent(hsb);
|
||||
|
||||
// RGB assertions are meaningless if B or S are zero
|
||||
if (hsb.getBrightness().doubleValue() == 0 || hsb.getSaturation().doubleValue() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// RGB assertions are meaningless if W exceeds max head-room
|
||||
if (rgbw[3] > 100 - Math.max(rgbw[0], Math.max(rgbw[1], rgbw[2]))) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
assertEquals(rgbw1[i].doubleValue() + rgbw1[3].doubleValue(),
|
||||
rgbw2[i].doubleValue() + rgbw2[3].doubleValue(), 0.05);
|
||||
}
|
||||
} catch (AssertionError e) {
|
||||
throw new AssertionError(
|
||||
String.format("RGB1:[%.6f,%.6f,%.6f,%.6f] - HSB:[%.6f,%.6f,%.6f] - RGB2:[%.6f,%.6f,%.6f,%.6f] - %s",
|
||||
rgbw1[0].doubleValue() / 100, rgbw1[1].doubleValue() / 100, rgbw1[2].doubleValue() / 100,
|
||||
rgbw1[3].doubleValue() / 100, hsb.getHue().doubleValue() / 100,
|
||||
hsb.getSaturation().doubleValue() / 100, hsb.getBrightness().doubleValue() / 100,
|
||||
rgbw2[0].doubleValue() / 100, rgbw2[1].doubleValue() / 100, rgbw2[2].doubleValue() / 100,
|
||||
rgbw2[3].doubleValue() / 100, e.getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user