Performance optimization is crucial when building modern web applications like Look Scanned, especially when dealing with images. A powerful yet underutilized tool for achieving significant performance gains is the ImageBitmap interface. In this blog post, we’ll explore what ImageBitmap is, why it’s effective, and how it has been integrated into Look Scanned to enhance rendering performance.

What is ImageBitmap?

ImageBitmap is an HTML5 interface that represents a bitmap image. It enables efficient image decoding and processing off the main thread, reducing rendering overhead and improving responsiveness. Once created, an ImageBitmap object can be directly used in rendering contexts like Canvas 2D or WebGL, making it a powerful tool for image-heavy applications.

Why Use ImageBitmap in Look Scanned?

Previously, Look Scanned relied on Blob to pass image data between processing functions. However, Blob is not ideal for performance optimization since it requires encoding and decoding steps. In contrast, ImageBitmap provides direct access to image data, eliminating redundant decoding operations and significantly improving rendering performance.

How We Use ImageBitmap in Look Scanned

Look Scanned supports older browsers, so it cannot fully transition to ImageBitmap without maintaining support for Blob. This dual support ensures compatibility across a wide range of browsers. For browser compatibility details, refer to caniuse.com. Additionally, Safari’s limited canvas support necessitates using WebAssembly (WASM) for image processing, which requires Blob as an input format.

To manage this, Look Scanned employs a hybrid approach that supports both Blob and ImageBitmap while gradually migrating to the latter. Below are key implementation details:

Loading and Decoding

async function loadImage(url): Promise<ImageBitmap | Blob> {
  const response = await fetch(url);
  const blob = await response.blob();
  if (window.createImageBitmap) {
    return createImageBitmap(blob);
  }
  // Fallback to using Blob
  return blob;
}

WebAssembly Integration

For advanced processing, Blob is passed to WASM to ensure functionality even in unsupported browsers. We need to draw the image to canvas first and then use canvas.toBlob to get Blob.

Fallback Rendering

async function renderImage(canvas, imageUrl) {
  const ctx = canvas.getContext("2d");
  const image = await loadImage(imageUrl);
  if (image instanceof ImageBitmap) {
    ctx.drawImage(image, 0, 0);
  } else {
    const img = new Image();
    img.src = URL.createObjectURL(image);
    img.onload = () => ctx.drawImage(img, 0, 0);
  }
}

Performance Improvements

By integrating ImageBitmap, Look Scanned has achieved a significant reduction in image processing times—from 50ms to 20ms per image. This improvement translates to a smoother and more responsive user experience, particularly for tasks involving scanned documents.

Unexpected Findings

During implementation, we discovered that creating a new ImageBitmap copy using createImageBitmap before transferring it to a worker yields better performance than directly passing the original object. This behavior may be due to browser engine optimizations for transferable objects.

Browser Support

ImageBitmap is supported in most modern browsers, including the latest versions of Chrome, Firefox, Edge, and Safari. For details on compatibility, check createImageBitmap in caniuse.com.

Conclusion

Integrating ImageBitmap into Look Scanned has markedly improved performance by enabling asynchronous decoding, efficient rendering, and better compatibility with Web Workers. While maintaining support for older browsers requires continued use of Blob, the gradual migration to ImageBitmap ensures long-term performance benefits.

By leveraging these advancements, Look Scanned delivers a faster, more responsive experience for its users. Try it out and experience the difference firsthand!