PaddlePaddle/PaddleOCR · error · Error

Failed to create a 2D canvas context in the OCR worker.

Error message

Failed to create a 2D canvas context in the OCR worker.

What it means

Thrown inside the OCR worker when an OffscreenCanvas is successfully created but getContext("2d", { willReadFrequently: true }) returns null. This is rarer than the capability gate and indicates the canvas could not allocate a 2D context at all - typically resource exhaustion (too many live canvases/GPU memory) or a browser engine defect, since the worker already verified OffscreenCanvas exists.

Source

Thrown at paddleocr-js/packages/core/src/platform/worker.ts:22

 */

import type { OpenCv, Mat } from "@techstark/opencv-js";
import type { SourceMatResult } from "./browser";
import { ensureServedFromHttp } from "./browser";

export interface WorkerSourcePayload {
  kind: "imageBitmap";
  imageBitmap: ImageBitmap;
}

function imageBitmapToImageData(imageBitmap: ImageBitmap): ImageData {
  if (typeof OffscreenCanvas !== "function") {
    throw new Error("Worker mode requires OffscreenCanvas support in this browser.");
  }
  const canvas = new OffscreenCanvas(imageBitmap.width, imageBitmap.height);
  const ctx = canvas.getContext("2d", { willReadFrequently: true });
  if (!ctx) {
    throw new Error("Failed to create a 2D canvas context in the OCR worker.");
  }
  ctx.drawImage(imageBitmap, 0, 0);
  return ctx.getImageData(0, 0, imageBitmap.width, imageBitmap.height);
}

function imageDataToMat(cv: OpenCv, imageData: ImageData): Mat {
  return cv.matFromArray(imageData.height, imageData.width, cv.CV_8UC4, imageData.data);
}

function isWorkerSourcePayload(source: unknown): source is WorkerSourcePayload {
  if (typeof source !== "object" || source === null) return false;
  const candidate = source as Record<string, unknown>;
  return (
    candidate["kind"] === "imageBitmap" &&
    typeof ImageBitmap !== "undefined" &&
    candidate["imageBitmap"] instanceof ImageBitmap
  );
}

View on GitHub (pinned to 2661c7c0ef)

Solutions

  1. Throttle concurrent OCR jobs in the worker so only a few OffscreenCanvas instances are live at once
  2. Reuse a single OffscreenCanvas resized per job instead of allocating per image
  3. Reduce input image resolution before OCR to lower canvas memory
  4. If persistent, fall back to main-thread OCR

Example fix

// before
// worker processes all jobs in parallel, each allocating OffscreenCanvas
await Promise.all(images.map(i => client.recognize(i))); // may exhaust contexts

// after
const CONCURRENCY = 2;
for (let i = 0; i < images.length; i += CONCURRENCY) {
  await Promise.all(images.slice(i, i + CONCURRENCY).map(img => client.recognize(img)));
}
Defensive patterns

Strategy: fallback

Validate before calling

// Hard to pre-validate; bound concurrent worker jobs instead
const MAX_CONCURRENT = 2; // keep live OffscreenCanvas count low

Try / catch

try {
  await workerClient.recognize(bitmap);
} catch (e) {
  if (e instanceof Error && e.message.includes("OCR worker")) {
    await workerClient.dispose();
    return mainThreadOcr.recognize(bitmap); // fallback path
  }
  throw e;
}

Prevention

When it happens

Trigger: High-throughput worker OCR pipelines that allocate a new OffscreenCanvas per image without releasing prior ones; devices under severe memory pressure; browsers that disable 2D contexts in certain worker configurations.

Common situations: Batch-processing hundreds of images through the worker without throttling; low-memory mobile devices; automation environments with software rendering.

Related errors


AI-assisted analysis of PaddlePaddle/PaddleOCR@2661c7c0ef (2026-08-14). Data as JSON: /api/errors/7a1e25043dc36a9a. Report an issue: GitHub.