From 2571f77b5b5da267414c03a42cf72d80ecb11628 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20M=C3=BCller?= Date: Tue, 17 Oct 2023 23:14:22 +0200 Subject: [PATCH] Add conversion for HSB to RGBW and back for KNX DPT251.600 to use all 4 colors. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../java/org/openhab/core/util/ColorUtil.java | 101 +++++++++++++++++- .../org/openhab/core/util/ColorUtilTest.java | 65 +++++++++++ 2 files changed, 165 insertions(+), 1 deletion(-) diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/util/ColorUtil.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/util/ColorUtil.java index 23295f0e38a..fb85974e684 100644 --- a/bundles/org.openhab.core/src/main/java/org/openhab/core/util/ColorUtil.java +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/util/ColorUtil.java @@ -46,7 +46,10 @@ public class ColorUtil { private static final BigDecimal BIG_DECIMAL_60 = BigDecimal.valueOf(60); 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"); + private static final BigDecimal BIG_DECIMAL_0 = BigDecimal.valueOf(0); + private static final BigDecimal BIG_DECIMAL_127_POINT_5 = BigDecimal.valueOf(127.5); public static final Gamut DEFAULT_GAMUT = new Gamut(new double[] { 0.9961, 0.0001 }, new double[] { 0, 0.9961 }, new double[] { 0, 0.0001 }); @@ -79,7 +82,7 @@ public static int[] hsbToRgb(HSBType hsb) { * 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 +143,102 @@ public static PercentType[] hsbToRgbPercent(HSBType hsb) { return new PercentType[] { red, green, blue }; } + /** + * Transform HSV based {@link HSBType} to RGBW. + * + * See Converting RGB to RGBW. + * + * 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) { + int[] rgb = hsbToRgb(hsb); + final BigDecimal Ri = new BigDecimal(rgb[0]); + final BigDecimal Gi = new BigDecimal(rgb[1]); + final BigDecimal Bi = new BigDecimal(rgb[2]); + // Get the maximum between R, G, and B + final BigDecimal tM = Ri.max(Gi.max(Bi)); + + // If the maximum value is 0, immediately return pure black. + if (tM.floatValue() == 0) { + 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_255.divide(tM, 0, RoundingMode.DOWN); + final BigDecimal hR = Ri.multiply(multiplier); + final BigDecimal hG = Gi.multiply(multiplier); + final BigDecimal hB = Bi.multiply(multiplier); + + // This calculates the Whiteness (not strictly speaking Luminance) of the color + final BigDecimal M = hR.max(hG.max(hB)); + final BigDecimal m = hR.min(hG.min(hB)); + final BigDecimal Luminance = ((M.add(m).divide(BIG_DECIMAL_2).subtract(BIG_DECIMAL_127_POINT_5)) + .multiply(BIG_DECIMAL_255.divide(BIG_DECIMAL_127_POINT_5))).divide(multiplier); + + // Calculate the output values + BigDecimal Ro = (Ri.subtract(Luminance).multiply(BIG_DECIMAL_100).divide(BIG_DECIMAL_255, 0, + RoundingMode.DOWN)); + BigDecimal Go = (Gi.subtract(Luminance).multiply(BIG_DECIMAL_100).divide(BIG_DECIMAL_255, 0, + RoundingMode.DOWN)); + BigDecimal Bo = (Bi.subtract(Luminance).multiply(BIG_DECIMAL_100).divide(BIG_DECIMAL_255, 0, + RoundingMode.DOWN)); + BigDecimal Wo = Luminance.multiply(BIG_DECIMAL_100).divide(BIG_DECIMAL_255, 0, RoundingMode.DOWN); + + // check range + Ro = BIG_DECIMAL_100.min(Ro.max(BIG_DECIMAL_0)); + Go = BIG_DECIMAL_100.min(Go.max(BIG_DECIMAL_0)); + Bo = BIG_DECIMAL_100.min(Bo.max(BIG_DECIMAL_0)); + Wo = BIG_DECIMAL_100.min(Wo.max(BIG_DECIMAL_0)); + + return new PercentType[] { new PercentType(Ro), new PercentType(Go), new PercentType(Bo), new PercentType(Wo) }; + } + + /** + * Transform HSV based {@link HSBType} to RGBW. + * + * See Converting RGB to RGBW. + * + * 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 rgb array of three int with the RGB values in the range 0 to 255. + * @return hsb an {@link HSBType} value. + * + */ + public static HSBType rgbwTohsb(double r, double g, double b, double w) { + + BigDecimal Luminance = BigDecimal.valueOf(w); + BigDecimal Ri = BigDecimal.valueOf(r).add(Luminance); + BigDecimal Gi = BigDecimal.valueOf(g).add(Luminance); + BigDecimal Bi = BigDecimal.valueOf(b).add(Luminance); + + // Get the maximum between R, G, and B + final BigDecimal tM = BIG_DECIMAL_255.min(Ri.max(Gi.max(Bi)).max(BIG_DECIMAL_0)); + + // If the maximum value is 0, immediately return pure black. + if (tM.floatValue() == 0) { + return HSBType.BLACK; + } + + final BigDecimal multiplier = BIG_DECIMAL_255.divide(tM, 0, RoundingMode.DOWN); + + BigDecimal Ro = Ri.divide(multiplier).min(BIG_DECIMAL_255).max(BIG_DECIMAL_0); + BigDecimal Go = Gi.divide(multiplier).min(BIG_DECIMAL_255).max(BIG_DECIMAL_0); + BigDecimal Bo = Bi.divide(multiplier).min(BIG_DECIMAL_255).max(BIG_DECIMAL_0); + + return HSBType.fromRGB(Ro.intValue(), Go.intValue(), Bo.intValue()); + } + /** * Transform HSV based {@link HSBType} * to the RGB value representing the color in the default diff --git a/bundles/org.openhab.core/src/test/java/org/openhab/core/util/ColorUtilTest.java b/bundles/org.openhab.core/src/test/java/org/openhab/core/util/ColorUtilTest.java index 21acc54d127..e1577389fc8 100644 --- a/bundles/org.openhab.core/src/test/java/org/openhab/core/util/ColorUtilTest.java +++ b/bundles/org.openhab.core/src/test/java/org/openhab/core/util/ColorUtilTest.java @@ -59,6 +59,71 @@ public void inversionTest(HSBType hsb) { 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); + } + + public void rgbwtohsbTest() { + // Test Red + HSBType hsb = ColorUtil.rgbwTohsb(255, 0, 0, 0); + int[] convertedRgb = ColorUtil.hsbToRgb(hsb); + assertRgbEquals(new int[] { 255, 0, 0 }, convertedRgb); + + // Test Green + hsb = ColorUtil.rgbwTohsb(0, 255, 0, 0); + convertedRgb = ColorUtil.hsbToRgb(hsb); + assertRgbEquals(new int[] { 0, 255, 0 }, convertedRgb); + + // Test Blue + hsb = ColorUtil.rgbwTohsb(0, 0, 255, 0); + convertedRgb = ColorUtil.hsbToRgb(hsb); + assertRgbEquals(new int[] { 0, 0, 255 }, convertedRgb); + + // Test White + hsb = ColorUtil.rgbwTohsb(0, 0, 0, 255); + convertedRgb = ColorUtil.hsbToRgb(hsb); + assertRgbEquals(new int[] { 255, 255, 255 }, convertedRgb); + + // Test Black + hsb = ColorUtil.rgbwTohsb(0, 0, 0, 0); + convertedRgb = ColorUtil.hsbToRgb(hsb); + assertRgbEquals(new int[] { 0, 0, 0 }, convertedRgb); + } + @ParameterizedTest @MethodSource("invalids") public void invalidXyValues(double[] xy) {