On-the-fly image resizing using Nginx and LuaJIT (OpenResty)

It has been quite some time since I was inspired by an article Resize images on the fly that set up image resizing using ngx_http_image_filter_module and everything was working as intended. However, one problem arose when the manager needed to obtain images with exact dimensions for uploads to certain services, as these were their technical requirements. For example, if we have an original image size of 1200×1200, and during the resizing we specify something like ?resize=600×400, we get an image proportionally reduced along the smallest edge to a size of 400×400. It is also impossible to obtain an image with higher resolution (upscale). That is, ?resize=1500×1500 will return the same image 1200×1200

An article came to the rescue OpenResty: Transforming NGINX into a Full-Fledged Application Server to understand how Nginx works with Lua and the Lua library itself isage/lua-imagick — Lua pure-c bindings to ImageMagick. Why this solution was chosen instead of, say, something in Python — because it is fast and convenient. You won't even need to create any files, everything can be done directly in the Nginx configuration (not mandatory).

So, what do we need

Examples will be provided 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 the libraries magickwand for 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 (or download 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 successfully, you can configure Nginx.

I will provide an example of the backend host config, which is responsible for resizing. It is proxied by the front server also with Nginx, where caching occurs for a certain amount of time (a day) and other 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
    }
}

The required task (extending the image at the edges) is done using img:extent() and is defined using the parameter resize with a sign + at the end.

The following parameters 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)

Summary table of resizing 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)

On-the-fly image resizing using Nginx and LuaJIT (OpenResty)

Summary

Considering the power and simplicity of this approach, you can implement features with quite complex logic, such as adding watermarks or enabling access-controlled authorization. To explore the API capabilities for image processing, you can refer to the library documentation. isage/lua-imagick

Source: habr.com

Buy reliable website hosting with DDoS protection, VPS VDS servers 🔥 Buy reliable website hosting with DDoS protection, VPS VDS servers | ProHoster