Resize images on the fly using Nginx and LuaJIT (OpenResty)

For quite some time now, inspired by the article Resizing Images on the Fly , image resizing with ngx_http_image_filter_module was set up and everything worked as it should. But there was one problem when the manager needed to get images with exact sizes for uploading to some services, because these were their technical requirements. For example, if we have an original image of size 1200x1200 , and when resizing we write something like ? Resize = 600x400 , we will get a proportionally reduced image at the smallest edge, size 400x400 . It is also impossible to obtain an image with a higher resolution (upscale). Those. ? resize = 1500x1500 will return all the same image1200x1200


An OpenResty article came to the rescue: we turn NGINX into a full-fledged application server to understand how Nginx works with Lua and the library for Lua isage / lua-imagick itself - Lua pure-c bindings to ImageMagick. Why such a solution was chosen, and not, say, something in python - because it is fast and convenient. You don’t even need to create any files, everything is right in the Nginx config (optional).


So what do we need


Examples will be given based on Debian.


Install nginx and nginx-extras


apt-get update
apt-get install nginx-extras

Install LuaJIT


apt-get -y install lua5.1 luajit-5.1 libluajit-5.1-dev

Install imagemagick


apt-get -y install imagemagick

and the magickwand libraries to it, in my case for version 6


apt-cache search libmagickwand
apt-get -y install libmagickwand-6.q16-3 libmagickwand-6.q16-dev

Lua-imagick build


We clone the repository (well, or take the zip and unpack it)


cd ~
git clone https://github.com/isage/lua-imagick.git
cd lua-imagick
mkdir build
cd build
cmake ..
make
make install

If everything went well, you can configure Nginx.


backend , , , . Nginx, () . .


nginx backend config
# Backend image server
server {
    listen       8082;
    listen [::]:8082;
    set $files_root /var/www/example.lh/frontend/web;
    root $files_root;
    access_log off;
    expires 1d;

    location /files {
        #   
        set $w 700;
        set $h 700;
        set $q 89;

        #1-89 allowed
        if ($arg_q ~ "^([1-9]|[1-8][0-9])$") {
            set $q $arg_q;
        }

        if ($arg_resize ~ "([\d\-]+)x([\d\+\!\^]+)") {  
            set $w $1;
            set $h $2;
            rewrite  ^(.*)$   /resize/$w/$h/$q$uri     last;
        }

        rewrite  ^(.*)$   /resize/$w/$h/$q$uri     last;
    }

    location ~* ^/resize/([\d]+)/([\d\+\!\^]+)/([\d]+)/files/(.+)$ {
        default_type 'text/plain';

        set $w $1;
        set $h $2;
        set $q $3;
        set $fname $4;

        #     Lua    
        # content_by_lua_file /var/www/some.lua;
        # lua_code_cache off; #dev
        content_by_lua '
        local magick = require "imagick"
        local img = magick.open(ngx.var.files_root .. "/files/" .. ngx.var.fname)
        if not img then ngx.exit(ngx.HTTP_NOT_FOUND) end
        img:set_gravity(magick.gravity["CenterGravity"])

        if string.match(ngx.var.h, "%d+%+") then
            local h = string.gsub(ngx.var.h, "(%+)", "")
            resize = ngx.var.w .. "x" .. h
            --  png   
            img:set_bg_color(img:has_alphachannel() and "none" or img:get_bg_color())
            img:smart_resize(resize)
            img:extent(ngx.var.w, h)
        else
                img:smart_resize(ngx.var.w .. "x" .. ngx.var.h)
        end

        if ngx.var.arg_q then img:set_quality(ngx.var.q) end

        ngx.say(img:blob())
        ';
    }
}

# Upstream
upstream imageserver {
    server localhost:8082;
}

server {
    listen 80;
    server_name examaple.lh;

    #   jpg  png   imageserver
    location ~* ^/files/.+\.(jpg|png) {
        proxy_buffers 8 2m;
        proxy_buffer_size 10m;
        proxy_busy_buffers_size 10m;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        proxy_pass     http://imageserver;  # Backend image server
    }
}

, ( ) img:extent() resize + .


:


  • WxH (Keep aspect-ratio, use higher dimension)
  • WxH^ (Keep aspect-ratio, use lower dimension (crop))
  • WxH! (Ignore aspect-ratio)
  • WxH+ (Keep aspect-ratio, add side borders)


uri
?resize=400x200200x200
?resize=400x200^400x400
?resize=400x200!400x200 ( )
?resize=400x200+400x200 ()



Given the power and simplicity of this approach, you can implement things with a rather complicated logic, for example, add watermark'i or implement authorization with limited access. In order to find out the capabilities of the API for working with images, you can refer to the documentation of the isage / lua-imagick library


All Articles