Image resizing on the fly with Nginx and LuaJIT (OpenResty)

For a long time, inspired by the article Image resizing on the fly image resizing was configured with ngx_http_image_filter_module and everything worked as it should. But there was one problem when the manager needed to get images with exact dimensions for uploading to some services, because. those were their specifications. For example, if we have an original image of size 1200 Γ— 1200, and when resizing we write something like ?resize=600Γ—400, then we get a proportionally reduced image along the smallest edge, size 400 Γ— 400. It is also impossible to get an image with a higher resolution (upscale). Those. ?resize=1500Γ—1500 will return all the same image 1200 Γ— 1200

Article came to the rescue OpenResty: turning NGINX into a full-fledged application server to understand how Nginx works with Lua and the library itself for Lua isage/lua-imagick - 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

The examples will be based on Debian.

Installing nginx and nginx-extras

apt-get update
apt-get install nginx-extras

Installing LuaJIT

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

Installing imagemagick

apt-get -y install imagemagick

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

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

Building lua-imagick

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.

I will give an example of the backend host config, which, in fact, is engaged in resizing. It is proxied by the front server in the same way as with Nginx, on which caching takes place for a certain time (day), etc. things.

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
    }
}

What was required (expanding the image at the edges) is done with img:extent() and is determined using the parameter resize with sign + at the end.

The following options are available:

  • 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)

Pivot table with resize results

Request uri parameter
Output image size

?resize=400Γ—200
200 Γ— 200

?resize=400Γ—200^
400 Γ— 400

?resize=400Γ—200!
400Γ—200 (Not proportional)

?resize=400Γ—200+
400Γ—200 (Proportional)

Image resizing on the fly with Nginx and LuaJIT (OpenResty)

Π‘onclusion

Given the power and simplicity of this approach, you can implement things with rather complex logic, for example, add watermarks or implement authorization with limited access. In order to learn about the capabilities of the API for working with images, you can refer to the library documentation. isage/lua-imagick

Source: habr.com

Add a comment