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.
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'm trying to downsample every image that I consider large for my site on uploading, so when a user tries to upload an image I firstly check if it has an acceptable resolution, otherwise I want to drop this resolution. The code I use for this is:
if ($image->isValid()){
$imagick = new \Imagick();
$imagick->readImage($image);
$resolution = $imagick->getImageResolution();
$resolution_x = $resolution['x'];
$resolution_y = $resolution['y'];
if ($resolution_x > 30 && $resolution_y > 30){
$imagick->setImageResolution($resolution_x,$resolution_x);
$imagick->resampleImage($resolution_x/2,$resolution_x/2,\imagick::FILTER_CATROM,1);
}
$imagick->writeImage($uploadDir.$path);
}
This code was supposed to set the resolution of an image with resolution 300 dpi for example to 150dpi. Instead of this, the resolution remains 300 dpi and the image dimensions drop to the half of their previous values (e.g an image (1200x800) turns into (600x400)). Am I missing something about the functionality of Imagick::resampleImage or is there any error in my code? I've done a lot of search before post this question and tried lot of different ways to succeed my goal using Imagick but I cannot get it done!
The 'resolution' in the setImageResolution and getImageResolution functions refer to the dots per inch setting of the image, which is a hint to printers of what size to print the image i.e. how many dots per inch it should be printed at.
It does not affect the pixel dimensions of the image, and so does not have a noticeable effect on the image on a computer, which does not use the DPI setting to render an image.
You want to use either just $imagick->getImageWidth() and $imagick->getImageHeight() or $imagick->getImageGeometry() to get the pixel size of the image, and then resample it based on those pixel dimension, rather than the printer hint setting.
It sounds like the resolution values should be the same in both setImageResolution and resampleImage. Have you tried this yet?
$imagick->setImageResolution($resolution_x/2,$resolution_x/2);
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.
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
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.