Issue
I'm trying to change color space of given image by using PyQt. I can't understand how QColor works. Speaking about HSV we have 3 channels: H - from 0 to 359, S - from 0 to 100, V - from 0 to 100. But in documentation:
The value of s, v, and a must all be in the range 0-255; the value of h must be in the range 0-359.
How can be S and V values be in range 0-255? The same question is about HSL, where S and L should be in range 0-100
The value of s, l, and a must all be in the range 0-255; the value of h must be in the range 0-359.
And one more question. Should be the image, converted from rgb to hsl / rgb to hsv look the same and has the same colors?
Solution
Speaking about HSV we have 3 channels: H - from 0 to 359, S - from 0 to 100, V - from 0 to 100.
That's just a common convention, but it's not part of the HSV color space definition, nor its "parent" HSL from which it origined.
Those values are always intended as a range between a minimum and a maximum, not a discrete-based value range.
First of all, they both are alternative representations of the RGB color model.[1]
Then, colors are not discrete, our "digital usage" forces us to make them so, and their value range is completely arbitrary.
The commonly used RGB model is based on 8 bits for each primary color (providing a 256 value range for each of them, from 0 to 255), but even if it's normally fine for most usage, it's actually limited, especially when shown in a video or animation: in some cases (notably, with gradients), even a value change of 1 in a component can be clearly seen.
Color model representations in digital world commonly use discrete integer values of color spaces using limited ranges for performance reasons, and that's also valid for the values you're referring to. The range depends on the implementation.
For instance, the CSS rgb()
notation accepts values with the 8-bit notation and percentages. Those values are almost never consistent, and for obvious reasons: while the theoretical range is of 256 values, the range of a percentage always refers to the maximum (255), meaning that 50%
(or 0.5
) is actually 127.5
.
In fact, rgb(50%, 50%, 50%)
normally results in #808080
, which is rgb(128, 128, 128)
(since 127.5 is rounded), meaning that rgb(50%, 50%, 50%)
and rgb(128, 128, 128)
are not the same, conceptually speaking.[2]
So, to the point, the value range only depends on the implementation. The only difference is that the hue
component is wrapping because it's based on a circle, meaning that it always truly is a 0-360 range value: 50% (or 0.5) will always be 180 degrees, and that's because the maximum (360°, or 100%) equals the minimum (0).
Qt chose to use a 8-bit standard (0-255) for integer values that, following convention, use 0-255 or percentage ranges, with the exception of the hue
component that uses the common 360 degrees notation.
If you want something more consistent with your habits, then you can add it with a simple helper function, but remember that, as the documentation explains, "components are stored using 16-bit integers" (note that this is still valid even for Qt6[3]), meaning that results might slightly differ.
def fromHsv100(*args, alpha=None):
if isinstance(args[0], QColor):
args = args[1:]
h, s, v = args[:3]
if alpha is None:
if len(args) == 4:
alpha = args[3]
else:
alpha = 100
return QColor.fromHsvF(
(h / 360) % 1,
(s * .01) % 1,
(v * .01) % 1,
(alpha * .01) % 1
)
def getHsv100(color):
return (
color.hue(),
round(color.saturationF() * 100),
round(color.valueF() * 100),
round(color.alphaF() * 100)
)
QColor.fromHsv100 = fromHsv100
QColor.getHsv100 = getHsv100
# usage:
color = QColor.fromHsv100(120, 55, 89)
print(color.getHsv100())
Finally, remember that, due to the nature of hue-based color models, you can create different colors that are always shown as "black" if their value
(for HSV) or lightness
(for HSL) component is 0, while they can have different hue
and saturation
values:
>> print(QColor.fromHsv(60, 0, 0).name())
#000000
>> print(QColor.fromHsv(240, 50, 0).name())
#000000
About your last question, since HSL and HSV are just alternative representations of the RGB color model, an image created with any of the above will theoretically look the same as long as it uses the same color space, and as long as the resulting integer values of the colors are compatible and rounded in the same way. But, since those values are always rounded based on their ranges, and those ranges are proportional to the actual model (which is not consistent for obvious reasons), that might not always happen.
For instance:
>>> hue = 290
>>> rgb = QColor.fromHsv(hue, 150, 150).getRgb()
>>> print(rgb)
(135, 62, 150, 255)
>>> newHue = QColor.fromRgb(*rgb).hue()
>>> print(hue == newHue, hue, newHue)
False 290 289
This means that if you create or edit images using multiple conversions between different color spaces, you might end up with images that are not actually identical.
[1] See the related Wikipedia article
[2] Actual values of the resulting 24-bit RGB (which, as of late 2022, is the final color shown by a non-HDR browser/system) might depend on the browser and its rounding implementation; note that rounding is not always consistent, for instance, Python uses the Round half to even (aka, the "bankers' rounding") method for round()
, meaning that both 127.5
and 128.5
are rounded to 128
.
[3] Even if most modern devices support wider color dynamic ranges, QColor is intended for basic, performant behavior, since it's used in a lot of basic classes that expect fast results, like displaying labels, buttons or texts of items in a model view; things for which such dynamic ranges are quite pointless.
Answered By - musicamante
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.