I am trying to compress a PNG file until I hit a target mark, using a dirty-ish method. But I am struggling to get any compression going. I know that at the final point, I set compression to 0, but that is because I expect the file to have been compressed.
1) Upload a png with a form
2) While the size is greater than 25000, compress it by 5 (imagepng())
I need to use the buffer to re-capture the image instead of constantly saving it (I think that's a good way). The following code isn't my production code but it's the exact method.
$postedImg = $_FILES['file']['tmp_name'];
$size = filesize($postedImg); // eg 35000
$info = getimagesize($postedImg);
$mime = $info['mime'];
if($mime === 'image/png')
$img = imagecreatefrompng($postedImg);
while($size > 25000){
ob_start();
imagepng($img , NULL, 5);
$obImgStr = ob_get_contents();
$img = imagecreatefromstring($obImgStr);
ob_end_clean();
$size = filesize($obImgStr);
}
imagepng($img, 'image/goes/here.png', 0);
PNG compression is lossless. If you compress it to 5, then to 5 again, you're going to end up with 5 still. In the same way, when you finally output your image at 0 (which would only happen if your first attempt at 5 hit the 25k), what you're actually doing is undoing any compression you just did. That's because the quality of the image is not affected at all by the compression (the doc's name for that option is misleading). It's just like a zip... all it does is reduce the file size, with the drawback that the file needs to be reinflated on the other end.
A better way of approaching this is to compress it incrementally, starting at e.g. 1, then 2, then 3 until you get the size you want. Even so, as said in the comments above, you may never get your target size.
Related
I'm trying to compress uploaded images down to a specific size of 200Kb. I don't want to compress them any more than they need to be and using lossless compression like PNG isn't enough. Simply setting it to imagejpeg($image, null, 40) creates different compressed sizes for different images. Is there a way to set the desired compression size in bytes or at least have some algorithm that can find out the compression output without looping through imagejpeg() from 100 to 0 quality?
I found a way to use ob to view the file size of the image before it is uploaded so I used it in a loop
// Get get new image data
ob_start();
// Build image with minimal campression
imagejpeg($newImage, NULL, 100);
// Get the size of the image file in bytes
$size = ob_get_length();
// Save new image into a variable
$compressedImage = addslashes(ob_get_contents());
// Clear memory
ob_end_clean();
// If image is larger than 200Kb
if ($size > 200000) {
// This variable will decrease by 2 every loop to try most combinations
// from least compressed to most compressed
$compressionValue = 100;
for ($i=0; $i < 50; $i++) {
$compressionValue = $compressionValue - 2;
ob_start();
imagejpeg($newImage, NULL, $compressionValue);
$size = ob_get_length();
// Overwrite old compressed image with the new compressed image
$compressedImage = addslashes(ob_get_contents());
// Clear memory
ob_end_clean();
// If image is less than or equal to 200.5Kb stop the loop
if ($size <= 200500) {
break;
}
}
}
This is incredibly well optimized on its own too. The whole process only takes a few milliseconds with a 1.5Mb starting image even when it is trying 50 combinations.
There really isn't any way to predict the level of compression beforehand. The effect of compression depends upon the image source. One of the problems is that there is a myriad of JPEG compression settings.
The quantization tables (up to 3) with 64 different values.
Sub sampling.
Spectral selection in progressive scans.
Successive Approximation in progressive scans.
Optimize huffman tables.
So there are billions upon billions of parameters that you can set.
There are JPEG optimization applications out there that will look at the compressed data.
I am using php to resize and crop images. However the cropped image file is greater than ( much greater in some cases) than the original file. I even tried without cropping (copying the original image) but the resulting size is more than the original image.
$ImageName = '/IMAGES/testImage.jpg';
//Download the Image File
$base_url = 'https://image.jimcdn.com/app/cms/image/transf/none/path/sa6549607c78f5c11/image/ia62ed3191fcc424f/version/1457278719/athens-european-best-destinations.jpg';
$image_size = getimagesize($base_url);
$image_width = $image_size[0];
$image_height = $image_size[1];
$src_image = imagecreatefromjpeg($base_url);
//Copy Image Object To File
imagejpeg($src_image, $ImageName, 100);
Technically, the quality rate of the conversion is not derivable from
the jpeg data, it just tells the converter which tradeoff to make
between size and quality.
Some converters store it in the EXIF data of the JPEG header though,
so if that is still present you can use it with exif_read_data on it,
and see if the compression information is returned.
Source
try to reduce quality and set 80 for example. Quality will be still ok, it's even higher then default quality which should be 75 if you set nothing.
So, put like this:
imagejpeg($src_image, $ImageName, 80);
I think you can try this ImageCopyResampled in this case if you want to keep every pixel like original image. It's slower but better than imagejpeg.
And about quality of image using imagejpeg the quaility property refers to how much jpeg compression you want to apply to the saved or created output image.
0% quality means maximum compression and 100% quality means minimum compression.
The more compression you apply, the smaller the output filesize will be relative to the original uncompressed filesize.
Just exclude the image quality parameter:
imagejpeg($src_image, $ImageName);
GD will automatically create a smaller image for you
I have a folder that will consist of hundreds of PNG files and I want to write a script to make them all interlaced. Now, images will be added to that folder over time and processing all the images in the folder (wether their interlaced or progressive) seems kinda silly.
So I was wondering, is there any way to use PHP to detect if an image is interlaced or not that way I can choose wether to process it or not.
Thanks heaps!
You can also take the low-level approach - no need of loading the full image, or to use extra tools or libraries. If we look at the spec, we see that the "interlaced" flag it's just the byte 13 of the iHDR chunk, so we have to skip 8 bytes from the signature, plus 8 bytes of the iHDR Chunk identifier+length, plus 12 bytes of the chunk... That gives 28 bytes to be skipped, and if the next byte is 0 then the image is not interlaced.
The implementation takes just 4 lines of code:
function isInterlaced( $filename ) {
$handle = fopen($filename, "r");
$contents = fread($handle, 32);
fclose($handle);
return( ord($contents[28]) != 0 );
}
BTW, are you sure you want to use interlaced PNG? (see eg)
I think ImageMagick could solve your problem.
http://php.net/manual/en/imagick.identifyimage.php
Don't know if all the attributes are returned, but if you look at the ImageMagick tool documentation you can find that they can spot if an image is interlaced or not.
http://www.imagemagick.org/script/identify.php
At worst you can run the command for ImageMagick via PHP if the ImageMagick extension is not installed and parse the output for the "Interlace" parameter.
I'm making an image sharing website with php. I was reading about google's pagespeed, and they said I needed to optimize my images, so I've been trying to do that with imagejpeg, then hopefully I'll be able to use basically the same code with png and gif.
This is the code I have at the moment:
$img = imagecreatefromjpeg($original);
if ($extension === "jpg" || $extension === "jpeg"){
$location = $_SERVER['DOCUMENT_ROOT'] . "/uploads/" . $id . "/fullsize." . $extension;
$temp = fopen($location, 'w');
imagejpeg($img, $location, 80);
fclose($temp);
}
And it does the job of taking the image at $original, and saving a copy that's been through imagejpeg to $location.
But sometimes the resulting file is larger than the original was! This is where I get stumped.
Why is this happening?
How can I make it stop happening?
I'm trying to reduce image file sizes without hurting their appearance, is there a better way to do it than this?
Thanks,
Liam
imagejpeg will only shrink the file if you save it at a lower quality setting than the original was produced with
e.g.
imagejpeg($gd, 'temp.jpg', 50);
$temp = imagecreatefromjpeg('temp.jpg');
imagejpeg($gd, 'temp2.jpg', 70);
will probably produce a larger temp2 file, because it's at a higher quality setting, which roughly translates to "don't compress as much".
There's no way to predict what the final output size will be for a given image and quality setting without actually going through the whole jpeg compress process. You may have to do a few trial recompresses of an image and lower the quality setting each time until you start getting some shrinkage.
of course, if you lower the quality TOO low, you'll basically trash the image and definitely introduce visible artifacts.
It definitely depends on the quality of the original image. If your image is quality 60 originally, saving at quality 80 will generally result in a larger file size.
However, it also depends on what software created the original image. For example, I've observed many times where Adobe Photoshop can save an image at quality 60 that looks as good as 80 from GD (the graphics library you are using). Also, Photoshop can often save images at the same quality as another graphics package but with a smaller size. This is subjective, of course. I'm not necessarily concluding Photoshop is "better," but it does appear to have it's options well tuned.
I recommend you try ImageMagick, as I've seen it generally do a better job (file sizes, image quality) than GD. You should also determine the original quality setting per image so you don't accidentally grow images.
I am resizing images using:
//load file and dimensions
$obj_original_image = imagecreatefromjpeg($str_file_path);
list($int_width, $int_height, $image_type) = getimagesize($str_file_path);
//compress file
$int_thumbnail_width = 320;
$int_ratio = $int_thumbnail_width / $int_width;
$int_new_width = $int_thumbnail_width;
$int_new_height = $int_height * $int_ratio;
$obj_image = imagecreatetruecolor($int_new_width, $int_new_height);
imagecopyresampled($obj_image, $obj_original_image, 0, 0, 0, 0, $int_new_width, $int_new_height, $int_width, $int_height);
imagejpeg($obj_image, $GLOBALS['serverpath'] . '/images/uploaded/video-thumbs/'.$arr_data['thumbnail_folder'].'/' . $arr_data['id']. '.jpg', $int_compression = 85);
And this is generally working perfectly. However occasionally, it is producing a blank, white image. I have read this note at http://php.net/manual/en/function.imagecopyresampled.php:
There is a problem due to palette image limitations (255+1 colors). Resampling or filtering an image commonly needs more colors than 255, a kind of approximation is used to calculate the new resampled pixel and its color. With a palette image we try to allocate a new color, if that failed, we choose the closest (in theory) computed color. This is not always the closest visual color. That may produce a weird result, like blank (or visually blank) images. To skip this problem, please use a truecolor image as a destination image, such as one created by imagecreatetruecolor().
However, I am already using imagecreatetruecolor. I am always scaling the same sized images so it can't be a width/height issue. It is only happening sometimes, most times the image scaling is working fine. Any ideas as to how to fix this?
There's no reason to be using getimagesize() when you've already opened the image with imagecreatefromjpeg(). You can use the imagesx() and imagesy() to get the width/height. getimagesize is seperate from GD and will re-open the image, re-parse it, etc...
Also note that GD is pretty stupid and forces you to determine what image type you've got and call the appropriate createfrom function. imagecreatefromjpeg() will fail if you try to open anything OTHER than a .jpg image.
$obj_original_image = imagecreatefromjpeg($str_file_path);
if ($obj_original_image === FALSE) {
die("Unable to load $str_file_path. Not a jpg?");
}
etc... etc...
$status = imagecopyresampled($obj_image, $obj_original_image, 0, 0, 0, 0, $int_new_width, $int_new_height, $int_width, $int_height);
if ($status === FALSE) {
die("imagecopyresampled failed!");
}
As well, add some debugging to your height/width calculations - output the values you're generating. Maybe one or more of them is coming out as 0, so you end up resampling to a non-existent size.
You mentioned that the size of the image being processed is always the same, but is the resolution always the same? If you have a particularly high resolution image you're working with, you may be running out of memory for certain operations which will produce a blank white image, in my experience. If this is the case, you could try to fix it by increasing the amount of memory allocated to PHP in php.ini under the memory_limit parameter. As far as I know, that value applies to image manipulations.