v145 · Web APIs · Format Guide

Format Guide

The BMP file structure, the BI_JPEG/BI_PNG compression fields, why they were removed, and how to detect and migrate affected files.

JPEG-in-BMP and PNG-in-BMP are printer driver formats (Microsoft Windows GDI) not intended for web use. They never appeared in any web image specification. Chrome's support was an accidental side effect of sharing the Windows image decoder.
Interactive BMP header analyzer
Choose a sample or drop in a local BMP. The analyzer reads bytes 0-63 only; nothing is uploaded.
Run the analyzer to classify the BMP container.
signature
pending
biCompression
pending
payload signature
pending
Affected files should be served as direct JPEG or PNG assets, or converted to a normal BMP/WebP/AVIF pipeline.

why Chrome removed it

Security Nesting JPEG or PNG decoders inside a BMP container expands attack surface for a format that is otherwise simple.
Interoperability This BMP extension was Chrome-only web behavior, with no official web image specification behind it.
Usage ChromeStatus reports no registered UMA usage, so the removal ships without a deprecation period.
Migration Use the embedded JPEG or PNG directly instead of wrapping it in a BMP file container.

BMP file structure

// BMP file layout (simplified):
// [14 bytes] BITMAPFILEHEADER
//   - bfType: 'BM' (0x42 0x4D)
//   - bfSize: file size
//   - bfOffBits: offset to pixel data
//
// [40 bytes] BITMAPINFOHEADER
//   - biSize: 40
//   - biWidth, biHeight
//   - biBitCount: bits per pixel
//   - biCompression: compression type  ← key field
//     0 = BI_RGB (uncompressed)
//     1 = BI_RLE8
//     2 = BI_RLE4
//     3 = BI_BITFIELDS
//     4 = BI_JPEG  ← removed in Chrome 145
//     5 = BI_PNG   ← removed in Chrome 145
//
// [pixel data]
//   For BI_JPEG: a valid JPEG stream (starts with 0xFF 0xD8 0xFF)
//   For BI_PNG: a valid PNG stream (starts with 0x89 0x50 0x4E 0x47)

detecting affected files

// Read the BMP header to check biCompression:
async function detectBmpVariant(url) {
  const resp = await fetch(url);
  const buf = await resp.arrayBuffer();
  const view = new DataView(buf);

  // Check BMP signature
  const sig = String.fromCharCode(view.getUint8(0), view.getUint8(1));
  if (sig !== 'BM') return 'Not a BMP file';

  // biCompression is at offset 30 (little-endian uint32)
  const biCompression = view.getUint32(30, true);
  switch (biCompression) {
    case 0: return 'BI_RGB (safe)';
    case 1: return 'BI_RLE8 (safe)';
    case 2: return 'BI_RLE4 (safe)';
    case 4: return 'BI_JPEG (REMOVED in Chrome 145)';
    case 5: return 'BI_PNG (REMOVED in Chrome 145)';
    default: return 'Unknown (' + biCompression + ')';
  }
}

see also