When porting an existing, stable website to a new server, I ran into an intermittent problem with a small amount of code that dynamically creates images using Imagick.
The code parses a GET request (for example, example.com/image.php?ipid=750123&r=0&w=750&h=1000), and then scales and rotates the image stored on the server and serves its 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.
When moving to a new, much faster server (from Debian Squeeze to Ubuntu 12.04), I encounter a problem where in about 50% of cases the image is not displayed, and instead the server sends a 0 byte "png file". PHP errors or server errors do not exist.
Depending on whether the images were 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
Image Header Failure:
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 idea why this is happening?
Is there a way to get "png images" to send chunked, since I am wondering if this is at the root of the problem. I tried various workarounds when I send the image size, or "Transfer-encoding: chunked" as a header via the PHP header () function, but it doesnโt work, and in these cases the browser claims that 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 this helps locate the problem:
I created a universal workaround that works in all cases, as a stop measure. I create an 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. It is cumbersome, and I would prefer to do it in a โneatโ way, but it seems to me that the problem is with these two lines: header('Content-type:image/png'); echo $img->getImage(); header('Content-type:image/png'); echo $img->getImage(); and Apache, PHP, or Imagick crashes to process the resource.