{"record":{"id":"4a159fd884d42443","repo":"mihomo-party-org/clash-party","slug":"failed-to-get-2d-context","errorCode":null,"errorMessage":"Failed to get 2D context","messagePattern":"Failed to get 2D context","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/renderer/src/utils/image.ts","lineNumber":18,"sourceCode":"export async function cropAndPadTransparent(\n  base64: string,\n  finalSize = 256,\n  border = 24\n): Promise<string> {\n  const img = new Image()\n  img.src = base64\n  await new Promise((resolve, reject) => {\n    img.onload = resolve\n    img.onerror = reject\n  })\n\n  const canvas = document.createElement('canvas')\n  canvas.width = img.width\n  canvas.height = img.height\n  const ctx = canvas.getContext('2d')\n  if (!ctx) {\n    throw new Error('Failed to get 2D context')\n  }\n  ctx.clearRect(0, 0, canvas.width, canvas.height)\n  ctx.drawImage(img, 0, 0)\n\n  const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height)\n\n  const { data, width, height } = imgData\n  let top = height,\n    bottom = 0,\n    left = width,\n    right = 0\n  for (let y = 0; y < height; y++) {\n    for (let x = 0; x < width; x++) {\n      const i = (y * width + x) * 4 + 3\n      if (data[i] > 10) {\n        if (x < left) left = x\n        if (x > right) right = x\n        if (y < top) top = y","sourceCodeStart":1,"sourceCodeEnd":36,"githubUrl":"https://github.com/mihomo-party-org/clash-party/blob/911e090537acdf7c50bee1c3aebecc2ef119a8b5/src/renderer/src/utils/image.ts#L1-L36","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Guard the call: only invoke after img.complete && img.naturalWidth > 0 (await decode() first).","If dimensions may be huge, cap them (downscale first) so canvas allocation succeeds.","If using an ImageBitmap/SVG, render it onto an intermediate canvas with explicit width/height before cropping.","Keep the try-catch and surface a user-facing 'image could not be processed' message instead of crashing the flow."],"exampleFix":"// before\nconst ctx = canvas.getContext('2d')\nif (!ctx) throw new Error('Failed to get 2D context')\n// after\nawait img.decode()\nif (!img.naturalWidth || !img.naturalHeight) throw new Error('Image has no size')\nconst ctx = canvas.getContext('2d', { willReadFrequently: true })\nif (!ctx) throw new Error('Failed to get 2D context')","handlingStrategy":"type-guard","validationCode":"if (!img.complete || !img.naturalWidth || !img.naturalHeight) {\n  throw new Error('Image not loaded or has zero size')\n}\nif (img.naturalWidth * img.naturalHeight > 4096 * 4096) {\n  throw new Error('Image too large for canvas processing')\n}","typeGuard":"function isDecodedImage(img: HTMLImageElement): img is HTMLImageElement & { naturalWidth: number; naturalHeight: number } {\n  return img.complete && img.naturalWidth > 0 && img.naturalHeight > 0\n}","tryCatchPattern":"try {\n  const result = await cropAndPadTransparent(img)\n} catch (e) {\n  if (e.message === 'Failed to get 2D context') {\n    // fall back to showing the original image unprocessed\n  } else {\n    throw e\n  }\n}","preventionTips":["Await img.decode() (or onload) before canvas operations.","Check naturalWidth/naturalHeight > 0 before processing.","Downscale very large images before canvas cropping to respect browser canvas limits.","Handle promise rejections at call sites instead of letting them bubble into unhandled rejections."],"tags":["canvas","browser-api","image-processing"],"backgroundTag":"canvas-context-null","analyzedSha":"911e090537acdf7c50bee1c3aebecc2ef119a8b5","analyzedAt":"2026-08-30T13:00:49.174Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}