logo hsb.horse
← Back to blog index

Blog

Implementing Image Processing with OffscreenCanvas and Web Workers

How to process images with Web Workers and OffscreenCanvas without blocking the main thread. This article organizes a concrete WebP conversion example.

Published:

By combining OffscreenCanvas with Web Workers, you can process images without blocking the main thread.

Technologies used

  • Web Worker
  • Offscreen Canvas
  • Blob
  • Bitmap

Interface

export interface WorkerRequestBody {
buffer: ArrayBuffer;
type: string;
size: {
height: number;
width: number;
};
}
export interface WorkerResponseBody {
buffer: ArrayBuffer;
contentType: string;
}

Process an image inside a Web Worker

async function imageProcessing(
data: WorkerRequestBody
): Promise<WorkerResponseBody> {
const { buffer, type, size } = data;
// Convert the ArrayBuffer back into a Blob
const blob = new Blob([buffer], { type });
// Create an OffscreenCanvas
const canvas = new OffscreenCanvas(size.width, size.height);
// Create a Bitmap from the Blob
const bitmap = await createImageBitmap(blob);
// Get a CanvasRenderingContext2D
const ctx = canvas.getContext("2d");
if (!ctx) throw new Error("Context2D is not defined");
// Draw the image onto the canvas
ctx.drawImage(bitmap, 0, 0);
// Convert to WebP
const webp = await canvas.convertToBlob({
type: "image/webp",
quality: 0.85,
});
// Convert the WebP Blob into an ArrayBuffer
const buf = await webp.arrayBuffer();
return {
buffer: buf,
contentType: "image/webp",
};
}
self.addEventListener("message", async (e: MessageEvent<WorkerRequestBody>) => {
const output = await imageProcessing(e.data);
// Pass the second argument so ownership of the ArrayBuffer moves to the UI thread
self.postMessage(output, [output.buffer]);
});

Pass an image to the worker and receive the converted image

import MyWorker from "./worker.ts";
const worker = new MyWorker();
const fileInput = document.querySelector<HTMLInputElement>("#js-file-input");
worker.addEventListener("message", (e: MessageEvent<WorkerResponseBody>) => {
const { buffer, contentType } = e.data;
const blob = new Blob([buffer], { type: contentType });
// do something
});
fileInput.addEventListener("change", async (e) => {
const [file] = Array.from(fileInput.files);
if (!file) return;
const buffer = await file.arrayBuffer();
const type = file.type;
const size = { height: 1080, width: 1980 }; // obtain image size
// Send to the Web Worker and transfer ownership
worker.postMessage(
{
buffer,
type,
size
},
[buffer]
);
});

Key points

  1. OffscreenCanvas: a Canvas API that works inside Web Workers
  2. ArrayBuffer transfer: hand off ownership for efficient data passing
  3. convertToBlob: convert to formats such as WebP using OffscreenCanvas

Summary

By combining OffscreenCanvas and Web Workers, you can run image processing without blocking the main thread.

When you need to process many images or run expensive conversions, this approach helps preserve UI responsiveness and improves the user experience.