Closed andrewfg closed 1 year ago
Ping to @holgerfriedrich / @J-N-K ..
@andrewfg xy was giving me a headache yesterday evening because back conversion did not work properly (even after modifying rgbtohsb to use float input properly).... I have pushed to my private repo.
Did you notice that point has a ctor which accepts only int,int?
I can take a look later.
point has a ctor which accepts only int,int?
Hmm. No.
public Point(double x, double y) {
Forget my last comment. This happens when your IDE tries to resolve it to Java standard class Point.
But nevertheless, I still have the problem when I try to convert HSB 0,100,100 to xy and back. Using floats in RGB to HSB does not help, as the computation of RGB in hsbtoxy has parameters 255.0 0.0 16.434406..... I am lacking of a deeper understanding of the hsbtoxy transform and which corner cases are hard to transform.
btw: find my implementation below, it passes the rgb to hsb and back conversion without any errors.
public static HSBType rgbToHsb(int[] rgb) throws IllegalArgumentException {
if (rgb.length != 3 || !inByteRange(rgb[0]) || !inByteRange(rgb[1]) || !inByteRange(rgb[2])) {
throw new IllegalArgumentException("RGB array only allows values between 0 and 255");
}
return rgbToHsb(new float[] { (float) rgb[0], (float) rgb[1], (float) rgb[2] });
}
private static HSBType rgbToHsb(float[] rgb) {
final float r = rgb[0];
final float g = rgb[1];
final float b = rgb[2];
float max = (r > g) ? r : g;
if (b > max) {
max = b;
}
float min = (r < g) ? r : g;
if (b < min) {
min = b;
}
LOGGER.warn("RGB: {} {} {}", r, g, b);
float tmpHue;
final float tmpBrightness = max / 2.55f;
final float tmpSaturation = (max >= 0.01 ? ((float) (max - min)) / ((float) max) : 0) * 100.0f;
// smallest possible saturation: 0 (max=0 or max-min=0), other value closest to 0 is 100/255 (max=255, min=254)
// -> avoid float comparision to 0
// if (tmpSaturation == 0) {
if (max < 0.01f || Math.abs(max - min) < 0.1f) {
tmpHue = 0;
} else {
float red = ((float) (max - r)) / ((float) (max - min));
float green = ((float) (max - g)) / ((float) (max - min));
float blue = ((float) (max - b)) / ((float) (max - min));
if (Math.abs(r - max) < 0.01) {
tmpHue = blue - green;
} else if (Math.abs(g - max) < 0.01) {
tmpHue = 2.0f + red - blue;
} else {
tmpHue = 4.0f + green - red;
}
tmpHue = tmpHue / 6.0f * 360;
if (tmpHue < 0) {
tmpHue = tmpHue + 360.0f;
}
}
// two places after the decimal seems sufficient for lossles conversions
return new HSBType(new DecimalType(BigDecimal.valueOf(tmpHue).setScale(2, RoundingMode.HALF_UP)),
new PercentType(BigDecimal.valueOf(tmpSaturation).setScale(2, RoundingMode.HALF_UP)),
new PercentType(BigDecimal.valueOf(tmpBrightness).setScale(2, RoundingMode.HALF_UP)));
}
Yeah. There are definitely inconsistencies in the XY -> RGB -> HSB -> RGB -> XY transform since the round trip always ends with markedly different results that what you start with. I think it is not just a matter of integer rounding, but probably something fishy in the formulae themselves. Ideally I would like to cut out the RGB stages entirely since they seem irrelevant..
The problem is that there is no 1:1 relationship between the different color models. Take e.g. a full brightness white, that can be expressed by any hue value, as long as saturation and brightness is 100%. So if you start with 120/0/100 as HSB, that will result in RGB 255/255/255 and if you convert it back to HSB there is no way to get the original hue value back. This is similar with xyY, no matter if you take the step with RGB or not. In addition the color space of xyY is larger than that of (s)RGB, you can't express every xyY color in RGB (or HSB), so it is a not a lossless conversion.
there is no 1:1 relationship between the different color models
@J-N-K your statement above is correct; but not really for the reasons that you cite :)
In fact the round trip XY -> RGB -> HSB -> RGB -> XY is accurate to within 6 or 7 decimal places .. but only if the original XY lies within the Gamut triangle .. and if that is not the case, the XY is adjusted to the closest value that does lie within the Gamut triangle .. and it is this latter correction which is indeed a one way process..
Following are proposed extensions to ColorUtil to enable higher precision conversion XY <=> HSB without intermediate transition via integer RGB values.
NOTE: this proposal is DRAFT for discussion purposes. It uses blunt clones of some methods to use
double
rather thanint
internally. So it is not code efficient. Efficiency could be improved either by using inner/outer methods, or perhaps generics.