Ресайз зображень на льоту за допомогою Nginx та LuaJIT (OpenResty)

Вже давно, надихнувшись статтею Ресайз зображень на льоту був налаштований ресайз зображень за допомогою ngx_http_image_filter_module і все працювало як слід. Але виникла одна проблема, коли менеджеру знадобилося отримувати зображення з точними розмірами для заливки деякі послуги, т.к. це були їхні технічні вимоги. Наприклад, якщо ми маємо оригінал зображення розміром 1200 × 1200, і при ресайзі ми пишемо щось на зразок ?resize=600×400, то отримаємо пропорційно зменшене зображення за найменшим краєм, розміром 400 × 400. Також неможливо отримати зображення з більшою роздільною здатністю (upscale). Тобто. ?resize=1500×1500 поверне все теж зображення 1200 × 1200

На допомогу прийшла стаття OpenResty: перетворюємо NGINX на повноцінний сервер додатків для розуміння як працює Nginx з Lua і сама бібліотека для Lua isage/lua-imagick - Lua pure-c bindings to ImageMagick. Чому було обрано таке рішення, а не, скажімо, що-небудь на python - тому що це швидко та зручно. Вам навіть не потрібно створювати жодних файлів, все прямо в конфізі Nginx (не обов'язково).

Отже, що нам знадобиться

Приклади наведені на базі Debian.

Установка nginx та nginx-extras

apt-get update
apt-get install nginx-extras

Установка LuaJIT

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

Установка imagemagick

apt-get -y install imagemagick

та бібліотеки чарівна паличка до нього, в моєму випадку для 6 версії

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

Складання lua-imagick

Клонуємо репозиторій (ну або забираємо zip та розпаковуємо)

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

Якщо все пройшло успішно, можна настроїти 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=400×200
200 × 200

?resize=400×200^
400 × 400

?resize=400×200!
400×200 (Не пропорційно)

?resize=400×200+
400×200 (Пропорційно)

Ресайз зображень на льоту за допомогою Nginx та LuaJIT (OpenResty)

Підсумок

Враховуючи всю міць та простоту такого підходу можна реалізувати речі з досить складною логікою, наприклад додавати watermark'і або реалізувати авторизацію з розмежованим доступом. Для того, щоб дізнатися про можливості API для роботи із зображеннями, можна звернутися до документації бібліотеки isage/lua-imagick

Джерело: habr.com

Додати коментар або відгук