How to calculate Hue, Saturation and Lightness values from a chosen color by input distance - colors

Given a starting hex code, I would like to know the maths to calculate the linear values of lightness in ascending and descending order. Same for Hue and Saturation.
It's kinda difficult for me to describe exactly what i want, forutnately i've found this page which make use of the exact algorithms i need:
http://www.workwithcolor.com/hsl-color-schemer-01.htm
If you checked the page you noticed that the last 3 redio buttons read: Linear by Hue, Linear by Saturation, Linear by Lightness. Each, gives you a list of hex codes in ascending order that correspond to the original hex code.
For example, for the lightness they give the following list (from color FFCE2E):
FFCE2E FFDA61 FFE694 FFF2C7 FFFEFA
I need the formulas, please.
Thanks in advance.

You can mash this up from multiple places. In a nutshell you need:
The HSL value of your picked color. Maybe this is obtained by converting an RGB to HSL (How do you get the hue of a #xxxxxx colour?) or on the website you just pick it on a palette
Now you have the 3 component (H, S, and L) and depending on which checkbox you choose, you start to decrement the component by the % value given in the edit box.
You'll obtain a list of values during this decrement and you'll now do a reverse conversion from the HSL value to the RGB (HSL to RGB color conversion).
// I gonna use rgbToHsl and hslToRgb from https://stackoverflow.com/questions/2353211/hsl-to-rgb-color-conversion
var initialRGB = [ir, ig, ib];
var initialHSL = rgbToHsl(initialRGB[0], initialRGB[1], initialRGB[2]);
var howManyVariants = 4;
var decrementPercent = 0.1; // 10%
// This example is for hue change
var decrement = initialHSL[0] * decrementPercent;
for (var i = 0; i < howManyVariants; i++) {
// Linear decrementation
var nextHue = initialHSL[0] - i * decrement;
var nextColor = hslToRgb(nextHue, initialHSL[1], initialHSL[2]);
// visualize somehow
}
Similarly, if you want to have a set of variation by saturation then you decrement only the second parameter/component, and if you want vary luminescence, you vary the 3rd parameter.
Hope this is clear.

Related

How do I find the RGBA value of a color from two RGB values?

I have a hunch this has been done before but I am a total layman at this and don't know how to begin to ask the right question. So I will describe what I am trying to do...
I have an unknown ARGB color. I only know its absolute RGB value as displayed over two known opaque background colors, for example black 0x000000 and white 0xFFFFFF. So, to continue the example, if I know that the ARGB color is RGB 0x000080 equivalent when displayed over 0x000000 and I know that the same ARGB color is RGB 0x7F7FFF equivalent when displayed over 0xFFFFFF, is there a way to compute what the original ARGB color is?
Or is this even possible???
So, you know that putting (a,r,g,b) over (r1,g1,b1) gives you (R1,G1,B1) and that putting it over (r2,g2,b2) gives you (R2,G2,B2). In other words -- incidentally I'm going to work here in units where a ranges from 0 to 1 -- you know (1-a)r1+ar=R1, (1-a)r2+ar=R2, etc. Take those two and subtract: you get (1-a)(r1-r2)=R1-R2 and hence a=1-(R1-R2)/(r1-r2). Once you know a, you can work everything else out.
You should actually compute the values of a you get from doing that calculation on all three of {R,G,B} and average them or something, to reduce the effects of roundoff error. In fact I'd recommend that you take a = 1 - [(R1-R2)sign(r1-r2) + (G1-G2)sign(g1-g2) + (B1-B2)sign(b1-b2)] / (|r1-r2|+|g1-g2|+|b1-b2), which amounts to weighting the more reliable colours more highly.
Now you have, e.g., r = (R1-(1-a)r1)/a = (R2-(1-a)r2)/a. These two would be equal if you had infinite-precision values for a,r,g,b, but of course in practice they may differ slightly. Average them: r = [(R1+R2)-(1-a)(r1+r2)]/2a.
If your value of a happens to be very small then you'll get only rather unreliable information about r,g,b. (In the limit where a=0 you'll get no information at all, and there's obviously nothing you can do about that.) It's possible that you may get numbers outside the range 0..255, in which case I don't think you can do better than just clipping.
Here's how it works out for your particular example. (r1,g1,b1)=(0,0,0); (r2,g2,b2)=(255,255,255); (R1,G1,B1)=(0,0,128); (R2,G2,B2)=(127,127,255). So a = 1 - [127+127+127]/[255+255+255] = 128/255, which happens to be one of the 256 actually-possible values of a. (If it weren't, we should probably round it at this stage.)
Now r = (127-255*127/255)*255/256 = 0; likewise g = 0; and b = (383-255*127/255)*255/256 = 255.
So our ARGB colour was 80,00,00,FF.
Choosing black and white as the background colors is the best choice, both for ease of calculation and accuracy of result. With lots of abuse of notation....
a(RGB) + (1-a)0xFFFFFF = 0x7F7FFF
a(RGB) + (1-a)0x000000 = 0x000080
Subtracting the second from the first...
(1-a)0xFFFFFF = 0x7F7FFF-0x000080 = 0x7F7F7F
So
(1-a) = 0x7F/0xFF
a = (0xFF-0x7F)/0xFF = 0x80/0xFF
A = 0x80
and RGB = (a(RGB))/a = 0x000080/a = 0x0000FF
You can do something very similar with other choices of background color. The smaller a is and the closer the two background colors are the less accurately you will be able to determine the RGBA value. Consider the extreme cases where A=0 or where the two background colors are the same.

How to alter brightness of a single rgb color simply and easily via php?

A quesion about RGB color and finding the simplest, tiniest, php conversion code for manipulating the lightness/darkness of a given RGB hue.
Imagine a variable $colorA containning a valid six char RGB color, like F7A100 which we want to make a bit lighter and/or darker:
$color = B1B100; // original RGB color manually set.
Then, at any page have that color bit darker/lighter on the fly:
$colorX = someFunction($color, +10); // original color 10 steps lighter
$colorY = someFunction($color, -25); // original color 25 steps darker
What would be YOUR way of solving this? Keep the RGB as is or first change it to HSL? Hints and suggestions are welcome. Your sample/code is welcome too.
This really focuses to the TINIES / SIMPLES / SHORTEST possible code to just make the same hue bit darker/lighter.
I deliberately do not suggest my code, as I want to keep possibilities open in here.
The absolutely simplest solution is to add some constant (like 1) to each part of the color representation: [R, G, B]. This is due to the fact that max values of all [R, G, B] represent white, while min values - black. In pseudo-code (assuming 255 is max, sorry, I don't know PHP):
lighter(R, G, B) = [
min(255, R + 1),
min(255, G + 1),
min(255, B + 1)
]
You must keep in mind though that this transformation is way too simplistic and the proper implementation would be to convert to HSL/HSB, increase H and transform back to RGB.
For slight alteration of brightness you can convert the hexadecimal values to decimal, manipulate them and convert back to hexadecimal like this:
function alterBrightness($color, $amount) {
$rgb = hexdec($color); // convert color to decimal value
//extract color values:
$red = $rgb >> 16;
$green = ($rgb >> 8) & 0xFF;
$blue = $rgb & 0xFF;
//manipulate and convert back to hexadecimal
return dechex(($red + $amount) << 16 | ($green + $amount) << 8 | ($blue + $amount));
}
echo alterColor('eeeeee', -10); //outputs e4e4e4
Beware that this code does not handle overflow for one color - if one color value becomes less than 0 or more than 255 you will get an invalid color value. This should be easy enough to add.
For drastic changes in brightness, convert to HSL and manipulate the lightness.
Using the functions from the Drupal code, this can be done like this:
$hsl = _color_rgb2hsl(_color_unpack('eeeeee'));
$hsl[2] -= 10;
$rgb = _color_pack(_color_hsl2rgb($hsl));
echo $rgb; //outputs e4e4e4

Plotting RGB spectrum as 2-d color matrix?

Any suggestions on how I might go about plotting the RGB color space as a 2-D matrix? I need a theoretical description of what's going on; a code sample or pseudocode would be helpful but is not required. Thanks!
If you want to represent every color in RGB space in a 2D grid, it may be impossible to avoid discontinuities / sharp borders in the result. But some mapping techniques will look better than others.
Examples from Possiblywrong.wordpress.com post allRGB: Hilbert curves and random spanning trees:
Traverse the pixels of the image via a 2-dimensional (order 12) Hilbert curve, while at the same time traversing the RGB color cube via a 3-dimensional (order 8) Hilbert curve, assigning each pixel in turn the corresponding color
"Breadth-first traversal of random spanning tree of pixels, assigning colors in Hilbert curve order."
Also check out allrgb.com, "The objective of allRGB is simple: To create images with one pixel for every RGB color (16777216); not one color missing, and not one color twice."
If you don't want to lose any information, you will need to use three dimension. If you can lose some dimensional information, then it's easy. Just do this:
// or HSV
int [256*256][256] colorMatrix;
for (int r = 0; r < 256; r++) {
for (int r = 0; r < 256; r++) {
for (int r = 0; r < 256; r++) {
colorMatrix[256*r+g][b] = color(r, g, b);
}
}
}
There isn't really a good answer for 2D, because you really need 3 dimensions. Of course, you can project a 3D space onto 2D, but to retain a meaningful amount of information you nearly need to provide the normal 3D manipulation, so you can see the projection viewed from various different angles and such.
Here are some experiments I tried based on matching hue to angles and lightness and saturation to distances from the centre of the image. I could not find a way to avoid discontinuities, dithering and ripples at the same time (original image link):

Distance measure between HSL colours

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.

HLSL tex2d sampler seemingly using inconsistent rounding; why?

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.

Resources