mihomo-party-org/clash-party · error · Error

Failed to get 2D context

Error message

Failed to get 2D context

What it means

cropAndPadTransparent() crops an image to its non-transparent bounding box and pads it into a square using a hidden canvas. It calls canvas.getContext('2d') on the source canvas and throws 'Failed to get 2D context' if the browser returns null. getContext returns null when the 2D context cannot be created, which is rare but possible.

Source

Thrown at src/renderer/src/utils/image.ts:18

export async function cropAndPadTransparent(
  base64: string,
  finalSize = 256,
  border = 24
): Promise<string> {
  const img = new Image()
  img.src = base64
  await new Promise((resolve, reject) => {
    img.onload = resolve
    img.onerror = reject
  })

  const canvas = document.createElement('canvas')
  canvas.width = img.width
  canvas.height = img.height
  const ctx = canvas.getContext('2d')
  if (!ctx) {
    throw new Error('Failed to get 2D context')
  }
  ctx.clearRect(0, 0, canvas.width, canvas.height)
  ctx.drawImage(img, 0, 0)

  const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height)

  const { data, width, height } = imgData
  let top = height,
    bottom = 0,
    left = width,
    right = 0
  for (let y = 0; y < height; y++) {
    for (let x = 0; x < width; x++) {
      const i = (y * width + x) * 4 + 3
      if (data[i] > 10) {
        if (x < left) left = x
        if (x > right) right = x
        if (y < top) top = y

View on GitHub (pinned to 911e090537)

Solutions

  1. Guard the call: only invoke after img.complete && img.naturalWidth > 0 (await decode() first).
  2. If dimensions may be huge, cap them (downscale first) so canvas allocation succeeds.
  3. If using an ImageBitmap/SVG, render it onto an intermediate canvas with explicit width/height before cropping.
  4. Keep the try-catch and surface a user-facing 'image could not be processed' message instead of crashing the flow.

Example fix

// before
const ctx = canvas.getContext('2d')
if (!ctx) throw new Error('Failed to get 2D context')
// after
await img.decode()
if (!img.naturalWidth || !img.naturalHeight) throw new Error('Image has no size')
const ctx = canvas.getContext('2d', { willReadFrequently: true })
if (!ctx) throw new Error('Failed to get 2D context')
Defensive patterns

Strategy: type-guard

Validate before calling

if (!img.complete || !img.naturalWidth || !img.naturalHeight) {
  throw new Error('Image not loaded or has zero size')
}
if (img.naturalWidth * img.naturalHeight > 4096 * 4096) {
  throw new Error('Image too large for canvas processing')
}

Type guard

function isDecodedImage(img: HTMLImageElement): img is HTMLImageElement & { naturalWidth: number; naturalHeight: number } {
  return img.complete && img.naturalWidth > 0 && img.naturalHeight > 0
}

Try / catch

try {
  const result = await cropAndPadTransparent(img)
} catch (e) {
  if (e.message === 'Failed to get 2D context') {
    // fall back to showing the original image unprocessed
  } else {
    throw e
  }
}

Prevention

When it happens

Trigger: Calling cropAndPadTransparent with an <img> whose width or height is 0 (e.g. image not loaded or broken src), so the canvas gets a 0-sized dimension and the 2D context cannot be instantiated; browser/hardware failing to allocate a canvas backbuffer (huge width*height from a corrupt or enormous image); hostile environments where getContext('2d') is stubbed to null.

Common situations: Pasting/processing an image before its onload fired so img.width is 0; an animated or SVG image without intrinsic size; very large screenshots (e.g. 30000px wide) exhausting GPU memory; WebView with accelerated canvas disabled.


AI-assisted analysis of mihomo-party-org/clash-party@911e090537 (2026-08-30). Data as JSON: /api/errors/4a159fd884d42443. Report an issue: GitHub.