Nginx filter module for adding overlays on JPEGs on-the-fly with libmodjpeg.
With libmodjpeg you can overlay a (masked) image onto an existing JPEG as lossless as possible. Changes in the JPEG only take place where the overlayed image is applied. All modifications happen in the DCT domain, thus the JPEG is decoded and encoded losslessly.
This filter module can add overlays (e.g. a logo, visual watermark) on JPEGs when they are requested.
A few ideas:
- Consider you are a photographer and have a image gallery on your website. Without hardcoding your logo (brand, watermark, ...) into these images you can apply it the moment the image is requested. Whenever you update your logo, just update the nginx configuration and it's done. No need to re-process all your images.
- You have an online shop with thousands of product images. With just configuring nginx you can add your logo to all of the product images. You don't have to process all product images.
- You have a paid service. Add a watermark to all images if the user is not subscribed. If the user is subscribed, don't apply the watermark or put just a small logo on the images without touching the original images.
- On your website, registered users can upload images. Add the avatar of the user to the image who uploaded the image without processing it after the upload. If the user changes her avatar, all her images will automatically have the new avatar on them.
In order to try out this filter module, pull the docker image
docker pull ioppermann/modjpeg-nginx:latest
The docker container exposes TCP port 80 and expects a directory with images mounted on /images
, e.g.
docker run -it --rm --name=modjpeg-nginx \
--mount type=bind,src=$PWD/images,dst=/images,readonly \
-p 8080:80 \
ioppermann/modjpeg-nginx:latest
Now you can browse to http://localhost:8080/ and click on the listed images. The modjpeg logo will be applied in the top left corner. By default
only images that are smaller than 10MB are processed by the filter. Stop the container by pressing Ctrl-c
.
The filter can be controlled by these environment variables:
Name | Default | Description |
---|---|---|
MJ_GRACEFUL | on | See jpeg_filter_graceful |
MJ_BUFFER | 10M | See jpeg_filter_buffer |
MJ_MAX_PIXEL | 0 | See jpeg_filter_max_pixel |
MJ_DROPON_ALIGN | "top left" | See jpeg_filter_dropon_align |
MJ_DROPON_OFFSET | "0 0" | See jpeg_filter_dropon_offset |
MJ_DROPON_FILE | "/usr/local/nginx/conf/dropon.png" | See jpeg_filter_dropon_file |
The following example will allow images with up to 150 megapixel (MJ_MAX_PIXEL
) and 100MB in file size (MJ_BUFFER
). The logo will be placed in bottom right corner (MJ_DROPON_ALIGN
)
with an offset of -15px horizontally and vertically (MJ_DROPON_OFFSET
).
docker run -it --rm --name=modjpeg-nginx \
--mount type=bind,src=$PWD/images,dst=/images,readonly \
-p 8080:80 \
-e MJ_MAX_PIXEL=150000000 \
-e MJ_BUFFER=100M \
-e MJ_DROPON_ALIGN="bottom right" \
-e MJ_DROPON_OFFSET="-15 -15" \
ioppermann/modjpeg-nginx:latest
In order to change the logo, you can mount an additional volume or put it into the directory you already mount, e.g.
docker run -it --rm --name=modjpeg-nginx \
--mount type=bind,src=$PWD/images,dst=/images,readonly \
-p 8080:80 \
-e MJ_DROPON_FILE="/images/logo.png" \
ioppermann/modjpeg-nginx:latest
An easy way to use the module in CentOS or RedHat 7, is to use precompiled dynamic nginx module. It is built for nginx stable. The repository includes latest stable nginx, the jpeg module, libmodjpeg and libpng16 dependencies:
yum -y install https://extras.getpagespeed.com/release-el7-latest.rpm
yum install nginx nginx-module-jpeg
For using the modjpeg-nginx filter module, follow these steps:
- Clone and install libmodjpeg (libjpeg and cmake are required)
- Clone this repository
- Download and extract the latest nginx
- Configure, compile, and install nginx
# Clone and install libmodjpeg
git clone https://github.com/ioppermann/libmodjpeg.git
cd libmodjpeg
cmake .
make
make install
cd ..
# Clone modjpeg-nginx
git clone https://github.com/ioppermann/modjpeg-nginx.git
# Download and install nginx
wget 'http://nginx.org/download/nginx-1.19.2.tar.gz'
tar -xvzf nginx-1.19.2.tar.gz
cd nginx-1.19.2
# Configure as static module, or ...
./configure --add_module=../modjpeg-nginx
# ... configure as dynamic module (as of nginx 1.9.11)
./configure --add_dynamic_module=../modjpeg-nginx
# If the libmodjpeg library is not found, add e.g. '--with-ld-opt=-L/usr/local/lib' to
# the configure options if it was installed to /usr/local/lib
# You may want to use the other './configure' options that are used
# in your current nginx build. Check the output of 'nginx -V'.
make
make install
If you configured modjpeg-nginx as dynamic module, you have to load the module in the beginning of the config
...
load_module modules/ngx_http_jpeg_filter_module.so;
...
This module has been tested with the following versions of nginx:
- 1.26.2
- 1.24.0
- 1.22.1
- 1.20.2
- 1.19.2
- 1.18.0
- 1.17.6
- 1.16.1
- 1.15.3
- 1.14.0
- 1.13.10
- 1.12.2
- 1.10.3
- 1.8.1 (only as static module)
...
location /gallery {
# enable jpeg filter module
jpeg_filter on;
# limit image sizes to 9 megapixel
jpeg_filter_max_pixel 9000000;
# limit image file size to 5 megabytes
jpeg_filter_buffer 5M;
# deliver the images unmodified if one of the limits apply
jpeg_filter_graceful on;
# pixelate the image
jpeg_filter_effect pixelate;
# add a masked logo in the bottom right corner
# with a distance of 10 pixel from the border
jpeg_filter_dropon_align bottom right;
jpeg_filter_dropon_offset -10 -10;
jpeg_filter_dropon_file /path/to/logo.jpg /path/to/mask.jpg;
}
...
Or use it with OpenResty's ngx_http_lua_module and a PNG logo:
...
location /gallery {
set_by_lua_block $valign {
local a = { 'top', 'center', 'bottom' }
return a[math.random(#a)]
}
set_by_lua_block $halign {
local a = { 'left', 'center', 'right' }
return a[math.random(#a)]
}
# enable jpeg filter module
jpeg_filter on;
# limit image sizes to 9 megapixel
jpeg_filter_max_pixel 9000000;
# limit image file size to 5 megabytes
jpeg_filter_buffer 5M;
# deliver the images unmodified if one of the limits apply
jpeg_filter_graceful on;
# pixelate the image
jpeg_filter_effect pixelate;
# add a logo in a random position
jpeg_filter_dropon_align $valign $halign;
jpeg_filter_dropon_file /path/to/logo.png;
}
...
Or generate a logo with Lua-GD:
http {
...
lua_package_cpath '/usr/local/lib/lua/5.1/?.so;;';
...
server {
...
location /gallery {
set_by_lua_block $logobytestream {
local gd = require "gd"
local im = gd.create(210, 70)
local white = im:colorAllocate(255, 255, 255)
local black = im:colorAllocate(0, 0, 0)
im:filledRectangle(0, 0, 140, 80, white)
im:string(gd.FONT_LARGE, 10, 10, "Hello modjpeg", black)
im:string(gd.FONT_LARGE, 10, 40, os.date("%c"), black);
return im:jpegStr(85)
}
# enable jpeg filter module
jpeg_filter on;
# limit image sizes to 9 megapixel
jpeg_filter_max_pixel 9000000;
# limit image file size to 5 megabytes
jpeg_filter_buffer 5M;
# deliver the images unmodified if one of the limits apply
jpeg_filter_graceful on;
# pixelate the image
jpeg_filter_effect pixelate;
# add a generated logo in the bottom right corner
# with a distance of 10 pixel from the border
jpeg_filter_dropon_align bottom right;
jpeg_filter_dropon_offset -10 -10;
jpeg_filter_dropon_memory $logobytestream;
}
...
}
...
}
- jpeg_filter
- jpeg_filter_max_pixel
- jpeg_filter_buffer
- jpeg_filter_optimize
- jpeg_filter_progressive
- jpeg_filter_arithmetric
- jpeg_filter_graceful
- jpeg_filter_effect
- jpeg_filter_dropon_align
- jpeg_filter_dropon_offset
- jpeg_filter_dropon_file
- jpeg_filter_dropon_memory
- Notes
Syntax: jpeg_filter on | off
Default: jpeg_filter off
Context: location
Enable the jpeg filter module.
This directive is turned off by default.
Syntax: jpeg_filter_max_pixel pixel
Default: 0
Context: http, server, location
Maximum number of pixel in image to operate on. If the image has more pixel (width * height) than pixel
, the jpeg filter will return a "415 Unsupported Media Type".
Set jpeg_filter_graceful to on
to deliver the image unchanged. Set the maximum pixel to 0 in order ignore the image dimensions.
This directive is set to 0 by default.
Syntax: jpeg_filter_buffer size
Default: 2M
Context: http, server, location
The maximum file size of the image to operate on. If the file size if bigger than size
, the jpeg filter will return a "415 Unsupported Media Type".
Set jpeg_filter_graceful to on
to deliver the image unchanged.
This directive is set to 2 megabyte by default.
Syntax: jpeg_filter_optimize on | off
Default: off
Context: http, server, location
Upon delivery, optimize the Huffman tables of the image.
This directive is turned off by default.
Syntax: jpeg_filter_progressive on | off
Default: off
Context: http, server, location
Upon delivery, enable progressive encoding of the image.
This directive is turned off by default.
Syntax: jpeg_filter_arithmetric on | off
Default: off
Context: http, server, location
Upon delivery, enable arithmetric encoding of the image. This will override the jpeg_filter_optimize directive. Arithmetric encoding is usually not supported by browsers.
This directive is turned off by default.
Syntax: jpeg_filter_graceful on | off
Default: off
Context: http, server, location
Allow to deliver the unchanged image in case the directives jpeg_filter_max_pixel or jpeg_filter_buffer would return a "415 Unsupported Media Type" error.
This directive is turned off by default.
Syntax: jpeg_filter_effect grayscale | pixelate
Syntax: jpeg_filter_effect darken | brighten value
Syntax: jpeg_filter_effect tintblue | tintyellow | tintred | tintgreen value
Default: -
Context: location
Apply an effect to the image.
grayscale
will remove all color components from the image. This only applies to images in the YCbCr color space.
pixelate
will pixelate the image in blocks of 8x8 pixel by setting the AC coefficients in all components to 0.
darken
will darken the image by decreasing the DC coefficients in the Y component by value
. This only applies to images in the YCbCr color space.
brighten
will brighten the image by increasing the DC coefficients in the Y component by value
. This only applies to images in the YCbCr color space.
tintblue
will tint the image blue by increasing the DC coefficients in the Cb component by value
. This only applies to images in the YCbCr color space.
tintyellow
will tint the image blue by decreasing the DC coefficients in the Cb component by value
. This only applies to images in the YCbCr color space.
tintred
will tint the image red by increasing the DC coefficients in the Cr component by value
. This only applies to images in the YCbCr color space.
tintgreen
will tint the image green by decreasing the DC coefficients in the Cr component by value
. This only applies to images in the YCbCr color space.
This directive is not set by default.
All parameters can contain variables.
Syntax: jpeg_filter_dropon_align [top | center | bottom] [left | center | right]
Default: center center
Context: location
Align the dropon on the image. Use the directive jpeg_filter_dropon_offset to offset the dropon from the alignment.
This directive must be set before jpeg_filter_dropon in order to have an effect on the dropon.
This directive will apply the dropon in the center of the image by default.
All parameters can contain variables.
Syntax: jpeg_filter_dropon_offset vertical horizontal
Default: 0 0
Context: location
Offset the dropon by vertical
and horizontal
pixels from the alignment given with the jpeg_filter_dropon_align directive.
Use a negative value to move the dropon up or left and a positive value to move the dropon down or right.
This directive must be set before jpeg_filter_dropon in order to have an effect on the dropon.
This directive will not apply an offset by default.
All parameters can contain variables.
Syntax: jpeg_filter_dropon_file image
Syntax: jpeg_filter_dropon_file image mask
Default: -
Context: location
Apply a dropon to the image. The dropon is given by a path to a JPEG or PNG image for image
and optionally a path to a JPEG image for mask
. If no mask image is
provided, the image will be applied without transcluency. If a mask image is provided, only the luminance component will be used. For the mask, black means
fully transcluent and white means fully opaque. Any values inbetween will blend the underlying image and the dropon accordingly. If image
is a path to a PNG, the
mask will be ignored.
This directive is not set by default.
All parameters can contain variables.
If none of the parameters contain variables, the dropon is loaded during loading of the configuration. If at least one parameter contains variables, the dropon will be loaded during processing of the request. After processing the request, the dropon will be unloaded.
PNG files as dropon are supported only if libmodjpeg has been compiled with PNG support.
Syntax: jpeg_filter_dropon_memory $image
Syntax: jpeg_filter_dropon_memory $image $mask
Default: -
Context: location
Apply a dropon to the image. The dropon is given by a variable holding a JPEG or PNG image bytestream for $image
and optionally a variable to a JPEG image bytestream for $mask
.
If no mask image is provided, the image will be applied without transcluency. If a mask image is provided, only the luminance component will be used. For the mask,
black means fully transcluent and white means fully opaque. Any values inbetween will blend the underlying image and the dropon accordingly. If $image
is a PNG, the
mask will be ignored.
This directive is not set by default.
All parameters are expected to be variables.
The dropon will always be loaded during processing of the request. After processing the request, the dropon will be unloaded.
PNG bytestreams as dropon are supported only if libmodjpeg has been compiled with PNG support.
The directives jpeg_filter_effect
, jpeg_filter_dropon_align
, jpeg_filter_dropon_offset
, and jpeg_filter_dropon
are applied in the order they
appear in the nginx config file, i.e. it makes a difference if you apply first an effect and then add a dropon or vice versa. In the former case the dropon will be
unaffected by the effect and in the latter case the effect will be also applied on the dropon.
This module is distributed under the BSD license. Refer to LICENSE.
This module is heavily inspired by the nginx image filter module with insights from "Emiller’s Guide To Nginx Module Development" and the nginx development guide.