Test files › Images › png-interlaced.png

Sample PNG file (interlaced)

PNG with the Adam7 interlace flag set in IHDR. Note: pixel data is stored non-interlaced, so strict decoders will reject it - that is intentional.

↓ Download png-interlaced.png
Filenamepng-interlaced.png
FormatPNG
Size426 bytes (426 bytes)
MIME typeimage/png
SHA-256575ae26f4ac35073b117c5c03101ef1debdc8b1e1b34e58c5d856bc1e90e89a0
What this file catchesDecoders that trust the IHDR interlace byte without validating the IDAT layout, and pipelines that assume every PNG they receive is progressive-safe.

Handle it in code

Each snippet below was run against this exact file with Python 3.14.4 and Node 24.15.0 when this page was built, and the output shown is what it printed.

Python

Read the width and height from the header and refuse oversized images before any decoder allocates memory. A 265 KB PNG can declare 8000x8000 pixels, which decodes to 256 MB of RGBA. Pillow's default guard (Image.MAX_IMAGE_PIXELS, about 89 million pixels) lets a 64-million-pixel image through without a warning, so set your own limit.

import struct

MAX_PIXELS = 25_000_000  # a decoder needs about 4 bytes per pixel

def image_size(path):
    with open(path, "rb") as f:
        head = f.read(64 * 1024)
    if head[:8] == b"\x89PNG\r\n\x1a\n":
        return "png", *struct.unpack(">II", head[16:24])
    if head[:6] in (b"GIF87a", b"GIF89a"):
        return "gif", *struct.unpack("<HH", head[6:10])
    if head[:2] == b"BM":
        w, h = struct.unpack("<ii", head[18:26])
        return "bmp", w, abs(h)  # a negative height means rows are stored top-down
    if head[:4] == b"RIFF" and head[8:12] == b"WEBP":
        chunk = head[12:16]
        if chunk == b"VP8 ":
            w, h = struct.unpack("<HH", head[26:30])
            return "webp", w & 0x3FFF, h & 0x3FFF
        if chunk == b"VP8L":
            bits = int.from_bytes(head[21:25], "little")
            return "webp", (bits & 0x3FFF) + 1, (bits >> 14 & 0x3FFF) + 1
        if chunk == b"VP8X":
            return "webp", int.from_bytes(head[24:27], "little") + 1, int.from_bytes(head[27:30], "little") + 1
    if head[:3] == b"\xff\xd8\xff":
        i = 2
        while i + 9 <= len(head) and head[i] == 0xFF:
            marker, length = head[i + 1], int.from_bytes(head[i + 2:i + 4], "big")
            if 0xC0 <= marker <= 0xCF and marker not in (0xC4, 0xC8, 0xCC):  # start of frame
                h, w = struct.unpack(">HH", head[i + 5:i + 9])
                return "jpg", w, h
            i += 2 + length
        raise ValueError("no JPEG frame header in the first 64 KB")
    raise ValueError("not a PNG, GIF, BMP, WebP or JPEG")

try:
    kind, w, h = image_size("png-interlaced.png")
except ValueError as e:
    print("rejected:", e)
else:
    if w * h > MAX_PIXELS:
        print(f"refused: {kind} {w}x{h} is {w * h:,} pixels, over the {MAX_PIXELS:,} limit")
    else:
        print(f"ok: {kind} {w}x{h}")

Run against this file, it prints: ok: png 128x128

Node.js

The same header check in Node, before the upload reaches sharp, jimp or a canvas.

const fs = require('node:fs');

const MAX_PIXELS = 25_000_000; // a decoder needs about 4 bytes per pixel

function imageSize(file) {
  const buf = Buffer.alloc(64 * 1024);
  const fd = fs.openSync(file, 'r');
  const h = buf.subarray(0, fs.readSync(fd, buf, 0, buf.length, 0));
  fs.closeSync(fd);
  const ascii = (a, b) => h.toString('latin1', a, b);
  if (h.subarray(0, 8).equals(Buffer.from('89504e470d0a1a0a', 'hex'))) return ['png', h.readUInt32BE(16), h.readUInt32BE(20)];
  if (ascii(0, 6) === 'GIF87a' || ascii(0, 6) === 'GIF89a') return ['gif', h.readUInt16LE(6), h.readUInt16LE(8)];
  if (ascii(0, 2) === 'BM') return ['bmp', h.readInt32LE(18), Math.abs(h.readInt32LE(22))];
  if (ascii(0, 4) === 'RIFF' && ascii(8, 12) === 'WEBP') {
    const chunk = ascii(12, 16);
    if (chunk === 'VP8 ') return ['webp', h.readUInt16LE(26) & 0x3fff, h.readUInt16LE(28) & 0x3fff];
    if (chunk === 'VP8L') {
      const bits = h.readUInt32LE(21);
      return ['webp', (bits & 0x3fff) + 1, ((bits >>> 14) & 0x3fff) + 1];
    }
    if (chunk === 'VP8X') return ['webp', h.readUIntLE(24, 3) + 1, h.readUIntLE(27, 3) + 1];
  }
  if (h[0] === 0xff && h[1] === 0xd8 && h[2] === 0xff) {
    for (let i = 2; i + 9 <= h.length && h[i] === 0xff; i += 2 + h.readUInt16BE(i + 2)) {
      const m = h[i + 1];
      if (m >= 0xc0 && m <= 0xcf && ![0xc4, 0xc8, 0xcc].includes(m)) return ['jpg', h.readUInt16BE(i + 7), h.readUInt16BE(i + 5)];
    }
    throw new Error('no JPEG frame header in the first 64 KB');
  }
  throw new Error('not a PNG, GIF, BMP, WebP or JPEG');
}

try {
  const [kind, w, h] = imageSize('png-interlaced.png');
  const n = (x) => x.toLocaleString('en-US');
  console.log(w * h > MAX_PIXELS
    ? 'refused: ' + kind + ' ' + w + 'x' + h + ' is ' + n(w * h) + ' pixels, over the ' + n(MAX_PIXELS) + ' limit'
    : 'ok: ' + kind + ' ' + w + 'x' + h);
} catch (e) {
  console.log('rejected:', e.message);
}

Run against this file, it prints: ok: png 128x128

Related guides and tools

Sample PNG fileFile signaturesMIME typesUpload validation checklistCheck a file's real typeVerify its SHA-256

Download & verify

curl -fsSL -o "png-interlaced.png" "https://files.hexaqa.com/image/png-interlaced.png"
curl -fsSL "https://files.hexaqa.com/image/png-interlaced.png" | sha256sum # expect: 575ae26f4ac35073b117c5c03101ef1debdc8b1e1b34e58c5d856bc1e90e89a0

More images test files

png-1x1.pngpng-8000x8000.pngpng-alpha-gradient.pngpng-bad-crc.pngpng-truncated.pngpng-valid-256.pngsvg-external-entity.svgsvg-plain.svg

← All Images files