mirror of
https://github.com/danieldemus/openhab-core.git
synced 2025-01-10 21:31:53 +01:00
[core] Add conversion for HSB to RGBW and back (#3849)
* Add conversion for HSB to RGBW and back for KNX DPT251.600 to use all 4 colors. With the new feature, the HSBType can converted into RGBW, and also back to HSB. Due to the conversion, some accuracy is lost, but the result is approximately correct. Signed-off-by: Marco Müller <marco@ms-mueller.ch>
This commit is contained in:
parent
81c1dcc4ab
commit
237f2ebb56
@ -44,8 +44,10 @@ public class ColorUtil {
|
||||
private static final BigDecimal BIG_DECIMAL_120 = BigDecimal.valueOf(120);
|
||||
private static final BigDecimal BIG_DECIMAL_100 = BigDecimal.valueOf(100);
|
||||
private static final BigDecimal BIG_DECIMAL_60 = BigDecimal.valueOf(60);
|
||||
private static final BigDecimal BIG_DECIMAL_50 = BigDecimal.valueOf(50);
|
||||
private static final BigDecimal BIG_DECIMAL_5 = BigDecimal.valueOf(5);
|
||||
private static final BigDecimal BIG_DECIMAL_3 = BigDecimal.valueOf(3);
|
||||
private static final BigDecimal BIG_DECIMAL_2 = BigDecimal.valueOf(2);
|
||||
private static final BigDecimal BIG_DECIMAL_2_POINT_55 = new BigDecimal("2.55");
|
||||
|
||||
public static final Gamut DEFAULT_GAMUT = new Gamut(new double[] { 0.9961, 0.0001 }, new double[] { 0, 0.9961 },
|
||||
@ -61,7 +63,7 @@ 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 #hsbToRgbw(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.
|
||||
@ -72,6 +74,24 @@ public class ColorUtil {
|
||||
convertColorPercentToByte(rgbPercent[2]) };
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 does rounding to integer valued components. It is the preferred way of doing HSB to RGBW
|
||||
* conversion.
|
||||
*
|
||||
* See also: {@link #hsbToRgbPercent(HSBType)}, {@link #hsbToRgbwPercent(HSBType)}, {@link #hsbTosRgb(HSBType)}
|
||||
*
|
||||
* @param hsb an {@link HSBType} value.
|
||||
* @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]) };
|
||||
}
|
||||
|
||||
/**
|
||||
* 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>.
|
||||
@ -79,7 +99,7 @@ public class ColorUtil {
|
||||
* 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)}, {@link #hsbToRgbwPercent(HSBType)}
|
||||
*
|
||||
* @param hsb an {@link HSBType} value.
|
||||
* @return array of three {@link PercentType} with the RGB values in the range 0 to 100 percent.
|
||||
@ -140,6 +160,55 @@ public class ColorUtil {
|
||||
return new PercentType[] { red, green, blue };
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform <a href="https://en.wikipedia.org/wiki/HSL_and_HSV">HSV</a> based {@link HSBType} to RGBW.
|
||||
*
|
||||
* See <a href=
|
||||
* "https://stackoverflow.com/questions/40312216/converting-rgb-to-rgbw">Converting RGB to RGBW</a>.
|
||||
*
|
||||
* 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)}, {@link #hsbToRgbPercent(HSBType)}
|
||||
*
|
||||
* @param hsb an {@link HSBType} value.
|
||||
* @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));
|
||||
|
||||
// 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 };
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
// 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);
|
||||
|
||||
// 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) };
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform <a href="https://en.wikipedia.org/wiki/HSL_and_HSV">HSV</a> based {@link HSBType}
|
||||
* to the RGB value representing the color in the default
|
||||
@ -211,23 +280,69 @@ 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}.
|
||||
*
|
||||
* @param rgb array of three int with the RGB values in the range 0 to 255.
|
||||
* @param rgbw array of three or four int with the RGB(W) 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.
|
||||
*/
|
||||
public static HSBType rgbToHsb(int[] rgb) throws IllegalArgumentException {
|
||||
if (rgb.length != 3 || !inByteRange(rgb[0]) || !inByteRange(rgb[1]) || !inByteRange(rgb[2])) {
|
||||
public static HSBType rgbToHsb(int[] rgbw) throws IllegalArgumentException {
|
||||
if (rgbw.length == 4) {
|
||||
return rgbwToHsb(rgbw);
|
||||
}
|
||||
if (rgbw.length != 3 || !inByteRange(rgbw[0]) || !inByteRange(rgbw[1]) || !inByteRange(rgbw[2])) {
|
||||
throw new IllegalArgumentException("RGB array only allows values between 0 and 255");
|
||||
}
|
||||
return rgbToHsb(new PercentType[] { convertByteToColorPercent(rgb[0]), convertByteToColorPercent(rgb[1]),
|
||||
convertByteToColorPercent(rgb[2]) });
|
||||
return rgbToHsb(new PercentType[] { convertByteToColorPercent(rgbw[0]), convertByteToColorPercent(rgbw[1]),
|
||||
convertByteToColorPercent(rgbw[2]) });
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform <a href="https://en.wikipedia.org/wiki/HSL_and_HSV">HSV</a> based {@link HSBType} to RGBW.
|
||||
*
|
||||
* See <a href=
|
||||
* "https://stackoverflow.com/questions/40312216/converting-rgb-to-rgbw">Converting RGB to RGBW</a>.
|
||||
*
|
||||
* 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)}, {@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(int[] rgbw) {
|
||||
if (rgbw.length != 4 || !inByteRange(rgbw[0]) || !inByteRange(rgbw[1]) || !inByteRange(rgbw[2])
|
||||
|| !inByteRange(rgbw[3])) {
|
||||
throw new IllegalArgumentException("RGBW array only allows values between 0 and 255 with 4 values");
|
||||
}
|
||||
|
||||
BigDecimal luminance = BigDecimal.valueOf(rgbw[3]);
|
||||
BigDecimal inRed = BigDecimal.valueOf(rgbw[0]).add(luminance);
|
||||
BigDecimal inGreen = BigDecimal.valueOf(rgbw[1]).add(luminance);
|
||||
BigDecimal inBlue = BigDecimal.valueOf(rgbw[2]).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;
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @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.
|
||||
*/
|
||||
|
@ -59,6 +59,72 @@ public class ColorUtilTest {
|
||||
assertThat(deltaBri, is(lessThanOrEqualTo(1.0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hsbToRgbwTest() {
|
||||
HSBType hsb = HSBType.WHITE;
|
||||
PercentType[] rgbw = ColorUtil.hsbToRgbwPercent(hsb);
|
||||
assertEquals(0.0, rgbw[0].doubleValue(), 0.01);
|
||||
assertEquals(0.0, rgbw[1].doubleValue(), 0.01);
|
||||
assertEquals(0.0, rgbw[2].doubleValue(), 0.01);
|
||||
assertEquals(100.0, rgbw[3].doubleValue(), 0.01);
|
||||
|
||||
hsb = HSBType.BLACK;
|
||||
rgbw = ColorUtil.hsbToRgbwPercent(hsb);
|
||||
assertEquals(0.0, rgbw[0].doubleValue(), 0.01);
|
||||
assertEquals(0.0, rgbw[1].doubleValue(), 0.01);
|
||||
assertEquals(0.0, rgbw[2].doubleValue(), 0.01);
|
||||
assertEquals(0.0, rgbw[3].doubleValue(), 0.01);
|
||||
|
||||
hsb = HSBType.RED;
|
||||
rgbw = ColorUtil.hsbToRgbwPercent(hsb);
|
||||
assertEquals(100.0, rgbw[0].doubleValue(), 0.01);
|
||||
assertEquals(0.0, rgbw[1].doubleValue(), 0.01);
|
||||
assertEquals(0.0, rgbw[2].doubleValue(), 0.01);
|
||||
assertEquals(0.0, rgbw[3].doubleValue(), 0.01);
|
||||
|
||||
hsb = HSBType.GREEN;
|
||||
rgbw = ColorUtil.hsbToRgbwPercent(hsb);
|
||||
assertEquals(0.0, rgbw[0].doubleValue(), 0.01);
|
||||
assertEquals(100.0, rgbw[1].doubleValue(), 0.01);
|
||||
assertEquals(0.0, rgbw[2].doubleValue(), 0.01);
|
||||
assertEquals(0.0, rgbw[3].doubleValue(), 0.01);
|
||||
|
||||
hsb = HSBType.BLUE;
|
||||
rgbw = ColorUtil.hsbToRgbwPercent(hsb);
|
||||
assertEquals(0.0, rgbw[0].doubleValue(), 0.01);
|
||||
assertEquals(0.0, rgbw[1].doubleValue(), 0.01);
|
||||
assertEquals(100.0, rgbw[2].doubleValue(), 0.01);
|
||||
assertEquals(0.0, rgbw[3].doubleValue(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rgbwToHsbTest() {
|
||||
// Test Red
|
||||
HSBType hsb = ColorUtil.rgbToHsb(new int[] { 255, 0, 0, 0 });
|
||||
int[] convertedRgb = ColorUtil.hsbToRgb(hsb);
|
||||
assertRgbEquals(new int[] { 255, 0, 0 }, convertedRgb);
|
||||
|
||||
// Test Green
|
||||
hsb = ColorUtil.rgbToHsb(new int[] { 0, 255, 0, 0 });
|
||||
convertedRgb = ColorUtil.hsbToRgb(hsb);
|
||||
assertRgbEquals(new int[] { 0, 255, 0 }, convertedRgb);
|
||||
|
||||
// Test Blue
|
||||
hsb = ColorUtil.rgbToHsb(new int[] { 0, 0, 255, 0 });
|
||||
convertedRgb = ColorUtil.hsbToRgb(hsb);
|
||||
assertRgbEquals(new int[] { 0, 0, 255 }, convertedRgb);
|
||||
|
||||
// Test White
|
||||
hsb = ColorUtil.rgbToHsb(new int[] { 0, 0, 0, 255 });
|
||||
convertedRgb = ColorUtil.hsbToRgb(hsb);
|
||||
assertRgbEquals(new int[] { 255, 255, 255 }, convertedRgb);
|
||||
|
||||
// Test Black
|
||||
hsb = ColorUtil.rgbToHsb(new int[] { 0, 0, 0, 0 });
|
||||
convertedRgb = ColorUtil.hsbToRgb(hsb);
|
||||
assertRgbEquals(new int[] { 0, 0, 0 }, convertedRgb);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("invalids")
|
||||
public void invalidXyValues(double[] xy) {
|
||||
|
Loading…
Reference in New Issue
Block a user