Der Kampf um die Bildqualität
- 23 Nov, 2024

Bei der Erstellung von diesem Blog bin ich auf ein Problem gestoßen: Ich möchte meine Bilder auf der Seite in einer Gallery anzeigen, allerdings in guter Auflösung.
Dies ist tatsächlich kein trivial zu lösendes Problem. In diesem Post möchte ich meine (bisherige) Lösung vorstellen - bisherige deshalb, weil ich mir sicher bin, dass es noch Verbesserungspotential gibt.
Übersicht
Ich empfehle auf jeden Fall sich erstmal meinen Blog-Post bezüglich des Tech-Stacks des Blogs anzuschauen, hier aber eine Übersicht den für das Problem relevanten Teilen:
-
Als Content-Management-System (CMS) nutze ich Hugo, ein statisches CMS, das mir erlaubt, meine Seiten in Markdown zu schreiben und dann in HTML zu kompilieren.
-
Meine Bilder bearbeite und verwalte ich in Adobe Lightroom, exportiere sie dann in einem Ordner und lade sie in den S3-Bucket hoch. Von dort auf kann ich auf sie (unter anderem) per Webbrowser zugreifen (Öffentlicher Link).
Das Problem
Die Bilder, die ich in meiner Gallery anzeigen möchte, sind in einer hohen Auflösung gespeichert. Das ist mir wichtig, da ich möchte das die Bilder in guter Qualität angezeigt werden. Allerdings laden die Bilder dadurch sehr langsam.
Wenn wir das Problem genauer betrachten, können wir feststellen, dass die Bilder zu groß sind, um sie schnell zu laden und dass daraus folgt, dass die Ansicht der Seite verzögert wird.
Die Lösung
Die Lösung dieses Problems ist zweigeteilt. Zuerst müssen wir die Bilder auch in einer kleineren Auflösung bereitstellen, damit sie schneller geladen werden können. Wenn der Nutzer dann auf das Bild klickt, kann das Bild in voller Auflösung angezeigt werden. Das einzelne Bild in hoher Auflösung zu laden ist dann kein Problem, da der Nutzer ja aktiv darauf geklickt hat.
Ich habe also das Problem in zwei Teile aufgeteilt:
- Bilder in kleinerer Auflösung exportieren
- Bilder besser in die Website einbinden
Schritt 1: Bilder in kleinerer Auflösung
Ich brauche also konkret ein Tool, das mir einfach kleinere Versionen meiner Bilder erstellt. Zusätzlich möchte ich, dass die Bilder in dem “progressiven JPEG”-Format gespeichert werden, da diese schneller geladen werden können.
Exkurs: Progressive JPEG
Progressive JPEGs sind eine Art von JPEGs, die es erlauben, dass das Bild in mehreren Schritten geladen wird. Im Gegensatz zu normalen JPEGs, die “interlaced” sind, laden progressive JPEGs das Bild in mehreren Schritten, wobei jeder Schritt eine höhere Auflösung des Bildes darstellt.
Laden wir also ein “interlaced” JPEG, sehen wir wie sich das Bild von oben nach unten aufbaut. Bei einem progressiven JPEG sehen wir ein Bild, das Schritt für Schritt schärfer wird.
Für eine Website bedeutet das, dass der Nutzer schneller ein Bild sieht, auch wenn es noch nicht in voller Auflösung geladen ist.
Zurück zur Lösung von Problem 1
Ich exportiere meine RAW-Dateien mit Lightroom, musste jedoch feststellen, dass Lightroom keine Möglichkeit bietet, progressive JPEGs zu exportieren. Darüber hinnaus habe ich festgestellt, dass es generell keine Software gibt, die dieses Problem (einfach) löst.
Was meine ich mit “einfach”? Ich möchte nicht jedes Bild einzeln bearbeiten müssen, sondern einfach einen Ordner auswählen und die Bilder dann in einem anderen Ordner mit den kleineren Bildern haben. Außerdem möchte ich meine Bilder auch nicht auf eine (oft monetarisierte) Website hochladen müssen, um sie zu bearbeiten.
Meine Lösung für 1.: Python
Ich habe bereits einige Erfahrung mit Python und der Bibliothek Pillow
, die es erlaubt, Bilder zu bearbeiten. Also
habe ich ein kleines Skript geschrieben, das genau das macht, was ich brauche. Es nimmt einen Ordner mit Bildern,
erstellt
davon kleinere Versionen und speichert sie als progressive JPEGs.
# Installiere
pip install jpegger
# Nutzen
jpegger /pfad/zu/bilder --output /pfad/zu/ausgabe
# Processing Images [##----------------------------------] 7% 00:00:57
Die einzige Voraussetzung ist, das Python (Version 3.10 oder höher) installiert ist. Das Skript ist Open-Source und
kann auf GitHub gefunden werden. Außerdem kann es über den Python Package
Manager pip
installiert werden.
Wo ich aber schonmal dabei war, habe ich das Script auch so erweitert, dass ich nicht nur das Encoding der Bilder ändern kann, sondern auch eine kleinere Version der Bilder erstellen kann (genannt “Thumbnails”). Darüber hinaus kann das Script die Bilder auch weiter komprimieren, Metadaten entfernen und noch mehr für die Web-Optimierung tun.
Eine ausführliche Beschreibung der Funktionalitäten gibt das Skript selbst:
jpegger --help
# ... Hilfe-Text ...
Schritt 2: Bilder besser in die Website einbinden
Um die Bilder einfach einzubinden, habe ich ein Hugo-Shortcode geschrieben, der mir die Bilder in einer Gallery anzeigt.
Ein Shortcode ist eine Art von Template, das in Hugo verwendet wird, um wiederkehrende Elemente zu vereinfachen. Ich kann also einfach den Shortcode in meinem Markdown-File einfügen und die Bilder werden automatisch eingebunden.
<gallery
baseUrl="https://s3.valentin-kolb.blog/gallery/color"
thumbPath="https://s3.valentin-kolb.blog/gallery/color/thumb"
filePattern="img-<num>.jpg"
start=1
end=91
>
Dieser Code erstellt eine Gallery, die Bilder von https://s3.valentin-kolb.blog/gallery/color
lädt. Die
Thumbnails werden von https://s3.valentin-kolb.blog/gallery/color/thumb
geladen. Die Bilder heißen img-1.jpg
,
img-2.jpg
, … bis img-91.jpg
.
Wie funktioniert das?
Der Code, der den Shortcode umsetzt, ist in einer Art HTML geschrieben, genau gesagt in der Go-Template Sprache.
<section class="gallery">
<!-- Get the base URL from the shortcode parameters -->
{{ $baseUrl := .Get "baseUrl" }}
<!-- Optional thumbnail path -->
{{ $thumbPath := .Get "thumbPath" | default $baseUrl }}
<!-- Get the file pattern from the shortcode parameters -->
{{ $filePattern := .Get "filePattern" }}
{{ $start := .Get "start" | default 1 }}
{{ $end := .Get "end" | default 10 }}
<div class="gallery-container">
<!-- Spinner -->
<div class="spinner"></div>
<!-- Progress text -->
<div class="progress-text"></div>
<!-- Gallery grid -->
<div class="gallery-grid hidden">
{{ range seq $start $end }}
<!-- Assemble the file name -->
{{ $fileName := replace $filePattern "
<num>" (printf "%d" .) }}
<!-- Gallery item -->
<div class="gallery-item">
<a href="{{ printf " %s/%s" $baseUrl $fileName }}" data-lightbox="gallery">
<img src="{{ printf " %s/%s" $thumbPath $fileName }}" alt="Image {{ . }}">
</a>
</div>
{{ end }}
</div>
</div>
</section>
Das Template nimmt die Parameter des Shortcodes und erstellt daraus eine Gallery. Es werden die Thumbnails geladen und die Bilder in voller Auflösung, wenn der Nutzer darauf klickt.
Allerdings haben wir selber bei kleinen Bildern immer noch das Problem, dass die Bilder etwas zu langsam laden. Damit die Seite mit der Gallery nicht “springt”, wenn die Bilder nach und nach geladen werden, habe ich einen Spinner eingebaut, der angezeigt wird, bis alle Bilder geladen sind.
Mit JavaScript wird dann der Spinner ausgeblendet und die Bilder angezeigt.
// called when the page is fully loaded
document.addEventListener("DOMContentLoaded", () => {
// get all html elements
const galleryGrid = document.querySelector(".gallery-grid");
const spinner = document.querySelector(".spinner");
const images = galleryGrid.querySelectorAll("img");
const loadingText = document.querySelector(".progress-text");
// function to check if all images are loaded
const checkAllLoaded = () => {
// get the number of loaded images
const loadedImages = Array.from(images).filter(
(img) => img.complete,
).length;
// get the progress in percentage
const progress = Math.round((loadedImages / images.length) * 100);
// update the progress text
loadingText.textContent = `${progress}%`;
// if all images are loaded
if (progress === 100) {
spinner.style.display = "none"; // Hide the spinner
loadingText.style.display = "none"; // Hide the loading text
galleryGrid.classList.remove("hidden"); // Show the gallery
}
};
// check for each image if it is loaded
images.forEach((img) => {
if (img.complete) {
// Image already loaded
checkAllLoaded();
} else {
// Wait for image to load
img.addEventListener("load", checkAllLoaded);
img.addEventListener("error", checkAllLoaded); // Handle errors gracefully
}
});
});
Der Ablauf ist also:
- Der Spinner wird angezeigt
- Jedes Bild wird geladen → wenn ein Bild geladen ist, wird der Fortschritt aktualisiert
- Wenn alle Bilder geladen sind, wird der Spinner ausgeblendet und die Bilder angezeigt
- Wenn auf ein Bild geklickt wird, wird das Bild in voller Auflösung geladen und angezeigt
Fazit
Mit dieser Lösung habe ich das Problem gelöst, dass meine Bilder zu langsam geladen werden. Die Bilder werden in kleinerer Auflösung angezeigt und in voller Auflösung geladen, wenn der Nutzer darauf klickt. Der Spinner sorgt dafür, dass die Seite nicht “springt”, wenn die Bilder nach und nach geladen werden.
Ich bin mir sicher, dass es noch Verbesserungspotential gibt, aber für den Moment bin ich zufrieden mit der Lösung.
Anmerkungen
Andere Websites haben ähnliche Probleme gelöst, indem sie die Bilder in einem “Lazy-Loading” Verfahren laden. Das bedeutet, dass die Bilder erst geladen werden, wenn sie im sichtbaren Bereich des Nutzers sind. Für mich kam das Aufgrund der Art der Gallery nicht infrage, aber es ist eine weitere Möglichkeit, das Problem zu lösen.