While transferring an existing, stable, website to a new server I've run into an intermittent problem with a bit of code that creates images dynamically using Imagick.
The code parses a GET query (eg example.com/image.php?ipid=750123&r=0&w=750&h=1000) and then scales and rotates an image stored on the server and serves it to the client.
ipid = id for an image stored on server
r = degrees of rotation
w = width to display
h = height to display.
The code has probably been used for at least 5 years with no problems.
On transferring to a new, much faster, server (from Debian Squeeze to Ubuntu 12.04), I encounter a problem where about 50% of the time the image does not display, and instead the server sends a 'png file' of 0 bytes. There are no PHP errors or server errors.
Depending on whether the images is sent successfully or not, different headers are sent:
Successful image headers:
Connection: Keep-Alive
Content-Type: image/png
Date: Tue, 23 Jul 2013 17:03:32 GMT
Keep-Alive: timeout=5, max=76
Server: Apache/2.2.22 (Ubuntu)
Transfer-Encoding: chunked
X-Powered-By: PHP/5.3.10-1ubuntu3.7
Failed image headers:
Connection Keep-Alive
Content-Length 0
Content-Type image/png
Date Tue, 23 Jul 2013 17:03:31 GMT
Keep-Alive timeout=5, max=78
Server Apache/2.2.22 (Ubuntu)
X-Powered-By PHP/5.3.10-1ubuntu3.7
Does anyone have any ideas why this is happening?
Is there a way to 'force' the png images to be sent chunked, as I wonder if that is at the root of the problem. I've tried various workarounds where I send the image size, or 'Transfer-Encoding: chunked' as a header via PHP's header() function, but did not work, and in these cases the browser states the image is corrupted.
<?php
//Class used to connect to Imagick and do image manipulation:
class Images
{
public $image = null;
public function loadImage($imagePath){
$this->image = new Imagick();
return $this->image->readImage($imagePath);
}
public function getImage(){
$this->image->setImageFormat("png8");
$this->image->setImageDepth(5);
$this->image->setCompressionQuality(90);
return $this->image;
}
// Resize an image by given percentage.
// percentage must be set as float between 0.01 and 1
public function resizeImage ($percentage = 1, $maxWidth = false, $maxHeight = false)
{
if(!$this->image){return false;}
if($percentage==1 && $maxWidth==false && $maxHeight == false){return true;}
$width = $this->image->getImageWidth();
$height = $this->image->getImageHeight();
$newWidth = $width;
$newHeight = $height;
if($maxHeight && $maxWidth){
if($height > $maxHeight || $width > $maxWidth){
$scale = ($height/$maxHeight > $width/$maxWidth) ? ($height/$maxHeight) : ($width/$maxWidth) ;
$newWidth = (int) ($width / $scale);
$newHeight = (int) ($height / $scale);
}
}else{
$newWidth = $width * $percentage;
$newHeight = $height * $percentage;
}
return $this->image->resizeImage($newWidth,$newHeight,Imagick::FILTER_LANCZOS,1);
}
public function resizeImageByWidth ($newWidth)
{
if ($newWidth > 3000){
$newWidth = 3000; //Safety measure - don't allow crazy sizes to break server.
}
if(!$this->image){return false;}
return $this->image->resizeImage($newWidth,0,Imagick::FILTER_LANCZOS,1);
}
public function rotateImage($degrees=0)
{
if(!$this->image){return false;}
return $this->image->rotateImage(new ImagickPixel(), $degrees);
}
}
//(simplified version of) procedural code that outputs the image to browser:
$img = new Images();
$imagePath = '/some/path/returned/by/DB/image.png';
if($imagePath){
$img->loadImage($imagePath);
$width = $img->image->getImageWidth();
$height = $img->image->getImageHeight();
if (!$img->resizeImageByWidth($newWidth))
{
die ("image_error: resizeImage() could not create image.");
}
if($rotation > 0){
if (!$img->rotateImage($rotation))
{
die ("image_error: rotateImage() could not create image.");
}
}
}else{
die("image_error: no image path specified");
}
header('Content-type:image/png');
echo $img->getImage();
exit(0);
?>
UPDATE: In case it helps identify the location of the problem:
I've created a cludgy workaround which works in all cases, as a stopgap measure. What I do is create the image, save it to disk as a temporary file. Open the file and send it to the client using passthru() and then delete the file from disk. Cumbersome, and I'd rather do it the 'tidy' way, but it suggests to me the problem is somehow associated with these two lines: header('Content-type:image/png'); echo $img->getImage();
and a failure by Apache, PHP or Imagick to handle the resource.