PHPOffice/PhpSpreadsheet · error · PhpOffice\PhpSpreadsheet\Writer\Exception

$bitmap doesn't appear to be a valid bitmap image.\n

Error message

$bitmap doesn't appear to be a valid bitmap image.\n

What it means

BMP files must begin with the 2-byte ASCII signature 'BM'. processBitmap() unpacks the first two bytes and throws when they differ, so PNG/JPEG/GIF/WEBP content is rejected even if the filename ends in .bmp. The file was opened and is large enough - it is simply not a bitmap.

Source

Thrown at src/PhpSpreadsheet/Writer/Xls/Worksheet.php:2480

        // Open file.
        $bmp_fd = @fopen($bitmap, 'rb');
        if ($bmp_fd === false || 0 === (int) filesize($bitmap)) {
            throw new WriterException("Couldn't import $bitmap");
        }

        // Slurp the file into a string.
        $data = (string) fread($bmp_fd, (int) filesize($bitmap));

        // Check that the file is big enough to be a bitmap.
        if (strlen($data) <= 0x36) {
            throw new WriterException("$bitmap doesn't contain enough data.\n");
        }

        // The first 2 bytes are used to identify the bitmap.

        $identity = unpack('A2ident', $data);
        if ($identity === false || $identity['ident'] != 'BM') {
            throw new WriterException("$bitmap doesn't appear to be a valid bitmap image.\n");
        }

        // Remove bitmap data: ID.
        $data = substr($data, 2);

        // Read and remove the bitmap size. This is more reliable than reading
        // the data size at offset 0x22.
        //
        $size_array = unpack('Vsa', substr($data, 0, 4)) ?: [];
        /** @var int */
        $size = $size_array['sa'];
        $data = substr($data, 4);
        $size -= 0x36; // Subtract size of bitmap header.
        $size += 0x0C; // Add size of BIFF header.

        // Remove bitmap data: reserved, offset, header length.
        $data = substr($data, 12);

View on GitHub (pinned to 65b080eef4)

Solutions

  1. Convert the image to an actual BMP (e.g. imagebmp() from GD) before inserting
  2. Detect the real format with getimagesize() and route accordingly
  3. Use the Drawing API with PNG/JPEG, which handles modern formats natively

Example fix

// before
$worksheetWriter->insertBitmap(1, 1, 'photo.png.bmphack'); // PNG data

// after
$im = imagecreatefrompng('photo.png');
imagebmp($im, 'photo.bmp');
$worksheetWriter->insertBitmap(1, 1, 'photo.bmp');
Defensive patterns

Strategy: validation

Validate before calling

function isWindowsBmp(string $path): bool
{
    $fh = fopen($path, 'rb');
    if ($fh === false) {
        return false;
    }
    $magic = fread($fh, 2);
    fclose($fh);

    return $magic === 'BM';
}

if (!isWindowsBmp($path)) {
    $info = getimagesize($path) ?: [];
    throw new InvalidArgumentException(
        'Not a BMP (detected ' . ($info[2] ?? 'unknown') . '); convert before insertBitmap()'
    );
}
$worksheetWriter->insertBitmap($row, $col, $path);

Try / catch

try {
    $worksheetWriter->insertBitmap($row, $col, $path);
} catch (\PhpOffice\PhpSpreadsheet\Writer\Exception $e) {
    if (str_contains($e->getMessage(), 'valid bitmap')) {
        // route through GD to convert whatever it is into a 24-bit BMP, then retry once
    }
}

Prevention

When it happens

Trigger: Passing a PNG or JPEG renamed to .bmp to insertBitmap(); passing a data-stream or non-image file that survived the size check.

Common situations: Assuming the file extension equals the real format; pipelines that 'convert' images by renaming; files served with the wrong content by an upstream system.

Related errors


AI-assisted analysis of PHPOffice/PhpSpreadsheet@65b080eef4 (2026-08-17). Data as JSON: /api/errors/89892b5cde478740. Report an issue: GitHub.