Dynamic image resizing with Google Cloud Functions

Resi

For a small project I needed dynamic image resizing, meaning I would call an image in an url, append some parameters for resizing and return a smaller image, so something like example.com/image.jpg?w=50&h=50. Now there are several open source projects capable of doing that, most notably Thumbor.

However, I wanted to try something new. I recently dabbled a little with Google Cloud Functions and it seemed like a good candidate. However, I feared the initial spin-up time of the functions might be problematic. Still, I wanted to try it out and deal with this issue later.

To begin with, I already had a Google Cloud Storage bucket with the original images. As said, I wanted the resizing to be dynamic. So, the response to the request is the resized image. I want the resizer to output images of 50 by 50 pixels so we’ll get some nice profile picture sized images.

We will begin with writing the cloud function. First some setup steps.

mkdir image-serve
cd image-serve
git init .
yarn init # Press enter on all the steps
yarn add @google-cloud/storage gm

We setup a git repository and use yarn to setup the project. Then we add two dependencies, the Google Cloud Storage api client and the gm package, a wrapper over imagemagick/graphicsmagick.

Next up is the writing of the actual cloud function. Create an index.js file with following contents.

//index.js

const storage = require('@google-cloud/storage')();
const gm = require('gm').subClass({imageMagick: true});

const bucket = "source-bucket-name";
const width = 50
const height = 50
const option = "!"
const quality = 90

exports.main = (req, res) => {
  const filename = req.query.f

  if(!filename) {
    res.sendStatus(500).send("No file specified");;
    return;
  }

  const file = storage.bucket(bucket).file(filename);

  let stream = file.createReadStream()

  stream.on('error', function(err) {
    console.error(err);
    res.sendStatus(err.code).end(err);
  });

  gm(stream)
    .resize(width, height, option)
    .quality(quality)
    .stream()
    .pipe(res);

  console.log('Served: ' + filename );
}

So what happens here? First we require the dependencies and set some constants such as the dimensions and quality of image output. We also set a source bucket here for the Google Cloud client to query. We also subclass the gm library to use the imagemagick binary already present in the Cloud Function environment.

Next is the main function. It accepts two parameters, a request(req) and response(res). This function is similar to the one in the Express framework. We look for a filename present in the url and trigger an error if it is not set.

After that, we fetch a read stream from the Google Cloud storage file (it assumes your file is publicly accessible), pass this stream to the gm library, resize it using the given options and write the output to the response stream and log filename. And that’s it for writing the Cloud Function. Now we need to deploy it. First we zip up our code.

zip -r image-resize.zip .

Now, in the sidebar in the Google Cloud Console there is a link to the Cloud Functions. This will give you an overview of your functions. Click on Create function. Fill in the form, it should look something like this:

We will a cloud function on a HTTP trigger so it becomes accessible with a GET request. Note that the function to execute main refers to the main function in the code. Also, you might need to create a Cloud Storage bucket for your function to be deployed in. Click create and you’re done.

You can now go to the https://us-central1-YOUR_PROJECT_ID.cloudfunctions.net/image-resize?f=filename.jpg and it’ll resize the filename.jpg-file in your source bucket to 50 by 50 pixels. Note that it is pretty slow, I’ll write about caching the response another time.

The code is available on Github: (https://github.com/maartenvanvliet/image-resize)

Photo by Andrik Langfield on Unsplash