Auto-generate a social media image in Statamic

When writing blog posts you likely want to share your post on Twitter or any other social network to give your posts an audience. Let's stay with Twitter for this post. While writing blog posts might be the main task, the title and content is clear, there is often an image used to and pulled by social media.

Instead of searching the web for meaningful backgrounds, why not just putting the title of your post and the name of your blog on an uni-color background. For creating this image we need to hook into Statamics event system to generate the image after saving the blog post.

Creating the event listener

To create a new listener that actually runs the image generation we run

php artisan make:listener GenerateBlogPostImage

which creates a new listener at app/Listeners/GenerateBlogPostImage.php. Next the listener needs to be run, when an entry is saved. For this the EventSaved event comes in handy. So let's register our listener by putting in it app/Providers/EventServiceProvider.php.

use App\Listeners\GenerateBlogPostImage;
use Statamic\Events\EntrySaved;

class EventServiceProvider extends ServiceProvider
{
    /**
     * The event listener mappings for the application.
     *
     * @var array
     */
    protected $listen = [
        // Some other events

        EntrySaved::class => [
            GenerateBlogPostImage::class,
        ],
    ];

    // Further stuff
}

Now, whenever an entry is saved, our GenerateBlogPostImage listener is called and can do its magic, generating a 1200x627 pixel image for Twitter. For creating the images I am going to use the Intervention Image library. Back to our listener file we need to load the Intervention Image library and generate the image.

Here is complete file:

use Illuminate\Support\Str;
use Intervention\Image\ImageManager;
use Statamic\Events\EntrySaved;
use Statamic\Entries\Entry;

class GenerateBlogPostImage
{
    const IMAGE_WIDTH = 1200;
    const IMAGE_HEIGHT = 627;

    protected string $baseImagePath;
    protected $imageManager;

    public function __construct()
    {
        $this->baseImagePath = public_path('assets/posts');

        $this->imageManager = new ImageManager([
            'driver' => 'imagick',
        ]);
    }

    public function handle(EntrySaved $event): void
    {
        /** @var Entry $entry */
        $entry = $event->entry;

        if($entry->collectionHandle() == 'posts' && $entry->published() === true) {
            // Create the image
            $image = $this->imageManager->canvas(self::IMAGE_WIDTH, self::IMAGE_HEIGHT, '#E5E7EB');

            // Title
            $image->text($entry->title, self::IMAGE_WIDTH/2, self::IMAGE_HEIGHT/2, function ($font) {
                $font->size(46);
                $font->color('#111827');
                $font->file(storage_path('Rubik.ttf'));
                $font->align('center');
                $font->valign('middle');
            });

            // Website
            $image->text('codedge.de', self::IMAGE_WIDTH/2, self::IMAGE_HEIGHT/1.25, function($font) {
                $font->size(28);
                $font->color('#ff6633');
                $font->file(storage_path('Rubik.ttf'));
                $font->align('center');
            });

            // Save the image
            $imagesPath = $this->baseImagePath . '/' . Str::slug($entry->title).'.png';
            $image->save($imagesPath, 100);
        }
    }
}

The requirement for Intervention is PHPs imagick extension. Alternatively you can use gd too. I restricted the generation to:

  • posts in the posts collections
  • only published items
Each time you save the previously generated image is overwritten. I guess the rest of the listener is pretty straight forward.

Intervention Image library

I suspect there is more than one libray to use for this use-case, but I am pretty happy with Intervention. I never used it before, but it seems to be stable and does exactly what it should do.

Imagick or GD

First I started with using gd but it turned out I cannot properly render text. When using gd you can specify built-in fonts 1 to 5, which wasn't my intention. I wanted to use my own font. With imagick I was able to render text properly with a custom .ttf file.