windmill-labs/windmill · error

Image has no dimensions

Error message

Image has no dimensions

What it means

normalizeImageDataUrl() loads a data URL into an HTMLImageElement and derives dimensions from naturalWidth/Height (falling back to width/height). If both are 0/falsy the image cannot be drawn or bounded, so the adapter throws this error immediately before any canvas work. It protects downstream resize/encode logic from division-by-zero and empty canvases.

Source

Thrown at frontend/src/lib/components/copilot/chat/imageUtils.ts:152

	return {
		dataUrl: (ctx ? flat : canvas).toDataURL('image/jpeg', 0.82),
		mediaType: 'image/jpeg'
	}
}

/**
 * Downscale a data URL to ≤ MAX_IMAGE_EDGE on its longest side and re-encode to
 * png/jpeg. Used by both the file-attach path and the screenshot tool.
 */
export async function normalizeImageDataUrl(
	dataUrl: string,
	name?: string,
	maxEdge: number = MAX_IMAGE_EDGE
): Promise<AttachedImage> {
	const img = await loadImage(dataUrl)
	const srcW = img.naturalWidth || img.width
	const srcH = img.naturalHeight || img.height
	if (!srcW || !srcH) throw new Error('Image has no dimensions')
	if (srcW * srcH > MAX_IMAGE_PIXELS) throw new Error('Image resolution is too large')
	const scale = Math.min(1, maxEdge / Math.max(srcW, srcH))
	const w = Math.max(1, Math.round(srcW * scale))
	const h = Math.max(1, Math.round(srcH * scale))
	const canvas = document.createElement('canvas')
	canvas.width = w
	canvas.height = h
	const ctx = canvas.getContext('2d')
	if (!ctx) throw new Error('Canvas 2D context unavailable')
	ctx.drawImage(img, 0, 0, w, h)
	return { ...encodeCanvas(canvas), name }
}

/** Read a user-provided image file and produce a bounded, model-ready AttachedImage. */
export async function fileToAttachedImage(file: File | Blob): Promise<AttachedImage> {
	if (file.size > MAX_IMAGE_BYTES) throw new Error('Image file is too large')
	const name = file instanceof File ? file.name : undefined
	const dataUrl = await blobToDataUrl(file)

View on GitHub (pinned to e474e8803c)

Solutions

  1. Inspect the data URL: verify the base64 payload is complete and the MIME type matches the data.
  2. For SVGs, add explicit width/height (or viewBox with fixed dimensions) before attaching.
  3. Ensure loadImage() awaits the 'load' event, not just element creation, before normalizing.
  4. Pre-convert odd formats (SVG/HEIC) to PNG/JPEG before attaching to the chat.

Example fix

// before
await normalizeImageDataUrl(svgDataUrl) // SVG without dimensions
// after
svgText = svgText.replace('<svg', '<svg width="1024" height="768"')
const fixed = 'data:image/svg+xml;base64,' + btoa(svgText)
const png = await rasterizeToPng(fixed)
await normalizeImageDataUrl(png)
Defensive patterns

Strategy: validation

Validate before calling

async function hasDimensions(dataUrl: string): Promise<boolean> {
  const img = new Image()
  await new Promise((res, rej) => { img.onload = res; img.onerror = rej; img.src = dataUrl })
  return (img.naturalWidth || img.width) > 0 && (img.naturalHeight || img.height) > 0
}

Type guard

function isSizedImage(img: HTMLImageElement): boolean {
  return (img.naturalWidth || img.width) > 0 && (img.naturalHeight || img.height) > 0
}

Try / catch

try {
  const attached = await fileToAttachedImage(file)
} catch (e) {
  if (e instanceof Error && e.message === 'Image has no dimensions') {
    notify('This image has no readable dimensions; try PNG or JPEG')
  } else throw e
}

Prevention

When it happens

Trigger: loadImage() resolved but img.naturalWidth/img.width or img.naturalHeight/img.height are 0 — e.g. the data URL decoded to an empty/broken image, an SVG without intrinsic dimensions, or a still-loading image passed directly.

Common situations: Attaching an SVG pasted from a design tool with no width/height attributes; a truncated base64 payload that still decodes; images constructed programmatically before load completed.

Related errors


AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03). Data as JSON: /api/errors/2e3704b6f0408642. Report an issue: GitHub.