A couple of years ago I authored a PHP (ZEND) module that I still use today in some of my projects. This module was built with a fairly rudimentary (i.e. copypasta) understanding of PHP image manipulation but works beautiful except in one case.
The module pulls blob data from a table, parses it into an image, uses imagcopyresampled() to resize it, and then sends the resulting .jpg to the browser, it is called as a standard controller action.
It seems to work in all cases except when the original image was saved by a user from facebook (i.e. right click on the facebook image viewer and download to desktop and then upload to client site). I've tested this a number of times myself and been able to replicate it. I've also been able to upload the same image when re-saved via photoshop without encountering the problem.
I suspect the facebook image display adds some sort of additional metadata inside the file that causes my system to break.
Is there a solution for this?
The code for the PHP image module is as follows:
private function _buildImage($mode) {
//Prepare the output image
//Currently CROP and RESIZE are using different output onstruction calls
//So $finalImage is initialized prior to entering the mode blocks.
$finalImage = imagecreatetruecolor($this->_width, $this->_height);
$backgroundFillColor = imagecolorallocate($finalImage, RED, BLUE, GREEN);
imageFill($finalImage, 0, 0, $backgroundFillColor);
$this->_table = $this->_getTable($mode);
$image = $this->_request->image;
$this->_imageData = $this->_table->fetchEntryAsRow($image);
//Set top and left to 0 to capture the top/left corner of the orignal image.
$top = 0;
$left = 0;
$inputImage = imagecreatefromstring( $this->_imageData->image);
list($inputWidth, $inputHeight) = $this->_getImageSize($this->_imageData->image);
//Ratio is the target ratio of $this->_width divided by $this->_height, as set in actions.
//For index thumbnails this ratio is .7
//For index large images this ratio is 2
$ratio = $this->_width / $this->_height;
//define offset width and offset height as being equal to input image width and height
$offsetWidth = $inputWidth;
$offsetHeight = $inputHeight;
//Define Original Ratio as found in the image in the table.
$inputRatio = $inputWidth / $inputHeight;
//Rendering maths for RESIZE and CROP modes.
//RESIZE forces the whole input image to appear within the frame of the output image.
//CROP forces the output image to contain only the relevantly sized piece of the input image, measured from the middle.
if($this->_mode == CROP) {
if($inputRatio > $ratio) {
//Original image is too wide, use $height as is. Modify $width;
//define $scale: input is scaled to output along height.
$scale = $inputHeight / $this->_height;
//Calculate $left: an integer calculated based on 1/2 of the input width * half of the difference in the rations.
$left = round(($inputWidth/2)*(($inputRatio-$ratio)/2), 0);
$inputWidth = round(($inputWidth - ($left*2)), 0);
$offset = $offsetWidth - $inputWidth;
} else {
//Original image is too high, use $width as is. Modify $height;
$scale = $inputWidth / $this->_width;
$inputHeight = round(($this->_height * $scale),0);
$offset = $offsetHeight - $inputHeight;
$top = $offset / 2;
}
imagecopyresampled($finalImage, //Destination Image
$inputImage, //Original Image
0, 0, //Destination top left Coord
$left, $top, //Source top left coord
$this->_width, $this->_height, //Final location Bottom Right Coord
$inputWidth, $inputHeight //Source bottom right coord.
);
} else {
if($inputRatio < $ratio) {
//Original image is too wide, use $height as is. Modify $width;
$scale = $inputHeight / $this->_height;
$calculatedWidth = round(($inputWidth / $scale), 0);
$calculatedHeight = $this->_height;
$offset = $this->_width - $calculatedWidth;
$left = round(($offset / 2), 0);
$top = 0;
} else {
//Original image is too high, use $width as is. Modify $height;
$scale = $inputWidth / $this->_width;
$calculatedHeight = round(($inputHeight / $scale),0);
$calculatedWidth = $this->_width;
$offset = $this->_height - $calculatedHeight;
$top = round(($offset / 2), 2);
}
imagecopyresampled($finalImage, //Destination Image
$inputImage, //Original Image
$left, $top, //Destination top left Coord
0, 0, //Source top left coord
$calculatedWidth, $calculatedHeight, //Final location Bottom Right Coord
$inputWidth, $inputHeight //Source bottom right coord.
);
}
imagejpeg($finalImage, null, 100);
imagedestroy($inputImage);
imagedestroy($finalImage);
}
I suspect that the problem may actually lay with the implementation of _getImageSize.
private function _getImageSize($data)
{
$soi = unpack('nmagic/nmarker', $data);
if ($soi['magic'] != 0xFFD8) return false;
$marker = $soi['marker'];
$data = substr($data, 4);
$done = false;
while(1) {
if (strlen($data) === 0) return false;
switch($marker) {
case 0xFFC0:
$info = unpack('nlength/Cprecision/nY/nX', $data);
return array($info['X'], $info['Y']);
break;
default:
$info = unpack('nlength', $data);
$data = substr($data, $info['length']);
$info = unpack('nmarker', $data);
$marker = $info['marker'];
$data = substr($data, 2);
break;
}
}
}
You can see another example of this problem at http://www.angelaryan.com/gallery/Image/22 which displays a blue square, rather than the image stored in the database.
Try automatically "re-saving" the image after the upload with
imagejpeg(imagecreatefromjpeg($filename),$filename,9);
This should re-create any malformed or unrecognised headers from the original Facebook image.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With