Skip to content

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

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions