Like so many before me, I'm writing a PHP script to do some image thumbnailing. The script has earned WOMM (works on my machine) certification, but when I move it to my host (1&1 Basic), there's an issue: images above a certain filesize cannot be processed. I've moved all operations to the filesystem, to be certain it's not some latent POST issue. Here's the relevant code:
function cropAndResizeImage( $imageLocation )
{
//
// Just to be certain
//
ini_set('display_errors','on');
error_reporting(E_ALL);
ini_set('memory_limit','128M');
ini_set('max_execution_time','300');
$image_info = getimagesize($imageLocation);
$image_width = $image_info[0];
$image_height = $image_info[1];
$image_type = $image_info[2];
switch ( $image_type )
{
// snip...
case IMAGETYPE_JPEG:
$image = imagecreatefromjpeg($imageLocation);
break;
default:
break;
}
// snip...
}
Using my mystical powers of println debugging, I've been able to determine that imagecreatefromjpeg is not returning; in fact, the script halts completely when it gets to it. Some facts:
This is correlated to filesize. Images under 1MB appear to be fine (spot-checked), but images around 3MB barf. No clue what the precise cutoff is, though.
This is not due to server timeouts; wget returns in <1s on 3MB images, significantly longer on "appropriately small" images (indicating no processing of large images).
Prefixing the function call with # to suppress errors has no effect. This matches well with the fact that the script is not throwing an error, it's simply silently terminating upon this function call.
If I had to guess, there may be some GD parameter that I don't know about (or have access to) that limits input file sizes on 1&1's servers — the config variable guess is due to the fact that it barfs immediately, and doesn't appear (heuristically) to do any actual loading of or computations on the image.
Any suggestions? Thanks for the help.
Update (courtesy #Darryl's comments): calls to phpinfo indicate that PHP is updating the max_execution_time and memory_limit variables correctly. This doesn't necessarily mean that these resources are being allocated, simply that they appear to be functioning as expected.
Update 2: following some references from The Google, I tried optimizing the JPEG (reduced in quality from 3MB to 200KB) with no luck, so it's not an image filesize issue. I then tried reducing the number of pixels of the original 3888x2592 image, and the first successful size is 1400x2592 (1401x and 1402x both result in half-parses and errors indicating "malformed JPEG", which doesn't make much sense unless the entire image isn't being loaded). By reducing further to 1300x2592, I can instantiate the 400x300 thumbnail image that I'm actually looking for; at 1400x2592, the imagecreatetruecolor call I'm using to take care of that task fails silently in the same manner as imagecreatefromjpeg.
As to why this is, I'm a little uncertain. 1400 * 2592 == 3.5MB gives nothing particularly meaningful, but I have to imagine this is a limit on the number of pixels GD + PHP will process.
Please see this note regarding memory usage on the php website.
*"The memory required to load an image using imagecreatefromjpeg() is a function of the image's dimensions and the images's bit depth, multipled by an overhead.
It can calculated from this formula:
Num bytes = Width * Height * Bytes per pixel * Overhead fudge factor"*
I'm guessing 1&1 doesn't allow you to change scripts memory_limit or max_execution_time, so it's probably running out of memory. Have you tried running phpinfo() to see what the limits are?
Related
Im working on an application that allows users to upload images. We are setting a maximum file size of 1MB. I know there are a few options out there for compressing JPEGs (and we are dealing with jpegs here).
From what ive seen, all of these functions just allow you to define a compression ratio and scale it down. Im wondering if there is a function that allows you to define a maximum file size and have it calculate the compression ratio necessary to meet that size.
If not, I was thinking my best approach would be to use a while loop that looks at size and just keep hitting the image with imagejpeg() in 10% increments until the file is below the pre-defined max-size.
Am I on the right track here?
It depends on the data but with images you can take small small samples. Downsampling would change the result. Here is an example:PHP - Compress Image to Meet File Size Limit.
I completed this task using the following code:
$quality = 90;
while(filesize($full_path) > 1048576 && $quality > 20) {
$img = imagecreatefromjpeg($full_path);
imagejpeg($img,$full_path,$quality);
$quality = $quality - 10;
clearstatcache();
}
if(filesize($full_path) > 1048576) {echo "<p>File too large</p>"; unlink($full_path); exit;}
The $quality > 20 part of the statement is to keep the script from reducing the quality to a point I would consider unreasonable. Im sure there is more to be done here. I could add in a resolution re-size portion as well, but this works for my current needs. This is with a max file size of 1MB. If the file is still too large after maximum quality scale, it returns a file too large error and deletes the image from the server.
Take note that clearstatcache() is very important here. Without this, the server caches the image size and will not notice a change in file size.
This script only applies to JPEGs, but there are other php functions for gifs, pngs, etc.
I have a PHP script that creates a very tall image and draws a lot of lines on it (an organizational web kind of look). For the tallest images I've tried creating, the line drawing just stops abruptly toward the middle to bottom of the image: http://i.imgur.com/4Plgr.png
I ran into this problem using imagecreate(), then I found out that imagecreatetruecolor() can handle larger images, so I switched to that. I'm still having the same problem, but the script can now handle somewhat larger images. I think it should be drawing about 1200 lines. The script doesn't take more than 3 seconds to execute. Here's an image that executed completely: http://i.imgur.com/PaXrs.png
I adjusted the memory limits with ini_set('memory_limit', '1000M') but my scripts never reach near the limit.
How do I force the script to keep drawing until it finishes? Or how can I use PHP to create an image using less memory (which I think is the problem)?
if(sizeof($array[0])<300)
$image=imagecreate($width,$height);
else
$image=imagecreatetruecolor($width,$height);
imagefill($image,0,0,imagecolorallocate($image,255,255,255));
for($p=0; $p<sizeof($linepoints); $p++){
$posx1=77+177*$linepoints[$p][0];
$posy1=-4+46*$linepoints[$p][1];
$posx2=77+177*$linepoints[$p][2];
$posy2=-4+46*$linepoints[$p][3];
$image=draw_trail($image,$posx1,$posy1,$posx2,$posy2);
}
imagepng($image,"images/table_backgrounds/table_background".$tsn.".png",9);
imagedestroy($image);
function draw_trail($image,$posx1,$posy1,$posx2,$posy2){
$black=imagecolorallocate($image,0,0,0);
if($posy1==$posy2)
imageline($image,$posx1,$posy1,$posx2,$posy2,$black);
else{
imageline($image,$posx1,$posy1,$posx1+89,$posy1,$black);
imageline($image,$posx1+89,$posy1,$posx1+89,$posy2,$black);
imageline($image,$posx1+89,$posy2,$posx2,$posy2,$black);
}
return $image;
}
I'm going to take a guess that you have created a memory leak, and as you do more operations on a larger image you are eventually hitting PHP's memory limit. Rather than raise the limit, it would be better to find the leak.
Try changing your code so it explicitly deallocates the color you are creating in draw_trail. Also, there is no reason to return $image since you are passing a resource around.
if(sizeof($array[0])<300)
$image=imagecreate($width,$height);
else
$image=imagecreatetruecolor($width,$height);
imagefill($image,0,0,imagecolorallocate($image,255,255,255));
for($p=0; $p<sizeof($linepoints); $p++)
{
$posx1=77+177*$linepoints[$p][0];
$posy1=-4+46*$linepoints[$p][1];
$posx2=77+177*$linepoints[$p][2];
$posy2=-4+46*$linepoints[$p][3];
draw_trail($image,$posx1,$posy1,$posx2,$posy2);
}
imagepng($image,"images/table_backgrounds/table_background".$tsn.".png",9);
imagedestroy($image);
function draw_trail($image,$posx1,$posy1,$posx2,$posy2)
{
$black=imagecolorallocate($image,0,0,0);
if($posy1==$posy2)
imageline($image,$posx1,$posy1,$posx2,$posy2,$black);
else
{
imageline($image,$posx1,$posy1,$posx1+89,$posy1,$black);
imageline($image,$posx1+89,$posy1,$posx1+89,$posy2,$black);
imageline($image,$posx1+89,$posy2,$posx2,$posy2,$black);
}
imagecolordeallocate($black);
}
OP here. I figured out, or at least got around, my problem. I was drawing a lot of lines on the image, and the way my code got those points created a lot of repeats, so I still suspect it was a memory problem. I consolidated all the points, taking out the repeats and now it works just fine. Thanks to anyone that tried to help.
I have a simple image upload script that uses SimpleImage.php
(http://www.white-hat-web-design.co.uk/blog/resizing-images-with-php/)
to resize and save 2 copies of an uploaded image.
There isn't a massive amount of validation, just checking that it exists and that the file extension is fine, and also an exif_imagetype(); call.
This has worked with no problems so far until I tried to upload a seemingly normal jpeg which turned out to be invisibly (and untestably?) corrupt. There was something not right about it, but I know very little about image corruption - it looked fine and opened no problem on anything, but when I tried to save a scaled copy in my script I got a white page.
The problem is definitely that specific image, I've tested exhastively with other images both from my local stock and from stock image sites, and only that one image breaks it.
I resized a copy using Photoshop (the predicted file size thingy gave me some wierd numbers - 45meg for top quality jpeg) and that uploaded with no issues.
So my question is, how do I test for this?
The image in question is here: http://chinawin.co.uk/broken.jpg //beware, 700k
notes: I've tested with similar resolutions, image sizes and names, everything else worked apart from this image.
UPDATE:
Through trial and error I've narrowed down where the script breaks to the line where I load the image into a var for SimpleImage. Strangely this is the second line that does so (the first being to create the large copy, this one to create a thumbnail).
Commenting it out means the rest works ok... perhaps some refactoring will avoid this problem.
2nd Update:
Here's a snippet of code and some context from the line that fails:
//check if our image is OK
if ($image && $imageThumb)
{
//check if image is a jpeg
if (exif_imagetype($_FILES[$k]['tmp_name']) == IMAGETYPE_JPEG)
{
list($width, $height, $type, $attr) = getimagesize($_FILES[$k]['tmp_name']);
//echo 1;
$image = new SimpleImage();
//echo 2;
$image->load($_FILES[$k]['tmp_name']);
//echo 3;
$imageThumb = new SimpleImage();
//echo 4;
//this next line topples my script, but only for that one image - why?:
$imageThumb->load($_FILES[$k]['tmp_name']);
//echo '5<br/><br/>-------<br/>';
//do stuff, save & update db, etc
}
}
Final edit:
Turns out my script was running out of memory, and with good reason - 4900x3900 image with 240 ppi turns out to be around 48 meg when loaded into memory, twice - so I was using probably > 90meg of ram, per image.
Hats off to #Pekka for spotting this.
Refactoring the script to only have the image loaded once, and then this variable used instead of it's sibling, fixed my script. Still having (different) issues with upoading larger (2.5meg) images but this is for another question.
This is most likely a memory issue: Your JPG is very large (more than 4000 x 4000 pixels) and, uncompressed, will indeed eat up around 48 Megabytes of RAM.
Activate error reporting to make sure. If it's the reason, see e.g. here on what to do: Uploading images with PHP and hitting the script memory limit
I've done some searching for this and I understand that it is not possible to recover from an exhausted memory fatal error. I have a script that runs imagecreatefromjpeg. I tried catching the exception, I tried running the function with # and then checking the return value for null or false, I tried running it with 'die()'. Nothing works. So I can't 'recover' from it.
So is it possible to anticipate it before I get to it? Is it possible to check the uncompressed size of a jpeg and then die gracefully? I want to sent a message to my users along the lines of "The image $image is too large to process. You will need to create a thumbnail manually".
My shared host won't allow me to increase memory size beyond 64mb so that's not an option. My code is as follows...
function createthumb($section,$filename,$constrain=100)
{
$dir = "$section/thumbs_$constrain";
if(file_exists($workingdir."$section/thumbs_$constrain/$filename")) return 1;
if(!file_exists($dir)) mkdir($dir);
$src = imagecreatefromjpeg($workingdir."$section/$filename");
...
As a rule of thumb, you might use the size in pixels returned by getimagesize() :
list ($width, $height) = getimagesize($file);
$memory = $width * $height * 4; // 4 bytes per pixel
If this number is greater than a predefined limit (to be determined empirically, start with something like 32*1024*1024), then don't load it with imagecreatefromjpeg().
Have you thought about using ImageMagic instead of GD? ImageMagic is able to resize jpegs during load, so it doesn't consume copious amounts of PHP's memory. See this StackOverflow answer for details.
I think only error handler can help you as exception handling may not work on memory exhausted
http://php.net/manual/en/function.set-error-handler.php
You can verify string source size before imagegreatefromjpeg.
Before attempting to resize an image in PHP using libGD, I'd like to check if there's enough memory available to do the operation, because an "out of memory" completely kills the PHP process and can't be catched.
My idea was that I'd need 4 byte of memory for each pixel (RGBA) in the original and in the new image:
// check available memory
if(!is_mem_available(($from_w * $from_h * 4) + ($to_w * $to_h * 4))){
return false;
}
Tests showed that this much more memory than the library really seem to use. Can anyone suggest a better method?
You should check this comment out, and also this one.
I imagine it must be possible to find out GD's peak memory usage by analyzing imagecopyresampled's source code, but this may be hard, require extended profiling, vary from version to version, and be generally unreliable.
Depending on your situation, a different approach comes to mind: When resizing an image, call another PHP script on the same server, but using http:
$file = urlencode("/path/to/file");
$result = file_get_contents("http://example.com/dir/canary.php?file=$file&width=1000&height=2000");
(sanitizing the file parameter, obviously)
If that script fails with an "out of memory" error, you'll know the image is too large.
If it successfully manages to resize the image, it could return the path to a temporary file containing the resize result. Things would go ahead normally from there.