toeverything/AFFiNE · warning · Error

Failed to read image size

Error message

Failed to read image size

What it means

Thrown by buildPropsWith (image block utils) when readImageSize(file) returns width*height === 0. readImageSize creates an Image from the file via createObjectURL and resolves {0,0} when the image fails to load (onerror) or the file type is not image/*. The throw aborts insertion; a user-facing toast 'Failed to read image size, please try another image' is shown first. A plain Error, not a BlockSuiteError.

Source

Thrown at blocksuite/affine/blocks/image/src/utils.ts:239

  if (exceeded) {
    const size = formatSize(maxFileSize);
    toast(std.host, `You can only upload files less than ${size}`);
  }

  return exceeded;
}

async function buildPropsWith(std: BlockStdScope, file: File) {
  const { size } = file;
  const [imageSize, sourceId] = await Promise.all([
    readImageSize(file),
    std.store.blobSync.set(file),
  ]);

  if (!(imageSize.width * imageSize.height)) {
    toast(std.host, 'Failed to read image size, please try another image');
    throw new Error('Failed to read image size');
  }

  return { size, sourceId, ...imageSize } satisfies Partial<ImageBlockProps>;
}

export async function addSiblingImageBlocks(
  std: BlockStdScope,
  files: File[],
  targetModel: BlockModel,
  placement: 'after' | 'before' = 'after'
) {
  files = files.filter(file => file.type.startsWith('image/'));
  if (!files.length) return [];

  if (hasExceeded(std, files)) return [];

  const flavour = ImageBlockSchema.model.flavour;

View on GitHub (pinned to 26c515e050)

Solutions

  1. Pre-validate files before addImageBlocks: check size > 0 and type starts with image/, and for raster types attempt a decode check (createImageBitmap) before insertion.
  2. Catch the error at the call site of addSiblingImageBlocks/addImageBlocks and skip the offending file, continuing with the rest.
  3. Advise the user (the built-in toast already does) to try another image; strip non-decodable files from the drop/paste batch upstream.

Example fix

// before
await addSiblingImageBlocks(std, files, targetModel);

// after
const decodable = await Promise.all(
  files.map(async f => {
    try { await createImageBitmap(f); return f; } catch { return null; }
  })
);
await addSiblingImageBlocks(std, decodable.filter(Boolean), targetModel);
Defensive patterns

Strategy: validation

Validate before calling

async function isDecodable(file) {
  if (!file.type.startsWith('image/') || file.size === 0) return false;
  try { await createImageBitmap(file); return true; } catch { return false; }
}

Type guard

function isPlausibleImage(file) {
  return file.type.startsWith('image/') && file.size > 0;
}

Try / catch

try { await addSiblingImageBlocks(std, files, targetModel); }
catch (e) {
  if (/read image size/i.test(e?.message ?? '')) {
    // skip bad files, retry with the rest
  } else throw e;
}

Prevention

When it happens

Trigger: addSiblingImageBlocks or addImageBlocks processing a File whose MIME is image/* (passes the filter) but the bytes are corrupt, truncated, zero-byte, or an unsupported/decodable format, so the browser's Image decoder fires onerror. Also when the file is a SVG (some browsers report 0x0 for object-URL SVGs without intrinsic dimensions).

Common situations: Uploading a corrupt or partially-downloaded image; a file with a spoofed image/* MIME type that is not actually decodable; SVG without width/height; an empty 0-byte file that passed the earlier filter; very large images that fail to decode under memory pressure.

Related errors


AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12). Data as JSON: /api/errors/52d88a936da8362e. Report an issue: GitHub.