Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add conversion for HSB to RGBW and back #3849

Merged
merged 9 commits into from
Nov 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand All @@ -61,7 +63,7 @@ private 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.
Expand All @@ -72,14 +74,32 @@ public static int[] hsbToRgb(HSBType hsb) {
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>.
*
* 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.
Expand Down Expand Up @@ -140,6 +160,55 @@ public static PercentType[] hsbToRgbPercent(HSBType hsb) {
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)}.
genesis81 marked this conversation as resolved.
Show resolved Hide resolved
*
* 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
Expand Down Expand Up @@ -211,23 +280,69 @@ public static double[] hsbToXY(HSBType hsb, Gamut gamut) {
* 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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just found this PR and was interested because I think this method could be used instead of doing two passes here:
https://github.com/openhab/openhab-addons/blob/a13fd80bfe5cf0b6c55e13756f42abc27b55796b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewRepeaterHandler.java#L264-L265

Anyway, commenting because this description seems to be copied from the method for the other direction. This one is RGBW to HSBType.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks, included in #3882

*
* 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());
}

genesis81 marked this conversation as resolved.
Show resolved Hide resolved
/**
* 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.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,72 @@ 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);
}

@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) {
Expand Down