Edit:
With the answer given I made this function
function grabclosestcolor($r, $g, $b){
$colors = array(array(124,12,12),array(7,7,11),array(110,224,219),array(123,123,123),array(124,177,74),array(130,86,53),array(77,77,77),array(164,124,68),array(204,196,132),array(164,148,147),array(163,123,67),array(26,122,26), array(195,195,50),array(193,193,193),array(255,248,73),array(243,243,243));
$differencearray = array();
foreach ($colors as $value) {
$difference = sqrt(pow($r-$value[0],2)+pow($g-$value[1],2)+pow($b-$value[2],2));
array_push($differencearray, $difference);
$smallest = min($differencearray);
$key = array_search($smallest, $differencearray);
return $colors[$key];
}
}
My goal is this. I grab a picture and loop through each pixel and grab its x,y, and rgb.
Instead of just grabbing the rgb, I have a predefined array and I'm looking for the closest match from the color I grabbed to the predefined array.
The goal here is to only use colors from the predefined array.
Here is my array of colors.
$colors = array(array(124,12,12),array(7,7,11),array(110,224,219),array(123,123,123),array(124,177,74),array(130,86,53),array(77,77,77),array(164,124,68),array(204,196,132),array(164,148,147),array(163,123,67),array(26,122,26), array(195,195,50),array(193,193,193),array(255,248,73),array(243,243,243));
and here is my existing code that loops through it all.
$int = imagesx($im) - 1;
$int2 = imagesy($im) - 1;
$start2 = 0;
do{
$start = 0;
do{
$rgb = imagecolorat($im, $start, $start2);
$r = ($rgb >> 16) & 0xFF;
$g = ($rgb >> 8) & 0xFF;
$b = $rgb & 0xFF;
$value = rgb2hex($r,$g,$b).":$start:$start2";
array_push($colorsofimage, $value);
} while($int > $start++);
} while($int2 > $start2++);
rgb2hex is a User Defined Function, but what I want to accomplish is to change that function with the function to grab the closest color.
$colorsofimage contains an array of each pixels info with hex:x:y
what i want it to be is rgb2hex(NEWFUNCTION($r,$g,$b));
So that the new hex is the 1 out of the predefined array.
I hope you understood, because I have no clue how to do it because I don't know the algorithm of a color.
You have to calculate the distance to each color, and pick the smallest.
There are a few ways to do this. A simple method would be to calculate the distance would be:
sqrt((r-r1)^2+(g-g1)^2+(b-b1)^2)
A better method might be to incorporate the weighted values to calculate a distance, for instance the values used when converting RGB->YUV:
Y = 0.299 * R + 0.587 * G + 0.114 * B
in that case you would use
sqrt(((r - r1) * .299)^2 + ((g - g1) * .587)^2 + ((b - b1) * .114)^2)
Of course, since you don't need the exact distances, just a comparison, you can and probably should just skip the square root, making the last calculation:
((r - r1) * .299)^2 + ((g - g1) * .587)^2 + ((b - b1) * .114)^2
The RGB colour-space is simply a cube. In 24-bit colour each side has a length of 256, allowing values from 0 to 255. In order to find the closest colour in within this cube, you need a distance function. The simplest and most intuitive is the Euclidean distance: if you have colour (r1, g1, b1) and another colour (r2, g2, b2) the distance would be sqrt((r2-r1)^2 + (g2-g1)^2 + (b2-b1)^2).
The challenge for you is then to find the best match across all the values in your predefined array. I suggest that you start simply by iterating over all your values and check the distance for each in turn. Note that for this purpose you do not need to perform the sqrt, simply comparing on the sum of the squares would be sufficient, and would have the benefit of being all based in integer maths. My PHP isn't great, but roughly you would do:
function dist($col1,$col2) {
$delta_r = $col1[0] - $col2[0];
$delta_g = $col1[1] - $col2[1];
$delta_b = $col1[2] - $col2[2];
return $delta_r * $delta_r + $delta_g * $delta_g + $delta_b * $delta_b;
}
$closest=$colors[0];
$mindist=dist($rgb,$colors[0]);
$ncolors=sizeof($colors);
for($i = 1; $i < $ncolors; ++$i)
{
$currdist = dist($rgb,$colors[$i]);
if($currdist<$mindist) {
$mindist=$currdist;
$closest=$colors[$i];
}
}
There are more complicated distance functions (for instance, taking better account of psychovisual interpretation of colour differences (look into Delta E) but I suspect this is more than you need.
Since this question is displayed in the top ten of goolge search results, here is a more complex function I wrote some years ago, which produced better results than the existing PHP functions.
/*
* Die Funktion gibt den Array-Schlüssel der Farbe ($palette),
* die am ehesten der Farbe $givenColor entspricht.
*
* Returns the index of the palette-color which is most similar
* to $givenColor.
*
* $givenColor und die Einträge in $palette können entweder
* Strings im Format (#)rrggbb
* (z. B. "ff0000", "4da4f3" oder auch "#b5d7f3")
* oder Arrays mit je einem Wert für Rot, Grün und Blau
* (z. B. $givenColor = array( 0xff, 0x00, 0x00 ) )
* sein.
*
* $givenColor and the colors in $palette should be either
* formatted as (#)rrggbb
* (e. g. "ff0000", "4da4f3" or "#b5d7f3")
* or arrays with values for red, green and blue
* (e. g. $givenColor = array( 0xff, 0x00, 0x00 ) )
*
* Referenzen/References:
* function rgb2lab
* - http://www.f4.fhtw-berlin.de/~barthel/ImageJ/ColorInspector//HTMLHilfe/farbraumJava.htm
* - http://www.brucelindbloom.com/index.html?Eqn_RGB_to_XYZ.html
* - http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_Lab.html
*
* function deltaE
* - http://www.brucelindbloom.com/index.html?Eqn_DeltaE_CMC.html
*/
function getNearestColor( $givenColor,
$palette = array('blue' => '0000ff','red' => 'ff0000','green' => '00ff00','yellow' => 'ffff00','black' => '000000','white' => 'ffffff','orange' => 'ff8800','purple' => 'ff00ff', 'teal' => '00ffff')
)
{
if(!function_exists('rgb2lab'))
{
function rgb2lab($rgb) {
$eps = 216/24389; $k = 24389/27;
// reference white D50
$xr = 0.964221; $yr = 1.0; $zr = 0.825211;
// reference white D65
#$xr = 0.95047; $yr = 1.0; $zr = 1.08883;
// RGB to XYZ
$rgb[0] = $rgb[0]/255; //R 0..1
$rgb[1] = $rgb[1]/255; //G 0..1
$rgb[2] = $rgb[2]/255; //B 0..1
// assuming sRGB (D65)
$rgb[0] = ($rgb[0] <= 0.04045)?($rgb[0]/12.92):pow(($rgb[0]+0.055)/1.055,2.4);
$rgb[1] = ($rgb[1] <= 0.04045)?($rgb[1]/12.92):pow(($rgb[1]+0.055)/1.055,2.4);
$rgb[2] = ($rgb[2] <= 0.04045)?($rgb[2]/12.92):pow(($rgb[2]+0.055)/1.055,2.4);
// sRGB D50
$x = 0.4360747*$rgb[0] + 0.3850649*$rgb[1] + 0.1430804*$rgb[2];
$y = 0.2225045*$rgb[0] + 0.7168786*$rgb[1] + 0.0606169*$rgb[2];
$z = 0.0139322*$rgb[0] + 0.0971045*$rgb[1] + 0.7141733*$rgb[2];
// sRGB D65
/*$x = 0.412453*$rgb[0] + 0.357580*$rgb[1] + 0.180423*$rgb[2];
$y = 0.212671*$rgb[0] + 0.715160*$rgb[1] + 0.072169*$rgb[2];
$z = 0.019334*$rgb[0] + 0.119193*$rgb[1] + 0.950227*$rgb[2];*/
// XYZ to Lab
$xr = $x/$xr; $yr = $y/$yr; $zr = $z/$zr;
$fx = ($xr > $eps)?pow($xr, 1/3):($fx = ($k * $xr + 16) / 116); $fy = ($yr > $eps)?pow($yr, 1/3):($fy = ($k * $yr + 16) / 116); $fz = ($zr > $eps)?pow($zr, 1/3):($fz = ($k * $zr + 16) / 116);
$lab = array();
$lab[] = round(( 116 * $fy ) - 16); $lab[] = round(500*($fx-$fy)); $lab[] = round(200*($fy-$fz));
return $lab;
} // function rgb2lab
}
if(!function_exists('deltaE'))
{
function deltaE($lab1, $lab2)
{
// CMC 1:1
$l = 1; $c = 1;
$c1 = sqrt($lab1[1]*$lab1[1]+$lab1[2]*$lab1[2]); $c2 = sqrt($lab2[1]*$lab2[1]+$lab2[2]*$lab2[2]);
$h1 = (((180000000/M_PI) * atan2($lab1[1],$lab1[2]) + 360000000) % 360000000)/1000000;
$t = (164 <= $h1 AND $h1 <= 345)?(0.56 + abs(0.2 * cos($h1+168))):(0.36 + abs(0.4 * cos($h1+35)));
$f = sqrt(pow($c1,4)/(pow($c1,4) + 1900));
$sl = ($lab1[0] < 16)?(0.511):((0.040975*$lab1[0])/(1 + 0.01765*$lab1[0]));
$sc = (0.0638 * $c1)/(1 + 0.0131 * $c1) + 0.638;
$sh = $sc * ($f * $t + 1 -$f);
return sqrt( pow(($lab1[0]-$lab2[0])/($l * $sl),2) + pow(($c1-$c2)/($c * $sc),2) + pow(sqrt(($lab1[1]-$lab2[1])*($lab1[1]-$lab2[1]) + ($lab1[2]-$lab2[2])*($lab1[2]-$lab2[2]) + ($c1-$c2)*($c1-$c2))/$sh,2) );
} // function deltaE
}
if(!function_exists('colorDistance'))
{
function colorDistance($lab1,$lab2)
{
return sqrt(($lab1[0]-$lab2[0])*($lab1[0]-$lab2[0])+($lab1[1]-$lab2[1])*($lab1[1]-$lab2[1])+($lab1[2]-$lab2[2])*($lab1[2]-$lab2[2]));
}
}
if(!function_exists('str2rgb'))
{
function str2rgb($str)
{
$str = preg_replace('~[^0-9a-f]~','',$str);
$rgb = str_split($str,2);
for($i=0;$i<3;$i++)
$rgb[$i] = intval($rgb[$i],16);
return $rgb;
} // function str2rgb
}
// split into RGB, if not already done
$givenColorRGB = is_array($givenColor)?$givenColor:str2rgb($givenColor);
$min = 0xffff;
$return = NULL;
foreach($palette as $key => $color)
{
// split into RGB
$color = is_array($color)?$color:str2rgb($color);
// deltaE
#if($min >= ($deltaE = deltaE(rgb2lab($color),rgb2lab($givenColorRGB))))
// euclidean distance
if($min >= ($deltaE = colorDistance(rgb2lab($color),rgb2lab($givenColorRGB))))
{
$min = $deltaE;
$return = $key;
}
}
return $return;
}
Calculate the distance from the input color to all possible candidates of your palette, and then pick the one with the smallest distance as the one to replace it with.
Distance can be defined in any way you like; Euclidean distance seems workable for RGB cubes, cylinders or HSL/HSV cones.
There is no point in taking the square root. Finding the shortest distance is the same as finding the shortest squared distance. sqrt is an expensive operation, so just skip it.
Whether it really matters, of course, depends of how often your program will make this calculation, but it's still pointless to have it.
How would I round this calculation up automatically??
$calc = (14.3/10)/2.33 Result = 6.137339055794
The value I would like to have is 6.13
Have searched the site but can't find any answers on this
Use PHP round().
$calc = round($calc, 2)
The result of (14.3/10)/2.33 is not 6.137339055794. It's 0.6137339055794.
I'm assuming: $calc = (14.3/10) / 2.33 * 10;.
The function round rounds half up, half down, half even or half odd numbers (floating point numbers), but the result of round($calc, 2, PHP_ROUND_HALF_UP) is not 6.13. It's 6.14.
Assuming you'd like to round down or truncate that number, a solution could be:
$truncated = (int)($calc * 100) / 100;
or
$precision = 2;
$tensPrecision = pow(10, $precision);
$truncated = (int)($calc * $tensPrecision) / $tensPrecision;
.
To round up, you could do:
$truncated = ceil($calc * 100) / 100;
or
$precision = 2;
$tensPrecision = pow(10, $precision);
$truncated = ceil($calc * $tensPrecision) / $tensPrecision;
I have 5 different variables that I need to calculate. Right now these work, but what I want to do is have the price recalculate every 50 increase. Now, I can code into the form to only allow a maximum purchase of 50 at a time, but I want to allow for as many of each as the person has money for (that is what the numbers are). So I think what I need is a parabola style formula that auto increases every 50, but I don't know exactly what I'm looking for or how to implement it.
$resprice = 20000+($cityFetch['residential']*502)+($cityNum*1000);
$comprice = 18000+($cityFetch['commercial']*506)+($cityNum*1000);
$indprice = 23000+($cityFetch['industrial']*508)+($cityNum*1000);
$landprice = 600+($cityFetch['land']*.008);
$milprice = 25000+($cityFetch['military']*512)+($cityNum*1000);
EDIT: I was indicated that a loop will work for this.
EDIT3: Finally landed on this, Havvy helped me out on IRC
$cityCheck = mysql_query("SELECT * FROM cities WHERE nation='$nation'");
$cityNum = mysql_num_rows($cityCheck);
function determinePrice($baseCost, $scalar, $current, $bought) {
$min = $baseCost + $scalar * ($current + 1) + ($cityNum * 1000);
$max = $baseCost + $scalar * ($current + $bought) + ($cityNum * 1000);
$average = ($min + $max) / 2;
return $average * $bought;
}
$resprice = determinePrice(20000, 502, $cityFetch['residential'], $cityFetch['residential']);
$comprice = determinePrice(18000, 506, $cityFetch['commercial'], $cityFetch['commercial']);
$indprice = determinePrice(23000, 508, $cityFetch['industrial'], $cityFetch['industrial']);
$milprice = determinePrice(25000, 502, $cityFetch['residential'], $cityFetch['military']);
$landprice = 600+($cityFetch['land']*.008);
I use a step size of 1, instead of 50. It's more logical (one less magic number) to not give discounts based on how much you buy at a time, and it would be confusing to new players having a stepwise-linear function.
The reasoning behind this function can be found at http://betterexplained.com/articles/techniques-for-adding-the-numbers-1-to-100/.
function determinePrice($baseCost, $scalar, $current, $bought) {
$min = $scalar * ($current + 1) + ($cityNum * 1000);
$max = $scalar * ($current + $bought) + ($cityNum * 1000);
$average = ($min + $max) / 2;
return $average * $bought;
}
Using it would look something like this:
$resprice = determinePrice(20000, 502, $cityFetch['residential'], $resBought);
It won't work for land though, because land doesn't include the city price in it. For land, you can copy the function and remove the $cityNum * 1000 portions. You could also add in a $isLand parameter to the signature, and use a ternary operator to add 0 instead.
If you don't like that $cityNum has to already be declared with the proper value before declaring this function, you can always add it to the function signature, making it a pure function.
$count=1
if($count==50)
{
You increase the prices then
$count=1;
}
$resprice = 20000+($cityFetch['residential']*502)+($cityNum*1000);
$comprice = 18000+($cityFetch['commercial']*506)+($cityNum*1000);
$indprice = 23000+($cityFetch['industrial']*508)+($cityNum*1000);
$landprice = 600+($cityFetch['land']*.008);
$milprice = 25000+($cityFetch['military']*512)+($cityNum*1000);
$count++;
I've been attempting to implement Vincenty's formulae with the following:
/* Implemented using Vincenty's formulae from http://en.wikipedia.org/wiki/Vincenty%27s_formulae,
* answers "Direct Problem".
* $latlng is a ('lat'=>x1, 'lng'=>y1) array
* $distance is in miles
* $angle is in degrees
*/
function addDistance($latlng, $distance, $bearing) {
//variables
$bearing = deg2rad($bearing);
$iterations = 20; //avoid too-early termination while avoiding the non-convergant case
//knowns
$f = EARTH_SPHEROID_FLATTENING; //1/298.257223563
$a = EARTH_RADIUS_EQUATOR_MILES; //3963.185 mi
$phi1 = deg2rad($latlng['lat']);
$l1 = deg2rad($latlng['lng']);
$b = (1 - $f) * $a;
//first block
$tanU1 = (1-$f)*tan($phi1);
$U1 = atan($tanU1);
$sigma1 = atan($tanU1 / cos($bearing));
$sinalpha = cos($U1)*sin($bearing);
$cos2alpha = (1 - $sinalpha) * (1 + $sinalpha);
$usquared = $cos2alpha * (($a*$a - $b*$b) / 2);
$A = 1 + ($usquared)/16384 * (4096+$usquared*(-768+$usquared*(320 - 175*$usquared)));
$B = ($usquared / 1024)*(256*$usquared*(-128 + $usquared * (74 - 47*$usquared)));
//the loop - determining our value
$sigma = $distance / ($b * $A);
for($i = 0; $i < $iterations; ++$i) {
$twosigmam = 2*$sigma1 + $sigma;
$delta_sigma = $B * sin($sigma) * (cos($twosigmam)+(1/4)*$B*(cos(-1 + 2*cos(cos($twosigmam))) - (1/6)*$B*cos($twosigmam)*(-3+4*sin(sin($sigma)))*(-3+4*cos(cos($twosigmam)))));
$sigma = $distance / ($b * $A) + $delta_sigma;
}
//second block
$phi2 = atan((sin($U1)*cos($sigma)+cos($U1)*sin($sigma)*cos($bearing)) / ((1-$f) * sqrt(sin($sinalpha) + pow(sin($U1)*sin($sigma) - cos($U1)*cos($sigma)*cos($bearing), 2))));
$lambda = atan((sin($sigma) * sin($bearing)) / (cos($U1)*cos($sigma) - sin($U1)*sin($sigma)*cos($bearing)));
$C = ($f / 16)* $cos2alpha * (4+$f*(4-3*$cos2alpha));
$L = $lambda - (1 - $C) * $f * $sinalpha * ($sigma + $C*sin($sigma)*(cos($twosigmam)+$C*cos($sigma)*(-1+2*cos(cos($twosigmam)))));
$alpha2 = atan($sinalpha / (-sin($U1)*sin($sigma) + cos($U1)*cos($sigma)*cos($bearing)));
//and return our results
return array('lat' => rad2deg($phi2), 'lng' => rad2deg($lambda));
}
var_dump(addDistance(array('lat' => 93.129, 'lng' => -43.221), 20, 135);
The issue is that the results are not reasonable - I'm getting variances of up to 20 latitude and longitude keeping the distance at 20. Is it not in units of elliptical distance on the sphere? Am I misunderstanding something, or is my implementation flawed?
There are a number of errors in transcription from the wikipedia page Direct Problem section:
Your u2 expression has 2 in the denominator where it should have b2;
Your A and B expressions are inconsistent about whether the initial fraction factor needs to be parenthesised to correctly express a / b * c as (a/b) * c - what happens without parentheses is a php syntax issue which I don't know the answer to, but you should favour clarity;
You should be iterating "until there is no significant change in sigma", which may or may not happen in your fixed number of iterations;
There are errors in your DELTA_sigma formula:
on the wikipedia page, the first term inside the square bracket [ is cos sigma (-1 etc, whereas you have cos (-1 etc, which is very different;
in the same formula and also later, note that cos2 x means (cos x)(cos x), not cos cos x!
Your phi_2 formula has a sin($sinalpha) where it should have a sin($sinalpha)*sin($sinalpha);
I think that's all.
Have you tried this:
https://github.com/treffynnon/Geographic-Calculations-in-PHP
So I've read the two related questions for calculating a trend line for a graph, but I'm still lost.
I have an array of xy coordinates, and I want to come up with another array of xy coordinates (can be fewer coordinates) that represent a logarithmic trend line using PHP.
I'm passing these arrays to javascript to plot graphs on the client side.
Logarithmic Least Squares
Since we can convert a logarithmic function into a line by taking the log of the x values, we can perform a linear least squares curve fitting. In fact, the work has been done for us and a solution is presented at Math World.
In brief, we're given $X and $Y values that are from a distribution like y = a + b * log(x). The least squares method will give some values aFit and bFit that minimize the distance from the parametric curve to the data points given.
Here is an example implementation in PHP:
First I'll generate some random data with known underlying distribution given by $a and $b
// True parameter valaues
$a = 10;
$b = 5;
// Range of x values to generate
$x_min = 1;
$x_max = 10;
$nPoints = 50;
// Generate some random points on y = a * log(x) + b
$X = array();
$Y = array();
for($p = 0; $p < $nPoints; $p++){
$x = $p / $nPoints * ($x_max - $x_min) + $x_min;
$y = $a + $b * log($x);
$X[] = $x + rand(0, 200) / ($nPoints * $x_max);
$Y[] = $y + rand(0, 200) / ($nPoints * $x_max);
}
Now, here's how to use the equations given to estimate $a and $b.
// Now convert to log-scale for X
$logX = array_map('log', $X);
// Now estimate $a and $b using equations from Math World
$n = count($X);
$square = create_function('$x', 'return pow($x,2);');
$x_squared = array_sum(array_map($square, $logX));
$xy = array_sum(array_map(create_function('$x,$y', 'return $x*$y;'), $logX, $Y));
$bFit = ($n * $xy - array_sum($Y) * array_sum($logX)) /
($n * $x_squared - pow(array_sum($logX), 2));
$aFit = (array_sum($Y) - $bFit * array_sum($logX)) / $n;
You may then generate points for your Javascript as densely as you like:
$Yfit = array();
foreach($X as $x) {
$Yfit[] = $aFit + $bFit * log($x);
}
In this case, the code estimates bFit = 5.17 and aFit = 9.7, which is quite close for only 50 data points.
For the example data given in the comment below, a logarithmic function does not fit well.
The least squares solution is y = -514.734835478 + 2180.51562281 * log(x) which is essentially a line in this domain.
I would recommend using library: http://www.drque.net/Projects/PolynomialRegression/
Available by Composer: https://packagist.org/packages/dr-que/polynomial-regression.
In case anyone is having problems with the create_function, here is how I edited it. (Though I wasn't using logs, so I did take those out.)
I also reduced the number of calculations and added an R2. It seems to work so far.
function lsq(){
$X = array(1,2,3,4,5);
$Y = array(.3,.2,.7,.9,.8);
// Now estimate $a and $b using equations from Math World
$n = count($X);
$mult_elem = function($x,$y){ //anon function mult array elements
$output=$x*$y; //will be called on each element
return $output;
};
$sumX2 = array_sum(array_map($mult_elem, $X, $X));
$sumXY = array_sum(array_map($mult_elem, $X, $Y));
$sumY = array_sum($Y);
$sumX = array_sum($X);
$bFit = ($n * $sumXY - $sumY * $sumX) /
($n * $sumX2 - pow($sumX, 2));
$aFit = ($sumY - $bFit * $sumX) / $n;
echo ' intercept ',$aFit,' ';
echo ' slope ',$bFit,' ' ;
//r2
$sumY2 = array_sum(array_map($mult_elem, $Y, $Y));
$top=($n*$sumXY-$sumY*$sumX);
$bottom=($n*$sumX2-$sumX*$sumX)*($n*$sumY2-$sumY*$sumY);
$r2=pow($top/sqrt($bottom),2);
echo ' r2 ',$r2;
}