Zum Inhalt

IIIF Image API

Using the IIIF Image API

Image Request

Info

See the IIIF Image API reference v2.1 or v3.0 for further details on the URI syntax (region, size, rotation, quality and format).

The IIIF Image API URI for requesting an image defines the following URI Template:

{scheme}://{server}{/prefix}/{identifier}/{region}/{size}/{rotation}/{quality}.{format}

Warning

Strong recommendation about output formats

The jpg format is the recommended output format, complying with the IIIF Image API level 2 specification and can be processed by IIIF viewers.

As our storage format is jxl format, we offer also jxl as output format. The most efficient request pattern corresponds with the sizes of the pre-generated image derivatives, as they can be delivered directly without re-encoding. Therefore we recommend this output format wherever applicable with your viewer / front end.

The png format is exposed mainly to preserve IIIF Image API level 2 compliance alongside jpg.

The webp format is also exposed, but it is strongly discouraged for regular viewer usage.

png and webp requests are generated on demand and can require very long encoding times.

For the University Library IIIF Server, the {scheme}://{server} part is https://iiif.ub.unibe.ch followed by /image/{version} as {prefix} where version is either v2.1 or v3.0. For the following examples we will use v3.0 as {version} (https://iiif.ub.unibe.ch/image/v3.0/...)

There are two ways on the University Library IIIF Server to call a specific image:

Info

When using the variant with the project slug and image source file name, an HTTP redirect to the first variant (image identifier) will be created. This means for every such request to the image API the browser has to do a second request. This causes a minimal delay (microseconds) on each request and increases the load on the web server. We recommend to use the first variant whenever possible.

Using JXL in a Viewer

The IIIF Image API of this server supports jxl as output format in addition to jpg, png and webp.

For viewer integrations, the recommended strategy is:

  1. Load the regular IIIF info.json as usual.
  2. Keep the IIIF metadata unchanged.
  3. Only rewrite the generated tile image requests from .jpg or .png to .jxl when the browser supports JXL.
  4. Fall back to the unmodified IIIF tile URLs when the browser does not support JXL.

This is the same pattern used by the administration viewer.

For OpenSeadragon, our current recommendation is:

  1. Use the IIIF v2.1 info.json URL as the tile source.
  2. Let OpenSeadragon build tile URLs normally.
  3. Patch getTileUrl() after the tile source is available and replace the file extension only.

Example:

import OpenSeadragon from 'openseadragon';

const viewer = OpenSeadragon({
    element: document.getElementById('viewer'),
    tileSources: ['https://iiif.ub.unibe.ch/image/v2.1/{identifier}/info.json']
});

viewer.addHandler('open', () => {
    const tiledImage = viewer.world.getItemAt(0);
    const originalGetTileUrl = tiledImage.source.getTileUrl;

    tiledImage.source.getTileUrl = function(level, x, y) {
        const url = originalGetTileUrl.call(this, level, x, y);
        return url ? url.replace(/\.(jpg|png)$/, '.jxl') : url;
    };
});

Detecting browser support

Only enable the JXL rewrite when the browser can decode image/jxl. If the browser cannot decode JXL, keep the original IIIF tile URLs unchanged.

In our administration viewer, support is detected in two phases:

  1. First, the server checks the browser's Accept header for image/jxl and passes that information to the Stimulus OpenSeadragon controller as an initial hint.
  2. If that hint is not present, the client performs its own fallback check with a tiny in-memory JXL image, so no extra network request is needed.

This means the administration viewer uses the header-based signal as a fast path and the tiny image probe as a fallback verification path.

If your application can inspect the request headers on the server side, you can expose the initial Accept: image/jxl result to the frontend and use it as the fast path. In this project, the admin page passes that hint into the Stimulus controller as the jxlSupported value. If not, the client-side probe alone is still a valid implementation.

Complete example:

import OpenSeadragon from 'openseadragon';

const JXL_PROBE_DATA_URL = 'data:image/jxl;base64,/woAEBAUNwIIAgEAHABLEqVChSQM';
const JXL_TILE_EXTENSION_PATTERN = /\.(jpg|png)$/;
let jxlSupportProbePromise;

function hasInitialJxlHint(jxlSupported) {
    return jxlSupported;
}

async function detectJxlSupport(jxlSupported = false) {
    if (hasInitialJxlHint(jxlSupported)) {
        return true;
    }

    if (!jxlSupportProbePromise) {
        jxlSupportProbePromise = new Promise((resolve) => {
            const image = new Image();
            let settled = false;

            const finish = (result) => {
                if (settled) {
                    return;
                }

                settled = true;
                resolve(result);
            };

            image.onload = () => finish(true);
            image.onerror = () => finish(false);
            image.src = JXL_PROBE_DATA_URL;

            // Browsers that neither decode nor error should not block viewer startup.
            window.setTimeout(() => finish(false), 1500);
        });
    }

    return jxlSupportProbePromise;
}

function enableJxlSupportAfterDetection(viewer) {
    const patchTileSources = () => {
        let patchedAnySource = false;
        const count = viewer.world.getItemCount();

        for (let i = 0; i < count; i++) {
            const tiledImage = viewer.world.getItemAt(i);
            if (!tiledImage.source || !tiledImage.source.getTileUrl || tiledImage.source._iiifJxlPatched) {
                continue;
            }

            const originalGetTileUrl = tiledImage.source.getTileUrl;
            tiledImage.source.getTileUrl = function(level, x, y) {
                const url = originalGetTileUrl.call(this, level, x, y);
                return url ? url.replace(JXL_TILE_EXTENSION_PATTERN, '.jxl') : url;
            };

            tiledImage.source._iiifJxlPatched = true;
            patchedAnySource = true;
        }

        return patchedAnySource;
    };

    viewer.addHandler('open', patchTileSources);

    return patchTileSources();
}

const imageInfoUrl = 'https://iiif.ub.unibe.ch/image/v2.1/{identifier}/info.json';
const viewer = OpenSeadragon({
    element: document.getElementById('viewer'),
    tileSources: [imageInfoUrl]
});

if (hasJxlAcceptHeader()) {
    enableJxlSupportAfterDetection(viewer);
} else {
    detectJxlSupport().then((isJxlSupported) => {
        if (!isJxlSupported) {
            return;
        }

        const didEnableJxl = enableJxlSupportAfterDetection(viewer);
        if (didEnableJxl && viewer.isOpen()) {
            viewer.open(imageInfoUrl);
        }
    });
}

Why the rewrite happens at the tile URL level

The important point is that the IIIF Image API description does not need to change for JXL usage in the viewer. The client can:

  • use the standard info.json
  • use the standard IIIF tiling metadata
  • only switch the requested tile format at request time

This keeps the integration compatible with viewers that do not support JXL and makes fallback behavior straightforward.

Image Information Request

Info

See the IIIF Image API reference v2.1 or v3.0 for further details on the URI syntax.

The IIIF Image API URI for requesting an image defines the following URI Template:

{scheme}://{server}{/prefix}/{identifier}/info.json

The same two addressing methods apply here. The following examples show the info.json endpoint for each:

Warning

When using the variant with the project slug and image source file name, a HTTP redirect to the first variant (image identifier) will be created. This means for every such request to the image API the browser has to do a second request. This causes a minimal delay (microseconds) on each request and increases the load on the web server. We recommend to use the first variant whenever possible.

Zurück zum Seitenanfang