aboutsummaryrefslogtreecommitdiff
path: root/_posts/posts/2020-03-27-create-placeholder-images-with-sharp.md
blob: c129396ec5238a0c937494be8f13a26bf2963aad (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
---
title: Create placeholder images with sharp Node.js image processing library
permalink: /create-placeholder-images-with-sharp.html
date: 2020-03-27T12:00:00+02:00
layout: post
type: post
draft: false
---

I have been searching for a solution to pre-generate some placeholder images for
image server I needed to develop that resizes images on S3. I though this would
be a 15min job and quickly found out how very mistaken I was.

Even though Node.js is not really the best way to do this kind of things (surely
something written in C or Rust or even Golang would be the correct way to do
this but we didn't need the speed in our case) I found an excellent library
[sharp - High performance Node.js image
processing](https://github.com/lovell/sharp).

Getting things running was a breeze.

## Fetch image from S3 and save resized

```js
const sharp = require('sharp');
const aws = require('aws-sdk');

const x,y = 100;
const s3 = new aws.S3({});

aws.config.update({
  secretAccessKey: 'secretAccessKey',
  accessKeyId: 'accessKeyId',
  region: 'region'
});

const originalImage = await s3.getObject({
  Bucket: 'some-bucket-name',
  Key: 'image.jpg',
}).promise();

const resizedImage = await sharp(originalImage.Body)
  .resize(x, y)
  .jpeg({ progressive: true })
  .toBuffer();

s3.putObject({
  Bucket: 'some-bucket-name',
  Key: `optimized/${x}x${y}/image.jpg`,
  Body: resizedImage,
  ContentType: 'image/jpeg',
  ACL: 'public-read'
}).promise();
```

All this code was wrapped inside a web service with some additional security 
checks and defensive coding to detect if key is missing on S3.

And at that point I needed to return placeholder images as a response in case
key is missing or x,y are not allowed by the server etc. I could have created
PNG in Gimp and just serve them but I wanted to respect aspect ratio and I
didn't want to return some mangled images.

> Main problem with finding a clean solution I could copy and paste and change a
> bit was a task. API is changing constantly and there weren't clear examples or
> I was unable to find them.

## Generating placeholder images using SVG

What I ended up was using SVG to generate text and created image with sharp and
used composition to combine both layers. Response returned by this function is a
buffer you can use to either upload to S3 or save to local file.

```js
const generatePlaceholderImageWithText = async (width, height, message) => {
  const overlay = `<svg width="${width - 20}" height="${height - 20}">
    <text x="50%" y="50%" font-family="sans-serif" font-size="16" text-anchor="middle">${message}</text>
  </svg>`;

  return await sharp({
    create: {
      width: width,
      height: height,
      channels: 4,
      background: { r: 230, g: 230, b: 230, alpha: 1 }
    }
  })
    .composite([{
      input: Buffer.from(overlay),
      gravity: 'center',
    }])
    .jpeg()
    .toBuffer();
}
```

That is about it. Nothing more to it. You can change the color of the image by
changing `background` and if you want to change text styling you can adapt SVG
to your needs.

> Also be careful about the length of the text. This function positions text at
> the center and adds `20px` padding on all sides. If text is longer than the
> image it will get cut.