mirror of
https://github.com/danieldemus/openhab-core.git
synced 2025-01-10 21:31:53 +01:00
Add ColorUtil for better support of xyY conversion (#3434)
* Add ColorUtil for better support of xyY conversion This has been refactored to align with the usually used conversion by a lot of ZigBee products like Hue or Deconz. Signed-off-by: Jan N. Klug <github@klug.nrw>
This commit is contained in:
parent
52e36a0216
commit
a32f1e0253
@ -26,6 +26,7 @@ import org.openhab.core.types.Command;
|
|||||||
import org.openhab.core.types.ComplexType;
|
import org.openhab.core.types.ComplexType;
|
||||||
import org.openhab.core.types.PrimitiveType;
|
import org.openhab.core.types.PrimitiveType;
|
||||||
import org.openhab.core.types.State;
|
import org.openhab.core.types.State;
|
||||||
|
import org.openhab.core.util.ColorUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The HSBType is a complex type with constituents for hue, saturation and
|
* The HSBType is a complex type with constituents for hue, saturation and
|
||||||
@ -51,14 +52,6 @@ public class HSBType extends PercentType implements ComplexType, State, Command
|
|||||||
public static final HSBType GREEN = new HSBType("120,100,100");
|
public static final HSBType GREEN = new HSBType("120,100,100");
|
||||||
public static final HSBType BLUE = new HSBType("240,100,100");
|
public static final HSBType BLUE = new HSBType("240,100,100");
|
||||||
|
|
||||||
// 1931 CIE XYZ to sRGB (D65 reference white)
|
|
||||||
private static final float XY2RGB[][] = { { 3.2406f, -1.5372f, -0.4986f }, { -0.9689f, 1.8758f, 0.0415f },
|
|
||||||
{ 0.0557f, -0.2040f, 1.0570f } };
|
|
||||||
|
|
||||||
// sRGB to 1931 CIE XYZ (D65 reference white)
|
|
||||||
private static final float RGB2XY[][] = { { 0.4124f, 0.3576f, 0.1805f }, { 0.2126f, 0.7152f, 0.0722f },
|
|
||||||
{ 0.0193f, 0.1192f, 0.9505f } };
|
|
||||||
|
|
||||||
private static final String UNIT_HSB = "%hsb%";
|
private static final String UNIT_HSB = "%hsb%";
|
||||||
private static final String UNIT_RGB = "%rgb%";
|
private static final String UNIT_RGB = "%rgb%";
|
||||||
|
|
||||||
@ -162,32 +155,20 @@ public class HSBType extends PercentType implements ComplexType, State, Command
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @deprecated Use {@link ColorUtil#xyToHsv(double[])} or {@link ColorUtil#xyToHsv(double[], ColorUtil.Gamut)}
|
||||||
|
* instead
|
||||||
|
*
|
||||||
* Returns a HSBType object representing the provided xy color values in CIE XY color model.
|
* Returns a HSBType object representing the provided xy color values in CIE XY color model.
|
||||||
* Conversion from CIE XY color model to sRGB using D65 reference white
|
* Conversion from CIE XY color model to sRGB using D65 reference white
|
||||||
* Returned color is set to full brightness
|
* Returned color is set to full brightness
|
||||||
*
|
*
|
||||||
* @param x, y color information 0.0 - 1.0
|
* @param x, y color information 0.0 - 1.0
|
||||||
* @return new HSBType object representing the given CIE XY color, full brightness
|
* @return new HSBType object representing the given CIE XY color, full brightness
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public static HSBType fromXY(float x, float y) {
|
public static HSBType fromXY(float x, float y) {
|
||||||
float tmpY = 1.0f;
|
return ColorUtil.xyToHsv(new double[] { x, y });
|
||||||
float tmpX = (tmpY / y) * x;
|
|
||||||
float tmpZ = (tmpY / y) * (1.0f - x - y);
|
|
||||||
|
|
||||||
float r = tmpX * XY2RGB[0][0] + tmpY * XY2RGB[0][1] + tmpZ * XY2RGB[0][2];
|
|
||||||
float g = tmpX * XY2RGB[1][0] + tmpY * XY2RGB[1][1] + tmpZ * XY2RGB[1][2];
|
|
||||||
float b = tmpX * XY2RGB[2][0] + tmpY * XY2RGB[2][1] + tmpZ * XY2RGB[2][2];
|
|
||||||
|
|
||||||
float max = r > g ? r : g;
|
|
||||||
if (b > max) {
|
|
||||||
max = b;
|
|
||||||
}
|
|
||||||
|
|
||||||
r = gammaCompress(r / max);
|
|
||||||
g = gammaCompress(g / max);
|
|
||||||
b = gammaCompress(b / max);
|
|
||||||
|
|
||||||
return HSBType.fromRGB((int) (r * 255.0f + 0.5f), (int) (g * 255.0f + 0.5f), (int) (b * 255.0f + 0.5f));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -281,11 +262,8 @@ public class HSBType extends PercentType implements ComplexType, State, Command
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
HSBType other = (HSBType) obj;
|
HSBType other = (HSBType) obj;
|
||||||
if (!getHue().equals(other.getHue()) || !getSaturation().equals(other.getSaturation())
|
return getHue().equals(other.getHue()) && getSaturation().equals(other.getSaturation())
|
||||||
|| !getBrightness().equals(other.getBrightness())) {
|
&& getBrightness().equals(other.getBrightness());
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public PercentType[] toRGB() {
|
public PercentType[] toRGB() {
|
||||||
@ -342,55 +320,17 @@ public class HSBType extends PercentType implements ComplexType, State, Command
|
|||||||
return new PercentType[] { red, green, blue };
|
return new PercentType[] { red, green, blue };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gamma compression (sRGB) for a single component, in the 0.0 - 1.0 range
|
|
||||||
private static float gammaCompress(float c) {
|
|
||||||
if (c < 0.0f) {
|
|
||||||
c = 0.0f;
|
|
||||||
} else if (c > 1.0f) {
|
|
||||||
c = 1.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
return c <= 0.0031308f ? 12.92f * c : (1.0f + 0.055f) * (float) Math.pow(c, 1.0f / 2.4f) - 0.055f;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gamma decompression (sRGB) for a single component, in the 0.0 - 1.0 range
|
|
||||||
private static float gammaDecompress(float c) {
|
|
||||||
if (c < 0.0f) {
|
|
||||||
c = 0.0f;
|
|
||||||
} else if (c > 1.0f) {
|
|
||||||
c = 1.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
return c <= 0.04045f ? c / 12.92f : (float) Math.pow((c + 0.055f) / (1.0f + 0.055f), 2.4f);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the xyY values representing this object's color in CIE XY color model.
|
* Returns the xyY values representing this object's color in CIE XY color model.
|
||||||
* Conversion from sRGB to CIE XY using D65 reference white
|
* Conversion from sRGB to CIE XY using D65 reference white
|
||||||
* xy pair contains color information
|
* xy pair contains color information
|
||||||
* Y represents relative luminance
|
* Y represents relative luminance
|
||||||
*
|
*
|
||||||
* @param HSBType color object
|
|
||||||
* @return PercentType[x, y, Y] values in the CIE XY color model
|
* @return PercentType[x, y, Y] values in the CIE XY color model
|
||||||
*/
|
*/
|
||||||
public PercentType[] toXY() {
|
public PercentType[] toXY() {
|
||||||
// This makes sure we keep color information even if brightness is zero
|
return Arrays.stream(ColorUtil.hsbToXY(this)).mapToObj(d -> new PercentType(new BigDecimal(d * 100.0)))
|
||||||
PercentType sRGB[] = new HSBType(getHue(), getSaturation(), PercentType.HUNDRED).toRGB();
|
.toArray(PercentType[]::new);
|
||||||
|
|
||||||
float r = gammaDecompress(sRGB[0].floatValue() / 100.0f);
|
|
||||||
float g = gammaDecompress(sRGB[1].floatValue() / 100.0f);
|
|
||||||
float b = gammaDecompress(sRGB[2].floatValue() / 100.0f);
|
|
||||||
|
|
||||||
float tmpX = r * RGB2XY[0][0] + g * RGB2XY[0][1] + b * RGB2XY[0][2];
|
|
||||||
float tmpY = r * RGB2XY[1][0] + g * RGB2XY[1][1] + b * RGB2XY[1][2];
|
|
||||||
float tmpZ = r * RGB2XY[2][0] + g * RGB2XY[2][1] + b * RGB2XY[2][2];
|
|
||||||
|
|
||||||
float x = tmpX / (tmpX + tmpY + tmpZ);
|
|
||||||
float y = tmpY / (tmpX + tmpY + tmpZ);
|
|
||||||
|
|
||||||
return new PercentType[] { new PercentType(Float.valueOf(x * 100.0f).toString()),
|
|
||||||
new PercentType(Float.valueOf(y * 100.0f).toString()),
|
|
||||||
new PercentType(Float.valueOf(tmpY * getBrightness().floatValue()).toString()) };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private int convertPercentToByte(PercentType percent) {
|
private int convertPercentToByte(PercentType percent) {
|
||||||
|
@ -0,0 +1,301 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.core.util;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.openhab.core.library.types.HSBType;
|
||||||
|
import org.openhab.core.library.types.PercentType;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link ColorUtil} is responsible for converting HSB to CIE
|
||||||
|
*
|
||||||
|
* The class is based work from Erik Baauw for the <a href="https://github.com/ebaauw/homebridge-lib">Homebridge</a>
|
||||||
|
* project
|
||||||
|
*
|
||||||
|
* @author Jan N. Klug - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class ColorUtil {
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(ColorUtil.class);
|
||||||
|
public static final Gamut DEFAULT_GAMUT = new Gamut(new double[] { 0.9961, 0.0001 }, new double[] { 0, 0.9961 },
|
||||||
|
new double[] { 0, 0.0001 });
|
||||||
|
|
||||||
|
private ColorUtil() {
|
||||||
|
// prevent instantiation
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform <a href="https://en.wikipedia.org/wiki/SRGB">sRGB</a> based {@link HSBType} to
|
||||||
|
* <a href="https://en.wikipedia.org/wiki/CIE_1931_color_space">CIE 1931</a> `xy` format.
|
||||||
|
*
|
||||||
|
* See <a href=
|
||||||
|
* "https://developers.meethue.com/develop/application-design-guidance/color-conversion-formulas-rgb-to-xy-and-back/">Hue
|
||||||
|
* developerportal</a>.
|
||||||
|
*
|
||||||
|
* @param hsbType a {@link HSBType} value
|
||||||
|
* @return double array with the closest matching CIE 1931 colour, x, y between 0.0000 and 1.0000.
|
||||||
|
*/
|
||||||
|
public static double[] hsbToXY(HSBType hsbType) {
|
||||||
|
return hsbToXY(hsbType, DEFAULT_GAMUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform <a href="https://en.wikipedia.org/wiki/SRGB">sRGB</a> based {@link HSBType} to
|
||||||
|
* <a href="https://en.wikipedia.org/wiki/CIE_1931_color_space">CIE 1931</a> `xy` format.
|
||||||
|
*
|
||||||
|
* See <a href=
|
||||||
|
* "https://developers.meethue.com/develop/application-design-guidance/color-conversion-formulas-rgb-to-xy-and-back/">Hue
|
||||||
|
* developer portal</a>.
|
||||||
|
*
|
||||||
|
* @param hsbType a {@link HSBType} value
|
||||||
|
* @param gamut the gamut supported by the light.
|
||||||
|
* @return double array with the closest matching CIE 1931 colour, x, y, Y between 0.0000 and 1.0000.
|
||||||
|
*/
|
||||||
|
public static double[] hsbToXY(HSBType hsbType, Gamut gamut) {
|
||||||
|
double r = inverseCompand(hsbType.getRed().doubleValue() / PercentType.HUNDRED.doubleValue());
|
||||||
|
double g = inverseCompand(hsbType.getGreen().doubleValue() / PercentType.HUNDRED.doubleValue());
|
||||||
|
double b = inverseCompand(hsbType.getBlue().doubleValue() / PercentType.HUNDRED.doubleValue());
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
double sum = X + Y + Z;
|
||||||
|
Point p = sum == 0.0 ? new Point() : new Point(X / sum, Y / sum);
|
||||||
|
Point q = gamut.closest(p);
|
||||||
|
|
||||||
|
double[] xyY = new double[] { ((int) (q.x * 10000.0)) / 10000.0, ((int) (q.y * 10000.0)) / 10000.0,
|
||||||
|
((int) (Y * 10000.0)) / 10000.0 };
|
||||||
|
|
||||||
|
LOGGER.trace("HSV: {} - RGB: {} - XYZ: {} {} {} - xyY: {}", hsbType, hsbType.toRGB(), X, Y, Z, xyY);
|
||||||
|
|
||||||
|
return xyY;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform <a href="https://en.wikipedia.org/wiki/CIE_1931_color_space">CIE 1931</a> `xy` format to
|
||||||
|
* <a href="https://en.wikipedia.org/wiki/SRGB">sRGB</a> based {@link HSBType}.
|
||||||
|
*
|
||||||
|
* See <a href=
|
||||||
|
* "https://developers.meethue.com/develop/application-design-guidance/color-conversion-formulas-rgb-to-xy-and-back/">Hue
|
||||||
|
* developer portal</a>.
|
||||||
|
*
|
||||||
|
* @param xy the CIE 1931 xy colour, x,y between 0.0000 and 1.0000.
|
||||||
|
* @return the corresponding {@link HSBType}.
|
||||||
|
*/
|
||||||
|
public static HSBType xyToHsv(double[] xy) {
|
||||||
|
return xyToHsv(xy, DEFAULT_GAMUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform <a href="https://en.wikipedia.org/wiki/CIE_1931_color_space">CIE 1931</a> `xy` format to
|
||||||
|
* <a href="https://en.wikipedia.org/wiki/SRGB">sRGB</a> based {@link HSBType}.
|
||||||
|
*
|
||||||
|
* See <a href=
|
||||||
|
* "https://developers.meethue.com/develop/application-design-guidance/color-conversion-formulas-rgb-to-xy-and-back/">Hue
|
||||||
|
* developer portal</a>.
|
||||||
|
*
|
||||||
|
* @param xy the CIE 1931 xy colour, x,y[,Y] between 0.0000 and 1.0000. <code>Y</code> value is optional.
|
||||||
|
* @param gamut the 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 xyToHsv(double[] xy, Gamut gamut) {
|
||||||
|
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 allowes two or three values between 0.0 and 1.0.");
|
||||||
|
}
|
||||||
|
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));
|
||||||
|
if (min < 0.0) {
|
||||||
|
r -= min;
|
||||||
|
g -= min;
|
||||||
|
b -= min;
|
||||||
|
}
|
||||||
|
|
||||||
|
// rescale
|
||||||
|
double max = Math.max(r, Math.max(g, b));
|
||||||
|
if (max > 1.0) {
|
||||||
|
r /= max;
|
||||||
|
g /= max;
|
||||||
|
b /= max;
|
||||||
|
}
|
||||||
|
|
||||||
|
r = compand(r);
|
||||||
|
g = compand(g);
|
||||||
|
b = compand(b);
|
||||||
|
|
||||||
|
// rescale
|
||||||
|
max = Math.max(r, Math.max(g, b));
|
||||||
|
if (max > 1.0) {
|
||||||
|
r /= max;
|
||||||
|
g /= max;
|
||||||
|
b /= max;
|
||||||
|
}
|
||||||
|
|
||||||
|
HSBType hsb = HSBType.fromRGB((int) Math.round(255.0 * r), (int) Math.round(255.0 * g),
|
||||||
|
(int) Math.round(255.0 * b));
|
||||||
|
LOGGER.trace("xy: {} - XYZ: {} {} {} - RGB: {} {} {} - HSB: {} ", xy, X, Y, Z, r, g, b, hsb);
|
||||||
|
|
||||||
|
return hsb;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gamma correction (inverse sRGB companding)
|
||||||
|
*
|
||||||
|
* @param value the value to process
|
||||||
|
* @return the processed value
|
||||||
|
*/
|
||||||
|
private static double inverseCompand(double value) {
|
||||||
|
return value > 0.04045 ? Math.pow((value + 0.055) / (1.0 + 0.055), 2.4) : value / 12.92;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inverse Gamma correction (sRGB companding)
|
||||||
|
*
|
||||||
|
* @param value the value to process
|
||||||
|
* @return the processed value
|
||||||
|
*/
|
||||||
|
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 final double x;
|
||||||
|
public final double y;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* a default point with x/y = 0.0
|
||||||
|
*/
|
||||||
|
public Point() {
|
||||||
|
this(0.0, 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* a point with the given values
|
||||||
|
*
|
||||||
|
* @param x the x-value (between 0.0 and 1.0)
|
||||||
|
* @param y the y-value (between 0.0 and 1.0)
|
||||||
|
*/
|
||||||
|
public Point(double x, double y) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* distance between this point and another point
|
||||||
|
*
|
||||||
|
* @param other the other point
|
||||||
|
* @return distance as double
|
||||||
|
*/
|
||||||
|
public double distance(Point other) {
|
||||||
|
double dx = this.x - other.x;
|
||||||
|
double dy = this.y - other.y;
|
||||||
|
return Math.sqrt(dx * dx + dy * dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* return the cross product of this tuple and the other tuple
|
||||||
|
*
|
||||||
|
* @param other the other point
|
||||||
|
* @return the cross product as double
|
||||||
|
*/
|
||||||
|
public double crossProduct(Point other) {
|
||||||
|
return this.x * other.y - this.y * other.x;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* return point closest to this point on a line between a and b
|
||||||
|
*
|
||||||
|
* @param a point a
|
||||||
|
* @param b point b
|
||||||
|
* @return the point closest to this point on a-b
|
||||||
|
*/
|
||||||
|
public Point closest(Point a, Point b) {
|
||||||
|
Point ap = new Point(this.x - a.x, this.y - a.y);
|
||||||
|
Point ab = new Point(b.x - a.x, b.y - a.y);
|
||||||
|
double t = Math.min(1.0, Math.max(0, (ap.x * ab.x + ap.y * ab.y) / (ab.x * ab.x + ab.y * ab.y)));
|
||||||
|
|
||||||
|
return new Point(a.x + t * ab.x, a.y + t * ab.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 `xy` coordinates for red, x, y between 0.0000 and 1.0000.
|
||||||
|
* @param g double array with `xy` coordinates for green, x, y between 0.0000 and 1.0000.
|
||||||
|
* @param b double array with `xy` coordinates for blue, x, y between 0.0000 and 1.0000.
|
||||||
|
*/
|
||||||
|
public Gamut {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* return point in color gamut closest to a given point
|
||||||
|
*
|
||||||
|
* @param p a color point
|
||||||
|
* @return the color point closest to {@param p} in this gamut
|
||||||
|
*/
|
||||||
|
public Point closest(Point p) {
|
||||||
|
Point r = new Point(this.r[0], this.r[1]);
|
||||||
|
Point g = new Point(this.g[0], this.g[1]);
|
||||||
|
Point b = new Point(this.b[0], this.b[1]);
|
||||||
|
|
||||||
|
Point v1 = new Point(g.x - r.x, g.y - r.y);
|
||||||
|
Point v2 = new Point(b.x - r.x, b.y - r.y);
|
||||||
|
Point q = new Point(p.x - r.x, p.y - r.y);
|
||||||
|
double v = v1.crossProduct(v2);
|
||||||
|
double s = q.crossProduct(v2) / v;
|
||||||
|
double t = v1.crossProduct(q) / v;
|
||||||
|
if (s >= 0.0 && t >= 0.0 && s + t <= 1.0) {
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
Point pRG = p.closest(r, g);
|
||||||
|
Point pGB = p.closest(g, b);
|
||||||
|
Point pBR = p.closest(b, r);
|
||||||
|
double dRG = p.distance(pRG);
|
||||||
|
double dGB = p.distance(pGB);
|
||||||
|
double dBR = p.distance(pBR);
|
||||||
|
|
||||||
|
double min = dRG;
|
||||||
|
Point retVal = pRG;
|
||||||
|
if (dGB < min) {
|
||||||
|
min = dGB;
|
||||||
|
retVal = pGB;
|
||||||
|
}
|
||||||
|
if (dBR < min) {
|
||||||
|
retVal = pBR;
|
||||||
|
}
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean inRange(double val) {
|
||||||
|
return val >= 0.0 && val <= 1.0;
|
||||||
|
}
|
||||||
|
}
|
@ -129,14 +129,8 @@ public class HSBTypeTest {
|
|||||||
public void testConversionToXY() {
|
public void testConversionToXY() {
|
||||||
HSBType hsb = new HSBType("220,90,50");
|
HSBType hsb = new HSBType("220,90,50");
|
||||||
PercentType[] xy = hsb.toXY();
|
PercentType[] xy = hsb.toXY();
|
||||||
assertEquals(new PercentType("16.969364"), xy[0]);
|
assertEquals(14.65, xy[0].doubleValue(), 0.01);
|
||||||
assertEquals(new PercentType("12.379659"), xy[1]);
|
assertEquals(11.56, xy[1].doubleValue(), 0.01);
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCreateFromXY() {
|
|
||||||
HSBType hsb = HSBType.fromXY(5f, 3f);
|
|
||||||
assertEquals(new HSBType("11,100,100"), hsb);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -0,0 +1,68 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.core.util;
|
||||||
|
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
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.assertThrows;
|
||||||
|
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
import org.openhab.core.library.types.HSBType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link ColorUtilTest} is a test class for the color conversion
|
||||||
|
*
|
||||||
|
* @author Jan N. Klug - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class ColorUtilTest {
|
||||||
|
private static Stream<Arguments> colors() {
|
||||||
|
return Stream.of(HSBType.BLACK, HSBType.BLUE, HSBType.GREEN, HSBType.RED, HSBType.WHITE,
|
||||||
|
HSBType.fromRGB(127, 94, 19)).map(Arguments::of);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("colors")
|
||||||
|
public void inversionTest(HSBType hsb) {
|
||||||
|
HSBType hsb2 = ColorUtil.xyToHsv(ColorUtil.hsbToXY(hsb));
|
||||||
|
|
||||||
|
double deltaHue = Math.abs(hsb.getHue().doubleValue() - hsb2.getHue().doubleValue());
|
||||||
|
deltaHue = deltaHue > 180.0 ? Math.abs(deltaHue - 360) : deltaHue; // if deltaHue > 180, the "other direction"
|
||||||
|
// is shorter
|
||||||
|
double deltaSat = Math.abs(hsb.getSaturation().doubleValue() - hsb2.getSaturation().doubleValue());
|
||||||
|
double deltaBri = Math.abs(hsb.getBrightness().doubleValue() - hsb2.getBrightness().doubleValue());
|
||||||
|
|
||||||
|
assertThat(deltaHue, is(lessThan(5.0)));
|
||||||
|
assertThat(deltaSat, is(lessThanOrEqualTo(1.0)));
|
||||||
|
assertThat(deltaBri, is(lessThanOrEqualTo(1.0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("invalids")
|
||||||
|
public void invalidXyValues(double[] xy) {
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> ColorUtil.xyToHsv(xy));
|
||||||
|
}
|
||||||
|
}
|
@ -22,6 +22,9 @@
|
|||||||
<suppress files=".+org.openhab.core.config.core.ConfigurableService" checks="ConstantNameCheck"/>
|
<suppress files=".+org.openhab.core.config.core.ConfigurableService" checks="ConstantNameCheck"/>
|
||||||
<suppress files=".+org.openhab.core.config.discovery.mdns.internal.MDNSDiscoveryService.java|.+org.openhab.core.config.discovery.upnp.internal.UpnpDiscoveryService.java|.+org.openhab.core.io.console.eclipse.internal.ConsoleSupportEclipse.java|.+org.openhab.core.io.console.rfc147.internal.CommandWrapper.java|.+org.openhab.core.library.unit.BinaryPrefix.java|.+org.openhab.core.library.unit.MetricPrefix.java" checks="MethodNameCheck"/>
|
<suppress files=".+org.openhab.core.config.discovery.mdns.internal.MDNSDiscoveryService.java|.+org.openhab.core.config.discovery.upnp.internal.UpnpDiscoveryService.java|.+org.openhab.core.io.console.eclipse.internal.ConsoleSupportEclipse.java|.+org.openhab.core.io.console.rfc147.internal.CommandWrapper.java|.+org.openhab.core.library.unit.BinaryPrefix.java|.+org.openhab.core.library.unit.MetricPrefix.java" checks="MethodNameCheck"/>
|
||||||
|
|
||||||
|
<!--suppress local variable naming check to stay consistent with the usual convention when calculating xyY/RGB/HSB conversion -->
|
||||||
|
<suppress files=".+org.openhab.core.util.ColorUtil.java" checks="LocalVariableNameCheck" />
|
||||||
|
|
||||||
<!-- Add suppression as discussed in https://github.com/openhab/static-code-analysis/issues/265 -->
|
<!-- Add suppression as discussed in https://github.com/openhab/static-code-analysis/issues/265 -->
|
||||||
<suppress files=".+org.openhab.core.common.registry.AbstractRegistry.java" checks="DeclarativeServicesDependencyInjectionCheck"/>
|
<suppress files=".+org.openhab.core.common.registry.AbstractRegistry.java" checks="DeclarativeServicesDependencyInjectionCheck"/>
|
||||||
<suppress files=".+org.openhab.core.thing.binding.BaseThingHandler.java" checks="DeclarativeServicesDependencyInjectionCheck"/>
|
<suppress files=".+org.openhab.core.thing.binding.BaseThingHandler.java" checks="DeclarativeServicesDependencyInjectionCheck"/>
|
||||||
|
Loading…
Reference in New Issue
Block a user