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
Related
I have a list of values which should be plotted to a map with a certain color.
The plotting to the map is already done, but I need to figure out a way to map the value n to a color that represents its value.
An example and my solution so far is to normalize the values based on the min and max and then assign them to hex color 0 for the lowest and 255 for the highest. This of course limits my self to the grey scale. Here is the code:
$color = ($value / $max) * 255 // (min is zero)
But how to do this if the values should go from blue to red for instance?
Is there any common libraries or tools that can solve this? So far I haven't been able to locate any.
There might be libs to do that. However let's get a short warm up into the general principles. In general you have following options:
A predefined color index, e.g. $coloridx=array(0=>'#FFFFFF',1=>'#FFEE00',...);
Any algorithm, e.g. linear gradient, which is basically an iterations based adaption of all three RGB channels (R = red, G = green, B = blue).
A combination of both, which usually puts the result of any complex algorithm to the color index and then goes from there.
If you include algorithms in your considerations you must understand that there is no true or false. It all depends on what you would like to implement. There might be occasions where it makes sense to render variations of green into n=0..10 and then have red to black in everything beyond n>10. Caps and multipliers help to set accents. Things like that.
One way of implementing a linear gradient would be:
function lineargradient($ra,$ga,$ba,$rz,$gz,$bz,$iterationnr) {
$colorindex = array();
for($iterationc=1; $iterationc<=$iterationnr; $iterationc++) {
$iterationdiff = $iterationnr-$iterationc;
$colorindex[] = '#'.
dechex(intval((($ra*$iterationc)+($rz*$iterationdiff))/$iterationnr)).
dechex(intval((($ga*$iterationc)+($gz*$iterationdiff))/$iterationnr)).
dechex(intval((($ba*$iterationc)+($bz*$iterationdiff))/$iterationnr));
}
return $colorindex;
}
$colorindex = lineargradient(
100, 0, 0, // rgb of the start color
0, 255, 255, // rgb of the end color
256 // number of colors in your linear gradient
);
$color = $colorindex[$value];
I UPDATED the code to add dechex, which feeds back on the comments.
Colors values are representations. Numeric colors as well as hexadecimal colors. A "not grayscale" color contains at least 2 different informations: Red value, green value or blue values may be different. Performing operation on its representation gives wrong result. The 'Mapping" must then be performed on each pieces of information. You need to extract red, green and blue values, perform the mapping seperatly, then build the representation of the result color. Here is a quick helper that use a "min color" and "max color", performs mapping on red, green and blue values, according to the "n" value you need to work with, then return the result color in hexadecimal string. It works for any colors or gray scale color as well.
function linear_color($from, $to, $ratio) {
// normalize ralio
$ratio = $ratio<0?0:($ratio>1?1:$ratio);
// unsure colors are numeric values
if(!is_numeric($from))$from=hexdec($from);
if(!is_numeric($to))$to=hexdec($to);
$rf = 0xFF & ($from >> 0x10);
$gf = 0xFF & ($from >> 0x8);
$bf = 0xFF & $from;
$rt = 0xFF & ($to >> 0x10);
$gt = 0xFF & ($to >> 0x8);
$bt = 0xFF & $to;
return str_pad( dechex(($bf + (($bt-$bf)*$ratio)) + ($gf + (($gt-$gf)*$ratio) << 0x8) + ($rf + (($rt-$rf)*$ratio) << 0x10)), 6,'0',STR_PAD_LEFT);
}
Just specify 2 colors as numeric value or hexadecimal string (without hash!) like this :
$color_from = hexdec('c2c2c2');
$color_to = hexdec('1eb02b');
for($i=-0.2; $i<=1.3; $i+=0.04){
echo '<div style="background-color: #';
echo linear_color($color_from, $color_to, $i);
echo '">';
echo 'Result color when n = <strong>'.$i.'</strong>';
echo '</div>';
}
The other answer was very usefull. I decided to use JS as it reduces server load. The requirements also changed. The bar has to go from Red to white in the middle and then from white to yellow. And if the value is 0 it should be black. Here is my code for anyone who ever encounters a similar situation.
var normalize_base = 2*255;
var no_con_color = "black";
function decimalToHex(d, padding) {
var hex = Number(d).toString(16);
padding = typeof (padding) === "undefined" || padding === null ? padding = 2 : padding;
while (hex.length < padding) {
hex = "0" + hex;
}
return hex;
}
function normalize(max, min, value)
{
var normalized = (value - min) / (max - min) ;
return Math.floor(normalized * normalize_base);
}
function value2Color(value)
{
if(value <= 0 || value == undefined)
{
return no_con_color;
}
var g = 0;
var b = 0;
var r = 0;
if(value < 255)
{
r = 255;
g = value;
b = value;
} else {
r = (2*255) - value;
g = 255;
b = (2*255) - value;
}
return "#" + decimalToHex(r) + decimalToHex(g) + decimalToHex(b);
}
There is I2UI for this.
<div data-i2="color:['#404040','#00FF21']">
<span data-i2="rate:1">A</span>
<span data-i2="rate:2">B</span>
<span data-i2="rate:3">C</span>
<span data-i2="rate:4">D</span>
<span data-i2="rate:5">E</span>
</div>
There is a color range: from "Gray" - to "Green". The span element that has lowest rate value get the "Gray" color, the element with the biggest rate get "Green" color.
Thus, the span that are between edges get the color that has direct ratio to its rate.
Also, call JavaScript i2.emph() after the previous HTML have been loaded.
See demo
This is one-function-only code to convert any number from any given range (let's say [0,20]) to particular color where 0 is red, 10 is yellow, 20 is green. You can use any colors and even use combination of 4 colors, so it's red - yellow - green - blue.
See the end of the gist file, where this function is used.
Gist with the code:
Click here
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 sinĀDocs (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).
I want to display a color between red, yellow, green depending on a number between 1 to 100.
1 being green and 100 being red, 50 being yellow. I want to basically create a gradient between that.
So far, I tried:
$r = floor(255 * ($number / 100));
$g = 255 - $r;
And it somewhat does it, but gives me brownish & dark colors, & no yellow at all.
It's because you shouldn't change both channels at once but rise R in the first half and lower G in the second.
Try a function like this:
function GreenYellowRed($number) {
$number--; // working with 0-99 will be easier
if ($number < 50) {
// green to yellow
$r = floor(255 * ($number / 50));
$g = 255;
} else {
// yellow to red
$r = 255;
$g = floor(255 * ((50-$number%50) / 50));
}
$b = 0;
return "$r,$g,$b";
}
To test it:
$output = "";
for ($i = 1; $i <= 100; $i++) {
$rgb = GreenYellowRed($i);
$output .= "<div style='background-color: rgb($rgb)'>$rgb</div>";
}
echo $output;
I've found that dealing with the HSV color model is easier than the RGB model. It helps you easily choose the color you want to work with; with RGB you'd need to understand how different values of R, G and B will combine to give you the color you want/don't want.
Also, this SO question might be useful: How can I cycle through hex color codes in PHP?
I don't know of a mathematical model for a "color curve" that passes through specified RGB color values (e.g. what you describe as green/yellow/red), which would allow you to calculate any intermediate color in that curve. In any case, a model of a function (which is what that would be) is only as good as the data points it needs to fit, so you 'd have to be much more specific than green/yellow/red to get decent results even if someone points out the math.
Remember that we are not interested in mathematical interpolation here, but rather in "color-space interpolation" (a term which I just made up) -- in other words, what would look like a "natural" interpolation to a human.
An easier solution for those of us who do not have the necessary color theory knowledge, and which I 'd suggest, is to pre-select a number of colors with a color picker tool, divide the 0-100 range into as many bands as the colors you picked, and use simple integer division to project from 0-100 to a color band.
Food for thought: Indeed, how does SO decide the color of the upvote count for comments?
Update: I just asked the above over on meta. Let's see...
After a bit of looking, none of the solutions looked pleasing. As stated above, HSV is probably the way to go, since modern browsers can render color with it just fine.
To get a good idea of the colors you are working with, check out this color wheel:
http://www.colorspire.com/rgb-color-wheel/
I want to start with blue, so I use 255 for normalization.
function temp_color($temp){
$start = 40;
$end = 85;
$normal = round(255-((($temp - $start)/($end-$start))*255));
$color = "hsl($normal, 100%, 30%);";
$span = "<span style=\"color: $color\">$temp</span>";
return $span;
}
I want users on my website to be able to pick a hex colour, and I just want to display white text for dark colours and black text for light colours. Can you work out the brightness from a hex code (preferably PHP)?
$hex = "78ff2f"; //Bg color in hex, without any prefixing #!
//break up the color in its RGB components
$r = hexdec(substr($hex,0,2));
$g = hexdec(substr($hex,2,2));
$b = hexdec(substr($hex,4,2));
//do simple weighted avarage
//
//(This might be overly simplistic as different colors are perceived
// differently. That is a green of 128 might be brighter than a red of 128.
// But as long as it's just about picking a white or black text color...)
if($r + $g + $b > 382){
//bright color, use dark font
}else{
//dark color, use bright font
}
I made one similar - but based on weightings of each colour (based on the C# version of this thread)
function readableColour($bg){
$r = hexdec(substr($bg,0,2));
$g = hexdec(substr($bg,2,2));
$b = hexdec(substr($bg,4,2));
$contrast = sqrt(
$r * $r * .241 +
$g * $g * .691 +
$b * $b * .068
);
if($contrast > 130){
return '000000';
}else{
return 'FFFFFF';
}
}
echo readableColour('000000'); // Output - FFFFFF
EDIT:
Small optimisation:
Sqrt is known as an expensive math operation, which is probably neglectable in most scenarios, but anyway, it could be avoided by doing something like this.
function readableColour($bg){
$r = hexdec(substr($bg,0,2));
$g = hexdec(substr($bg,2,2));
$b = hexdec(substr($bg,4,2));
$squared_contrast = (
$r * $r * .299 +
$g * $g * .587 +
$b * $b * .114
);
if($squared_contrast > pow(130, 2)){
return '000000';
}else{
return 'FFFFFF';
}
}
echo readableColour('000000'); // Output - FFFFFF
It simply doesn't apply the sqrt, instead it powers the desired cut off contrast by two, which is a much cheaper calculation
I know this is a very old topic, but for users who came from "Google Search", this link may be what they are looking for. I've searched for something like this and I think it's a good idea to post it here:
https://github.com/mexitek/phpColors
use Mexitek\PHPColors\Color;
// Initialize my color
$myBlue = new Color("#336699");
echo $myBlue->isLight(); // false
echo $myBlue->isDark(); // true
That's it.
You need to convert the RGB values to HLS/HSL (Hue Lightness and Saturation) you can then use the Lightness to determine whether you need light text or dark text.
This page has some details on how to the conversion in PHP as well as selecting complementary colour from this.
I've only just spotted that the site is an astrology site - so apologies if anyone's offended.
If you have imagemagick extension activated, you can simply create an ImagickPixel object, call setColor with your hex value, and then call getHSL() (and get the last item of the obtained array I suppose)...
I tried a different approach to this, I used HSL (hue, saturation & lightness) lightness percentage to check if the color is dark or light. (like #chrisf said in his answer)
function:
function colorislight($hex) {
$hex = str_replace('#', '', $hex);
$r = (hexdec(substr($hex, 0, 2)) / 255);
$g = (hexdec(substr($hex, 2, 2)) / 255);
$b = (hexdec(substr($hex, 4, 2)) / 255);
$lightness = round((((max($r, $g, $b) + min($r, $g, $b)) / 2) * 100));
return ($lightness >= 50 ? true : false);
}
On the return line it checks if the lightness percentage is higher than 50% and returns true otherwise false is returned. You can easily change it to return true if the color has 30% lightness and so on. The $lightness variable can return from 0 to 100 0 being the darkest and 100 being the lightest.
how to use the function:
$color = '#111111';
if ( colorislight($color) ) {
echo 'this color is light';
}
else {
echo 'this color is dark';
}
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.