Automatische Beitragsbilder

So können wir automatisch Beitragsbilder erzeugen, um sie dann beim Teilen auf Twitter und anderen Plattformen anzuzeigen.

An english translation can be found here.

Update: Ich habe auf Anfrage meinen aktuellen Stand mal hier online gepackt: https://github.com/mauricerenck/og-image das kann so als Plugin benutzt werden allerdings müssen das Bild und die Fonts entsprechend geändert werden. Vielleicht wird das irgendwann mal ein offizielles Plugin, gerade fehlt mir dazu die Zeit.

Wenn wir Blogposts bei Twitter und auf anderen Plattformen teilen, dann fallen diese Links besser auf, wenn sie ein Bild enthalten. Das lässt sich über eine Twitter-Card oder Open-Graph-Tags lösen. Das sind HTML Meta-Tags, die wir mit Informationen anreichern können (Titel, Beschreibung, Bild) und die dann dafür sorgen, dass Links zu den Seiten auf den Plattformen entsprechend gut aussehen.

Ein Teil dieser Daten ist ein Bild. In meinem CMS habe ich dazu zwei Felder, mit denen ich das Bild hochladen und festlegen kann. Doch nicht immer habe ich Bilder für meine Beiträge und möchte auch nicht immer krampfhaft danach suchen. Daher habe ich mir eine Idee von GitHub (und anderen Seiten) geklaut: Ich erstelle mir einfach selbst Bilder!

GitHub macht das besonders gut, finde ich. So sieht das z. B. für eins meiner Repositories aus:

GitHubs og:image

Diese Bilder werden automatisch generiert und genau das möchte ich auch erreichen.

Dazu habe ich mir in Affinity Designer eine Vorlage erstellt. Das muss nicht sein, aber ich sah so ein wenig mehr Spielraum für mich. Welches Programm man dazu benutzt, ist eigentlich egal, Hauptsache am Ende kommt ein PNG bei raus. So sieht meines derzeit aus:

Meine Vorlage fürs og:image

Ähnlichkeiten zu dem von GitHub sind eventuell vorhanden.

Bilder mit PHP erzeugen

Nun geht es ans Eingemachte. Der Weißraum links im Bild will nun gefüllt werden. Dargestellt werden soll sowohl der Titel der jeweiligen Seite als auch eine kurze Zusammenfassung, falls es so eine gibt. Ich benutze Kirby als CMS und habe mir dafür ein Plug-in geschrieben. Das macht alles etwas übersichtlicher, weil ich alles auf einem Haufen habe und ich einfach Routen definieren und auf Felder zugreifen kann.

Im PHP Code gilt es zunächst, ein Bild anzulegen.

$canvas = imagecreatetruecolor(1200, 600);

Unser Vorlagenbild dient hier als Grundlage, also laden wir es und kopieren es in unser neu erzeugtes Bild:

$background = imagecreatefrompng(__DIR__ . '/assets/background.png');

imagecopyresampled(
    $canvas,
    $background,
    0,
    0,
    0,
    0,
    imagesx($background),
    imagesy($background),
    imagesx($background),
    imagesy($background)
);

Wir könnten uns den Schritt mit dem Canvas auch sparen und direkt das vom PNG erzeugte Bild weiterverwenden. Aber ich habe diesen Schritt belassen, falls jemand einfach nur ein leeres Bild erzeugen und füllen möchte, ohne auf ein anderes Bild zuzugreifen. In dem Fall kann das Laden des PNGs einfach weggelassen werden.

Jetzt wollen wir unseren Seitentitel aufs Bild bringen, dazu nutzen wir die imagettftext() Funktion. Diese benötigt eine Schriftart, die wir zunächst laden müssen:

$fontRegular = __DIR__ . '/assets/GangsterGrotesk-Regular.ttf';
$fontBold = __DIR__ . '/assets/GangsterGrotesk-Bold.ttf';

Je nachdem, wo ihr den Font bei euch ablegt, wird dieser Pfad natürlich unterschiedlich sein. Ich lade direkt zwei Fonts, einen normalen für die Zusammenfassung und einen fetten Font für die Überschrift. Letztere können wir dann jetzt auch aufs Bild bannen:

[$titleX, $titleY] = imagettftext(
  $canvas,
  58,
  0,
  $margin,
  120,
  $black,
  $fontBold,
  $text
);

Wir erzeugen hier einen Text, und packen diesen auf unser Bild ($canvas). Die Schriftgröße ist 58, wir haben einen Abstand zum Rand, dazu benutze ich die Variable $margin, die steht bei mir auf 60. Der Abstand vom oberen Rand beträgt 120, den setze ich nur einmal und benötige deshalb keine Variable. Die Überschrift soll schwarz sein, dazu habe ich eine Farbe definiert:

$black = imagecolorallocate($canvas, 0, 0, 0);

Schließlich benutzen wir den fetten Font und übergeben unseren eigentlichen Text, der bei mir in der Variable $text steht. So sieht das dann aus:

Sieht schon mal relativ gut aus, allerdings haben wir ein noch nicht sichtbares Problem, und zwar folgendes:

Wenn der Titel zu lang ist, dann fließt er einfach aus dem Bild raus, wir müssen ihm also Grenzen setzen. Das geht leider nicht ganz so einfach wie in einer Bildbearbeitung. Was wir tun können, ist alle paar Zeichen einen Zeilenumbruch zu setzen. Nach wie vielen Zeilen dies der Fall sein muss, hängt von der Schriftart und der Schriftgröße ab, hier gibt es also keine feste Formel, da müssen wir probieren. Folgende Werte stimmen für mich:

$text = wordwrap($text, 30, "\n");

Alle 30 Zeichen füge ich also einen Zeilenumbruch ein. Das sieht dann so aus:

Schon besser, damit sind wir also in der Breite in Sicherheit. Natürlich könnte der Titel jetzt noch unten aus dem Bild herauslaufen, aber das ignorieren wir mal, denn so lang sollten Titel in der Regel auch nicht sein.

Weiter im Text (haha). Den wollen wir als Nächstes anzeigen. Der Beschreibungstext unter dem Titel soll etwas kleiner sein und ich hätte ihn gerne in Lila, dazu definiere ich wieder eine Farbe und gebe dann den Text aus:

$purple = imagecolorallocate($canvas, 139, 126, 164);
$text = wordwrap($text, 48, "\n");

imagettftext(
    $canvas,
    24,
    0,
    $margin,
    $titleY + 70,
    $purple,
    $fontRegular,
    $text
);

Weil der Text kleiner ist, können wir den Zeilenumbruch etwas später machen, bei 48 Zeichen. Wir benutzen den normalen Font und geben ihm die Größe 24. Zu beachten ist noch der Wert $titleY + 70. Wir können Texte immer nur absolut positionieren, würden wir ihm eine feste Position, vom oberen Rand aus gerechnet geben, könnte er sich mit dem Titel überschneiden, wenn dieser besonders lang ist. Also haben wir uns beim Erstellen des Titels u. a. den Wert $titleY zurückgeben lassen. Zu dieser Position auf der y-Achse rechnen wir noch einen Abstand von 70 Pixeln und setzen dieser Wert als Startposition für den neuen Text. So überschneiden sie sich nie.



Auch hier können wir wieder auf Probleme mit langen Titeln treffen. Diese können dafür sorgen, dass unser Beschreibungstext unten aus dem Bild rausläuft (weil wir ihn ja immer entsprechend weit unter den Titel schieben). Hier zum Beispiel:



In diesem Fall habe ich mich dazu entschieden, den Beschreibungstext einfach wegzulassen. Ein so langer Titel sollte dann ausreichend sein. Wir geben den Titel also nur dann aus, wenn die zurückgegebene Y-Position geringer als ein bestimmter Wert ist. Auch hier gibt es keine Formel und das hängt sehr von der Schrift und ihrer Größe ab:

if ($titleY <= 315) {
    $text = wordwrap($text, 48, "\n");

    imagettftext(
        $canvas,
        24,
        0,
        $margin,
        $titleY + 70,
        $purple,
        $fontRegular,
        $text
    );
}


Okay, das klappt also …

Als letztes Kleinigkeit hätte ich noch gerne die URL im lila Streifen unten stehen. Die Beitrags-URLs sind in der Regel zu lang, also muss die URL der Webseite langen. Wieder setzen wir einen Text, diesmal in Weiß:

imagettftext(
    $canvas,
    24,
    0,
    120,
    570,
    $white,
    $fontRegular,
    $url
);

Ich bin sehr zufrieden! Ein paar Anmerkungen habe ich aber noch!

Da mein CMS mir einige Arbeit abnimmt, sind in diesem Beitrag ein paar Punkte unter den Tisch gefallen. Da hätten wir etwa den Beschreibungstext. Der könnte natürlich auch beliebig lang sein und unten aus dem Bild herauslaufen. Deshalb lasse ich mir von Kirby einen gekürzten Text zurückgeben. Kirby macht hier schon die ganze Arbeit für mich:

$description = $page->intro()->excerpt(200);

Kirby kürzt den Text nach 200 Zeichen und fügt dann noch ein an. Das können wir natürlich auch direkt mit PHP tun, z. B. mit substr:

$description = substr($description, 0, 200) . '…';

Hier hängen die Nummern wieder vom Font ab.

Außerdem müssen wir das Bild natürlich noch irgendwie einbinden, dazu dienen entsprechende Meta-Tags:

<meta property="og:image" content="https://maurice-renck.de/de/your-website/automatische-beitragsbilder-im-blog/og-image">

<meta name="twitter:image" content="https://maurice-renck.de/de/your-website/automatische-beitragsbilder-im-blog/og-image">

Ich habe zwei Varianten in Verwendung, einmal die OpenGraph-Variante und die Twitter-Variante. Die müssen zwischen <head></head>.

Kirby ermöglicht mir auch das Routing. Das bedeutet, dass mein Plug-in immer dann automatisch das Bild für eine Seite erzeugt, wenn man an die Seiten-URL /og-image dran hängt. So muss ich mich wirklich um nichts kümmern. Ich habe bei mir noch eine kleine Weiche eingebaut. Im CMS habe ich einen Tab für die Sharing-Optionen:

Hier kann ich einzelne Werte überschreiben, ich kann individuelle Titel und Beschreibungen setzen und auch gezielt ein Bild hochladen. Sollte ich also bei einer Seite mal nicht das automatisch generierte Bild verwenden wollen, kann ich hier ein eigenes hochladen und das wird dann verwendet. Vielleicht ja eine nette Idee für die ein oder andere Leserin hier.

Mit dieser Lösung bin ich sehr zufrieden, sie erledigt die Bildgenerierung für mich, und wenn ich will, kann ich gezielt eingreifen. Vielleicht hilft das euch ja auch weiter.

Habt ihr noch Idee oder Fragen? Immer her damit! Die Kommentare sind offen!

Wie geht's von hier aus weiter?

Wenn du diesen Beitrag (nicht) gut findest, kannst du ihn kommentieren, woanders darüber schreiben oder ihn teilen. Wenn du mehr Beiträge dieser Art lesen willst, kannst du mir via RSS oder ActivityPub folgen, oder du kannst kannst dir ähnliche Beiträge ansehen.