Two Approaches of Dynamically Generating Images with Azure Functions
I recently started looking into how to generate images using JavaScript Azure functions on a consumption plan. This is something that until recently has been a challenge as in the past supporting libraries have had dependencies that needed to be pre-installed on servers.
I found two solutions that offered two very different approaches to solve the same problem. In this post I want to take you through both approaches with the pros and cons of each and why you might want to use one over the other.
HTML to Image
I initially came across a blog post by Thomas Pentenrieder on generating dynamic images with azure functions.
This approach uses and NPM package called node-html-to-image. The package wraps puppeteer which in turn is automating a chromium browser loading in the provided HTML and taking a screenshot saving it out as an image. The NPM package also supports templating through handlebars JS making it easy to create dynamic templates.
This is something that has recently been made possible on Azure consumption plan functions through the addition of some dependencies being added on the Azure servers. I am not going to cover any of the code or setup needed here, for that I suggest you read Thomas is great blog post on the topic.
In Thomas’s post he is generating banner images, however as its rendering HTML you could generate images from a HTML canvas (for complex drawing or charts), render a map or use more extreme CSS3 to apply text effects depending on your needs. Basically, anything that will render in the browser can be captured.
To try this out I created a dynamic sticker image with a google font, SVG text effects and a transparent background. The code is on GitHub and I have also talked over how this works in Live Code session on twitch.
The downside to this approach is that it will only work functions running on a Linux server, there’s a little bit of extra setup you need for your project and in my sample, I found it took between 3-5 seconds to render the image. The time to render is not a surprise as it is opening up a browser, rending the HTML then saving out the screenshot. The only small challenge I found to running this was that locally on a Mac we also needed to install chromium to get it to work locally, on windows I had no issues.
Image Generation Library
The Jimp NPM package is a fully node-based image manipulation library with no external dependencies. The focus of this package is very different to that of node-html-to-image. You start with a base image, in either a URL or image buffer then a set of methods allow you to change the image, resizing it, changing the colour, adding text, overlay other images etc. During this entire time, you are working with images and pixels rather than HTML Markup.
I could imagine this would work well for projects where you are creating dynamic gravatar like images or needed to dynamically resize images on demand.
To show how this works I created a typescript code sample that takes a simple image of a cat from an API that we then overlay a single line of text on to.
import { AzureFunction, Context, HttpRequest } from "@azure/functions"
import * as Jimp from 'jimp';
import fetch from 'node-fetch';
const httpTrigger: AzureFunction = async function (context: Context, req: HttpRequest): Promise<void> {
const urlResp = await fetch("https://api.thecatapi.com/v1/images/search?format=json", {
method: 'GET'
});
const catData = await urlResp.json();
const imgPath = catData[0].url;
const font = await Jimp.loadFont(Jimp.FONT_SANS_32_WHITE);
const text = req.query.text ?? '';
const newImageBytes = await Jimp.read(imgPath).then(img => {
return img
.print(
font,
0,
0, {
text: text,
alignmentX: Jimp.HORIZONTAL_ALIGN_CENTER,
alignmentY: Jimp.VERTICAL_ALIGN_BOTTOM
},
catData[0].width,
catData[0].height
)
.getBufferAsync(Jimp.MIME_JPEG);
});
context.res = {
headers:{"content-type":"image/jpg"},
body: newImageBytes
};
};
export default httpTrigger;
This sample when run renders an image in under 2 seconds although most of the time is the API call to get cat image. If we use a local image, then the render time is about half a second. The full sample can be found on GitHub.
The compromise to this approach is that working with text is more challenging. Fonts are effectively images themselves using BMFont format rather than flexible true type fonts. This means it’s not as easy to change the colour or styling and applying text effects such as drop shadow is virtually impossible.
Summary
In this post we looked at two different approaches to generate images inside Azure functions.
Using node image to HTML allows us lots of flexibility generating images from any HTML source including a wide range of fonts colours and content. There are however compromises such as performance.
In comparison using the Jimp library we can quickly generate and combine images. Fonts however, and more complex arrangements will be more challenging.
Generating great looking images is very possible in a cost-effective way using Azure Functions on a consumption plan. The approach will come down to your use case.