Does anyone know of a way to get HSL from an linearRGB color (not an sRGB color)? I've seen a lot of sRGB<->HSL conversions, but nothing for linearRGB<->HSL. Not sure if it is fundementally the same conversion with minor tweaks, but I'd appreciate any insight someone may have on this.
Linear RGB is not the same as linearizing sRGB (which is taking [0,255] and making it [0,1]). Linear RGB transformation from/to sRGB is at http://en.wikipedia.org/wiki/SRGB. In VBA, this would be expressed (taking in linearized sRGB values [0,1]):
Public Function sRGB_to_linearRGB(value As Double)
If value < 0# Then
sRGB_to_linearRGB = 0#
Exit Function
End If
If value <= 0.04045 Then
sRGB_to_linearRGB = value / 12.92
Exit Function
End If
If value <= 1# Then
sRGB_to_linearRGB = ((value + 0.055) / 1.055) ^ 2.4
Exit Function
End If
sRGB_to_linearRGB = 1#
End Function
Public Function linearRGB_to_sRGB(value As Double)
If value < 0# Then
linearRGB_to_sRGB = 0#
Exit Function
End If
If value <= 0.0031308 Then
linearRGB_to_sRGB = value * 12.92
Exit Function
End If
If value < 1# Then
linearRGB_to_sRGB = 1.055 * (value ^ (1# / 2.4)) - 0.055
Exit Function
End If
linearRGB_to_sRGB = 1#
End Function
I have tried sending in Linear RGB values to standard RGB_to_HSL routines and back out from HSL_to_RGB, but it does not work. Maybe because current HSL<->RGB algorithms account for gamma correction and Linear RGB is not gamma corrected - I don't know exactly. I have seen almost no references that this can be done, except for two:
A reference on
http://en.wikipedia.org/wiki/HSL_and_HSV#cite_note-9
(numbered item 10).
A reference on an open source
project Grafx2 #
http://code.google.com/p/grafx2/issues/detail?id=63#c22
in which the contributor states that
he has done Linear RGB <-> HSL
conversion and provides some C code in an attachment to his comment in a .diff file
(which I can't really read :( )
My intent is to:
send from sRGB (for example,
FF99FF (R=255, G=153, B=255)) to
Linear RGB (R=1.0,
G=0.318546778125092, B=1.0)
using the code above (for example,
the G=153 would be obtained in Linear
RGB from sRGB_to_linearRGB(153 /
255))
to HSL
modify/modulate the Saturation by
350%
send back from HSL->Linear
RGB->sRGB, the result would be
FF19FF (R=255, G=25, B=255).
Using available functions from .NET, such as .getHue from a System.Drawing.Color does not work in any sRGB space above 100% modulation of any HSL value, hence the need for Linear RGB to be sent in instead of sRGB.
It doesn't make much sense to convert to linear RGB, since HSL is defined in terms of gamma encoded values. Instead, write your own function convert sRGB to HSL, modulate the saturation with those values (allowing potentially out-of-gamut saturation values), and then convert back to sRGB, clamping intensities that are out of sRGB range (or disallowing saturation changes that can't be encoded in sRGB).
The System.Windows.Media.Color class lets you get or set scRGB via ScA,ScR,ScG,ScB properties, or RGB via A,R,G,B properties.
So you could convert RGB to HSL, manipulate that, then convert back to RGB and store in a Color instance. You can then read out the converted scRGB properties.
Not ideal, and might involve some information loss. But it's an option!
Based on your comment here, your issue isn't doing the conversion incorrectly; it's that you are performing successive conversions on quantized values. In other words, you are taking the result of sRGB=>RGB=>HSL=>HSL=>RGB=>sRGB and using that in subsequent color operations. The most straightforward way to maintain precision is to always keep around the original RGB value and accumulate the changes in HSL space you want to apply. That way, you are always applying the HSL space operations to the original color and you don't have to worry about repeatedly processing quantized values.
Does this help? There are a lot of interesting links in that question, maybe something that works in your case, too...
Related
One simple way is to say that when the RGB components are equal, they form a gray color.
However, this is not the whole story, because if they only have a slight difference, they will still look gray.
Assuming the viewer has a healthy vision of color, how can I decide if the given values would be perceived as gray (presumably with an adjustable threshold level for "grayness")?
A relatively straightforward method would be to convert RGB value to HSV color space and use threshold on the saturation component, e.g. "if saturation < 0.05 then 'almost grey', else not grey".
Saturation is actually the "grayness/colorfulness" by definition.
This method is much more accurate than using differences between R, G and B channels (since human eye perceives saturation differently on light and dark colors). On the other hand, converting RGB to HSV is computationally intensive. It is up to you to decide what is of more value - precise answer (grey/not grey) or performance.
If you need an even more precise method, you may use L*a*b* color space and compute chroma as sqrt(a*a + b*b) (see here), and then apply thresholding to this value. However, this would be even more computationally intensive.
You can also combine multiple methods:
Calculate simple differences between R, G, B components. If the color can be identified as definitely desaturated (e.g. max(abs(R-G), abs(R-B), abs(G-B)) <= 5) or definitely saturated (e.g. max(abs(R-G), abs(R-B), abs(G-B)) > 100), then stop.
Otherwise, convert to L*a*b*, compute chroma as sqrt(a*a + b*b) and use thresholding on this value.
r = 160;
g = 179;
b = 151;
tolerance = 20;
if (Math.abs(r-g) < 20 && Math.abs(r-b) < 20) {
#then perceived as gray
}
Is there a tool / program / color system that enables you to get colors of the same luminance (perceived brightness)?
Say I pick a color (determine RGB values) and the program gives me all the colors around the color wheel with the same luminance but different hues?
I haven't seen such tool yet, all I came across were three different algorithms for color luminance:
(0.2126*R) + (0.7152*G) + (0.0722*B)
(0.299*R + 0.587*G + 0.114*B)
sqrt( 0.241*R^2 + 0.691*G^2 + 0.068*B^2 )
Just to be clear, I'm talking about color luminance / perceived brightness or whatever you want to call it - the attribute that encounters that we perceive red hue brighter than blue for example. (So 255,0,0 has higher luminance value than 0,0,255.)
P.S.: Does anyone know which algorithm is used to determine color luminence on this website: http://www.workwithcolor.com/hsl-color-picker-01.htm
It looks like they used none of the posted algorithms.
In the HSL color picker you linked to, it looks like they are using the 3rd Lightness equation given here, and then making it a percentage. So the equation is:
L = (100 * 0.5 * (max(r,g,b) + min(r,g,b))) / 255
Edit: Actually, I just realized that they have an L value and a Lum value shown on that color picker. The equation above applies to the L value, but I don't know how they are arriving at the Lum value. It doesn't seem to follow any of the standard equations.
I'm trying to control some RGB LEDs and fade from red to violet. I'm using an HSV to RGB conversion so that I can just sweep from hue 0 to hue 300 (beyond that it moves back towards red). The problem I noticed though is that it seems to spend far to much time in the cyan and blue section of the spectrum. So I looked up what the HSV spectrum is supposed to look like, and found thisL
I didn't realize that more than half the spectrum was spent between green and blue.
But I'd really like it to look much more like this:
With a nice even blend of that "standard" rainbow colors.
I'd imagine that this would end up being some sort of s-curve of the normal hue values, but am not really sure how to calculate that curve.
An actual HSV to RGB algorithm that handles this internally would be great (any code really, though it's for an Arduino) but even just an explanation of how I could calculate that hue curve would be greatly appreciated.
http://www.fourmilab.ch/documents/specrend/ has a fairly detailed description of how to convert a wavelength to CIE components (which roughly correspond to the outputs of the three kinds of cone sensors in your eyes) and then how to convert those to RGB values (with a warning that some wavelengths don't have RGB equivalents in a typical RGB gamut).
Or: there are various "perceptually uniform colour spaces" like CIE L*a*b* (see e.g. http://en.wikipedia.org/wiki/Lab_color_space); you could pick one of those, take equal steps along a straight line joining your starting and ending colours in that space, and convert to RGB.
Either of those is likely to be overkill for your application, though, and there's no obvious reason why they should be much -- or any -- better than something simpler and purely empirical. So why not do the following:
Choose your starting and ending colours. For simplicity, let's suppose they have S=1 and V=1 in HSV space. Note them down.
Look along the hue "spectrum" that you posted and find a colour that looks to you about halfway between your starting and ending points. Note this down.
Now bisect again: find colours halfway between start and mid, and halfway between mid and end.
Repeat once or twice more, so that you've divided the hue scale into 8 or 16 "perceptually equal" parts.
Convert to RGB, stick them in a lookup table, and interpolate linearly in between.
Tweak the RGB values a bit until you have something that looks good.
This is totally ad hoc and has nothing principled about it at all, but it'll probably work pretty well and the final code will be basically trivial:
void compute_rgb(int * rp, int * gp, int * bp, int t) {
// t in the range 0..255 (for convenience)
int segment = t>>5; // 0..7
int delta = t&31;
int a=rgb_table[segment].r, b=rgb_table[segment+1].r;
*rp = a + ((delta*(b-a))>>5);
a=rgb_table[segment].g; b=rgb_table[segment+1].g;
*gp = a + ((delta*(b-a))>>5);
a=rgb_table[segment].b; b=rgb_table[segment+1].b;
*bp = a + ((delta*(b-a))>>5);
}
(you can make the code somewhat clearer if you don't care about saving every available cycle).
For what it's worth, my eyes put division points at hue values of about (0), 40, 60, 90, 150, 180, 240, 270, (300). Your mileage may vary.
FastLED does a a version of this: https://github.com/FastLED/FastLED/wiki/FastLED-HSV-Colors
HSLUV is another option: http://www.hsluv.org/. They have libraries in a bunch of different languages.
Also, this is an interesting technique: https://www.shadertoy.com/view/4l2cDm
const float tau = acos(-1.)*2.;
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 uv = fragCoord.xy / iResolution.xy;
vec3 rainbow = sqrt( //gamma
sin( (uv.x+vec3(0,2,1)/3.)*tau ) * .5 + .5
);
fragColor.rgb = rainbow;
}
Also see:
https://en.wikipedia.org/wiki/Rainbow#Number_of_colours_in_spectrum_or_rainbow for more info.
I am coding a program that allows a user to choose various foreground and background colours in RGB. I want to not allow them to chose foreground and backgrounds that are too similar and decided to convert to HSL and use HSL euclidean distance as a way to check for similarity.
Is there a good weighting to use for HSL space (rather than equal weighting for H, S and L)? I've looked at various sites and not found the exact thing I need; just things saying that HSL or HSB is better than RGB.
first convert the colors to Lab. This colorspace is designed so that the vectorial difference between any two colors closely approximate a 'subjective distance'.
In color management, a 'delta E' value is given as a measure of how perceptually faithful a given color transformation is. it's just the magnitude of the vector difference between original and final colors as expressed in Lab space.
My advice would be to skip HSL/HSB entirely, and go directly from RGB to LAB. Once you've done that, you can do a standard delta E computation.
I don't have exact figures for you, but I'd use a much higher weight for L than H or S. The eye is bad at discriminating between equal colors of different saturation, and nearly as bad at distinguishing different hues - expecially if it's fine detail you're trying to see, like text.
I just concluded an interesting study into color spaces. As others mentioned here, converting RGB to CIE-Lab and doing a Delta E computation will give you perceptual color distance. It produces okay results.
My goal was to find the closest index in a limited color palette. However, I found using CIE-Lab Delta E calculations ended up with "wrong" colors. Particularly grayscale would wind up getting too much saturation and select a red instead of a gray from the palette but other colors had issues too (I don't remember which ones). For better or worse, I wound up weighting hues at a 1.2x multiplier, saturation at 1.5x, and B values at either 1.0x or 2.0x depending on the direction. The results more or less work out better than just Delta E alone.
Calculating the distance of Hue is a bit tricky since it is a circle. For example, Hue 0 and Hue 359 are a distance of 1. The solution is to select the minimum of two different distances.
Here's my code based on the above:
// Finds the nearest color index in a RGB palette that matches the requested color.
// This function uses HSB instead of CIE-Lab since this function is intended to be called after GetReadableTextForegroundColors() and results in more consistent color accuracy.
public static function FindNearestPaletteColorIndex($palette, $r, $g, $b)
{
$hsb1 = self::ConvertRGBToHSB($r, $g, $b);
$result = false;
$founddist = false;
foreach ($palette as $key => $rgb)
{
$rgb = array_values($rgb);
$r = $rgb[0];
$g = $rgb[1];
$b = $rgb[2];
$hsb2 = self::ConvertRGBToHSB($r, $g, $b);
$hdiff = min(abs($hsb1["h"] - $hsb2["h"]), abs($hsb1["h"] - $hsb2["h"] + ($hsb1["h"] < $hsb2["h"] ? -360.0 : 360.0))) * 1.2;
$sdiff = ($hsb1["s"] - $hsb2["s"]) * 1.5;
$bdiff = $hsb1["b"] - $hsb2["b"];
if ($hsb1["b"] < $hsb2["b"]) $bdiff *= 2.0;
$hdiff *= $hdiff;
$sdiff *= $sdiff;
$bdiff *= $bdiff;
$dist = $hdiff + $sdiff + $bdiff;
if ($result === false || $founddist >= $dist)
{
$result = $key;
$founddist = $dist;
}
}
return $result;
}
Source: https://github.com/cubiclesoft/php-misc/blob/master/support/color_tools.php
Converting the above to use HSL instead of HSB/HSV shouldn't be too difficult. I prefer the HSB color space since it mirrors Photoshop, which allows me to confirm the numbers I'm looking for in software.
I have code that needs to render regions of my object differently depending on their location. I am trying to use a colour map to define these regions.
The problem is when I sample from my colour map, I get collisions. Ie, two regions with different colours in the colourmap get the same value returned from the sampler.
I've tried various formats of my colour map. I set the colours for each region to be "5" apart in each case;
Indexed colour
RGB, RGBA: region 1 will have RGB 5%,5%,5%. region 2 will have RGB 10%,10%,10% and so on.
HSV Greyscale: region 1 will have HSV 0,0,5%. region 2 will have HSV 0,0,10% and so on.
(Values selected in The Gimp)
The tex2D sampler returns a value [0..1].
[ I then intend to derive an int array index from region. Code to do with that is unrelated, so has been removed from the question ]
float region = tex2D(gColourmapSampler,In.UV).x;
Sampling the "5%" colour gave a "region" of 0.05098 in hlsl.
From this I assume the 5% represents 5/100*255, or 12.75, which is rounded to 13 when stored in the texture. (Reasoning: 0.05098 * 255 ~= 13)
By this logic, the 50% should be stored as 127.5.
Sampled, I get 0.50196 which implies it was stored as 128.
the 70% should be stored as 178.5.
Sampled, I get 0.698039, which implies it was stored as 178.
What rounding is going on here?
(127.5 becomes 128, 178.5 becomes 178 ?!)
Edit: OK,
http://en.wikipedia.org/wiki/Bankers_rounding#Round_half_to_even
Apparently this is "banker's rounding". I have no idea why this is being used, but it solves my problem. Apparently, it's a Gimp issue.
I am using Shader Model 2 and FX Composer. This is my sampler declaration;
//Colour map
texture gColourmapTexture <
string ResourceName = "Globe_Colourmap_Regions_Greyscale.png";
string ResourceType = "2D";
>;
sampler2D gColourmapSampler : register(s1) = sampler_state {
Texture = <gColourmapTexture>;
#if DIRECT3D_VERSION >= 0xa00
Filter = MIN_MAG_MIP_LINEAR;
#else /* DIRECT3D_VERSION < 0xa00 */
MinFilter = Linear;
MipFilter = Linear;
MagFilter = Linear;
#endif /* DIRECT3D_VERSION */
AddressU = Clamp;
AddressV = Clamp;
};
I never used HLSL, but I did use GLSL a while back (and I must admit it's terribly far in my head).
One issue I had with textures is that 0 is not the first pixel. 1 is not the second one. 0 is the edge of the texture and 1 is the right edge of the first pixel. The values get interpolated automatically and that can cause serious trouble if what you need is precision like when applying a lookup table rather than applying a normal texture. You need to aim for the middle of the pixel, so asking for [0.5,0.5], [1.5,0.5] rather than [0,0], [1, 0] and so on.
At least, that's the way it was in GLSL.
Beware: region in levels[region] is rounded down. When you see 5 % in your image editor, the actual value in the texture 8b representation is 5/100*255 = 12.75, which may be either 12 or 13. If it is 12, the rounding down will hit you. If you want rounding to nearest, you need to change this to levels[region+0.5].
Another similar thing (already written by Louis-Philippe) which might hit you is texture coordinates rounding rules. You always need to hit a spot in the texel so that you are not in between of two texels, otherwise the result is ill-defined (you may get any of two randomly) and some of your source texels may disapper while other duplicate. Those rules are different for bilinar and point sampling, you may need to add half of texel size when sampling to compensate for this.
GIMP uses banker's rounding. Apparently.
This threw out my code to derive region indicies.