aboutsummaryrefslogtreecommitdiff
path: root/posts/2020-03-27-create-placeholder-images-with-sharp.md
blob: ef035c924d130650064c85dac2362b84e0dcbd9a (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
---
Title: Create placeholder images with sharp Node.js image processing library
Description: Create placeholder images with sharp Node.js image processing library
Slug: create-placeholder-images-with-sharp
Listing: true
Created: 2020, March 27
Tags: []
---

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.