Goofy bitmap horrible nonsense garbage!

Started by
14 comments, last by frob 6 years, 10 months ago

I was trying to read bitmaps manually by just loading the files and reading bytes from them, and I noticed something very odd. I got some unexpected results when I read or manipulated the data for bitmaps in which the width and/or height is not divisible by 4, but it always seemed to work fine when they are divisible by 4.

I looked though the header information I was getting, and weirdly, the file size and image size fields (I don't know why it needs both, one's just the other + 54 for the header size, but whatever) are larger than they should be. They're still a difference of 54, but the image size is bigger than the actual number of pixels * bytes per pixel (3 for 24 bit color).

This seemed to be the cause of a problem when I tried to save a bitmap, so even when I tried to just read one and write it directly to another one, it wouldn't open in Paint or any image viewer. I suspected that it was because it thinks the file size is larger than it actually is, because the header info is wrong (which makes me wonder, what is Paint storing in the extra data?).

So I tried adjusting those 2 fields to equal the actual amount of bytes of my pixels (+ 54 for the file size), and now it will open, but looks oddly skewed. I thought that maybe it's padding the scan lines with blank pixels or something, but that would be wasteful, and nothing in the header seems to indicate any amount of padding. So I tried copying the data first before saving it, and copied pixel by pixel, so that it would come in correctly, but that doesn't fix it either.

I've used bitmaps before, and never had a problem like this. What could be wrong with it? And no, I DON'T have source code, but for God's sake, this is a common enough format that someone should understand it anyway, isn't it?

Advertisement

Oh, I just read somewhere that bitmap scan lines are padded to have a multiple of 4 bytes per line. That's dumb; what's the point of it? And can I be absolutely sure that it's universally true, or is it optional?

I've used bitmaps before, and never had a problem like this. What could be wrong with it? And no, I DON'T have source code, but for God's sake, this is a common enough format that someone should understand it anyway, isn't it?

Many people understand it, and it is extremely well documented. Not only in MSDN, but also many different file format sites, and even in Wikipedia.

The bitmap format is old, dating back to the early 1980s. That's why you see so many thing you might think are strange.

The sizes don't necessarily need to be run by fours, they can be different sizes. This is due to old hardware that worked with different size scan lines relative to the sizes of the displayed images.

There are plenty of places in the format where gaps appear. Again, this is due to hardware variation over the decades.

Data was originally one bit per pixel for black and white images (where the original bitmap name came from) up through 32 bits per pixel commonly used today. Colors can be stored in color tables, in bitplanes, in RGB values, and other formats besides. Those all come from hardware and software evolution over the decades.

Byte ordering of data can differ because of old hardware. People today use RGB or RGBA ordering. Back years ago BGR and BGRA were the common orderings, so those are supported in the format.

The same thing with color planes, back in CGA, EGA, and VGA monitor eras through the 80s and 90s data was not stored in RGB triplets. It wasn't until VGA introduced something called "chained" mode. It was more friendly to hobby developers because they could work in RGB sets instead of color planes with more complicated offsets, and it was slower for the graphics cards, and it meant you could only reference 64K instead of the full 256K of video memory. But most casual developers were wiling to take the tradeoffs of slower graphics and less memory in exchange for ease of use. So there's another historical change.

Many years ago devices commonly had pixel ordering that went bottom to top, so that's why they have positive height going bottom to top, negative height going top to bottom.

30 years of updates have caused many variations and subtleties in the file format.

Very frequently when accessing raw pixel data, you will need to use the "stride" of a row instead of the width when calculating the index for a given x,y.

ex:


index = y * stride + x; // right
index = y * width + x; // wrong unless you get lucky and stride == width
Is bitmap really that simple? Headers palettes then pixel data I thought pixel data is related to palette

Is bitmap really that simple? Headers palettes then pixel data I thought pixel data is related to palette
It varies. In black/white image formats (mono colours), it's literally a bit for each pixel. The colours aren't relevant mostly, it's commonly used for masks. (The XBM format is fun, its data is stored as C-text, so you can literally #include "myimage.xbm" and access the array.)

8bpp image formats like gif or png used a palette, mostly originating from the video hardware, where having 3 bytes for each pixel was too costly, so they had a 256 entries palette, and one byte for each pixel instead.

Nowadays, memory size is a non-issue, so you have 3 bytes for each pixel at least, and 4 bytes is common, since that aligns better in memory.

Is bitmap really that simple? Headers palettes then pixel data I thought pixel data is related to palette

Nope. BMP files have huge variations.

Pulling the data from the official source:

  • 7 different header types from different eras, you need to know which one you've got and complain about headers that are a higher version than you know how to process.
  • Many types of bit encoding. We already mentioned 1-bit bitmaps, there are other numbers of grayscale bits. There are indexed colors with various palette lengths (usually 8, 16, 20, 236, or 256 colors, but any 32-bit number is valid), RGB8, RGBA8, BGR8, BGRA8, also encodings with 444 (4 bytes per channel), 555, 565, 676, 685, and other sizes. Many of these must be packed together, so 565 takes 2 bytes, 444 takes 1.5 bytes for 1 pixel, 3 bytes for 2 pixels, 4.5 bytes for 3 pixels, and so on.
  • Color palettes, or not, depending on bit encoding
  • Variable sized gaps for pixel alginment
  • The pixel array that you mentioned, that encodes all the bits mentioned above. They may be packed, for example the 444 formats must be packed for 2 pixels per three bytes. They may be 8 pixels per byte for a pure black and white image, or 2.6 pixels per byte, 2 pixels per byte, 1.6 pixels per byte, 1 pixel per byte, 1 pixel per 1.87 bytes, 1 pixel per 1.5 bytes, 1 pixel per 2 bytes, 1 pixel per 2.5 bytes, 1 pixel per 2.75 bytes, and so on all the way up to 1 pixel per 4 bytes. They need to be properly packed to match the format.
  • The pixel array may also be encoded in color planes, where all the red components are in a row, then all the green components, then all the blue components. Also, potentially blue, then green, then red.
  • The pixel array may also contain compressed data, including JPG and PNG encoded data. Neither operate on a fixed byte-per-pixel size.
  • The pixel array may have a stride, a gap and the end of each row, or maybe not.
  • There may be another big gap of variable size
  • There may be a color profile to indicate things like gamma values.
  • There may be a link to a file containing additional color information. If so, that file must also be processed.

As you can hopefully see, the bitmap format is far more complex than the basic forms the two of you believed it to be.

Btw, 1) why are you using BMP and not PNG? and 2) why don't you use a simple library to handle that? I know for some cases it's interesting to make your own implementation of things for learning purpose, reducing dependencies or reducing executable size. But, BMP and PNG are not simple formats. Even if you achieve the implementation of the loader, you will be just executing a step-by-step (like in the list frob mentioned).

Btw, 1) why are you using BMP and not PNG? and 2) why don't you use a simple library to handle that? I know for some cases it's interesting to make your own implementation of things for learning purpose, reducing dependencies or reducing executable size. But, BMP and PNG are not simple formats. Even if you achieve the implementation of the loader, you will be just executing a step-by-step (like in the list frob mentioned).

1) There is a huge gap in complexity between the two if you're trying to write your own loader. Also, granted, my PNG code is slow to start with and I've added some minor optimizations to my BMP loader, but the two are likely to yield fairly different execution times.

2) BMP is rather a simple format if you're not trying to support every single variation out there. If you only ever need 24- or 32-bit 8bpp RGB(A) formats, then reading these from disk and passing directly to the GPU is actually trivial. This is particularly true if you doctor your own art and becomes a desirable limitation when you decide what formats you want to support in the long run. If you're dealing with complex formats or non-standard bpp values, then that is what your favorite Photoshop (clone) is for. With basic bitmaps all you really need to worry about is the vertical flip, which - if you're lazy - can be undone with a simple tex coord swap.

That being said, the true power of a custom-rolled BMP implementation comes when you need to dump images to disk. It's slow, but you can roll one out in minutes if you only ever need the abovementioned basic formats. And you won't need to worry about looking for an external library, managing dependencies, incompatibilities, lack of maintenance and so forth. Incidentally, the same applies to writing a wav importer/exporter.

TL;DR - if you only need support for basic color formats, then importing and exporting a bitmap pretty much becomes a block write operation.

Very frequently when accessing raw pixel data, you will need to use the "stride" of a row instead of the width when calculating the index for a given x,y.

Well where in the header is the "stride" stored, so that I can know for certain what it is? Based on what I've seen, there's just a width and height, and a horizontal and vertical resolution measured in pixels per meter (which seems arbitrary, and I don't use it for anything).

And yes, I realize that there are palette bitmaps and all that, which work differently, but I'm just using a plain old 24-bit image, which btw is specified as a field in the header, so that it's possible to know how to read/write the data. If the "stride" isn't specified anywhere, what am I supposed to do, just guess and hope I'm correct?!

This topic is closed to new replies.

Advertisement