1
votes

I have to upload image files that meet a max width dimension and max file size.

I have the code that checks width size and resizes the image to meet the max image width.

However, when I am saving the file I can set the quality

imagejpeg( $imgObject , 'resized/50.jpg' , 50 ); //save image and set quality

What I would like to do is avoid setting a standard quality, as the images being submitted vary highly from quality and may be low to begin with.

The quality of the image should be set as high as possible without going over the max file size limit.

The only solution I have is to save several versions of the image at varying qualities, check each file size and pick the best one. This works but is very slow and process intensive.

Any suggestions on how this could be done better?

Thanks

3
If you decide to go the 'multiple versions' route, you could create a simple learning algorithm that picks the dimensions in the first iteration based on averages from previous results. This would make the algorithm more efficient over time.Bastiaan ten Klooster

3 Answers

3
votes

Here is a way to achieve that. It does not save a file until it is satisfied that the file size will be lower than the specified maximum.

$original_file = 'original.jpg';
$resized_file = 'resized.jpg';
$max_file_size = '30000'; // maximum file size, in bytes

$original_image = imagecreatefromjpeg($original_file);

$image_quality = 100;

do {
    $temp_stream = fopen('php://temp', 'w+');
    $saved = imagejpeg($original_image, $temp_stream, $image_quality--);
    rewind($temp_stream);
    $fstat = fstat($temp_stream);
    fclose($temp_stream);

    $file_size = $fstat['size'];
}
while (($file_size > $max_file_size) && ($image_quality >= 0));

if (-1 == $image_quality) {
    echo "Unable to get the file that small. Best I could do was $file_size bytes at image quality 0.\n";
}
else {
    echo "Successfully resized $original_file to $file_size bytes using image quality $image_quality. Resized file saved as $resized_file.\n";
    imagejpeg($original_image, $resized_file, $image_quality + 1);
}

This requires no file to be written until you've successfully found the ideal compression ratio.

This loop starts with an $image_quality of 100, then counts downward trying each new value on a temporary in-memory buffer. If gets down to trying 0 and the file would still be too big, it returns an error message. Otherwise, if it gets to a value that works, it reports that and saves the new file using that image quality setting.

If you're feeling adventurous, you could implement a binary search algorithm to find the ideal compression ratio more quickly, but this quick-fix gives the same result, albeit possibly a tad slower.

-1
votes

No. It's the optimum possible.

-1
votes

Unfortunately, estimating final file size isn't something you can do as you have no access to the JPEG encoder's internal state.

One thing you can do is compress a smaller version of the image and extrapolate from there. Reducing the width and height by four means the computer has to deal with only one sixteenth of the pixels. The trick here is how to prepare a representative test image. Scaling the image down using imagecopyresampled() or imagecopyresized() won't work. It changes the compression characteristic too much. Instead, what you want to do is copy every fourth 8x8 tile from the original image:

$width1 = imagesx($img1);
$height1 = imagesy($img1);
$width2 = floor($width1 / 32) * 8;
$height2 = floor($height1 / 32) * 8;
$img2 = imagecreatetruecolor($width2, $height2);
for($x1 = 0, $x2 = 0; $x2 + 8 < $width2; $x1 += 32, $x2 += 8) {
    for($y1 = 0, $y2 = 0; $y2 + 8 < $height2; $y1 += 32, $y2 += 8) {
        imagecopy($img2, $img1, $x2, $y2, $x1, $y1, 8, 8);
    }
}

Compress the smaller image at various quality levels and get the respected file sizes. Multiplying them by 16 to should yield a reasonably good estimate of what you would get with the original image.