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.
Related
Opening a JPEG image using imagecreatefromjpeg can easily lead to fatal errors, because the memory needed exeeds the memory_limit.
A .jpg file that is less than 100Kb in size can easily exceed 2000x2000 pixels - which will take about 20-25MB of memory when opened. "The same" 2000x2000px image may take up 5MB on the disk using a different compression level.
So I obviously cannot use the filesize to determine if it can be opened safely.
How can I determine if a file will fit in memory before opening it, so I can avoid fatal errors?
According to several sources the memory needed is up to 5 bytes per pixel depending on a few different factors such as bit-depth. My own tests confirm this to be roughly true.
On top of that there is some overhead that needs to be accounted for.
But by examining the image dimensions - which can easily be done without loading the image - we can roughly estimate the memory needed and compare it with (an estimate of) the memory available like this:
$filename = 'black.jpg';
//Get image dimensions
$info = getimagesize($filename);
//Each pixel needs 5 bytes, and there will obviously be some overhead - In a
//real implementation I'd probably reserve at least 10B/px just in case.
$mem_needed = $info[0] * $info[1] * 6;
//Find out (roughly!) how much is available
// - this can easily be refined, but that's not really the point here
$mem_total = intval(str_replace(array('G', 'M', 'K'), array('000000000', '000000', '000'), ini_get('memory_limit')));
//Find current usage - AFAIK this is _not_ directly related to
//the memory_limit... but it's the best we have!
$mem_available = $mem_total - memory_get_usage();
if ($mem_needed > $mem_available) {
die('That image is too large!');
}
//Do your thing
$img = imagecreatefromjpeg('black.jpg');
This is only tested superficially, so I'd suggest further testing with a lot of different images and using these functions to check that the calculations are fairly correct in your specific environment:
//Set some low limit to make sure you will run out
ini_set('memory_limit', '10M');
//Use this to check the peak memory at different points during execution
$mem_1 = memory_get_peak_usage(true);
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 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.
I have narrowed down my problem to show the size of the file matters. I don't know when the file is to big however. How can I catch the error. A 1.1mg file causes imagecreatetruecolor to bail but it chuggs along just fine when processing a 688k file.
Thanks
From what I can tell in the PHP/GD documentation, this function creates a 24-bit RGB image with black as the default color. The width and height it takes as arguments are ints for the pixel dimensions. Therefore to calculate size, you could multiply them as follows to determine if the raw image (before compression) is to blame:
1536 * 1962 = 3,013,632 pixels
3,013,632 * 24 = 72,327,168 bits
72,327,168 / 8 = 9,040,896 bytes
1024 * 768 = 786,432 pixels
786,432 * 24 = 18,874,368 bits
18,874,368 / 8 = 2,359,296 bytes
It seems unusual to me that this function would cause problems at a size of 1.1 MB but perhaps you are referring to a compressed image such as a jpg, where the actual raw size could be much, much greater. (As you can see a "small" image of 1024x768 is still well over 1.1 MB raw.)
The function will throw an error if you try to create an image too big. Just suppress the error and handle it yourself. For example,
$img = #imagecreatetruecolor(100000, 100000);
if ($img === false)
{
// Handle error
}
Calling all PHP gurus!
I understand that you can use getimagesize() to get the actual pixel height and width of an image in PHP. However, if you open an image in photoshop and look at the image size dialog, you notice that there is a resolution value that determines the print size of the image.
Given an arbitrary jpg image file, I need to use PHP to determine this resolution number. It appears that this information is stored in the jpg file somewhere, so how do I get to it?
One other requirement - I only have gdlib available to me. I need to do this without the use of other php libraries (imagemagick, etc.)
Thanks for the help!
You could just read the JPEG file directly, bytes 14-18 specify:
byte 14: 01, X and Y density unit specifier (00: none, pixel ratios, 01: DPI,02: DPC)
bytes 15-16: horizontal pixel density,
byte 16-18: vertical pixel densit
Also see: http://www.obrador.com/essentialjpeg/headerinfo.htm
There are two places that a resolution (i.e. resolution of the JPEG when printed, also referred to in shorthand as DPI or dots per inch) can potentially be stored.
The first is in the JPEG's JFIF header, which is often (but NOT always) right at the start of the JPEG.
The other is in the EXIF data.
Note that resolution data is usually NOT present, as it only has meaning if associated with a physical output size. E.g. if a digital camera writes the value, it is usually meaningless . However, when the JPEG is being output to a printer (e.g.) then the value will have meaning.
Here is some code to get it from the JFIF header, provided one is present, and is inside an APP0 chunk which is the second chunk in the file. (The first chunk is always the SOI marker.):
function read_JFIF_dpi($filename)
{
$dpi = 0;
$fp = #fopen($filename, r);
if ($fp) {
if (fseek($fp, 6) == 0) { // JFIF often (but not always) starts at offset 6.
if (($bytes = fread($fp, 16)) !== false) { // JFIF header is 16 bytes.
if (substr($bytes, 0, 4) == "JFIF") { // Make sure it is JFIF header.
$JFIF_density_unit = ord($bytes[7]);
$JFIF_X_density = ord($bytes[8])*256 + ord($bytes[9]); // Read big-endian unsigned short int.
$JFIF_Y_density = ord($bytes[10])*256 + ord($bytes[11]); // Read big-endian unsigned short int.
if ($JFIF_X_density == $JFIF_Y_density) { // Assuming we're only interested in JPEGs with square pixels.
if ($JFIF_density_unit == 1) $dpi = $JFIF_X_density; // Inches.
else if ($JFIF_density_unit == 2) $dpi = $JFIF_X_density * 2.54; // Centimeters.
}
}
}
}
fclose($fp);
}
return ($dpi);
}
SOLUTION: User the PHP JPEG Metadata Toolkit - downloaded from here: http://www.ozhiker.com/electronics/pjmt/
This toolkit has some handy scripts that will do all sorts of things, including viewing and editing of the header, metadata, and jfif information in jpeg file. Here is a script that gives you the XDensity and the YDensity (the x and y print resolution) of a jpg:
<?php
include_once("./JPEG.php");
include_once("./JFIF.php");
$image_header = get_jpeg_header_data("./myImage.jpg");
$image_info = get_JFIF($image_header);
print( "XDensity:" . $image_info['XDensity'] . "<br />");
print( "YDensity:" . $image_info['YDensity'] . "<br />");
?>
Depending on how the image is saved, EXIF contains a metric crapload of information - Read more about it in the PHP manual. You may need to parse/process the results a bit, however (e.g. the flash info is, or at least has been, just a byte, expressing various states).
I don't understand this. Pixels = printsize x resolution, and the number of pixels is a set value. So, if you have an image of 300x300 pixels, you have 1"x1" of 300 DPI resolution, 2"x2" of 150 DPI resolution, or 4"x4" of 75 DPI resolution etc. An image doesn't have a resolution unless it has a physical size to compare to its pixel size.
What is it I'm missing? (and just how glaringly obvious is it to everyone else? =] )