Bildquelle: by USGS on Unsplash

Skalieren und Zuschneiden von Bildern mit Python

Web-Development
2020-02-05

Da ich sehr viele Bilder (und anfangs auch sehr große) Bilder hier verwendet habe, hat sich das enorm auf die Geschwindigkeit der Seite ausgewirkt. Seit Juli 2018 ist PageSpeed ein Ranking-Faktor für Suchmaschinen wie Google und Co und man möchte ja nicht irgendwo auf Seite 50 in den Suchergebnissen rumdümpeln sondern vorne mit dabei sein.

Die hier verwendeten Bilder sind hauptsächlich Satellitenbilder von ESA, die unter der Lizenz CC BY-SA 3.0 IGO IGO) veröffentlich wurden und somit auch für eigene Zwecke unter bestimmten Bedingungen genutzt werden dürfen.

Diese Bilder sind gerne auch mal ~30MB groß, was etwas zu groß für eine Website ist. + Da ich nicht alle Bilder manuell zuschneiden wollte, habe ich mich entschieden dieses Problem mit Python bzw. Pillow zu lösen.

Pillow Bibliothek

Pillow ist eine Python Bibliothek zur Bildverarbeitung, die man sich unter Windows mit

pip install Pillow

installieren und mit

from PIL import Image

in ein Python Script importieren kann.

Alle Bilder für Posts liegen in einem seperaten "images/" Ordner im Rootverzeichnis des Projekts. Als Erstes werden alle ".jpg" Dateien in einem bestimmten Verzeichnis mit Pillow geöffnet und alle Dateinamen in einen Array gespeichert. Außerdem wird eine Variable benötigt und später auf jeden Namen in dem Array zugreifen zu können.

count = 0
image_list = []

for file in glob.iglob('path/to/images/*.jpg'):
    im=Image.open(file)
    image_list.append(os.path.basename(file))

Größen definieren und Seitenverhältnis berechnen

Nun solltem man wissen, auf welche Größen die Bilder zugeschnitten und ob zum Beispiel Größenverhältnisse beibehalten werden sollen. Bei allen "PostCover" (Bilder in Beiträgen) wird das Seitenverhältnisse ignoriert und das Bild einfach auf eine bestimmte Größe zugeschnitten, die in einer globalen Variable deklariert wird.

size = (1903,453) #(width,height)

Bei allen "PostThumbnails" (Bilder Vorschau) soll das Seitenverhältnis beibehalten werden und sozusagen nur kleiner skaliert werden. Dafür wird eine globale Standardbreite definiert.

basewidth = 500

Anschließend wird die Originalbreite und -höhe der Bilder ermittelt, da wir diese brauchen um das Seitenverhältnis berechnen und beibehalten zu können. Hier wird nur die neue Höhe gebraucht, da die Standardbreite bereits vordefiniert wurde.

    width, height = im.size
    wpercent = (basewidth / float(im.size[0]))
    hsize = int((float(im.size[1]) * float(wpercent)))

"Cropping" und "Rescaling"

Nun kann man die Bilder mit Image.cropzuschneiden bzw. mit Image.resize skalieren. Bei der Skalierung werden jetzt die neue Breite "basewidth" und die berechnete Höhe "hsize" als Parameter verwendet.

    imThumbnail = im.resize((basewidth, hsize), Image.LANCZOS)
    imCover = im.crop(((width-size[0])//2, (height-size[1])//2, (width+size[0])//2, (height+size[1])//2))

Folgend habe ich die das Thumbnail noch umbenannt und beide neuen Dateien unter static/assets/ mit einer Qualität von "85" abspeichert. Mit dem zustätzlichen Parameter "optimize=True" können auch noch einmal ein paar KB gespart werden.

    newCover = 'static/assets/{}'.format(image_list[count])
    newThumbnail = 'static/assets/{}_thumbnail.jpg'.format(image_list[count].replace(".jpg", ""))
    imCover.save(newCover,optimize=True,quality=85)
    imThumbnail.save(newThumbnail,optimize=True,quality=90)
    count +=1 

Gesamtskript:

from PIL import Image
import glob, os

count = 0
image_list = []
basewidth = 500
size = (1903,453)
 

for file in glob.iglob('path/to/images/*.jpg'):
    im=Image.open(file)
    image_list.append(os.path.basename(file))
    width, height = im.size
    wpercent = (basewidth / float(im.size[0]))
    hsize = int((float(im.size[1]) * float(wpercent)))
    imThumbnail = im.resize((basewidth, hsize), Image.LANCZOS)
    imCover = im.crop(((width-size[0])//2, (height-size[1])//2, (width+size[0])//2, (height+size[1])//2))
    newCover = 'static/assets/{}'.format(image_list[count])
    newThumbnail = 'static/assets/{}_thumbnail.jpg'.format(image_list[count].replace(".jpg", ""))
    imCover.save(newCover,optimize=True,quality=85)
    imThumbnail.save(newThumbnail,optimize=True,quality=90)
    count +=1 

Skript automatisch ausführen

Um das Skript nicht jedes mal automatisch ausführen zu müssen kann man in "package.json" noch folgendes ergänzen.

    "develop": "py ./src/utils/scripts/resize_images.py && gatsby develop",
    "build": "py ./src/utils/scripts/resize_images.py && gatsby build",

Somit kann man mit npm run develop alle Bilder optimieren und den Development-Server starten. Mit npm run build werden die Bilder optimiert und die App kompiliert.

Ein erneuter Lighthouse Audit sah danach wie folgt aus: Lighthouse Audit

Vor der Bildoptimierung war das Ergebnis bei Performance immer um die 80-90 Punkte.

Zum Schluss noch einmal drei Bilder im Vergleich:

Originalbild Originalbild "PostThumbnail: PostCover "PostCover" PostCover

Nächster Beitrag:

Gatsby CLI Automatisierung mit GitHub und Buddy