I'm looking for a function that can accurately represent the distance between two colours as a number or something.
For example I am looking to have an array of HEX values or RGB arrays and I want to find the most similar colour in the array for a given colour
eg. I pass a function a RGB value and the 'closest' colour in the array is returned
Each color is represented as a tuple in the HEX code. To determine close matches you need to subtract each RGB component separately.
Example:
Color 1: #112233
Color 2: #122334
Color 3: #000000
Difference between color1 and color2: R=1, G=1 B=1 = 0x3
Difference between color3 and color1: R=11, G=22, B=33 = 0x66
So color 1 and color 2 are closer than
1 and 3.
edit
So you want the closest named color? Create an array with the hex values of each color, iterate it and return the name. Something like this;
function getColor($rgb)
{
// these are not the actual rgb values
$colors = array(BLUE =>0xFFEEBB, RED => 0x103ABD, GREEN => 0x123456);
$largestDiff = 0;
$closestColor = "";
foreach ($colors as $name => $rgbColor)
{
if (colorDiff($rgbColor,$rgb) > $largestDiff)
{
$largestDiff = colorDiff($rgbColor,$rgb);
$closestColor = $name;
}
}
return $closestColor;
}
function colorDiff($rgb1,$rgb2)
{
// do the math on each tuple
// could use bitwise operates more efficiently but just do strings for now.
$red1 = hexdec(substr($rgb1,0,2));
$green1 = hexdec(substr($rgb1,2,2));
$blue1 = hexdec(substr($rgb1,4,2));
$red2 = hexdec(substr($rgb2,0,2));
$green2 = hexdec(substr($rgb2,2,2));
$blue2 = hexdec(substr($rgb2,4,2));
return abs($red1 - $red2) + abs($green1 - $green2) + abs($blue1 - $blue2) ;
}
Here is a paper on the subject which should give a good answer.
I was thinking that converting to HSL/HSV first would be a good idea also, but then I realized that at extreme values of S & L/V, H doesn't matter, and in the middle, it matters most.
I think if you want a simple solution, staying in the RGB space would be wiser. I'd use cartesian distance. If you're considering color R G B against Ri Gi Bi for several i, you want the i that minimizes
(R - Ri)^2 + (G - Gi)^2 + (B - Bi)^2
First you have to choose th appropriate color space you want the color comparisons to occur in (RGB, HSV, HSL, CMYK, etc.).
Assuming you want to know how close two points in the 3-dimenionsal RGB space are to each other, you can calculate the Pythagorean distance between them, i.e.,
d2 = (r1 - r2)**2 + (g1 - g2)**2 + (b1 - b2)**2;
This actually gives you the square of the distance. (Taking the square root is not necessary if you're comparing only the squared values.)
This assumes that you want to treat the R, G, and B values equally. If you'd rather weight the individual color components, such as what happens when you convert RGB into grayscale, you have to add a coefficient to each term of the distance, i.e.,
d2 = 30*(r1-r2)**2 + 59*(g1-g2)**2 + 11*(b1-b2)**2;
This assumes the popular conversion from RGB to grayscale of 30% red + 59% green + 11% blue.
Update
That last equation should probably be
d2 = (30*(r1-r2))**2 + (59*(g1-g2))**2 + (11*(b1-b2))**2;
A very simple approach is to calculate the summarized distance among the three dimensions. For example simple_distance("12,10,255","10,10,250")=7
A more sophisticated approach would be to take the square of the distances for each components and sum those - this way components being too far would be "punished" more: square_distance("12,10,255","10,10,250")=2*2+0*0+5*5=29.
Of course you would have to iterate over the list of colors and find the closest one.
you can convert your RGB value to HSL or HSV. then the colors are easy to compare: order colors by hue first, then by saturation, then by luminance. in the resulting order, 2 colors next to each other will appear as being very close perceptually.
beware that hue wraps around: for a hue ranging from 0 to 255, a hue of 0 and a hue of 255 are very close.
see the wikipedia article on HSL http://en.wikipedia.org/wiki/HSL_and_HSV for the formula which will allow you to convert RGB to HSL
(note that other color spaces, like L.a.b., may give better results, but conversion is more complicated)
let's define this mathematically:
distance(A(h,s,l), B(h,s,l)) = (A(h)-B(h)) * F^2 + (A(s)-B(s)) * F + (A(l)-B(l))
where F is a factor carefully chosen (something like 256...)
the above formula does not take into account the hue wraparound...
Color perception is not linear because the human eye is more sensitive to certain colors than others.
You need to use a special formula.
Look here.
Related
I'm working in php with 3D geometries(not the best choice,I know...).
I have K coplanar 3D points, also with x,y,z value. Together they form a polygon. I need to triangulate this polygon. I have already a working delaunay traingulation function which works for 2D Polygons.
So I want to rotate the given points, so that they lay on a plane parallel to the x,y plane. After that I can triangulated it using the x,y values. The following pseudocode shall describe how I want to get to this goal.
I build up the following code with reference on this (I'm usign the answer accepted from the OP): https://math.stackexchange.com/questions/180418/calculate-rotation-matrix-to-align-vector-a-to-vector-b-in-3d, but it doesn't work as I expected. In order to know if it worked, every mapped point shall then have the same 'z' value.
Here is the question, how do I get the correct rotation matrix? Or did I made a conceptual mistake?
function matrixRotationMapping(Point $p, Point $q, Point $r)
{
$normalPolygon =calculatePlaneNormal($p, $q, $r);
$v = crossProduct($normalPolygon, new Point(0, 0, 1));
$c = dotProduct($normalPolygon, new Point(0, 0, 1));
$matrix = buildRotationMatrix($v, $c);
return $matrix;
}
function buildRotationMatrix($v, $c)
{
$R2 = new Matrix(array(array(1, -$v->z, $v->y), array($v->z, 1, -$v->x), array(-$v->y, $v->x, 1)));
$costant = 1/(1+$c);
$R3 = multiplyMatrices($R2, $R2);
$R3 = multiplyMatricesWithFactor($R3, $costant);
$finalMatrix = sumMatrices($R2, $R3);
return $finalMatrix;
}
function calc2DMapping($points)
{
$rotationMatrix = matrixRotationMapping($points[0], $points[1], $points[2]);
foreach($points as $point)
{
$mappedPoint = $rotationMatrix->multiplyWithPoint($point);
$mappedPoints[] = new MappedPoint($mappedPoint);
}
}
I found another helpful description of the problem, but I wasn't able to implement it: Mapping coordinates from plane given by normal vector to XY plane
Thanks in advance for your attention.
You need basis vectors X,Y,Z first. So let take the mid point A and two distant points to it B,C (not on single line) from your data set first. The X,Y should lie in the plane and Z should be normal to it so:
X = B-A // any non zero vector inside plane
X = X / |X| // unit in size
Y = C-A // any non zero vector inside plane
(X.Y) != 0 // but not parallel to X !!!
Y = Y / |Y| // unit in size
Compute normal to the plane your points lie in and correct Y axis.
Z = X x Y // cross product gives you perpendicular vector
Y = Z x X // now all vectors are perpendicular and unit
So feed these 3 vectors to rotation part of your transform matrix and set origin to A. But as you need to go from your data set to the plane local coordinate you need inverse matrix (or use pseudo inverse based on transposing)
Anyway now with the basis vectors you can map your plane parametrically like this:
P(u,v) = A + u*X + v*Y
Where u,v = <-inf,+inf> are surface distances form A in X,Y directions. That can get handy sometimes. If you need to compute u,v from P then exploit dot product:
u = ((P-A).X) = dot(P-A,X)
v = ((P-A).Y) = dot(P-A,Y)
Which can be also used to transform to 2D instead of using matrix ...
How can I calculate the closest colourblind-friendly colour from a HEX colour code like #0a87af or from the three RGB values (0-255).
I'm searching for an efficient way to calculate or do this so I can implement it in PHP or Python and the algorithm can be used for better website accessibility for colourblind people.
As the others mentionned in their comments/answer, the contrast between two colours will be of importance.
The W3 already created a method defining a minimum contrast between colours in order to pass dfferent levels of accessibility.
They provide the description here and the formula to calculate it is on the same page, at the bottom, here :
contrast ratio = (L1 + 0.05) / (L2 + 0.05)
For this apparently simple formula, you will need to calculate the relative luminance noted L1 and L2 of both colours using an other formula that you find here :
L = 0.2126 * R + 0.7152 * G + 0.0722 * B where R, G and B are defined as:
if RsRGB <= 0.03928 then R = RsRGB/12.92 else R = ((RsRGB+0.055)/1.055) ^ 2.4
if GsRGB <= 0.03928 then G = GsRGB/12.92 else G = ((GsRGB+0.055)/1.055) ^ 2.4
if BsRGB <= 0.03928 then B = BsRGB/12.92 else B = ((BsRGB+0.055)/1.055) ^ 2.4
and RsRGB, GsRGB, and BsRGB are defined as:
RsRGB = R8bit/255
GsRGB = G8bit/255
BsRGB = B8bit/255
The minimum contrast ratio between text and background should be of 4.5:1 for level AA and 7:1 for level AAA. This still leaves room for creation of nice designs.
There is an example of implementation in JS by Lea Verou.
This won't give you the closest color as you asked, because on a unique background there will more than one front colour giving the same contrast result, but it's a standard way of calculating contrasts.
A single color is not a problem for color-blind users (unless you want to transport a very specific meaning of that color tone); the difference between colors is.
Given two or more colors, you can convert them to HLS using colorsys and check whether the difference in Lightness is sufficient. If the difference is too small, increase it, like this:
import colorsys
import re
def rgb2hex(r, g, b):
return '#%02x%02x%02x' % (r, g, b)
def hex2rgb(hex_str):
m = re.match(
r'^\#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$', hex_str)
assert m
return (int(m.group(1), 16), int(m.group(2), 16), int(m.group(3), 16))
def distinguish_hex(hex1, hex2, mindiff=50):
"""
Make sure two colors (specified as hex codes) are sufficiently different.
Returns the two colors (possibly changed). mindiff is the minimal
difference in lightness.
"""
rgb1 = hex2rgb(hex1)
rgb2 = hex2rgb(hex2)
hls1 = colorsys.rgb_to_hls(*rgb1)
hls2 = colorsys.rgb_to_hls(*rgb2)
l1 = hls1[1]
l2 = hls2[1]
if abs(l1 - l2) >= mindiff: # ok already
return (hex1, hex2)
restdiff = abs(l1 - l2) - mindiff
if l1 >= l2:
l1 = min(255, l1 + restdiff / 2)
l2 = max(0, l1 - mindiff)
l1 = min(255, l2 + mindiff)
else:
l2 = min(255, l2 + restdiff / 2)
l1 = max(0, l2 - mindiff)
l2 = min(255, l1 + mindiff)
hsl1 = (hls1[0], l1, hls1[2])
hsl2 = (hls2[0], l2, hls2[2])
rgb1 = colorsys.hls_to_rgb(*hsl1)
rgb2 = colorsys.hls_to_rgb(*hsl2)
return (rgb2hex(*rgb1), rgb2hex(*rgb2))
print(distinguish_hex('#ff0000', '#0000ff'))
Contrast-Finder is an open source online tool (written by Open-S and M. Faure) that, given foreground and background colors, will calculate the contrast ratio and if it's insufficient according to WCAG formula, will give you a bunch of background OR foreground colors with sufficient contrast ratio and thus options, using different algorithms (you must tell it if you want to keep the foreground color or the background color and if you want contrast ratio higher than 4.5:1 or 3:1 - level AA - or 7:1 / 4.5:1 - level AAA).
It's pretty spot on for many couples of colors.
Sources - in Java - are on GitHub.
Note: as already written in other answers, colourblind people ("people with colour deficiencies") are only a part of people concerned by the choice of colors: partially sighted people also are. And when a webdesigner chooses #AAA on #FFF, it's a problem for many people without any loss of sight or colour perception; they just've a shiny Retina® screen in non-optimal light conditions... :p
I am rendering a waveform in PHP by downsampling it with the lame encoder and then drawing the waveform from the resulting data points. I am currently getting images like this:
What I would like to do is modify my code so that the apparent dynamic range of the waveform is essentially 'compressed'. To produce a waveform that looks more like this:
The equation I am currently using to render the height of each data point is as follows:-
// draw this data point
// relative value based on height of image being generated
// data values can range between 0 and 255
$v = (int) ( $data / 255 * $height );
// don't print flat values on the canvas if not necessary
if (!($v / $height == 0.5 && !$draw_flat))
// draw the line on the image using the $v value and centering it vertically on the canvas
imageline(
$img,
// x1
(int) ($data_point / DETAIL),
// y1: height of the image minus $v as a percentage of the height for the wave amplitude
$height * $wav - $v,
// x2
(int) ($data_point / DETAIL),
// y2: same as y1, but from the bottom of the image
$height * $wav - ($height - $v),
imagecolorallocate($img, $r, $g, $b)
);
With the actual amplitude being defined by the first line of this code:-
$v = (int) ( $data / 255 * $height );
Unfortunately my math skill is poor at best. What I need to do is essentially apply a 'curve' to the value of $v so that when the number input into the equation is lower, the resulting output is higher and as the input number is increased the equation reduces the amplification until finally when the input reaches 255 the output should also be 255. Also the curve should be such so that with an input of 0 the output is also 0.
I apologise if this is not clear but I am finding this question very hard to articulate with my limited math experience.
Perhaps a visual representation would help describe my intent:-
When the value of $v is either 0 or 255 the output of the equation should be exactly the input (0 or 255). However, when the input is a value inbetween, it should follow the resulting output of the curve above. (the above was only a rough drawing to illustrate.)
EDIT:
Based on Alnitiks 'pow' function solution I am now generating waveforms that look like this:-
Using the replacement equation for the $v variable as follows:-
$v = pow($data / 255.0, 0.4) * $height;
I have tried upping the 0.4 value but the result is still not as intended.
EDIT 2:
As requested here is a raw datadump of my $data variable:
Raw Data
This gets passed into the equation to return $v before being used to draw the waveform (you can see what I do to variable $v in the original code I posted above. $height is simple the number of pixels high I have set the image to render.
This data is a comma seperated list of values. I hope this helps. It appears your assertion that the mean value is 128 is correct. So far I have been unable to get my head around your correction for this. I'm afraid it is slightly beyond my current understanding.
With no math skills (and probably useful to have a speedy display):
You have 256 possible values. Create an array that contains the "dynamic" value for each of these values:
$dynamic = array(
0 => 0,
1 => 2,
...
);
That done, you can easily get the dynamic value:
$v = (int) ($dynamic[(int) $data / 255] * $height);
You might lose some precision, but it's probably useful.
Natural dynamic values are generated by the math sine and cosine functions, in PHP this sinDocs (and others linked there).
You can use a loop and that function to prefill the array as well and re-use the array so you have pre-computed values:
$sine = function($v)
{
return sin($v * 0.5 * M_PI);
};
$dynamic = array();
$base = 255;
for ($i = 0; $i <= $base; $i++)
{
$dynamic[$i] = $i/$base;
}
$dynamic = array_map($sine, $dynamic);
I use a variable function here, so you can write multiple and can easily test which one matches your needs.
You need something similar to gamma correction.
For input values x in the range 0.0 -> 1.0, take y = pow(x, n) when n should be in the range 0.2 - 0.7 (ish). Just pick a number that gives the desired curve.
As your values are in the range 0 -> 255 you will need to divide by 255.0, apply the pow function, and then multiply by 255 again, e.g.
$y = 255 * pow($x / 255.0, 0.4);
The pow formula satisfies the criteria that 0 and 1 map to themselves, and smaller values are "amplified" more than larger values.
Here's a graph showing gamma curves for n = 1 / 1.6, 1 / 2, 1 / 2.4 and 1 / 2.8, vs the sin curve (in red):
The lower the value of n, the more "compression" is applied to the low end, so the light blue line is the one with n = 1 / 2.8.
Note how the sin curve is almost linear in the range 0 to 0.5, so provides almost no low end compression at all.
If as I suspect your values are actually centered around 128, then you need to modify the formula somewhat:
$v = ($x - 128.0) / 128.0;
$y = 128 + 127 * sign($v) * pow(abs($v), 0.4);
although I see that the PHP developers have not included a sign function in the PHP library.
Simple downsampling is not going to give you a correct render, as it will leave the original signal with only low frequencies, whilst all frequencies contribute to amplitudes. So you need to build the peak data (min and max for a certain range of values) from the original waveform to visualize it. You shouldn't apply any non-linear functions to your data, as the waveform representation is linear (unlike gamma-compressed images).
Is it possible to sort colors by php on lightness .
Now i calc the differenct with this function
public function colorDiff($rgb1,$rgb2)
{
// do the math on each tuple
// could use bitwise operates more efeceintly but just do strings for now.
$red1 = hexdec(substr($rgb1,0,2));
$green1 = hexdec(substr($rgb1,2,2));
$blue1 = hexdec(substr($rgb1,4,2));
$red2 = hexdec(substr($rgb2,0,2));
$green2 = hexdec(substr($rgb2,2,2));
$blue2 = hexdec(substr($rgb2,4,2));
return abs($red1 - $red2) + abs($rgreen1 - $green2) + abs($blue2 - $blue2) ;
}
But this will not sort images on lightness.
You can get a decent value for luminance (the perceived lightness) with the following formula:
$red * .3 + $green * .59 + $blue * .11
Quoting from the linked article:
The explanation for these weights is due to the fact that for equal amounts of color the eye is most sensitive to green, then red, and then blue. This means that for equal amounts of green and blue light the green will, nevertheless, seem much brighter."
1) You need math defination of lightness. So it should function from color to integer that represent lightness
2) If you suppose (for example) than it is sum of $red+$green+$blue you can use this kind of sort
usort($colors,function ($rgb1,$rgb2){
$red1 = hexdec(substr($rgb1,0,2));
$green1 = hexdec(substr($rgb1,2,2));
$blue1 = hexdec(substr($rgb1,4,2));
$red2 = hexdec(substr($rgb2,0,2));
$green2 = hexdec(substr($rgb2,2,2));
$blue2 = hexdec(substr($rgb2,4,2));
return ($red1+$green1+$blue1) - ($reg2+$green2+$blue2);
})
You could convert your RGB color to HSL colorspace and the sort on the L component:
http://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c
I want to find a range of colours from the value of one RGB value
If I was given rgb(0,100,200) for example it would give me everything between rgb(0,0,255) and rgb(0,255,255). However not rgb(255,0,255).
Similarly rgb(150,50,0). Return: rgb(255,0,0) and rgb(255,255,0). Not rgb(255,0,255).
Making sense?
Im using PHP
The algorithm you explain is basically: "A color consists of two RGB components. Let the strongest RGB component be S and the other component O. Create 255 variations where you let S be (255) and O ranges from 0 to 255."
E.g. all of the below example yield the same result:
a) rgb(0,100,200)
b) rgb(0,199,200)
c) rgb(0,254,255)
d) rgb(0,1,2)
Result: rgb(0,[0-255],255)
This means you only have a 6 variations.
Red is the largest component, Green next largest => rgb(255,[0-255],0)
Red is the largest component, Blue next largest => rgb(255,0,[0-255])
Green is the largest component, Red next largest => rgb([0-255],255,0)
Green is the largest component, Blue next largest => rgb([0-255],0,255)
Blue is the largest component, Red next largest => rgb([0-255],0,255)
Blue is the largest component, Green next largest => rgb(0,[0-255],255)
The intention of your algorithm is not clear, so I am guessing your use case is actually different from what you explain. It does not handle colors with either 1 or 3 components (most colors actually).
If your goal is to find similar colors (e.g. a color in a certain distance) there are better approaches. For example you could convert your colors to a HSV color space (Hue, Saturation, Value) and then say that the color is similar if the either of the components H, S or V are +/- 10 steps from your original.
E.g.:
rgb(255,100,0) => hsv(24,100,100)
Your range is then hsv([14-34],[90-110],[90-110])
Hm, not sure I understand this correctly, but I think you are saying that one of the numbers is higher than the other two, and one of the values is always zero. If this is the case, you should be able to use a simple if-else statement similar to
if (r > g && r > b) {
if (g > 0) {
color1 = rgb(255, 0, 0);
color2 = rgb(255, 255, 0);
}
else {
color1 = rgb(255, 0, 0);
color2 = rgb(255, 0, 255);
}
}
else if (r < g && g > b) {
.
.
.
}
Hope that will help you out with your problem.