An Elixir plug implementing the International Image Interoperability Framework (IIIF) image API specification.
- The goal of IIIF is to define a standardised REST API for serving high resolution images (art, photographes or archival material published by museums, universities and similar institutions).
- This plug library needs you to define a mapping between image identifier (used in the REST API) and file system path, and will then do the image transformations based on the other request parameters for you.
- There exist several generic Javascript IIIF viewers that utilize this API to allow for optimized viewing (dynamic loading of parts of the image data based on zoom level/viewport).
- WebGIS Javascript libraries like leaflet or OpenLayers support IIIF in one way or the other.
- For the time beeing only (the current) Image API 3.0 is implemented, check out the IIIF documentation for its capabilities.
- The IIIF image API implemented by this library is just one (the foundational) of currently six API standards the IIIF community defines for serving multimedia content and metadata.
- The image processing is handled by libvips via Vix.
The package can be installed
by adding iiif_image_plug
to your list of dependencies in mix.exs
:
def deps do
[
{:iiif_image_plug, "~> 0.7.0"}
]
end
Assuming you want to serve IIIF in your plug based server at "/iiif/v3", add a forward route like this:
forward("/iiif/v3",
to: MyApp.IIIFPlug,
init_opts: %IIIFImagePlug.V3.Options{}
)
For Phoenix it would look slightly different:
forward("/iiif/v3", MyApp.IIIFPlug, %IIIFImagePlug.V3.Options{})
A plug implementation may look something like this:
defmodule MyApp.IIIFPlug do
use IIIFImagePlug.V3
# There are two required callbacks you have to implement, plus
# several optional ones. See the `IIIFImagePlug.V3`
# documentation for more.
@impl true
def info_metadata(identifier) do
# The first required callback lets you inject some metadata
# from your application into the plug when it is responding to
# an information request (info.json) for a specific `identifier`.
# The only required field is `:path`, which tells the plug the
# file system path matching the given `identifier`.
MyApp.ContextModule.get_image_metadata(identifier)
|> case do
%{path: path, rights_statement: rights} ->
{
:ok,
%IIIFImagePlug.V3.InfoRequestMetadata{
path: path,
rights: rights
}
}
{:error, :not_found} ->
{
:error,
%IIIFImagePlug.V3.RequestError{
status_code: 404,
msg: :not_found
}
}
end
end
@impl true
def data_metadata(identifier) do
# The second required callback lets you inject some metadata
# from your application into the plug when it is responding to
# an actual image data request for a specific `identifier`. As
# with `info_metadata/1`, the only required field is `:path`, which
# tells the plug the file system path matching the given `identifier`.
MyApp.ContextModule.get_image_path(identifier)
|> case do
{:ok, path} ->
{
:ok,
%IIIFImagePlug.V3.DataRequestMetadata{
path: path,
response_headers: [
{"cache-control", "public, max-age=31536000, immutable"}
]
}
}
{:error, :not_found} ->
{
:error,
%IIIFImagePlug.V3.RequestError{
status_code: 404,
msg: :not_found
}
}
end
end
For your service to fully implement the API specification, you need to properly configure Cross-Origin Resource Sharing (CORS). You could
either set the correct headers in your info_metadata/1
or data_metadata/1
implementation or configure the appropriate headers in a plug
before this one (cors_plug was used in this example):
(..)
plug(CORSPlug, origin: ["*"])
plug(:match)
plug(:dispatch)
forward("/",
to: MyApp.IIIFPlug,
init_opts: (..)
)
end
(..)
Because this plug is just a library and only part of your overall application, you might want to test your service's IIIF compliance against the official validator:
- https://iiif.io/api/image/validator (web based)
- https://github.com/IIIF/image-validator (repository with python based validator)
This repository comes with a minimalistic server, run the server with:
iex -S mix run
The metadata of the main sample file test/images/bentheim.jpg can now be accessed at http://localhost:4000/bentheim.jpg/info.json:
{
"id": "http://localhost:4000/bentheim.jpg",
"profile": "level2",
"type": "ImageServer3",
"protocol": "http://iiif.io/api/image",
"rights": "https://creativecommons.org/publicdomain/zero/1.0/",
"width": 3000,
"height": 2279,
"@context": "http://iiif.io/api/image/3/context.json",
"maxHeight": 10000,
"maxWidth": 10000,
"maxArea": 100000000,
"extra_features": [
"mirroring",
"regionByPct",
"regionByPx",
"regionSquare",
"rotationArbitrary",
"sizeByConfinedWh",
"sizeByH",
"sizeByPct",
"sizeByW",
"sizeByWh",
"sizeUpscaling"
],
"preferredFormat": [
"jpg"
],
"extraFormats": [
"webp",
"png",
"tif"
],
"extraQualities": [
"color",
"gray",
"bitonal"
]
}
The sample image can be viewed at http://localhost:4000/bentheim.jpg/full/max/0/default.jpg and you can start experimenting with the IIIF API parameters.