Home » Php » How to speed up image resizing in a CodeIgniter PHP application?

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

Posted by: admin July 12, 2020 Leave a comment

Questions:

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.

How to&Answers:

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?

Answer:

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 😛), 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;
}