Skip to content

BITMAPCOREHEADER DIB resources decoded incorrectly (wrong color table stride, wrong row stride for 1-bit) #9466

@elemental-admin-dev

Description

@elemental-admin-dev

Bug

When loading raw DIB (device-independent bitmap) data embedded in Windows PE resource sections (e.g. cards.dll), Pillow misreads bitmaps that use the old OS/2 BITMAPCOREHEADER format (12-byte header, bcSize=12).

Two bugs

1. 4-bit paletted images

Color table is read as RGBQUAD (4 bytes/entry) instead of RGBTRIPLE (3 bytes/entry), shifting the pixel data offset by 16 bytes and producing wrong colors.

2. 1-bit monochrome images

Row stride calculation is wrong, producing a solid black stripe ~17px wide on the left of every decoded image.

Repro

Extract any bitmap resource from Windows XP cards.dll using pefile, write the raw bytes (without a BITMAPFILEHEADER) and open with:

from PIL import Image, ImageFile
ImageFile.LOAD_TRUNCATED_IMAGES = True
img = Image.open("raw_dib_resource.bmp")

Workaround

Manually parse the BITMAPCOREHEADER with struct and decode pixel data directly:

import struct
from PIL import Image

def decode_bitmapcoreheader(raw):
    bc_size, = struct.unpack_from("<I", raw, 0)
    assert bc_size == 12
    width, height, planes, bitcount = struct.unpack_from("<HHHH", raw, 4)
    num_colors = 2 ** bitcount if bitcount <= 8 else 0
    # RGBTRIPLE: 3 bytes each (NOT RGBQUAD)
    colors = []
    for i in range(num_colors):
        b, g, r = struct.unpack_from("<BBB", raw, 12 + i * 3)
        colors.append((r, g, b))
    px_offset = 12 + num_colors * 3
    row_stride = ((width * bitcount + 31) // 32) * 4
    px_data = raw[px_offset:]
    img = Image.new("RGB", (width, abs(height)))
    pixels = img.load()
    for row_idx in range(abs(height)):
        y = abs(height) - 1 - row_idx if height > 0 else row_idx
        for col in range(width):
            if bitcount == 1:
                byte_idx = row_idx * row_stride + col // 8
                bit = (px_data[byte_idx] >> (7 - col % 8)) & 1
                pixels[col, y] = colors[bit]
            elif bitcount == 4:
                byte_idx = row_idx * row_stride + col // 2
                nibble = (px_data[byte_idx] >> 4) & 0xF if col % 2 == 0 else px_data[byte_idx] & 0xF
                pixels[col, y] = colors[nibble]
    return img

Environment

  • Pillow 11.x
  • Python 3.14
  • Linux x86_64
  • Source: Windows XP cards.dll PE bitmap resources

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions