PHP - Compress image to pre-defined size - php

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.

Related

Scale image according a maximum file size

I'm using Imagick and like to scale an image to a maximum file size of 2.5MB
I had a look to this SOF question: ImageMagick: scale JPEG image with a maximum file-size which is exactly what I want to do but the extent() method from Imagick does not have the size parameter: http://www.php.net/manual/en/imagick.extentimage.php
Anyone knows how I could to it? At the moment I'm trying to calculate a coefficient between the original file size and the target file size to calculate a new resolution but found out that the resolution is not proportional to the file size.
Update - The output format is always JPEG so if there is a way to calculate the size before to save it that would be great
Thanks in advance, Maxime
The extent function you're calling is just to set the size of an image.
The function to set the jpeg extent option is:
$imagick->setOption('jpeg:extent', '2500kb');
Interestingly, the function $imagick->getImageBlob() seems to crash after setting this option. You are forced to write the file to disk, rather than being able to get it's bytes directly.
The output format is always JPEG so if there is a way to calculate the size before to save it that would be great
There isn't. The amount of detail that is in each image determines what size the image will be after compression, for a given image quality. So it's not possible to calculate the quality level that will give a final size.
The C code from the underlying Image Magick library that limits the file size is:
maximum=101;
for (minimum=2; minimum < maximum; )
{
jpeg_image->quality=minimum+(maximum-minimum+1)/2;
status=WriteJPEGImage(jpeg_info,jpeg_image);
if (GetBlobSize(jpeg_image) <= extent)
minimum=jpeg_image->quality+1;
else
maximum=jpeg_image->quality-1;
}
}
I.e. it just recompresses the file at different image quality levels, until it finds the level that gives the acceptable file size for the given value.

PHP: Changing the image quality when condition is met?

I have a php script where the user can upload images.
I want to make the script lower the image quality (jpeg) if the file size is bigger than 'X' kbytes.
Something like this:
if( $_FILES['uploaded_img']['size'] > $file_size_limit ){
// code that lowers the quality of the uploaded image but keeps the image width and height
}
What is the best approach for this?
ps: I don't want to change image width and height.
Sure you can. Do something like this.
$upload = $_FILES['uploaded_img'];
$uploadPath = 'new/path/for/upload/';
$uploadName = pathinfo($upload['name'], PATHINFO_FILENAME);
$restrainedQuality = 75; //0 = lowest, 100 = highest. ~75 = default
$sizeLimit = 2000;
if($upload['size'] > $sizeLimit) {
//open a stream for the uploaded image
$streamHandle = #fopen($upload['tmp_name'], 'r');
//create a image resource from the contents of the uploaded image
$resource = imagecreatefromstring(stream_get_contents($streamHandle));
if(!$resource)
die('Something wrong with the upload!');
//close our file stream
#fclose($streamHandle);
//move the uploaded file with a lesser quality
imagejpeg($resource, $uploadPath . $uploadName . '.jpg', $restrainedQuality);
//delete the temporary upload
#unlink($upload['tmp_name']);
} else {
//the file size is less than the limit, just move the temp file into its appropriate directory
move_uploaded_file($upload['tmp_name'], $uploadPath . $upload['name']);
}
This will accept any image format supported by PHP GD (Assuming that it's installed on your server. Most likely is). If the image is less than the limit, it will just upload the original image to the path you specify.
Your basic approach (which is implemented in Austin's answer) will work some of the time, but it's important to keep in mind that quality != file size. While they are generally correlated, it is perfectly possible (even common) that reducing the quality of a jpeg file will actually result in a LARGER file. This is because any JPEG uploaded to your system has already been run through the JPEG compression formula (often with a quality of 79 or 80). Depending on the original image, this process will create artifacts/alter the resulting image. When you run this already optimized image through the jpeg compression algorithm a second time it doesn't "know" what the original image looked like... so it treats the incoming jpeg as if it's a brand new lossless file and tries to copy it as closely as possible... including any artifacts created in the original process. Couple this with the fact that the original jpeg compression already took advantage of most of the "easy" compression tricks, it ends up being quite likely that compressing a second time results in a crappier looking image (copy of a copy problem) but not smaller file.
I did a few tests to see where the cutoff was, and unsurprisingly if the original image had a low compression ratio (q=99) a lot of space was saved re-compressing to q=75. If the original was compressed at q=75 (pretty common for graphic program defaults) then the secondary q=75 compression looked worse but resulted in virtually the same file-size as the original. If the original had a lower compression level (q=50) then the secondary q=75 compression resulted in a significantly larger file (for these tests I used three complex photos... obviously images with specific palates/compositions will have different performances going through these compressions). Note: I'm using Fireworks cs4 for this test... I realize that these quality indicators have no standardization between platforms
As noted in the comments below, moving from file formats like PNG to JPEG will usually end up significantly smaller (though without any transparency), but from JPEG -> JPEG (or GIF->JPEG, especially for simple or small-palate images) will often not help.
Regardless, you can still try using the compression method described by Austin, but make sure you compare the file-sizes of the two images when you're done. If there is only a small incremental gain or the new file is larger, then default back to the original image.

Display the color histogram for an image

I'm searching for a function in PHP which extract the histogram from an image to an PNG file. This PNG file will be located in a different folder than the actual image and the function must handle large images (over 3 MB). I did find a function almost similar to my request but the function can not handle large images and it didn't showed the histogram nor the image as showed on their website (it showed only a blank window with a border).
I hope that you guys can help me with this.
Thanks in advance.
We've been using this one for our projects:
http://www.histogramgenerator.com/
We did not experience issues with large images. It's not free, but we definetly feel it's
worth the money we paid for. The class also offers many additional interesting features.
Regards
It is a script to draw a simple histogram like Photoshop does (only similar, because I suspect it scale both axes with a sigmoid function, or something like that).
I wrote a scale() function where you can use a last bool argument to do a linear histogram, or use a square root scale to boost low values.
<?php
//Just in case GD needs more memory
ini_set('memory_limit', '64M');
$filename='image1.png';
//Attempt to open
[$width, $height, $type]=getimagesize($filename);
if($type==IMAGETYPE_PNG){
$img=imagecreatefrompng($filename);
}
//Histogram initialization
$hist = array(
'red'=>array_fill(0,256,0),
'green'=>array_fill(0,256,0),
'blue'=>array_fill(0,256,0)
);
//Counting colors
for($x=0;$x<$width;++$x){
for($y=0;$y<$height;++$y){
$bytes=imagecolorat($img,$x,$y);
$colors=imagecolorsforindex($img,$bytes);
++$hist['red'][$colors['red']];
++$hist['green'][$colors['green']];
++$hist['blue'][$colors['blue']];
}
}
//Drawing histogram as a 256x128px image
$width=256;
$height=128;
$newimg=imagecreatetruecolor($width,$height);
//Max frequency for normalization
$maxr=max($hist['red']);
$maxg=max($hist['green']);
$maxb=max($hist['blue']);
$max=max($maxr,$maxg,$maxb);
function scale($value,$max,$height,$scale=FALSE){
$result=$value/$max; //normalization: value between 0 and 1
$result=$scale?$result**0.5:$result; //sqrt scale
$result=$height-round($result*$height); //scaling to image height
return $result;
}
$top=220; //255 seems too bright to me
for($x=0;$x<$width;++$x){
for($y=0;$y<$height;++$y){
$r=($y>scale($hist['red'][$x],$maxr,$height,TRUE))?$top:0;
$g=($y>scale($hist['green'][$x],$maxg,$height,TRUE))?$top:0;
$b=($y>scale($hist['blue'][$x],$maxb,$height,TRUE))?$top:0;
$colors=imagecolorallocate($newimg,$r,$g,$b);
imagesetpixel($newimg,$x,$y,$colors);
}
}
//Saving the histogram as you need
imagepng($newimg,'.subfolder/histogram.png');
//Use the next lines, and remove the previous one, to show the histogram image instead
//header('Content-Type: image/png');
//imagepng($newimg);
exit();
?>
Note I'm not checking if filename exist, neither if getimagesize() or imagecreatefrompng() failed.
I tested this with a 2MB (5800 x 5800) PNG Image. Basicaly the "imagecreatefrompng()" method is consuming lot of memory.
So before making the call, I increased the memory al the way up to 512M and set the execution time to 5 mins
ini_set('memory_limit', '512M');
set_time_limit(5*60);
After the Image is created, restore the memory limit
$im = ImageCreateFromPng($source_file);
ini_restore('memory_limit');
Reference: http://www.php.net/manual/en/function.imagecreatefrompng.php#73546

php compressing images showing varying results

I was using the firebug page speed utility and one of the suggestions given was to compress the images - So I wrote the following code to compress the image
$filename="http://localhost.com/snapshots/picture.png";
$img = imagecreatefrompng($filename);
$this->_response->setHeader('Content-Type', 'image/png');
imagepng($img,null,9);
imagedestroy($img);
Now the actual image size is 154K
So I experimented by giving different quality levels and here is what I found
imagepng($img,null,0); --> Size = 225K
imagepng($img,null,1); --> Size = 85.9K
imagepng($img,null,2); --> Size = 83.7K
imagepng($img,null,3); --> Size = 80.9K
imagepng($img,null,4); --> Size = 74.6K
imagepng($img,null,5); --> Size = 73.8K
imagepng($img,null,6); --> Size = 73K
imagepng($img,null,7); --> Size = 72.4K
imagepng($img,null,8); --> Size = 71K
imagepng($img,null,9); --> Size = 70.6K
Do these results look accurate - I'm not sure why with quality 0 - the image size is larger than the actual size.
Secondly is this the best way to go about in PHP to compress images before rendering them in the browser to improve performance.
Based on the suggestions, that its better to compress the image once at the time of saving - I digged up the code that is called by the flash program to generate the snap shot -
$video = $this->_getParam('video');
$imgContent = base64_decode($this->_getParam('snapshot'));
file_put_contents("snapshots/" . $video . ".png", $imgContent);
EDITED
Based on Alvaro's suggestion, I have made the following modification to the code which generates a much small jpg file
$video = $this->_getParam('video');
$imgContent = base64_decode($this->_getParam('snapshot'));
file_put_contents("snapshots/" . $video . ".png", $imgContent);
$filename="snapshots/".$video.".png";
$img = imagecreatefrompng($filename);
imagejpeg($img,'test.jpg',75);
So now this is a 3 step process
create the initial image using file_put_contents
Use imagecreatefrompng and imagejpeg to compress the file and generate a smaller image
Delete the orig image
Is this the best optimal way to go about it.
Since PNG uses lossless data compression, the only way to achieve a decent compression in a PNG image (edge cases apart) is to save it as palette (rather than true colour) and reduce the number of colours. You appear to be processing some sort of screenshots. You may obtain smaller file sizes is you use a lossy compression, i.e., save as JPEG. In either cases, you reduce both file size and picture quality. You could also try the GIF format, which tends to be smaller for small graphs.
Last but not least, you should compress images once (typically when they get uploaded), not every time they're served. I suppose your code is just a quick test but I mention just in case.
Answer to updated question:
I'm not familiar with PHP image functions but you should probably use a combination of imagecreatefrompng() and imagejpeg(). Also, consider whether you need to keep the original PNG for future reference or you can discard it.
You have not understood the last parameter, that's not quality that's the compression level so increasing it will decrease the image size. Anyway i've used that method before to compress png images and it works well so i think you should continue to use it.
1- The result seems accurate since 0 means no compression
quality
Compression level: from 0 (no
compression) to 9.
It's normal for the 0ed file to be larger than the original (that can be slightly compressed to begin with). You need to understand file compression and PHP GD image constructor.
2- IMHO, the wisest choice would be to compress your png file before uploading them on your server (of course, it states only if you have the choice : static, few files).
Help for that :
http://www.webreference.com/dev/graphics/compress.html
http://www.punypng.com/
http://omaralzabir.com/reduce_website_download_time_by_heavily_compressing_png_and_jpeg/
If it means to be dynamic, the php is the choice.

imagecreatefromjpeg is silently terminating scripts

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?

Categories