Skip to content

Can't read some 16-bit bitmaps #54

@Enyium

Description

@Enyium

I tried to create a 16-bit bitmap and searched for an online converter. I found https://online-converting.com/image/convert2bmp/, which can convert to RGB565. But the file produced by it couldn't be read by tinybmp: I got ParseError::UnexpectedEndOfFile. Because of that, I later converted to RGB565 using GIMP, which worked. But since the format produced by online-converting.com seems to be valid according to my inspection of it using ImHex, which can decode and visualize the bitmap struct fields, and because XnView can display it, I'm posting this issue.

How to create an RGB565 bitmap with online-converting.com:

  • Go to https://online-converting.com/image/convert2bmp/.
  • Deselect the checkbox "Automatically start" at the bottom of the form before uploading any file.
  • In the "Color" drop-down box, choose "16 (5:6:5, RGB Hi color)".
  • Upload a file.
  • Click "Convert".

Here's an image I converted: 5x1px-16bpp-rgb565.bmp.

The file uses the BITMAPINFOHEADER. For it's biCompression member, which contains BI_BITFIELDS, Microsoft documents:

Value Meaning
BI_RGB Uncompressed RGB.
BI_BITFIELDS Uncompressed RGB with color masks. Valid for 16-bpp and 32-bpp bitmaps.

[...]

For 16-bpp bitmaps, if biCompression equals BI_RGB, the format is always RGB 555. If biCompression equals BI_BITFIELDS, the format is either RGB 555 or RGB 565. Use the subtype GUID in the AM_MEDIA_TYPE structure to determine the specific RGB type.

Since we're not decoding video and there's no AM_MEDIA_TYPE, this isn't very helpful. But there are hints about how to handle BI_BITFIELDS when only having BITMAPINFOHEADER:

  • In an old Windows CE documentation for BITMAPINFOHEADER, it says under biCompression:
Value Description
BI_RGB An uncompressed format.
BI_BITFIELDS Specifies that the bitmap is not compressed and that the color table consists of three DWORD color masks that specify the red, green, and blue components of each pixel. This is valid when used with 16- and 32-bpp bitmaps.
  • In the "Color table" section of Wikipedia's "BMP file format" article, its says:

The color table (palette) occurs in the BMP image file directly after the BMP file header, the DIB header, and after the optional three or four bitmasks if the BITMAPINFOHEADER header with BI_BITFIELDS (12 bytes) or BI_ALPHABITFIELDS (16 bytes) option is used. Therefore, its offset is the size of the BITMAPFILEHEADER plus the size of the DIB header (plus optional 12-16 bytes for the three or four bit masks).

I didn't dive into your code in detail, but your src/header/dib_header.rs only knows HeaderType::Info and HeaderType::V3 and not HeaderType::V2, and it has this bit of code:

        let (_dib_header_data, channel_masks) = if header_type.is_at_least(HeaderType::V3)
            && matches!(compression_method, CompressionMethod::Bitfields)
        {
            let (dib_header_data, mask_red) = try_const!(le_u32(dib_header_data));
            let (dib_header_data, mask_green) = try_const!(le_u32(dib_header_data));
            let (dib_header_data, mask_blue) = try_const!(le_u32(dib_header_data));
            let (dib_header_data, mask_alpha) = try_const!(le_u32(dib_header_data));

            (
                dib_header_data,
                Some(ChannelMasks {
                    red: mask_red,
                    green: mask_green,
                    blue: mask_blue,
                    alpha: mask_alpha,
                }),
            )
        } else {
            (dib_header_data, None)
        };

But the "DIB header (bitmap information header)" section of the Wikipedia article also lists a BITMAPV2INFOHEADER, which only added "RGB bit masks," whereas, the BITMAPV3INFOHEADER added the single additional "alpha channel bit mask." So, it seems, tinybmp needs to differentiate between just interpreting the first 3 vs. the first 4 RGBQUADs from BITMAPINFO. Adding HeaderType::V2 seems to be incorrect, because your code reads dib_header_length, which would still be sizeof(BITMAPINFOHEADER). As a quick assessment, you'd need to remove the first part of the condition if header_type.is_at_least(HeaderType::V3) && matches!(compression_method, CompressionMethod::Bitfields) and then only retrieve mask_alpha, if header_type.is_at_least(HeaderType::V3) is true.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions