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) {