Generating images in Workers

I'm trying to generate an image in a Worker, and then store the image in an R2 bucket. My current approach (example code included below) was to: 1. Use the Canvas API to generate the image, 2. Use HTMLCanvasElement.toDataURL() and dataURItoBlob() to convert the image to a Blob, and then 3. Put the Blob in an R2 bucket. However, as document is not defined and the Canvas API is not supported (https://github.com/cloudflare/workerd/discussions/212), this approach does not work in Workers. Are there any other approaches I could try to achieve this in Workers?
interface Env {
BUCKET: R2Bucket;
}

export const onRequest: PagesFunction<Env> = async (context) => {
const canvas = document.createElement("canvas");

canvas.width = 200;
canvas.height = 200;

const ctx = canvas.getContext("2d");

if (ctx === null) {
return new Response(null, { status: 500 });
}

ctx.fillStyle = "red";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText("text", canvas.width / 2, canvas.width / 2);

const dataUrl = canvas.toDataURL("image/jpeg");
const blobData = dataURItoBlob(dataUrl);

await context.env.BUCKET.put("my-image.jpeg", blobData);

return new Response(null, { status: 200 });
};

function dataURItoBlob(dataURI: string) {
const binary = Buffer.from(dataURI.split(",")[1], "base64").toString();
const array = [];

for (let i = 0; i < binary.length; i++) {
array.push(binary.charCodeAt(i));
}

return new Blob([new Uint8Array(array)], { type: "image/jpeg" });
}
interface Env {
BUCKET: R2Bucket;
}

export const onRequest: PagesFunction<Env> = async (context) => {
const canvas = document.createElement("canvas");

canvas.width = 200;
canvas.height = 200;

const ctx = canvas.getContext("2d");

if (ctx === null) {
return new Response(null, { status: 500 });
}

ctx.fillStyle = "red";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText("text", canvas.width / 2, canvas.width / 2);

const dataUrl = canvas.toDataURL("image/jpeg");
const blobData = dataURItoBlob(dataUrl);

await context.env.BUCKET.put("my-image.jpeg", blobData);

return new Response(null, { status: 200 });
};

function dataURItoBlob(dataURI: string) {
const binary = Buffer.from(dataURI.split(",")[1], "base64").toString();
const array = [];

for (let i = 0; i < binary.length; i++) {
array.push(binary.charCodeAt(i));
}

return new Blob([new Uint8Array(array)], { type: "image/jpeg" });
}
7 Replies
Hello, I’m Allie!
The Cloudflare Blog
Automate an isolated browser instance with just a few lines of code
Workers Browser Rendering API is our out-of-the-box solution for simplifying developer workflows, including capturing images or screenshots, by running browser automation in Workers. If you’re interested, sign up for the waitlist.
Kamran
Kamran2y ago
Using the Rendering API, you might then have a worker (or the same worker) actually serve a webpage and then use another worker request to capture it and store it and serve it as an image.
Hello, I’m Allie!
You should be able to capture it directly in one request
Kamran
Kamran2y ago
@HardAtWork interesting, you mean have Puppeteer actually generate it within the same request? Inject a script? Would be interested to see that.
Hello, I’m Allie!
You can already inject a script with HTMLRewriter if that is what you mean
Kamran
Kamran2y ago
@HardAtWork I think I mean to say that to be able to generate the image (using canvas, or even CSS/HTML) in the same request, you'd need to evaluate some script within puppeteer/rendering API before capturing it, if you didn't point it at an existing page to navigate to. So like page.evaluate(myscript) and then await page.screenshot().
bennycondon
bennycondon2y ago
Sounds like the Rendering API is what I'm after - thanks for your help @HardAtWork and @kamranicus!
Want results from more Discord servers?
Add your server