How to speed up image resizing in a CodeIgniter PHP application?
Asked Answered
B

2

7

I have an application that allows users to upload images. The test case I use is a jpeg of 1.6MB with dimensions 3872 x 2592px. The upload script in the back-end will resize the uploaded image into 6 additional formats:

  • square (small 75 x 75)
  • Small Thumb (50 x 38)
  • Thumb (100 x 76)
  • Small (240 x 161)
  • Medium (500 x 378)
  • Large (1024 x 774)

I know it's a lot but trust me, I need this. I do the resizing using Code Igniter's Image Manipulation class, which uses either GD, GD2 or ImageMagick to do the resizing. I first configured it to use GD2 and noticed that the total resize process takes 11 secs.

Since the user has to wait for this process, it is not acceptable. After a lot of reading I learned that ImageMagick is a much faster and efficient manipulation library, so I switched to that:

$sourceimage = $data['filedata']['file_path'] . $data['imagedata']['user_id'] . "/" . $imageid . $data['filedata']['file_ext'];
$resize_settings['image_library'] = 'imagemagick';
$resize_settings['library_path'] = '/usr/bin';
$resize_settings['source_image'] = $sourceimage;
$resize_settings['maintain_ratio'] = false;
$resize_settings['quality'] = "100%";
$this->load->library('image_lib', $resize_settings);

Much to my surprise, the resize process now takes longer: 15 secs to be specific.

Having a look at my log I see that each resize action takes 2 seconds, no matter the file format it is resizing to. I guess this is because I always resize from the original, which is very large.

I would hate to offload the resizing process to a scheduled process, because that would decrease the usability of the site. It would mean that users have to wait a few minutes before they can start seeing/working with the image.

So, are there any smart ways to dramatically speed up this resizing process so that I can keep it in real-time? Just be clear: allowing for smaller resolutions is not an option, this is a photography site I'm building. Also, I really need the six formats mentioned.

Bohannon answered 30/12, 2009 at 16:37 Comment(1)
Any chance you could provide us the 1.6MB original JPEG image?Polychasium
J
7

As an idea, you could resize from the uploaded size to a more sensible intermediate size and then use this as the basis for further operations.

Alternatively, you could exec out to the command line version of ImageMagick and carry out (at least the bulk of) the image transformations in the background using the process described within Asynchronous shell exec in PHP.

Finally, whilst it's a bit off topic, are you going to allow for portrait orientation, or isn't this likely to be a factor?

Jigger answered 30/12, 2009 at 16:55 Comment(1)
Thank you so much. I now resize using a trickle down effect, meaning I start from large to resize to medium, next I use the medium image (instead of the large image) to resize to small, etc. It now takes less then 3 seconds to do the crunching. I did rip out one format (square) and switched back to GD2, instead of imagemagick. As far as I can tell, there are no visible quality issues using this trickle down resize. Thanks once again!Bohannon
P
9

Seems like you've already accepted an answer but I'm gonna still post mine.

First of all, you really don't need to use 100% in quality, a 90% or 85% value will do just fine while decreasing your processing time and image size (if you don't believe me just run some tests).

I've also done some benchmarks with this image and a custom JPEG() function, first test case:

JPEG('./original.jpg', null, '1024*774', './output/large.jpg');
JPEG('./original.jpg', null, '500*378', './output/medium.jpg');
JPEG('./original.jpg', null, '240*161', './output/small.jpg');
JPEG('./original.jpg', null, '100*76', './output/thumb.jpg');
JPEG('./original.jpg', null, '50*38', './output/small_thumb.jpg');
JPEG('./original.jpg', null, '75*75', './output/square.jpg');

This takes an average of 60 seconds on my slow slow computer.


Second test case:

JPEG('./original.jpg', null, '1024*774', './output/large.jpg');
JPEG('./output/large.jpg', null, '500*378', './output/medium.jpg');
JPEG('./output/medium.jpg', null, '240*161', './output/small.jpg');
JPEG('./output/medium.jpg', null, '100*76', './output/thumb.jpg');
JPEG('./output/medium.jpg', null, '50*38', './output/small_thumb.jpg');
JPEG('./output/medium.jpg', null, '75*75', './output/square.jpg');

This one takes "only" 16 seconds (my computer is really slow ATM :P), almost 4 times faster.


Here is the JPEG() function in case you want to make your own benchmarks:

function JPEG($source, $crop = null, $scale = null, $destination = null)
{
    $source = ImageCreateFromJPEG($source);

    if (is_resource($source) === true)
    {
        $size = array(ImageSX($source), ImageSY($source));

        if (isset($crop) === true)
        {
            $crop = array_filter(explode('/', $crop), 'is_numeric');

            if (count($crop) == 2)
            {
                $crop = array($size[0] / $size[1], $crop[0] / $crop[1]);

                if ($crop[0] > $crop[1])
                {
                    $size[0] = $size[1] * $crop[1];
                }

                else if ($crop[0] < $crop[1])
                {
                    $size[1] = $size[0] / $crop[1];
                }

                $crop = array(ImageSX($source) - $size[0], ImageSY($source) - $size[1]);
            }

            else
            {
                $crop = array(0, 0);
            }
        }

        else
        {
            $crop = array(0, 0);
        }

        if (isset($scale) === true)
        {
            $scale = array_filter(explode('*', $scale), 'is_numeric');

            if (count($scale) >= 1)
            {
                if (empty($scale[0]) === true)
                {
                    $scale[0] = $scale[1] * $size[0] / $size[1];
                }

                else if (empty($scale[1]) === true)
                {
                    $scale[1] = $scale[0] * $size[1] / $size[0];
                }
            }

            else
            {
                $scale = array($size[0], $size[1]);
            }
        }

        else
        {
            $scale = array($size[0], $size[1]);
        }

        $result = ImageCreateTrueColor($scale[0], $scale[1]);

        if (is_resource($result) === true)
        {
            if (ImageCopyResampled($result, $source, 0, 0, $crop[0] / 2, $crop[1] / 2, $scale[0], $scale[1], $size[0], $size[1]) === true)
            {
                return ImageJPEG($result, $destination, 90);
            }
        }
    }

    return false;
}
Polychasium answered 30/12, 2009 at 18:29 Comment(5)
Thank you, your answer definitely enriches the thread. Actually, I did try to change the quality, as an extreme test case I used 10%. The result was that that the total resize procedure still took exactly as long as before. This led me to believe that the resolution was the problem, not the quality. I just tested that same 10% quality again in my improved resize logic and again it takes exactly as long as with 100% quality. Not sure why though.Bohannon
One more remark, if possible I really would like to stay with the CodeIgniter image class, since that can handle almost all image file types and hides the complexity of dealing with lower level image resize routines. Only if all else fails and if there is a clear advantage I would switch.Bohannon
@Ferdy: Yeah, it shouldn't matter much in terms of speed, but you still should use a lower quality, 90% reduces the size of the image by half and maintains all the quality.Polychasium
@Alix, thank you so much. I just changed the quality to 90% and noticed that the file size dropped by a factor between 2 and 3. The factor depends on the size of the image, the larger it is, the larger the factor. I cannot see the quality difference indeed. This is beautiful because I have a scheduled service that transfers these images to Amazon S3, so you just saved me a lot of money!Bohannon
No problem Ferdy, glad I could help. =)Polychasium
J
7

As an idea, you could resize from the uploaded size to a more sensible intermediate size and then use this as the basis for further operations.

Alternatively, you could exec out to the command line version of ImageMagick and carry out (at least the bulk of) the image transformations in the background using the process described within Asynchronous shell exec in PHP.

Finally, whilst it's a bit off topic, are you going to allow for portrait orientation, or isn't this likely to be a factor?

Jigger answered 30/12, 2009 at 16:55 Comment(1)
Thank you so much. I now resize using a trickle down effect, meaning I start from large to resize to medium, next I use the medium image (instead of the large image) to resize to small, etc. It now takes less then 3 seconds to do the crunching. I did rip out one format (square) and switched back to GD2, instead of imagemagick. As far as I can tell, there are no visible quality issues using this trickle down resize. Thanks once again!Bohannon

© 2022 - 2024 — McMap. All rights reserved.