shumc/imagor
Fast, secure image processing server and Go library, using libvips
100K+
imagor is a fast, secure image processing server and Go library.
imagor uses one of the most efficient image processing library libvips. It is typically 4-8x faster than using the quickest ImageMagick and GraphicsMagick settings. imagor implements libvips streaming that facilitates parallel processing pipelines, achieving high network throughput.
imagor features a ton of image processing use cases, available as a HTTP server with first-class Docker support. It adopts the thumbor URL syntax representing a high-performance drop-in replacement.
imagor is a Go library built with speed, security and extensibility in mind. Alongside there is imagorvideo bringing video thumbnail capability through ffmpeg C bindings.
docker run -p 8000:8000 shumc/imagor -imagor-unsafe -imagor-auto-webp
Original images:
https://raw.githubusercontent.com/cshum/imagor/master/testdata/gopher.png
https://raw.githubusercontent.com/cshum/imagor/master/testdata/dancing-banana.gif
https://raw.githubusercontent.com/cshum/imagor/master/testdata/gopher-front.png
Try out the following image URLs:
http://localhost:8000/unsafe/fit-in/200x200/filters:fill(white)/https://raw.githubusercontent.com/cshum/imagor/master/testdata/gopher.png
http://localhost:8000/unsafe/200x200/smart/filters:fill(white):format(jpeg):quality(80)/https://raw.githubusercontent.com/cshum/imagor/master/testdata/gopher.png
http://localhost:8000/unsafe/fit-in/-180x180/10x10/filters:hue(290):saturation(100):fill(yellow)/raw.githubusercontent.com/cshum/imagor/master/testdata/gopher.png
http://localhost:8000/unsafe/30x40:100x150/filters:fill(cyan)/raw.githubusercontent.com/cshum/imagor/master/testdata/dancing-banana.gif
http://localhost:8000/unsafe/fit-in/200x150/filters:fill(yellow):watermark(raw.githubusercontent.com/cshum/imagor/master/testdata/gopher-front.png,repeat,bottom,0,40,40)/raw.githubusercontent.com/cshum/imagor/master/testdata/dancing-banana.gif
imagor endpoint is a series of URL parts which defines the image operations, followed by the image URI:
/HASH|unsafe/trim/AxB:CxD/fit-in/stretch/-Ex-F/GxH:IxJ/HALIGN/VALIGN/smart/filters:NAME(ARGS):NAME(ARGS):.../IMAGE
HASH
is the URL signature hash, or unsafe
if unsafe mode is usedtrim
removes surrounding space in images using top-left pixel colorAxB:CxD
means manually crop the image at left-top point AxB
and right-bottom point CxD
. Coordinates can also be provided as float values between 0 and 1 (percentage of image dimensions)fit-in
means that the generated image should not be auto-cropped and otherwise just fit in an imaginary box specified by ExF
stretch
means resize the image to ExF
without keeping its aspect ratios-Ex-F
means resize the image to be ExF
of width per height size. The minus signs mean flip horizontally and verticallyGxH:IxJ
add left-top padding GxH
and right-bottom padding IxJ
HALIGN
is horizontal alignment of crop. Accepts left
, right
or center
, defaults to center
VALIGN
is vertical alignment of crop. Accepts top
, bottom
or middle
, defaults to middle
smart
means using smart detection of focal pointsfilters
a pipeline of image filter operations to be applied, see filters sectionIMAGE
is the image path or URI
?
character, this will interfere the URL query and should be encoded with encodeURIComponent
or equivalentFilters /filters:NAME(ARGS):NAME(ARGS):.../
is a pipeline of image operations that will be sequentially applied to the image. Examples:
/filters:fill(white):format(jpeg)/
/filters:hue(290):saturation(100):fill(yellow):format(jpeg):quality(80)/
/filters:fill(white):watermark(raw.githubusercontent.com/cshum/imagor/master/testdata/gopher-front.png,repeat,bottom,10):format(jpeg)/
imagor supports the following filters:
background_color(color)
sets the background color of a transparent image
color
the color name or hexadecimal rgb expression without the “#” characterblur(sigma)
applies gaussian blur to the imagebrightness(amount)
increases or decreases the image brightness
amount
-100 to 100, the amount in % to increase or decrease the image brightnesscontrast(amount)
increases or decreases the image contrast
amount
-100 to 100, the amount in % to increase or decrease the image contrastfill(color)
fill the missing area or transparent image with the specified color:
color
- color name or hexadecimal rgb expression without the “#” character
focal(AxB:CxD)
or focal(X,Y)
adds a focal region or focal point for custom transformations:
AxB
and right-bottom point CxD
, or a point X,Y
.format(format)
specifies the output format of the image
format
accepts jpeg, png, gif, webp, tiff, avifgrayscale()
changes the image to grayscalehue(angle)
increases or decreases the image hue
angle
the angle in degree to increase or decrease the hue rotationlabel(text, x, y, size, color[, alpha[, font]])
adds a text label to the image. It can be positioned inside the image with the alignment specified, color and transparency support:
text
text label, also support url encoded text.x
horizontal position that the text label will be in:
p
e.g. 20p means calculating the value from the image width as percentageleft
,right
,center
align left, right or centered respectivelyy
vertical position that the text label will be in:
p
e.g. 20p means calculating the value from the image height as percentagetop
,bottom
,center
vertical align top, bottom or centered respectivelysize
- text label font sizecolor
- color name or hexadecimal rgb expression without the “#” characteralpha
- text label transparency, a number between 0 (fully opaque) and 100 (fully transparent).font
- text label font typemax_bytes(amount)
automatically degrades the quality of the image until the image is under the specified amount
of bytesmax_frames(n)
limit maximum number of animation frames n
to be loadedorient(angle)
rotates the image before resizing and cropping, according to the angle value
angle
accepts 0, 90, 180, 270proportion(percentage)
scales image to the proportion percentage of the image dimensionquality(amount)
changes the overall quality of the image, does nothing for png
amount
0 to 100, the quality level in %rgb(r,g,b)
amount of color in each of the rgb channels in %. Can range from -100 to 100rotate(angle)
rotates the given image according to the angle value
angle
accepts 0, 90, 180, 270round_corner(rx [, ry [, color]])
adds rounded corners to the image with the specified color as background
rx
, ry
amount of pixel to use as radius. ry = rx if ry is not providedcolor
the color name or hexadecimal rgb expression without the “#” charactersaturation(amount)
increases or decreases the image saturation
amount
-100 to 100, the amount in % to increase or decrease the image saturationsharpen(sigma)
sharpens the imagestrip_exif()
removes Exif metadata from the resulting imagestrip_icc()
removes ICC profile information from the resulting imageupscale()
upscale the image if fit-in
is usedwatermark(image, x, y, alpha [, w_ratio [, h_ratio]])
adds a watermark to the image. It can be positioned inside the image with the alpha channel specified and optionally resized based on the image size by specifying the ratio
image
watermark image URI, using the same image loader configured for imagorx
horizontal position that the watermark will be in:
p
e.g. 20p means calculating the value from the image width as percentageleft
,right
,center
positioned left, right or centered respectivelyrepeat
the watermark will be repeated horizontallyy
vertical position that the watermark will be in:
p
e.g. 20p means calculating the value from the image height as percentagetop
,bottom
,center
positioned top, bottom or centered respectivelyrepeat
the watermark will be repeated verticallyalpha
watermark image transparency, a number between 0 (fully opaque) and 100 (fully transparent).w_ratio
percentage of the width of the image the watermark should fit-inh_ratio
percentage of the height of the image the watermark should fit-inUtility Filters
These filters do not manipulate images but provide useful utilities to the imagor pipeline:
attachment(filename)
returns attachment in the Content-Disposition
header, and the browser will open a "Save as" dialog with filename
. When filename
not specified, imagor will get the filename from the image sourceexpire(timestamp)
adds expiration time to the content. timestamp
is the unix milliseconds timestamp, e.g. if content is valid for 30s then timestamp would be Date.now() + 30*1000
in JavaScript.preview()
skips the result storage even if result storage is enabled. Useful for conditional cachingimagor Loader
, Storage
and Result Storage
are the building blocks for loading and saving images from various sources:
Loader
loads image. Enable Loader
where you wish to load images from, but without modifying it e.g. static directory.Storage
loads and saves image. This allows subsequent requests for the same image loads directly from the storage, instead of HTTP source.Result Storage
loads and saves the processed image. This allows subsequent request of the same parameters loads from the result storage, saving processing resources.imagor provides built-in adaptors that support HTTP(s), Proxy, File System, AWS S3 and Google Cloud Storage. By default, HTTP Loader
is used as fallback. You can choose to enable additional adaptors that fit your use cases.
File System
Docker Compose example with file system, using mounted volume:
version: "3"
services:
imagor:
image: shumc/imagor:latest
volumes:
- ./:/mnt/data
environment:
PORT: 8000
IMAGOR_UNSAFE: 1 # unsafe URL for testing
FILE_LOADER_BASE_DIR: /mnt/data # enable file loader by specifying base dir
FILE_STORAGE_BASE_DIR: /mnt/data # enable file storage by specifying base dir
FILE_STORAGE_MKDIR_PERMISSION: 0755 # optional
FILE_STORAGE_WRITE_PERMISSION: 0666 # optional
FILE_RESULT_STORAGE_BASE_DIR: /mnt/data/result # enable file result storage by specifying base dir
FILE_RESULT_STORAGE_MKDIR_PERMISSION: 0755 # optional
FILE_RESULT_STORAGE_WRITE_PERMISSION: 0666 # optional
ports:
- "8000:8000"
AWS S3
Docker Compose example with AWS S3. Also works with S3 compatible such as MinIO, DigitalOcean Space.
version: "3"
services:
imagor:
image: shumc/imagor:latest
environment:
PORT: 8000
IMAGOR_SECRET: mysecret # secret key for URL signature
AWS_ACCESS_KEY_ID: ...
AWS_SECRET_ACCESS_KEY: ...
AWS_REGION: ...
S3_LOADER_BUCKET: mybucket # enable S3 loader by specifying bucket
S3_LOADER_BASE_DIR: images # optional
S3_STORAGE_BUCKET: mybucket # enable S3 storage by specifying bucket
S3_STORAGE_BASE_DIR: images # optional
S3_STORAGE_ACL: public-read # optional - see https://docs.aws.amazon.com/AmazonS3/latest/userguide/acl-overview.html#canned-acl
S3_RESULT_STORAGE_BUCKET: mybucket # enable S3 result storage by specifying bucket
S3_RESULT_STORAGE_BASE_DIR: images/result # optional
S3_RESULT_STORAGE_ACL: public-read # optional
ports:
- "8000:8000"
Configure custom S3 endpoint for S3 compatible such as MinIO, DigitalOcean Space:
S3_ENDPOINT: http://minio:9000
S3_FORCE_PATH_STYLE: 1
By default, S3 prepends bucket name as subdomain to the request URL:
http://mybucket.minio:9000/image.jpg
this may not be desirable for a self-hosted endpoint. You can also switch to path-style requests using S3_FORCE_PATH_STYLE=1
such that the host remains unchanged:
http://minio:9000/mybucket/image.jpg
Set the following environment variables to override the global AWS Credentials for S3 Loader, Storage and Result Storage:
AWS_LOADER_REGION
AWS_LOADER_ACCESS_KEY_ID
AWS_LOADER_SECRET_ACCESS_KEY
S3_LOADER_ENDPOINT
AWS_STORAGE_REGION
AWS_STORAGE_ACCESS_KEY_ID
AWS_STORAGE_SECRET_ACCESS_KEY
S3_STORAGE_ENDPOINT
AWS_RESULT_STORAGE_REGION
AWS_RESULT_STORAGE_ACCESS_KEY_ID
AWS_RESULT_STORAGE_SECRET_ACCESS_KEY
S3_RESULT_STORAGE_ENDPOINT
Google Cloud Storage
Docker Compose example with Google Cloud Storage:
version: "3"
services:
imagor:
image: shumc/imagor:latest
volumes:
- ./googlesecret:/etc/secrets/google
environment:
PORT: 8000
IMAGOR_SECRET: mysecret # secret key for URL signature
GOOGLE_APPLICATION_CREDENTIALS: /etc/secrets/google/appcredentials.json # google cloud secrets file
GCLOUD_LOADER_BUCKET: mybucket # enable loader by specifying bucket
GCLOUD_LOADER_BASE_DIR: images # optional
GCLOUD_STORAGE_BUCKET: mybucket # enable storage by specifying bucket
GCLOUD_STORAGE_BASE_DIR: images # optional
GCLOUD_STORAGE_ACL: publicRead # optional - see https://cloud.google.com/storage/docs/json_api/v1/objects/insert
GCLOUD_RESULT_STORAGE_BUCKET: mybucket # enable result storage by specifying bucket
GCLOUD_RESULT_STORAGE_BASE_DIR: images/result # optional
GCLOUD_RESULT_STORAGE_ACL: publicRead # optional
ports:
- "8000:8000"
Storage and Result Storage Path Style
Storage
and Result Storage
path style enables additional hashing rules to the storage path when loading and saving images:
IMAGOR_STORAGE_PATH_STYLE=digest
foobar.jpg
becomes e6/86/1a810ff186b4f747ef85f7c53946f0e6d8cb
IMAGOR_RESULT_STORAGE_PATH_STYLE=digest
fit-in/16x17/foobar.jpg
becomes 61/4c/9ba1725e8cdd8263a4ad437c56b35f33deba
IMAGOR_RESULT_STORAGE_PATH_STYLE=suffix
166x169/top/foobar.jpg
becomes foobar.45d8ebb31bd4ed80c26e.jpg
17x19/smart/example.com/foobar
becomes example.com/foobar.ddd349e092cda6d9c729
URL Signature
In production environment, it is highly recommended turning off IMAGOR_UNSAFE
and setting up URL signature using IMAGOR_SECRET
, to prevent DDoS attacks that abuse multiple image operations.
The URL signature hash is based on SHA digest, created by taking the URL path (excluding /unsafe/
) with secret. The hash is then Base64 URL encoded.
An example in Node.js:
const crypto = require('crypto');
function sign(path, secret) {
const hash = crypto.createHmac('sha1', secret)
.update(path)
.digest('base64')
.replace(/\+/g, '-').replace(/\//g, '_')
return hash + '/' + path
}
console.log(sign('500x500/top/raw.githubusercontent.com/cshum/imagor/master/testdata/gopher.png', 'mysecret'))
// cST4Ko5_FqwT3BDn-Wf4gO3RFSk=/500x500/top/raw.githubusercontent.com/cshum/imagor/master/testdata/gopher.png
Custom HMAC Signer
imagor uses SHA1 HMAC signer by default, the same one used by thumbor. However, SHA1 is not considered cryptographically secure. If that is a concern it is possible to configure different signing method and truncate length. imagor supports sha1
, sha256
, sha512
signer type:
IMAGOR_SIGNER_TYPE=sha256
IMAGOR_SIGNER_TRUNCATE=40
The Node.js example then becomes:
const crypto = require('crypto');
function sign(path, secret) {
const hash = crypto.createHmac('sha256', secret)
.update(path)
.digest('base64')
.slice(0, 40)
.replace(/\+/g, '-').replace(/\//g, '_')
return hash + '/' + path
}
console.log(sign('500x500/top/raw.githubusercontent.com/cshum/imagor/master/testdata/gopher.png', 'mysecret'))
// IGEn3TxngivD0jy4uuiZim2bdUCvhcnVi1Nm0xGy/500x500/top/raw.githubusercontent.com/cshum/imagor/master/testdata/gopher.png
Image Bombs Prevention
imagor checks the image type and its resolution before the actual processing happens. The processing will be rejected if the image dimensions are too big, which protects from so-called "image bombs". You can set the max allowed image resolution and dimensions using VIPS_MAX_RESOLUTION
, VIPS_MAX_WIDTH
, VIPS_MAX_HEIGHT
:
VIPS_MAX_RESOLUTION=16800000
VIPS_MAX_WIDTH=5000
VIPS_MAX_HEIGHT=5000
Allowed Sources
Whitelist specific hosts to restrict loading images only from the allowed sources using HTTP_LOADER_ALLOWED_SOURCES
. Accept csv wth glob pattern e.g.:
HTTP_LOADER_ALLOWED_SOURCES=*.foobar.com,my.foobar.com,mybucket.s3.amazonaws.com
Error Response Body
By default, when image processing failed, imagor returns error status code with the original source as response body. This is with assumption that the image source is trusted, so that the original image can be served as fallback in case of failure.
However, if the source image involves user generated content, it is advised to disable the original source fallback using IMAGOR_DISABLE_ERROR_BODY
, to prevent untrusted content being loaded:
IMAGOR_DISABLE_ERROR_BODY=1
imagor provides metadata endpoint that extracts information such as image format, resolution and Exif metadata. Under the hood, it tries to retrieve data just enough to extract the header, without reading and processing the whole image in memory.
To use the metadata endpoint, add /meta
right after the URL signature hash before the image operations. Example:
http://localhost:8000/unsafe/meta/fit-in/50x50/raw.githubusercontent.com/cshum/imagor/master/testdata/Canon_40D.jpg
{
"format": "jpeg",
"content_type": "image/jpeg",
"width": 50,
"height": 34,
"orientation": 1,
"pages": 1,
"bands": 3,
"exif": {
"ApertureValue": "368640/65536",
"ColorSpace": 1,
"ComponentsConfiguration": "Y Cb Cr -",
"Compression": 6,
"DateTime": "2008:07:31 10:38:11",
"ISOSpeedRatings": 100,
"Make": "Canon",
"MeteringMode": 5,
"Model": "Canon EOS 40D",
//...
}
}
Prepending /params
to the existing endpoint returns the endpoint attributes in JSON form, useful for previewing the endpoint parameters. Example:
curl 'http://localhost:8000/params/g5bMqZvxaQK65qFPaP1qlJOTuLM=/fit-in/500x400/0x20/filters:fill(white)/raw.githubusercontent.com/cshum/imagor/master/testdata/gopher.png'
imagor is a Go library built with speed, security and extensibility in mind. It facilitates high-level image processing in a modular architecture made up of a series of Go packages:
imagor.Processor
implementationimagor.Loader
implementationimagor.Storage
implementationimagor.Storage
implementationimagor.Storage
implementationInstall libvips and enable CGO:
brew install vips
for MacCGO_CFLAGS_ALLOW=-Xpreprocessor
being set to compile GoSee example below and also examples folder for various ways you can use imagor:
package main
import (
"context"
"github.com/cshum/imagor"
"github.com/cshum/imagor/imagorpath"
"github.com/cshum/imagor/loader/httploader"
"github.com/cshum/imagor/vips"
"io"
"os"
)
func main() {
app := imagor.New(
imagor.WithLoaders(httploader.New()),
imagor.WithProcessors(vips.NewProcessor()),
)
ctx := context.Background()
if err := app.Startup(ctx); err != nil {
panic(err)
}
defer app.Shutdown(ctx)
blob, err := app.Serve(ctx, imagorpath.Params{
Image: "https://raw.githubusercontent.com/cshum/imagor/master/testdata/gopher.png",
Width: 500,
Height: 500,
Smart: true,
Filters: []imagorpath.Filter{
{"fill", "white"},
{"format", "jpg"},
},
})
if err != nil {
panic(err)
}
reader, _, err := blob.NewReader()
if err != nil {
panic(err)
}
defer reader.Close()
file, err := os.Create("gopher.jpg")
if err != nil {
panic(err)
}
defer file.Close()
if _, err := io.Copy(file, reader); err != nil {
panic(err)
}
}
imagor supports command-line arguments and environment variables for the arguments equivalent in capitalized snake case, see available options imagor -h
.
For instances -imagor-secret
would become IMAGOR_SECRET
:
# both are equivalent
imagor -debug -imagor-secret 1234
DEBUG=1 IMAGOR_SECRET=1234 imagor
Configuration can also be specified in a .env
environment variable file and referenced with the -config
flag:
imagor -config path/to/config.env
config.env:
PORT=8000
IMAGOR_SECRET=mysecret
DEBUG=1
Available options
imagor -h
Usage of imagor:
-debug
Debug mode
-port int
Sever port (default 8000)
-version
imagor version
-config string
Retrieve configuration from the given file (default ".env")
-imagor-secret string
Secret key for signing imagor URL
-imagor-unsafe
Unsafe imagor that does not require URL signature. Prone to URL tampering
-imagor-auto-webp
Output WebP format automatically if browser supports
-imagor-auto-avi
docker pull shumc/imagor