A simple solution for image processing on the fly with result caching

    Today, in almost any web application that uses images, there is a need to create small copies of these images with a possible additional modification, for example: watermark, shades of gray, sepia, etc.
    For details, we denote the following list of requirements:

    1. resize images to any size (adding new sizes should not cause a headache)
    2. image modification: adding a watermark, applying grayscale, sepia effects and generally adding new effects should not be a difficult task
    3. image processing should not affect the main stream (page loading speed)
    4. to speed up the loading of images on the page, the solution should allow to bypass the limit of simultaneous connections in browsers, more about the limit ( rus )
    5. avoid the possibility of clogging the server by explicitly passing the resize parameters to url
    6. cache work results


    Decision


    To begin, I argue the choice of means to solve this problem. Among the considered libraries for working with images in PHP, I identified 3 main ones: Wideimage , PHPThumb , Imagine . My choice fell on the latter, since the first two have not been updated for a very long time and it is not clear whether they will be. I chose Twig as the template engine for which I adapted this solution. Well, as the web servers I chose Nginx and Apache. Nginx - for dedicated servers, and Apache for health on shared hosting.

    GitHub repository A

    ready-made solution looks like this:
    in PHP
    echo thumb(__DIR__ . '/images/Chrysanthemum.jpg', '200x100', [ "watermark" => "right top" ]);
    

    on twig

    For this filter to be available, you need to connect the extension for Twig.
    $twig->addExtension(new \Bazalt\Thumbs\Extension());
    


    1. Resize images for any size

    As you can see, the second parameter of the function thumbtakes the size of the image; by default, the algorithm for trimming the edges to the specified size is valid if, after a proportional reduction in size, one of the sides climbs out. If one of the parameters is set to 0, then the image will be proportionally reduced, and the size of the second side will be proportionally calculated, for example, an image of size 400x300 with a parameter of size '200x0' at the output will have a size of 200x150, and with a size of '0x200' - 266x200.

    2. Image modification

    Expanding the functionality of image modification is made very simple. There is an Operations class , which in the basic version has only a function size. To add your own functionality, you just need to inherit it and describe the necessary functions in it. The third parameter of the function thumbis responsible precisely for calling these additional modifiers, this is an array whose key is the name of the function, and the value of the option that will be passed to this function.

    Example of several modifiers
    class Operations extends \Bazalt\Thumbs\Operations
    {
        public function watermark(\Imagine\Image\ImageInterface $image, $options, $allOptions)
        {
            $imagine = new \Imagine\Gd\Imagine();
            $wm = $imagine->open(__DIR__ . '/images/watermark.png');
            $size = $image->getSize();
            $wmSize = $wm->getSize();
            list($x, $y) = explode(' ', $options);
            if (!is_numeric($x)) {
                $x = ($x == 'right') ? ($size->getWidth() - $wmSize->getWidth()) : 0;
                if ($x < 0) $x = 0;
            }
            if (!is_numeric($y)) {
                $y = ($y == 'bottom') ? ($size->getHeight() - $wmSize->getHeight()) : 0;
                if ($y < 0) $y = 0;
            }
            $point = new \Imagine\Image\Point($x, $y);
            return $image->paste($wm, $point);
        }
        public function grayscale(\Imagine\Image\ImageInterface $image, $options, $allOptions)
        {
            $image->effects()->grayscale();
            return $image;
        }
        public function sepia(\Imagine\Image\ImageInterface $image, $options, $allOptions)
        {
            $image->effects()
                  ->grayscale()
                  ->colorize(new \Imagine\Image\Color('#643200'));
            return $image;
        }
    }
    



    3. The removal of image processing from the main stream

    Processing the image immediately in the place where it appears in the code is bad, because with the increase in the number of images the page loading speed also increases. The solution is simple - the generation of the processed image only upon request to it. There are a lot of ready-made solutions on the Internet, what differs from what I have already created is that I propose to save the image processing parameters not in the url in the form of GET request parameters or parts of the url itself, but to create a certain configuration file and write the path to the image there, sizes thumbnails and all other options. Looking ahead, I want to note right away that this also solves the 5 point of the set requirements.

    4. Bypassing the limit of simultaneous connections in browsers

    According to the HTTP 1.1 standard, a browser cannot load more than 2 requests from the same domain at the same time. Decision? Make several subdomains for statics ( cookieless domain ) at the same time and traffic will be saved due to the fact that extra cookies will not be transmitted.

    In the code, you can use one of 3 options:
    // данный вариант я оставил для того чтобы можно было потестировать
    // локально без поднятия веб-сервера, через `php -S localhost:8080`
    \Bazalt\Thumbs\Image::initStorage(__DIR__ . '/static', '/thumb.php?file=/static');
    // этот вариант подходит если коректно настроет веб-сервер
    \Bazalt\Thumbs\Image::initStorage(__DIR__ . '/static', '/static');
    // данный вариант для коректно настроеного веб-сервера и cookieless доменов
    \Bazalt\Thumbs\Image::initStorage(__DIR__ . '/static', 'http://img%s.example.com/static');
    // на место %s подставляется значение в пределах 0x0-0xF в шестнадцатеричном представлении
    // img0.example.com, img1.example.com, ..., imge.example.com, imgf.example.com
    


    A large project is considering a CDN option, but this is beyond the scope of this article.

    5. Avoiding the possibility of clogging the server by explicitly passing the resize parameters to url

    As partially described in paragraph 3, in the url by which the thumbnail is generated, there are no explicit parameter indications, which protects against parameter enumeration in the url. Of course, this problem could be solved by generating a secret key in addition to the request parameters and checking it on the server, but I believe that passing parameters to url has its own limitations. I also want to emphasize the plus in my decision that you can configure the web server so that it will check for thumbnails even without a script request.

    6. Caching work results

    The thumb function simply generates a configuration file if one is not already. The name of this file is calculated by the hash of the processing parameters and the name of the image file. The name of the configuration file matches the name of the thumbnail plus some extension. For example, if the address " /static/d1/7e/d17e248758722c42d8c88d21d8b538d7.jpg" should be a thumbnail, then the configuration file will be called " /static/d1/7e/d17e248758722c42d8c88d21d8b538d7.jpg.pre".
    Processing is already performed when the browser sends a second request for a thumbnail.

    Settings for nginx:
    location /static/ {
        root /www/public;
        try_files $uri /thumb.php?file=$uri;
    }
    


    For Apache:
    RewriteCond %{REQUEST_URI} ^(/static/)
    RewriteCond %{SCRIPT_FILENAME} !-f
    RewriteRule ^(.*)$ thumb.php?file=$1 [L]
    


    As you can see from the settings, if the file already exists, the web server gives it directly, and if not, the thumb.php file will be called, which will create a thumbnail and save it to disk.
    These settings are not ideal, just to show the idea.

    As a result, we have a simple solution to such a frequent task, I tried to describe all the pitfalls that I encountered, if I missed something, I will be grateful for piece-rate comments.

    Also popular now: