paperclipai/paperclip · error

Decoded image exceeds the pixel limit

Error message

Decoded image exceeds the pixel limit

What it means

validatePhotonImage decodes metadata via sharp (with limitInputPixels: MAX_PIXELS and failOn: 'error') and throws when width/height are missing or width*height*pages exceeds MAX_PIXELS (50,000,000). This rejects decoded images that are too large — including animated/multi-page formats where total pixels scale by page count.

Solutions

  1. Downscale client-side before upload (e.g. browser canvas or sharp resize to max 8192px) to stay under 50MP.
  2. If it's an animated GIF/WebP, trim frames or convert to a video/shorter animation so width*height*pages fits the budget.
  3. If larger images are a legitimate product need, raise MAX_PIXELS in media.ts (and note sharp's memory implications).
  4. Catch this error in ingestAttachments and return a 413/422 with a user-facing 'image resolution too high' message rather than failing the whole request.

Example fix

// before
await validatePhotonImage(rawBuffer, 'image/png'); // 80MP panorama -> throws
// after
const resized = await sharp(rawBuffer).resize({ width: 8000, withoutEnlargement: true }).png().toBuffer();
await validatePhotonImage(resized, 'image/png');
Defensive patterns

Strategy: validation

Validate before calling

import sharp from 'sharp';
export async function isWithinPixelLimit(body: Buffer): Promise<boolean> {
  const meta = await sharp(body, { limitInputPixels: 50_000_000 }).metadata().catch(() => null);
  if (!meta?.width || !meta?.height) return false;
  return meta.width * meta.height * (meta.pages ?? 1) <= 50_000_000;
}

Type guard

function hasBoundedDecodedSize(m: { width?: number; height?: number; pages?: number }): boolean {
  return typeof m.width === 'number' && typeof m.height === 'number' &&
    m.width > 0 && m.height > 0 &&
    m.width * m.height * (m.pages ?? 1) <= 50_000_000;
}

Try / catch

try {
  await validatePhotonImage(body, contentType);
} catch (err) {
  if (err instanceof Error && err.message === 'Decoded image exceeds the pixel limit') {
    const downscaled = await sharp(body).resize({ width: 8192, withoutEnlargement: true }).toBuffer();
    return validatePhotonImage(downscaled, contentType);
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling validatePhotonImage (from ingestAttachments or photonHeifPreview's re-validation of converted JPEG) with a non-HEIF image/* buffer whose decoded dimensions exceed 50MP, or an animated GIF/WebP whose frame count times frame size exceeds 50MP; also zero-dimension metadata from corrupt images.

Common situations: Users uploading 60MP+ DSLR panoramas; large animated GIFs with hundreds of frames; corrupt images where sharp returns no width/height; screenshots at extreme resolutions.

Understand the failure class

Background: "File too large" / "file size exceeds limit" errors: why libraries cap file sizes and how to fix them — this error's family across 46 libraries.

Related errors


AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18). Data as JSON: /api/errors/444ed354c5157629. Report an issue: GitHub.

Appendix: source

Thrown at server/src/services/photon/media.ts:88

    throw new Error("HEIF dimensions could not be verified");
}

export async function validatePhotonImage(
  body: Buffer,
  contentType: string,
): Promise<void> {
  if (!contentType.startsWith("image/")) return;
  if (HEIF_CONTENT_TYPES.has(contentType)) return validateHeifDimensions(body);
  const metadata = await sharp(body, {
    limitInputPixels: MAX_PIXELS,
    failOn: "error",
  }).metadata();
  if (
    !metadata.width ||
    !metadata.height ||
    metadata.width * metadata.height * (metadata.pages ?? 1) > MAX_PIXELS
  )
    throw new Error("Decoded image exceeds the pixel limit");
  const formats: Record<string, string[]> = {
    "image/jpeg": ["jpeg"],
    "image/jpg": ["jpeg"],
    "image/png": ["png"],
    "image/webp": ["webp"],
    "image/gif": ["gif"],
  };
  if (!formats[contentType]?.includes(metadata.format ?? ""))
    throw new Error("Image bytes do not match the declared content type");
}

/** Isolate the native converter with a deadline and bounded input/output.
 * Native packages exist for macOS, Windows and Linux glibc x64/arm64.
 * Unsupported hosts retain the original and report an unavailable preview. */
export async function photonHeifPreview(body: Buffer): Promise<Buffer> {
  validateHeifDimensions(body);
  const modulePath = require.resolve("heif2jpeg");
  const script = `const {heifToJpeg}=require(process.argv[1]);const chunks=[];process.stdin.on('data',c=>chunks.push(c));process.stdin.on('end',async()=>{try{const jpeg=await heifToJpeg(Buffer.concat(chunks),{quality:80});process.stdout.end(jpeg);}catch{process.exitCode=1;}});`;

View on GitHub (pinned to 3f1d897a7c)