Glide: Easy Dynamic on-Demand Image Resizing

Share this article

Glide kayaks image

Glide is an image processing library built on top of Intervention. Its purpose is to facilitate on-demand image processing. That’s a fancy way of saying it creates images as they’re requested if they don’t exist.

For example, you might have a large, high quality portrait for your user profile. But when opening on a mobile device, downloading a 10 MB image is not only wasteful, it is slow. Instead, you could add a media query into your CSS, like so:

@media (min-width: 400px) {
	.profilePic {
		background-image: url('/images/myprofile.jpg');	
	}
}

@media (max-width: 400px) {
    .profilePic {
		background-image: url('/images/myprofile-320.jpg');			
	}
}

This would make all devices under 400px of width load the smaller background image, thus downloading faster. But manually resizing every image you might need in your app is tedious, time consuming, and error prone. That’s where Glide comes in.

Glide can be configured to respond to missing image requests (such as the non-existant myprofile-320.jpg from the example above) by creating them from a predetermined source. In a nutshell, if the requested image doesn’t exist, but its source does, the requested image gets created from it. What’s more, the image can be saved into a cache, so that future requests don’t invoke the library and waste precious CPU resources on re-rendering.

Let’s configure it.

If you’d like to follow along, feel free to use our no-framework application and Homestead Improved for quickly setting up an environment and sample application to test this in.

Bootstrapping

Step one is installing Glide:

composer require league/glide

Then, we need to configure the Glide server. In the NOFW project above, this happens in app/config.php where all the services are configured for future injection via PHP-DI. This is how it’s done:

// ...

    'glide' => function () {
        $server = League\Glide\ServerFactory::create(
            [
                'source' => new League\Flysystem\Filesystem(
                    new League\Flysystem\Adapter\Local(
                        __DIR__ . '/../assets/image'
                    )
                ),
                'cache' => new League\Flysystem\Filesystem(
                    new League\Flysystem\Adapter\Local(
                        __DIR__ . '/../public/static/image'
                    )
                ),
                'driver' => 'gd',
            ]
        );

        return $server;
    },

// ...

Now we can push (or pull) the glide instance into a controller when needed. The source and cache values determine where original images are found, and where generated images should be stored respectively, while the driver key specifies that the built-in PHP GD image manipulation extension should be used to modify the images.

You should alter the image paths to apply to your own.

While these are the main ones, there are many other settings, too – feel free to study them at your own leisure.

Routing

Next, we need to define a route which will get triggered when an image which does not exist is requested. The no-framework application uses fastRoute, so defining the route is as simple as adding the following into app/routes.php:

['GET', '/static/image/{image}', ['MyControllers\Controllers\ImageController', 'renderImage']]

What ever {image} in the route evaluates to will get passed as $image to the renderImage method of ImageController (which we will write next).

Processing

Let’s build the ImageController now:

<?php

namespace MyControllers\Controllers;

use League\Glide\Server;
use Standard\Abstracts\Controller;

class ImageController extends Controller
{
    /**
     * @Inject("glide")
     * @var Server
     */
    private $glide;

    public function renderImage($image)
    {
        $width = $this->getWidth($image);

        $imageSource = str_replace('-'.$width, '', $image);
        $this->glide->outputImage($imageSource, ['w' => $width]);
    }

    /**
     * @param string $imagePath
     * @return int
     */
    private function getWidth(string $imagePath) : int
    {
        $fragments = explode('-', $imagePath);
        return (int)explode('.', $fragments[1])[0];
    }
}

The Glide server instance is automatically injected into the controller with PHP-DI’s @Inject annotation. If this is confusing or interesting, please see the documentation. Then, renderImage is given the image path, calls the internal getWidth method which extracts the width from the image name, and finally resizes and renders the image.

Note that we’re not using $_GET params for passing in resize settings as per Glide docs – this is because I prefer my image URLs to look like real URLs to static resources, so that they can later be used as actual static resources.

This works, but astute readers might notice a problem…

Securing

As it is, the application is vulnerable to mass-image-editing attacks. Someone could write a loop of thousands of different image sizes, slap them onto every image they can harvest from your website, and make your server convert to arbitrary and pointless sizes 24/7. The Glide docs recommend signing the URL so that only authorized URLs pass, but this is both complicated and very difficult to achieve when dealing with external stylesheets. We’d have to pre-process our CSS and regenerate all image URLs inside it – a complex procedure made even more complicated by the fact that we probably have some other, unrelated front end asset compilation steps, too (for that, see here).

The right approach is, instead, to limit the sizes to which images can be resized with either routes or specific rules. Since we only have one route which leads to our images, let’s take the latter approach – allowing only specific sizes to be generated right there in the processing logic.

<?php

namespace MyControllers\Controllers;

use League\Glide\Server;
use Standard\Abstracts\Controller;

class ImageController extends Controller
{
    /**
     * @Inject("glide")
     * @var Server
     */
    private $glide;

    private $allowedWidths = [
        320,
        768,
        992,
        1280,
        1920,
    ];

    public function renderImage($image)
    {
        $width = $this->getWidth($image);

        $imageSource = str_replace('-'.$width, '', $image);
        $this->glide->outputImage($imageSource, ['w' => $width]);
    }

    /**
     * @param string $imagePath
     * @return int
     */
    private function getWidth(string $imagePath) : int
    {
        $fragments = explode('-', $imagePath);

        if (count($fragments) < 2) {
            header(
                ($_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0') . ' 500 ' . 'Server Error ImgErr00'
            );
            die("Nope! Image fragments missing.");
        }

        $width = (int)explode('.', $fragments[1])[0];
        if (!in_array($width, $this->allowedWidths)) {
            header(
                ($_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0') . ' 500 ' . 'Server Error ImgErr01'
            );
            die("Nope! Disallowed width.");
        }

        return $width;
    }
}

The getWidth method was extended with some primitive checks – the first to make sure the image has a size extension (so no more generating original-size images! – that saves bandwidth, too) – and the second to make sure the size is allowed, by comparing it to the $allowedWidths array property in the controller.

Naturally, this can be extended with heights, sizing mode, and all the other Glide options on offer.

A Note on Saving with Glide

Our images are now being safely resized and rendered.

Unfortunately, Glide is “PHP only” in that it is not possible to have a server point to an image directly after it’s been generated. This is because the generated images always looks like this:

- images/
   - myprofile.jpg/
       lhgn3q489uncdue7b9qdny98xq3

… rather than the requested myprofile-320.jpg. The image name will actually be the folder, and the image will be an anonymous file in that folder. This means that in order to get back the myprofile-320.jpg image, PHP will always have to invoke Glide, which will always have to check if the image exists and then serve it, or generate it if it doesn’t.

Generally, this isn’t a dealbreaker due to the images being served with extra long-lasting headers:

        header('Cache-Control:'.'max-age=31536000, public');
        header('Expires:'.date_create('+1 years')->format('D, d M Y H:i:s').' GMT');

… and the fact that a single PHP request to serve an image before, again, using those super long expiry headers, isn’t that much of an overhead, but is something you might want to keep in mind when planning that next high traffic app. The situation can further be improved by putting a pull zone in front of everything, and even a more powerful caching server like Varnish, but that’s a story for another day.

Alternatively…

To completely bypass the PHP cycle in the subsequent renders, you could do something like this:

    public function renderImage($image)
    {
        $width = $this->getWidth($image);

        $imageSource = str_replace('-'.$width, '', $image);

        $imagePath = $this->glide->makeImage($imageSource, ['w' => $width]);
        $imageBase = $this->glide->getCache()->read($imagePath);
        $this->glide->getCache()->put($image, $imageBase);
        $this->glide->outputImage($imageSource, ['w' => $width]);
    }

makeImage creates and saves the image, but returns only its path. We then read the image’s contents with said path from the cache. Finally, we re-save the image under its originally requested name, and then output like we did before. Thus, only this first call will be expensive (several I/O operations and a conversion), and all future calls to this image URL will go straight to the image – bypassing PHP completely. In fact, if you power down PHP with sudo service php-fpm stop, the image will still load.

Conclusion

In this tutorial, we looked at the ease of use of Glide, an image manipulation package which can generate modified images on-demand, saving you the trouble of having to generate them before deploying your application.

Are you using Glide? An alternative approach perhaps? Let us know!

Frequently Asked Questions (FAQs) on Dynamic On-Demand Image Resizing with Glide

How Does Glide Perform Dynamic Image Resizing?

Glide is a powerful open-source PHP library that facilitates on-the-fly image processing. It works by taking an original image and parameters defined in the URL, such as size, filters, and effects, to generate a modified version of the image. The process involves Glide receiving an HTTP request, interpreting the parameters, and then resizing or manipulating the image accordingly. This dynamic resizing allows for a more efficient and flexible approach to handling images on your website.

What Are the Benefits of Using Glide for Image Resizing?

Glide offers several advantages for image resizing. Firstly, it allows for dynamic, on-demand image manipulation, meaning you can adjust images in real-time based on specific requirements. This eliminates the need for multiple versions of the same image in different sizes. Secondly, Glide supports a wide range of operations including cropping, resizing, brightness adjustments, and applying effects like blur. Lastly, Glide uses HTTP caching headers to optimize performance, reducing the load on your server and improving page load times.

How Can I Install and Use Glide in My Project?

Glide can be easily installed using Composer, a tool for dependency management in PHP. You can add Glide to your project by running the command composer require league/glide. Once installed, you can use Glide’s API to define your image manipulations. For example, to resize an image, you would use the resize method and provide the desired width and height as parameters.

Can Glide Handle Different Image Formats?

Yes, Glide is capable of handling various image formats including JPEG, PNG, and GIF. It uses the GD library or Imagick extension in PHP to process images, both of which support a wide range of image formats. This makes Glide a versatile tool for image manipulation in your web projects.

Is Glide Suitable for Large-Scale Web Applications?

Glide is designed to be efficient and scalable, making it suitable for both small and large-scale web applications. It uses caching to improve performance, meaning it can handle a high volume of image processing requests without overloading your server. However, as with any tool, it’s important to monitor your application’s performance and make adjustments as necessary.

How Does Glide Compare to Other Image Resizing Tools?

Glide stands out for its simplicity, flexibility, and performance. Unlike some other tools, Glide allows for dynamic, on-demand image manipulation, meaning you can adjust images in real-time based on specific requirements. It also supports a wide range of operations and image formats. However, the best tool for you will depend on your specific needs and the nature of your project.

Can I Use Glide Without Composer?

While it’s recommended to use Composer for managing PHP dependencies, it’s possible to use Glide without it. You would need to manually download the Glide library and include it in your project. However, this approach is more complex and may not provide the same level of compatibility and ease of updates as using Composer.

How Can I Optimize Performance When Using Glide?

Glide uses HTTP caching headers to optimize performance. This means that once an image has been processed, the modified version is stored in the cache and can be quickly retrieved for subsequent requests. You can also optimize performance by ensuring your server is properly configured and by monitoring your application’s performance regularly.

Can Glide Process Images Stored in Different Locations?

Yes, Glide can process images stored in various locations. It uses a file system abstraction layer, which means it can work with images stored locally, on a remote server, or even in a cloud storage service like Amazon S3. This makes Glide a flexible solution for projects with diverse storage requirements.

Is Glide Secure for Image Processing?

Glide is designed with security in mind. It uses secure hash-based URLs to prevent unauthorized image manipulations. Additionally, Glide does not directly expose your original images, providing an extra layer of protection. However, as with any tool, it’s important to follow best practices for web security and to keep your software up to date.

Bruno SkvorcBruno Skvorc
View Author

Bruno is a blockchain developer and technical educator at the Web3 Foundation, the foundation that's building the next generation of the free people's internet. He runs two newsletters you should subscribe to if you're interested in Web3.0: Dot Leap covers ecosystem and tech development of Web3, and NFT Review covers the evolution of the non-fungible token (digital collectibles) ecosystem inside this emerging new web. His current passion project is RMRK.app, the most advanced NFT system in the world, which allows NFTs to own other NFTs, NFTs to react to emotion, NFTs to be governed democratically, and NFTs to be multiple things at once.

BrunoSgdgenerate imageglideimage editingimage resizeOOPHPPHP
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week