I'm using Curl via Proxies to download images with a scraper I have developed.
Unfortunately, it gets the odd image which looks like these and the last one is completely blank :/
When I test the images via imagemagick (using identify) it tells me they are valid images.
When I test the images via exif_imagetype() and imagecreatefromjpeg() again, both these functions tell me the images are valid.
Does anyone have a way to determine if the image has majority of greyness or is completely blank/white and these are indeed corrupted images?
I have done a lot of checking with other questions on here, but I haven't had much luck with other solutions. So please take care in suggesting this is a duplicate.
Thanks
After knowing about imgcolorat, I did a search and stumbled on some code. I came up with this:
<?php
$file = dirname(__FILE__) . "/images/1.jpg";
$img = imagecreatefromjpeg($file);
$imagew = imagesx($img);
$imageh = imagesy($img);
$xy = array();
$last_height = $imageh - 5;
$foo = array();
$x = 0;
$y = 0;
for ($x = 0; $x <= $imagew; $x++)
{
for ($y = $last_height;$y <= $imageh; $y++ )
{
$rgb = #imagecolorat($img, $x, $y);
$r = ($rgb >> 16) & 0xFF;
$g = ($rgb >> 8) & 0xFF;
$b = $rgb & 0xFF;
if ($r != 0)
{
$foo[] = $r;
}
}
}
$bar = array_count_values($foo);
$gray = (isset($bar['127']) ? $bar['127'] : 0) + (isset($bar['128']) ? $bar['128'] : 0) + (isset($bar['129']) ? $bar['129'] : 0);
$total = count($foo);
$other = $total - $gray;
if ($gray > $other)
{
echo "image corrupted \n";
}
else
{
echo "image not corrupted \n";
}
?>
Anyone see some potential pitfalls with this? I thought about getting the last few rows of the image and then comparing the total of r 127,128,129 (which are gray) against the total of other colours. If gray is greater than the other colours then the image is surely corrupted.
Opinions welcome! :)
found this page when looking for a way to check visually corrupted images like this. Here is a way to solve the problem using bash (anyway, the convert command line can be easily adapted for php or python) :
convert INPUTFILEPATH -gravity SouthWest -crop 20%x1% -format %c -depth 8 histogram:info:- | sed '/^$/d' | sort -V | head -n 1 | grep fractal | wc -l
It crops a little square in the southwest corner of the picture, then gets the histogram of this picture. If the main color of the histogram has the name "fractal" instead of an rgb color, it means this zone is corrupted and so the output will be 1 and 0 otherwise.
Hope this helps!
If the image it is returning is a valid file, then I would recommend running the scrape twice (ie. download it twice and check to see if they are the same).
Another option would be to check the last few pixels of the image (ie. bottom-right corner) to see if they match that color of grey exactly. If they do, then redownload. (obviously this approach fails if you download an image that is actually supposed to be grey in that corner, in that exact colour...but if you check several of the last pixels it should reduce the chance of that to an acceptable level).
I use this one. If the most of pixels in right bottom corner (5x5) are grey, then image is broken.
define('MIN_WIDTH',500);
define('MIN_HEIGHT',200);
function isGoodImage($fn){
list($w,$h)=getimagesize($fn);
if($w<MIN_WIDTH || $h<MIN_HEIGHT) return 0;
$im=imagecreatefromstring(file_get_contents($fn));
$grey=0;
for($i=0;$i<5;++$i){
for($j=0;$j<5;++$j){
$x=$w-5+$i;
$y=$h-5+$j;
list($r,$g,$b)=array_values(imagecolorsforindex($im,imagecolorat($im,$x,$y)));
if($r==$g && $g==$b && $b==128)
++$grey;
}
}
return $grey<12;
}
ImageMagick's identify command will identify far more corrupt images if you call it with the -verbose option. And there's a -regard-warnings option as well, which will make it treat warnings as errors. Try these against a bad image, and see if the result is a non-zero error code.
Related
I'm looking for the fastest way to compute a directional vector based on an arbitrary color in an image (a Rpi camera, but a JPEG file for testing is OK for now), a.k.a. tracking a colored ball project. Please note that the resulting vector (or centroid coordinates, whatever) needs to be passed to PHP for the program execution, so the solution I'm looking for needs to end with PHP, but can be anything before, given it can be implemented on both Windows and Linux.
Consider an input JPEG image:
Here are 2 example directional vectors I'm after, obtained based on a 1) teal color input and 2) purple color input. Obviously, only 1 vector will ever be asked at a time, I put 2 to demonstrate multiple examples into 1 image, but it's always gonna be only 1 vector at a time. Note that the resulting vectors ("v") are standardized to -1.0 (bottom/left) to +1.0 (bottom/right) so that zero is the middle of the picture.
Here are the various solutions I've implemented/tested so far and how much time the whole process takes, based on a 960x640 JPEG picture, but the implemented solution will be tied to a Rpi camera input, I do not have the camera yet so I use a JPEG image until the camera arrives from China.
1) 2700ms : Use GD2 that is bundled with PHP, for loop over each pixels, push pixels matching ~10% RGB values in XY arrays, average the XY arrays, compute/normalize directional vector from XY arrays.
$arr_matching_pixels = array('arr_x' => array(), 'arr_y' => array());
for($y = 0; $y < $h - 1; $y++){
for($x = 0; $x < $w - 1; $x++){
$arr_pixel = imagecolorsforindex($img, imagecolorat($img, $x, $y));
if(abs($arr_pixel['red'] - $arr_seek_color['red']) < 30){
if(abs($arr_pixel['green'] - $arr_seek_color['green']) < 30){
if(abs($arr_pixel['blue'] - $arr_seek_color['blue']) < 30){
array_push($arr_matching_pixels['arr_x'], $x);
array_push($arr_matching_pixels['arr_y'], $y);
}
}
}
}
}
// Compute centroid of color... etc...
2) 700ms : Same as #1 except begin by resizing the canvas by 50% (acceptable loss) using imagecreatefromjpeg('_test_cam_img.jpg');
3) 560ms : Same as #2 except use ImageMagick with a pixel iterator loop to read the pixels
$imagick = new Imagick(realpath($o_img));
$arr_matching_pixels = array('arr_x' => array(), 'arr_y' => array());
$arr_pixel = array();
$iterator = $imagick->getPixelIterator();
foreach($iterator as $y => $pixels){
foreach($pixels as $x => $pixel){
$arr_pixel = $pixel->getColor();
if(abs($arr_pixel['r'] - $arr_seek_color['red']) < 30){
if(abs($arr_pixel['g'] - $arr_seek_color['green']) < 30){
if(abs($arr_pixel['b'] - $arr_seek_color['blue']) < 30){
array_push($arr_matching_pixels['arr_x'], $x);
array_push($arr_matching_pixels['arr_y'], $y);
}
}
}
}
}
// Compute centroid of color... etc...
4) 340ms : Call the system's ImageMagick binary via the exec() function, pass it the image location, the chroma/color key, a resize by 50% param, a 10% fuzz param, and the sparse-color: modifier to extract a textual (CSV-like) list representation of desired pixels, then use PHP to loop over each line, explode commas and push all pixels in XY arrays, average the XY arrays, compute/normalize directional vector from XY arrays. I noted that calling exec() proves to be quite slower than executing the same command directly from the Windows command line.
$imagick = new Imagick(realpath($o_img));
$out = exec('"E:\Users\Ben\Roaming Apps\imagemagick-6.9.3\convert" E:\wamp64\www\test_cam_img.jpg -resize 50% -fuzz 10% +transparent rgb(' . $arr_seek_color['red'] . ',' . $arr_seek_color['green'] . ',' . $arr_seek_color['blue'] . ') sparse-color:');
$arr_lines = explode(' ', $out);
$arr_matching_pixels = array('arr_x' => array(), 'arr_y' => array());
foreach($arr_lines as $str_line){
$arr_xy_coords = explode(',', $str_line);
array_push($arr_matching_pixels['arr_x'], $arr_xy_coords[0]);
array_push($arr_matching_pixels['arr_y'], $arr_xy_coords[1]);
}
// Compute centroid of color... etc...
5) 32ms : PHP creates an "in" text file containing the image path and the chroma/color key and begins looping until it reads an "out" text file. A python+OpenCV script already/always runs a (stoppable) infinite loop constantly looking for an "in" text file and when it exists, it read it, explodes the values, makes a 1-bit mask using the HSV values ~10% (cv2.inRange) from the "in" file, then makes an array using cv2.findNonZero(mask) and computes the array mean value and writes it to an "out" text file that PHP immediately reads, containing the directional vector value. This is by far, the fastest way I have found, but it is awkward, because it implies that the python script will have to be programmed in a CRONJOB and monitored/relaunched in a single instance if it crashes.
file_put_contents('_avg_color_coords_in.txt', $o_img . "\n" . $arr_seek_color['h'] . ',' . $arr_seek_color['s'] . ',' . $arr_seek_color['l']);
$starttime = time();
while((time() - $starttime) < 5){ // Max 5 seconds (exaggerated)
if(file_exists('_avg_color_coords_out.txt')){
$dir_vector = (float) file_get_contents('_avg_color_coords_out.txt');
if(!#unlink('_avg_color_coords_out.txt')){
sleep(1);
unlink('_avg_color_coords_out.txt');
}
break;
}
usleep(2000);
}
// $dir_vector ("v", the centroid of the color) is already computed by Python
// ---------- PYTHON SCRIPT ----------
import math
import cv2
import numpy as np
import os
import time
#cap = cv2.VideoCapture(0)
#while (1):
# _, frame = cap.read()
if(os.path.exists('_avg_color_coords_stop.txt')):
exit()
while not os.path.exists('_avg_color_coords_in.txt'):
time.sleep(0.002)
f = open('_avg_color_coords_in.txt', 'r')
imgsrc = f.readline().rstrip('\n')
rgbcol = [int(x) for x in f.readline().rstrip('\n').split(',')]
frame = cv2.imread(imgsrc)
h, w = frame.shape[:2]
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
hfacl = rgbcol[0] / 360 * 180 * 0.95
hfach = rgbcol[0] / 360 * 180 * 1.05
sfacl = rgbcol[1] / 100 * 255 * 0.9
sfach = rgbcol[1] / 100 * 255 * 1.1
vfacl = rgbcol[2] / 100 * 255 * 0.9
vfach = rgbcol[2] / 100 * 255 * 1.1
lower_color = np.array([hfacl, sfacl, vfacl]) # 0..180, 0..255, 0..255 not percentage!
upper_color = np.array([hfach, sfach, vfach]) # 0..180, 0..255, 0..255 not percentage!
mask = cv2.inRange(hsv, lower_color, upper_color)
#cv2.imshow('mask', mask)
points = cv2.findNonZero(mask)
if(points.any()):
avg = np.mean(points, axis=0)
else:
avg = [0,0]
#print(avg)
v = -math.atan(((w * 0.5) - avg[0][0]) / (h - avg[0][1])) / (3.1415 * 0.5);
f2 = open('_avg_color_coords_out.txt', 'w+')
f2.write("%s" % str(v))
# k = cv2.waitKey(5) & 0xff
# if k == 27:
# break
#cv2.destroyAllWindows()
#cap.release()
f2.close()
f.close()
os.remove('_avg_color_coords_in.txt')
6) 38ms : Same as #5 except begin by resizing the canvas by 50% (acceptable loss) which doesn't seem to speed up things at all, and even seems counterproductive a little bit.
Is there a faster way or is this optimal? This will run every second on a 900mhz Rpi, so it needs to be quick. I think 30ms on a 900mhz CPU will be around 150-200ms (not tested yet, waiting for the camera to ship)
I had a quick go in php-vips:
#!/usr/bin/env php
<?php
require __DIR__ . '/vendor/autoload.php';
use Jcupitt\Vips;
$image = Vips\Image::newFromFile($argv[1], ['access' => 'sequential']);
# Target colour in RGB.
$target = [50, 10, 100];
# Select pixels where all bands are less than 10 away from the target.
# (and render it to memory ... we'll be reusing this mask image).
# The mask image will have one band with 0 for false and 255 for true.
$mask = $image->subtract($target)->abs()->less(10)->bandand()->copyMemory();
# The number of set pixels in the mask.
$n_set = $mask->avg() * $mask->width * $mask->height / 255;
# Handy for debugging: uncomment to write the mask image for inspection.
# $mask->writeToFile("x.png");
# Make a two-band image where band 0 is x coordinates and band 1 is y
# coordinates.
$coords = Vips\Image::xyz($mask->width, $mask->height);
# Make an indexed histogram: sum $coords at each position.
$pos = $coords->hist_find_indexed($mask);
# fetch the sum of the 255 value (true) pixels
[$x_sum, $y_sum] = $pos->getpoint(255, 0);
echo("x = " . $x_sum / $n_set . "\n");
echo("y = " . $y_sum / $n_set . "\n");
I can run it like this:
$ time ./locate-rgb.php ~/pics/x.jpg
x = 483.375
y = 487.75
real 0m0.079s
user 0m0.085s
sys 0m0.022s
So about 80ms on this modest laptop. That includes PHP startup and shutdown, and decompressing the JPG image.
That's only going to work in very constrained lighting and camera setups, but perhaps that's OK? It would be easy to make the ball detection fancier, but of course it would slow it down a bit.
I need to create color palette with PHP. I use code like this:
for ($x = 0; $x < $this->width; $x += $level) {
for ($y = 0; $y < $this->height; $y += $level) {
$index = imagecolorat($this->workingImage, $x, $y);
$rgb = imagecolorsforindex($this->workingImage, $index);
$color = $this->getClosestColor($rgb["red"], $rgb["green"], $rgb["blue"]);
$hexarray[] = $this->RGBToHex($color[0], $color[1], $color[2]);
}
}
How can I do it? For any picture I need at least 10 colors. I am tried to use another methods, pixelate image before receiving color palette, but it is not helping for me.
Unfortunately you have mixed up your starting image with your incorrect results and your desired results, so I have separated it out as image.png below:
Now, I plan to use pnmquant which is part of the NetPBM package which is available precompiled and ready to install on most platforms from SourceForge.
So, first I need to convert your PNG to NetPBM's PPM format for the pnmquant tool to work - I'll use pngtopnm for that. Then I want to extract the colours as a swatch and enlarge the swatch so you can see something bigger than the extracted 21 pixels -I'll use ImageMagick for that, and I want to make a text representation of it so you can see the numbers, and I'll use ImageMagick for that.
pngtopnm image.png | pnmquant 21 | pnmtopng > result.png
convert image.png pnm:- | pnmquant 21 | convert - -unique-colors -scale 400x swatch.png
convert image.png pnm:- | pnmquant 21 | convert - -unique-colors -depth 8 txt:
# ImageMagick pixel enumeration: 21,1,65535,srgb
0,0: (65278,9766,2827) #FE260B srgb(254,38,11)
1,0: (65278,15163,2570) #FE3B0A srgb(254,59,10)
2,0: (65535,10794,5140) #FF2A14 srgb(255,42,20)
3,0: (65535,11565,5140) #FF2D14 srgb(255,45,20)
4,0: (65278,11308,5140) #FE2C14 srgb(254,44,20)
5,0: (65535,11822,5140) #FF2E14 srgb(255,46,20)
6,0: (65278,11822,5140) #FE2E14 srgb(254,46,20)
7,0: (65278,19532,3341) #FE4C0D srgb(254,76,13)
8,0: (65278,20817,3855) #FE510F srgb(254,81,15)
9,0: (65278,16962,5140) #FE4214 srgb(254,66,20)
10,0: (65535,20303,4369) #FF4F11 srgb(255,79,17)
11,0: (65278,20817,4626) #FE5112 srgb(254,81,18)
12,0: (63479,33410,28270) #F7826E srgb(247,130,110)
13,0: (62451,56540,55769) #F3DCD9 srgb(243,220,217)
14,0: (62708,61166,59110) #F4EEE6 srgb(244,238,230)
15,0: (62451,62708,62965) #F3F4F5 srgb(243,244,245)
16,0: (65278,63736,62965) #FEF8F5 srgb(254,248,245)
17,0: (62194,65278,65535) #F2FEFF srgb(242,254,255)
18,0: (65278,64250,63736) #FEFAF8 srgb(254,250,248)
19,0: (65535,65535,65535) #FFFFFF white
20,0: (65278,65278,65278) #FEFEFE srgb(254,254,254)
As regards PHP, you can shell out and exec the command-line tools above, or look at the source code and adapt to PHP.
Iam trying to calculate a zoom Effect beetween 12 Images. Every Image is 100% larger then the one before. Its geting near to perfect, but there is only a issue at the transition beetween the images. It isn't fluid zoom beetween each image.
Please see the video: http://youtu.be/dUBbDjewpO0
I think the Exponential expression pow() isnt coorect for some reason.
Here is the PHP script, but i cant find the issue:
<?php
$imageFiles=array(
'1.jpg',
'2.jpg',
'3.jpg',
'4.jpg');
$targetFrameRate=$targetDuration='18';
$imageCount = count($imageFiles);
$totalFrames = ($targetFrameRate*$targetDuration);
$sourceIndex = 0;
$firstIndex = 1;
$lastIndex = $totalFrames; //==total frames
$currentScale = 1;//image scaling for first scale
$deltaScale = ((($imageCount-1)*($scaleFactor-$currentScale))/$totalFrames);
for ($i=$firstIndex; $i<=$lastIndex; $i++) {
// prepare filename
$filename = createImageFilename($i, $imageType);
// determine source..
if ($i == $firstIndex) {
$newSourceIndex = 0;
}
else if ($i == $lastIndex) {
$newSourceIndex = ($imageCount-1);
}
else {
$newSourceIndex = intval(($i*($imageCount-1))/$totalFrames);
}
// create frame..
if ($newSourceIndex != $sourceIndex) {
$sourceIndex = $newSourceIndex;
$currentScale = pow($scaleFactor, $sourceIndex);
$nextScale = pow($scaleFactor, ($sourceIndex+1));
$deltaScale = ((($imageCount-1)*($nextScale-$currentScale))/$totalFrames);
copyImage($imageFiles[$sourceIndex],
sprintf('%s/%s', $outputDir, $filename),
$imageWidth,
$imageHeight,
$imageType);
}
else {
createImage($imageFiles[$sourceIndex],
sprintf('%s/%s', $outputDir, $filename),
($currentScale/pow($scaleFactor, $sourceIndex)),
$imageWidth,
$imageHeight,
$imageType);
}
//DEBUG: buffer some values for optional debug-output
if (isDebugOutputEnabled()) {
$debug_idx[$i] = $filename;
$debug_inf[$i] = sprintf('sourceIndex=%d , scale=%01.2f<br />', $sourceIndex, $currentScale);
}
// advance..
$currentScale += $deltaScale;
}
?>
rendering is well
shell_exec('ffmpeg -f image2 -i /var/www/htdocs/image2/i%d.jpg -s 1280x720 -movflags faststart -b:v 5500k -r 18 output.flv');
The problem comes from the fact that you are adding a delta to your scale instead of multiplying it by a constant amount each frame:
$currentScale += $deltaScale;
An exponential zoom means you increase the zoom by a constant factor (not difference) for a given constant amount of time, so you need to change that line to:
$currentScale *= $deltaScale;
and also calculate $deltaScale differently:
$deltaScale = pow($nextScale / $currentScale, ($imageCount-1) / $totalFrames);
This will compute a fractional power of the scale difference between the images, so that when you multiply it with the $currentScale value $totalFrames / ($imageCount-1) times (the number of frame you render between the current scale and next scale), the result will be an increase by a factor of $nextScale / $currentScale.
Simplification:
Because the zoom is at a constant rate for the whole animation, $deltaScale is constant the whole time, so you can compute it outside the loop like this:
$deltaScale = pow($scaleFactor, ($imageCount-1) / $totalFrames);
I'm using Curl via Proxies to download images with a scraper I have developed.
Unfortunately, it gets the odd image which looks like these and the last one is completely blank :/
When I test the images via imagemagick (using identify) it tells me they are valid images.
When I test the images via exif_imagetype() and imagecreatefromjpeg() again, both these functions tell me the images are valid.
Does anyone have a way to determine if the image has majority of greyness or is completely blank/white and these are indeed corrupted images?
I have done a lot of checking with other questions on here, but I haven't had much luck with other solutions. So please take care in suggesting this is a duplicate.
Thanks
After knowing about imgcolorat, I did a search and stumbled on some code. I came up with this:
<?php
$file = dirname(__FILE__) . "/images/1.jpg";
$img = imagecreatefromjpeg($file);
$imagew = imagesx($img);
$imageh = imagesy($img);
$xy = array();
$last_height = $imageh - 5;
$foo = array();
$x = 0;
$y = 0;
for ($x = 0; $x <= $imagew; $x++)
{
for ($y = $last_height;$y <= $imageh; $y++ )
{
$rgb = #imagecolorat($img, $x, $y);
$r = ($rgb >> 16) & 0xFF;
$g = ($rgb >> 8) & 0xFF;
$b = $rgb & 0xFF;
if ($r != 0)
{
$foo[] = $r;
}
}
}
$bar = array_count_values($foo);
$gray = (isset($bar['127']) ? $bar['127'] : 0) + (isset($bar['128']) ? $bar['128'] : 0) + (isset($bar['129']) ? $bar['129'] : 0);
$total = count($foo);
$other = $total - $gray;
if ($gray > $other)
{
echo "image corrupted \n";
}
else
{
echo "image not corrupted \n";
}
?>
Anyone see some potential pitfalls with this? I thought about getting the last few rows of the image and then comparing the total of r 127,128,129 (which are gray) against the total of other colours. If gray is greater than the other colours then the image is surely corrupted.
Opinions welcome! :)
found this page when looking for a way to check visually corrupted images like this. Here is a way to solve the problem using bash (anyway, the convert command line can be easily adapted for php or python) :
convert INPUTFILEPATH -gravity SouthWest -crop 20%x1% -format %c -depth 8 histogram:info:- | sed '/^$/d' | sort -V | head -n 1 | grep fractal | wc -l
It crops a little square in the southwest corner of the picture, then gets the histogram of this picture. If the main color of the histogram has the name "fractal" instead of an rgb color, it means this zone is corrupted and so the output will be 1 and 0 otherwise.
Hope this helps!
If the image it is returning is a valid file, then I would recommend running the scrape twice (ie. download it twice and check to see if they are the same).
Another option would be to check the last few pixels of the image (ie. bottom-right corner) to see if they match that color of grey exactly. If they do, then redownload. (obviously this approach fails if you download an image that is actually supposed to be grey in that corner, in that exact colour...but if you check several of the last pixels it should reduce the chance of that to an acceptable level).
I use this one. If the most of pixels in right bottom corner (5x5) are grey, then image is broken.
define('MIN_WIDTH',500);
define('MIN_HEIGHT',200);
function isGoodImage($fn){
list($w,$h)=getimagesize($fn);
if($w<MIN_WIDTH || $h<MIN_HEIGHT) return 0;
$im=imagecreatefromstring(file_get_contents($fn));
$grey=0;
for($i=0;$i<5;++$i){
for($j=0;$j<5;++$j){
$x=$w-5+$i;
$y=$h-5+$j;
list($r,$g,$b)=array_values(imagecolorsforindex($im,imagecolorat($im,$x,$y)));
if($r==$g && $g==$b && $b==128)
++$grey;
}
}
return $grey<12;
}
ImageMagick's identify command will identify far more corrupt images if you call it with the -verbose option. And there's a -regard-warnings option as well, which will make it treat warnings as errors. Try these against a bad image, and see if the result is a non-zero error code.
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;
}