Bildpyramiden mit libvips, sharp und Leaflet

Bild von Sandro Botticelli - Die Geburt der Venus

Die Darstellung großer Grafikdateien im Internet, wie Bilder oder Pläne, kann in einem Browser entweder langsam (aufgrund hoher Auflösung und großer Datenmenge) oder qualitativ minderwertig (aufgrund von Kompression) sein. Die Lösung? Bildpyramiden. Bei dieser Methode wird ein großes Bild für verschiedene Zoomstufen in quadratische 'Kacheln' zerteilt. Der Browser lädt nur die für den jeweiligen Ausschnitt notwendigen 'Tiles' und fügt sie nahtlos zusammen.

Erstellung der Bildpyramide

Bei der Erstellung von Bildpyramiden wird eine Ordnerstruktur erstellt. Für jede Zoomstufe (Z) wird ein eigener Ordner angelegt. Innerhalb dieser Ordner werden Unterordner für die Bildzeilen (Y) erstellt, in denen sich die Bilddateien (X) befinden. Je mehr Zoomstufen, desto mehr Bilder. Unser Beispiel "Die Geburt der Venus" besteht aus insgesamt 2180 Bilddateien.

venus/
├─ 0               <- Zoomstufe Z
│  ├─ 0            <- Zeile Y
│     ├─ 0.jpg     <- Tile X
├─ 1               <- Zoomstufe Z
│  ├─ 0            <- Zeile Y
│  │  ├─ 0.jpg     <- Tile X
│  │  ├─ 1.jpg     <- Tile X
│  ├─ 1            <- Zeile Y
│     ├─ 0.jpg     <- Tile X
│     ├─ 1.jpg     <- Tile X
...
    

Die in unserem Beispiel eingesetzte Software:

libvips

Da die bereitgestellte Version der libvips in den Linux-Distributionen oft veraltet ist und wir die gewünschten Funktionen selbst bestimmen möchten, werden wir libvips nun eigenhändig kompilieren. Ans Werk!

Das obligatorische Update:

apt update

Die grundlegenden Abhängigkeiten:

apt install build-essential pkg-config libglib2.0-dev libexpat1-dev python3 ninja-build meson

Optionale Abhängigkeiten, siehe Doku:

apt install libarchive-dev libjpeg-dev libexif-dev librsvg2-dev libcgif-dev libtiff-dev libspng-dev libimagequant-dev libpangocairo-1.0-0 libwebp-dev libheif-dev libopenexr-dev libpoppler-glib-dev liblcms2-dev libfftw3-dev

Wir halten Ausschau nach der aktuellen libvips-Version auf Github, ersetzen entsprechend die X-e, laden das Archiv herunter und entpacken es:

curl -fLO https://github.com/libvips/libvips/releases/download/vX.X.X/vips-X.X.X.tar.xz
tar xfJ vips-X.X.X.tar.xz      

In das Verzeichnis wechseln und kompilieren:

cd vips-X.X.X
meson setup build --libdir=/usr/lib --buildtype=release
cd build
meson compile
meson test
meson install   

Falls alles geklappt hat, sollten wir libvips über die Kommandozeile bedienen können:

vips --help

Dieses war der erste Streich, doch der Zweite folgt sogleich.

sharp

sharp ist ein schnelles und flexibles Node.js-Paket für die Bildmanipulation, das auf libvips basiert.

Falls bei der Installation eine aktuelle libvips-Version gefunden wird, wird diese kompiliert und um deren Funktionalität erweitert. (Bildpyramiden, PDF etc.)

Um uns zu vergewissern, dass sharp libvips finden kann (sollte die libvips-Version zurückgeben):

pkg-config --modversion vips-cpp

Wir wechseln in unser node.js-Projekt und installieren die für die Kompilierung nötigen Pakete:

npm install node-addon-api node-gyp

...und dann sharp:

npm install sharp

Hier das Script zur Erzeugung unserer Pyramide

import sharp from 'sharp'

const tileinfo = await sharp('./riesenbild.jpg', { limitInputPixels: false })
  .jpeg({ quality: 70 })
  .tile({ size: 512, layout: 'google', background: { r: 63, g: 63, b: 63, alpha: 1 } })
  .toFile('./pyramidenverzeichnis')

tileinfo.width   // Breite des Originalbildes
tileinfo.height  // Höhe des Originalbildes

Leaflet

Leaflet ist eine freie JavaScript-Bibliothek und wird üblicherweise zum Anzeigen von interaktivem Kartenmaterial eingesetzt. In unserem Beispiel verwenden wir das einfache L.CRS.Simple Koordinatenreferenzsystem (quadratischer Raster) - perfekt für Anhänger der Flat-Earth-Theorie.

Hier unser Script, um Leaflet zu starten:
const mapContainer = document.getElementById('container');
const maxzoom = 6       // Verzeichnistiefe -1
const picWidth = 30000  // Breite des Originalbildes
const picHeight = 18840 // Höhe des Originalbildes
const picURL = 'https://domain.org/pyramidenverzeichnis' + '/{z}/{y}/{x}.jpg'

//Map mit Leaflet erstellen
map = L.map(mapContainer, {
  crs: L.CRS.Simple,
  center: [0, 0],
  zoom: 1,
  minZoom: 1,
  maxZoom: maxzoom,
  zoomControl: true,
  attributionControl: false
})

//Festlegen der Bildgrenzen
const northEast = map.unproject([picWidth, 0], map.getMaxZoom());
const southWest = map.unproject([0, picHeight], map.getMaxZoom());
const bounds = L.latLngBounds(southWest, northEast);
map.fitBounds(bounds, true);
map.options.minZoom = map.getBoundsZoom(bounds);

//Kachellayer zur Map hinzufügen
L.tileLayer(picURL, {
  tileSize: 512,
  minZoom: map.getMinZoom(),
  maxZoom: map.getMaxZoom(),
  attributionControl: false,
  noWrap: true,
  bounds: bounds
}).addTo(map)

Demo

Die beschriebene Vorgangsweise mit lipvips und Leaflet ist ein sauberer und effizienter Weg, um Bilder und Pläne mit großen Datenmengen im Browser darzustellen. Viel Spaß beim Ausprobieren!