Engineering Journal
Pdf Processor
Pdf Processor

Hot Take: PDF Image Extraction Is a Render Problem, Not a Decode Problem

2026-06-04

TLDR

Treating PDF image extraction as a binary resource decoding problem ("how do I unpack embedded JPEG bytes?") is fundamentally flawed for browser applications. PDF images are paint commands (paintImageXObject) evaluated dynamically within Current Transformation Matrices (CTM). Render the page onto a high-resolution OffscreenCanvas inside Web Workers, then crop image bounding boxes from the composite canvas.

Extraction ModelParadigmWeb Worker FeasibilityImage Crop Quality
Binary Resource DecodingUnpack raw embedded PDF streamsFails (Unpopulated page.objs)Fails (Lacks CTM scale/transform)
Canvas Render InterceptCrop pixels from page.render()Passes (OffscreenCanvasFactory)100% Sharp ($384\text{ DPI}$ crops)

Technical problem statement: the fallacy of raw stream extraction

Standard PDF Processors attempt to fetch raw image bytes directly from page.objs. However, PDF.js populates page.objs exclusively during page.render() execution loops.

Querying page.objs synchronously prior to rendering yields un-resolved object promises:

$$\text{Error: Requesting object that is not resolved yet}$$


Technical solution: high-resolution render-and-crop architecture

Execute a high-resolution render pass ($4.0\times$ scale) inside a Web Worker, then crop image bounding boxes into Base64 PNG data URLs:

// High-Resolution Canvas Crop Architecture
export async function extractRenderedImageCrop(
  pageCanvas,
  bbox,
  scaleRatio = 2.0,
) {
  const sx = Math.max(0, Math.round(bbox.x * scaleRatio));
  const sy = Math.max(0, Math.round(bbox.y * scaleRatio));
  const sw = Math.min(Math.round(bbox.w * scaleRatio), pageCanvas.width - sx);
  const sh = Math.min(Math.round(bbox.h * scaleRatio), pageCanvas.height - sy);

const cropCanvas = new OffscreenCanvas(sw, sh); cropCanvas .getContext("2d") .drawImage(pageCanvas, sx, sy, sw, sh, 0, 0, sw, sh);

const blob = await cropCanvas.convertToBlob({ type: "image/png" }); const arrayBuffer = await blob.arrayBuffer();

return data:image/png;base64,${arrayBufferToBase64(arrayBuffer)}; }

Rule of thumb: Treat PDF image extraction as a canvas rendering intercept problem rather than a binary resource decoding task.
Read this post in the full Engineering Journal →