Tuesday, September 29, 2026

Bitmap (BMP) Files


Hi there. I initially had started this article as the second part in the VGA Palette series, but in this article, I've decided to focus on the bitmap (.bmp) file viewer, which I originally developed for the second part and consequently on bitmap files. That way, I can get back to VGA palette operations in mode 13h in the next article, without losing focus with file format specifications.


BMP File Format

In my previous post, I mentioned that I was developing a tiny bmp file viewer in C for mode 13h. Bitmap (bmp) files have a quite simple structure, so that I can cover the necessary aspects of this topic in this article.

BMP files consist of a simple two-part file header and pixel data. The first header contains the file size and a pointer to the image data. The second header contains information such as width, height, color depth and compression information. On the right figure, the first header is the area marked in red, while the second header is the area marked in yellow. The gray area shows the palette, which I will discuss later in this post. This figure shows a hex dump of an image with 320x200 pixels. I've marked fields such as width and height with boxes.

Technically speaking, BMP files can be compressed with RLE (run length encoding), but it's very unusual to find a compressed one. Even though the format was developed by Microsoft and IBM, not even MS Paint(brush) was able to save compressed BMP files, if I recall correctly. For this reason, I won't discuss RLE at all.

Although I don't want to go into the details of the code in this chapter, it will be still easier for me to explain the file format structures using the data structures in the code's header file ( VGA13.H ). At the very beginning of a BMP file is 'BM' file signature. The only important field in the bitmapfileheader structure is the OffsPixelArray pointer, which points to the pixel array, the area shown in the figure above with the values 36h, 04h. This is followed by bitmapimageheader, which holds the image-related info. I've also specified the file offsets of these fields in the VGA13.H file.

According to the BMP file format article, there are seven different versions of bitmapimageheader structure. The 40-byte version called BITMAPINFOHEADER, which I also used as a basis of my code, is the most common version. I tested my code with 23 files, some saved with various image processing software including GIMP, and some downloaded from internet. I cannot include one of these files, but I uploaded my test set of 22 files. Btw, even though GIMP has developed the latest version of bitmap header, BITMAPV5HEADER, it does not use it by default when saving bmp files.

Bitmap files support four different color depths. These are stored in BitsPerPixel field. If BitsPerPixel is;

  • 1, then the image has two colors, black and white and each byte contains 8 pixels.
  • 4, then the image is 16-color, and it contains a palette for these colors. Each byte contains two pixels.
  • 8, then the image has 256 colors and a palette for these colors. Each byte represents a single pixel.
  • 24, then each pixel of the image consists of R, G, B triplets each with 8 bits. And the image doesn't contain any palette.

I focused only on 8 bit per pixel images. Because, first, I can't display more than that with mode 13h, and second, the palette is only available for 4 and 8-bit color depths. I created some of the images in my test set for this article, by resizing the image of three parrots siting on a wire mesh fence from VGA article in Wikipedia to 320x200.

Direct Link

I need to make a note about 8-bit images here. Logically, grayscale palette consists of (i, i, i) RGB triplets, where i is their index. Therefore, there is no need to save the palette along with the file. I knew, that if a bitmap file has 8-bit color depth but no palette, it's treated as grayscale image, and some programs still used to save the palette in the file for compatibility anyway. A grayscale image, I saved with GIMP has been saved with a palette, and even after manually removing the palette from the file, and trying to open it with four different programs, it did not succeed. Moreover, Wikipedia mentions that a palette is mandatory for files with a color depth of 8 or lower. On the other hand, the bmp_08.bmp file in the test set is an example for such an 8-bit file without palette. In short, I think, this is a matter on which there is still no consensus. From coding perspective, this worked out quite well for me, as I did not have to do anything extra for this case. My code opens this file, but since it doesn't know that it needs to load a grayscale palette, it opens it with the default mode 13h palette.

Palette follows the bitmapimageheader structure. The size of bitmapfileheader is fixed 14 bytes. The size of bitmapimageheader is given in HeaderSize field. Therefore, the offset of the palette can be found by adding 14 to this value. If this sum equals to bitmapfileheader.OffsPixelArray value, it indicates that there is no palette (e.g. for 24-byte color depth). Each palette entry consists of 4 bytes representing R,G,B and A respectively. However the alpha channel and transparency implementation are not standard in bmp files. Therefore A is nearly always zero. And in total, the number of palette entries is 2BitsPerPixel.

Palette must end at bitmapfileheader.OffsPixelArray offset, and is followed by pixel array. Here, the pixels are stored from left to right and from bottom to top. The first data corresponds to the bottom-left pixel. If the image's width is not a multiple of four, the rows are padded with zeros to make them a multiple of four. For example, if an image is 133 pixels wide, three zeros are added at the end of each line while saving, to pad the data to 136.

Screenshot from DOSBox

BMP Viewer

Above is an example, demonstrating the programs output.

I will review my code starting with the main(...) function on line 174. Here, the memory is allocated statically for both file headers first (lines 177-178). If a BMP file has not been passed to the program as a command line parameter, the user is asked for the file name (line 188), and the file is then opened for reading (line 193).

The readFirstHeader(...) function reads from the file directly into the data structure, and if the file signature does not equal to 'BM' (line 43), it exists with a non-zero error and this also terminates the program (line 199).

The readSecondHeader(...) function on line 199, similarly reads the second header from the file into the data structure. To determine file properties easily, I added a small showBMPinfo function (line 22) to print them. This function call can be commented out or combined with a debugging parameter. If the file does not meet the specifications we can display, i.e. if the color depth is different than eight or if the file is compressed, the function exits with an error value and it terminates the program. On line 69, a warning is printed to the screen if the size of the image is bigger than 320x200, but the file is opened anyway and viewed partially.

I simply implemented mode13() and textmode() functions with assembly (line 78 and 86).

As I mentioned above, there are multiple versions of bitmapimageheader. Common fields of these header versions are width, height and color depth. If the header of the input file happens to be something different than BITMAPINFOHEADER (maybe a header of a different length), the readSecondHeader(...) function will not read the header completely. For this reason, on line 207, any unread bytes are skipped. The file pointer is moved to the start offset of the palette, and processing of the palette begins with setPalette(...). In this function, the palette is read ColorCount times 4-byte chunks. On line 105, the color index is written to port 0x3C8, followed by R, G, B values written to port 0x3C9, as I explained in the previous post. Since VGA colors are 18-bit, the 8-bit values are shifted right two bits to convert them to 6-bit.

The fseek(...) on line 210 is there to read the correct offset, if the file's header version is not BITMAPINFOHEADER. Normally, the file pointer should already point to the firstHeader.OffsPixelArray value at that moment. setImage(...) reads the pixel array and renders the image to the screen. I wrote this function twice. The first version on line 134 is inefficient, because it reads the byte by byte. With the second version, images are read line by line. For this, I round the width up to the nearest multiple of 4 using ceilTo4 and allocate a memory line of that size (line 160). Finally, I read the bytes from the file with image width sized blocks (line 167) and write them to the screen with a for loop. Instead of PutPixel function, something like this could have been done here:

lds si, linebuffer
push A000
pop es
mov ax, i
mov di,ax
shr ax,8
shr di,6
add di,ax
mov cx, c4
rep movsb

This snippet is of course just a sketch. The linebuffer pointer is loaded into DS:SI. i is multiplied by 320 using shr and loaded to DI. Image width is loaded to CX, and the linebuffer could be copied block by block to VGA screen memory using rep movsb. In this case, the MIN macro should also need to be translated to assembly. In this way, it should run even faster than my recent approach, yet I did not implement it, because I did not want to deal with assembly and far pointers in C, which could have increased debugging time significantly.

Tuesday, August 25, 2026

VGA Palette #1


Hi there. In this new series of articles, I'll be writing about color palettes of VGA. Since this is a relatively deep topic, I planned to cover it over several posts. In this post, I'll first focus on VGA 640 x 480 (aka screen 12) and text mode to explain what the color palette is, how to access and change it. I'll mention mode 13h in a separate post later and discuss some visual effects, based on VGA palette.


VGA introduced two important graphics modes. First one is mode 12h with 640 x 480 resolution and 16 colors, and the other one is well-known mode 13h, with 320 x 200 resolution and 256 colors. Other modes were retained for backwards compatibility with older graphics hardware. The VGA card's digital-analog converter (DAC) can display colors from 18-bit RGB color gamut (218 = 256 K = 262 144 colors) on the screen [1], but under mode 13h, only 256 of these at the same time, and under mode 12h only 16. This subset of colors from this color gamut is called a palette.

Color information consists of a palette index and an 18-bit RGB color value (6+6+6). For example, if the index is 1 and the RGB code is 0,0,63, the first color is blue; if the index is 2 and the RGB code is 24,24,24, the second color is gray, and so on. The index value starts at zero, and the zeroth color is the background color, which is therefore usually black (RGB 0,0,0). If the indices from 0 to 255 are filled with linearly increasing t values in RGB t,t,t form, a grayscale palette is obtained. Or if they are filled in the form RGB 0,r,0, a purely green toned palette is obtained.

Colors are processed through the DAC of graphics card. Therefore, palette operations are carried out using three* DAC registers of VGA card [3]:

DAC Address Read Mode Register (Write at 3C7h)
76543210
DAC Read Address

Actually, 3C7h has two functions. If this register is read, the two least significant bits indicate, whether the DAC is in read or write mode. However, if I've already executed an out instruction, I've written, or if I've executed an in instruction, I've read. This is why, this function of the register isn't used any often. To read the palette, the index value is written to this register. Then, the DAC data register on port 3C9h is read three times each in byte-length:


DAC Data Register (Read/Write at 3C9h)
76543210


DAC Data

When writing to the palette, the color index is written to port 3C8h, and three byte-length values are sent to the DAC data register.


DAC Address Write Mode Register (Read/Write at 3C8h)
76543210
DAC Write Address

*There are actually four DAC registers. The DAC Mask register, which I did not mention above, is accessed via port 3C6h and always contains 0FFh value. Writing any other value to this register disables access to the DAC [2].

The aforementioned byte-length read and write operations refer to in al,dx and out dx,al instructions. However, only the lowest 6 bits of the read and written values are significant. Remember that the color gamut is 6+6+6=18 bits.


Let's focus on the text mode and mode 12h, first. In these two modes, a maximum of 16 colors can be displayed on the screen. In this respect, they are similar. One detail, which goes often unnoticed is that the text mode palette can also be modified. In my first VGA post [5], I explained, how to change text and background colors of a text directly. Now, let's take a look at the text mode palette using a simple BASIC code:

FOR I% = 0 TO 63
    OUT &H3C7, I%
    PRINT "("; I%; "="; INP(&H3C9); INP(&H3C9); INP(&H3C9); ")";
NEXT I%

DEF SEG = &HB800

FOR J% = 1 TO 15
    FOR I% = 1 TO 159 STEP 2
        POKE (I% + J% * 160), J%
    NEXT I%
NEXT J%

In first part, I send the color index value from 3C7h, then read the color codes from 3C9h, and printed them to the screen. In second part, I accessed the text mode video memory and colored the first line with the first palette color, the second line with the second palette color, and so on:

I mentioned that 16 colors can be used in text mode, but I printed 64 color codes to the screen, and interestingly, it appears that non-zero codes have been assigned to the indices between [16, 63]. The codes for indices between [64, 255] are zero, therefore not printed, but colors can be assigned to them as well if needed. So, what's the meaning of this? Normally, a character on the screen consists of 2 bytes: one byte is its ASCII code, and the next one holds its color information. The lower 4 bits of the color byte represent the character's color, while the upper 4 bits represent the character's background color. Since just 4 bits are allocated for colors, assigning a color code to the indices 16 and above might seem pointless at first glance, but there is a trick: The VGA Attribute Register (3C0h) [4] can be used to change a color's palette index. Without getting into too much detail, here is a simple code snippet:

A% = INP(&H3DA)
OUT &H3C0, 5
OUT &H3C0, 60
OUT &H3C0, &H20

where I assigned the 60th color to the fifth one, by writing the value 60, into the fifth attribute register.

Getting back to the screenshot, the eighth palette entry (0, 0, 21) should be navy blue or dark blueish, and the ninth palette entry (0, 0, 63) should be pure blue. However, assuming that we're counting from zero, the eighth row is actually dark grey instead of navy blue, and the ninth row, which supposed to be vivid blue, is just a pale blue (neon blue). If these color codes printed on the screen were represented the actual colors of the rows, the screen would actually look like the right side of the image below. The left side shows, what actually visible is. For an easy comparison, I've put two images side by side:


The conclusion is, that the text mode is actually using attribute registers for the colors [8, 15]. To render the left side of the above image, I manually assigned the first fifteen colors to the first fifteen palette indices manually.

Everything about text mode palette also applies to mode 12h palette. Even though 16 colors can be shown at any time, the default palette contains 64 colors. Hint: If you switch from mode 13h to mode 12h or to text mode, mode 13h palette will also stay in DAC for other modes. In other words, when the computer (or DosBox) starts up, all colors in the range [64, 255] are set to (0, 0, 0). If you just enter mode 13h and switch back to text mode (or mode 12h), these indices won't be containing zeros anymore. 

Similar to text mode, the colors on the screen are selected from the palette, but the attribute register provides a second conversion layer. Here is another BASIC code snippet to demonstrate all these:

SCREEN 12
CONST K = 20

FOR I% = 0 TO 63
    OUT &H3C7, I%
    PRINT "("; I%; "="; INP(&H3C9); INP(&H3C9); INP(&H3C9); ")";
NEXT I%

SLEEP

FOR I% = 0 TO 15
    ' PRINTING RECTANGLES
    LINE (0, I% * K)-(640, (I% + 1) * K), I%, BF
NEXT I%

FOR I% = 0 TO 15
    ' SETTING PALETTE
    OUT &H3C8, I%
    OUT &H3C9, I% * 4
    OUT &H3C9, I% * 0
    OUT &H3C9, 63 - I% * 4
NEXT I%

SLEEP

' Change fifth color
A% = INP(&H3DA)
OUT &H3C0, 5
OUT &H3C0, 60
OUT &H3C0, &H20
 

In the first part, color codes of indices [0, 63] are printed to the screen. In the next part, 16 rectangles, whose size set by the const K, are drawn on the screen using first 16 colors and then 16 colors ranging from blue to red are assigned to the palette. As demonstrated, colors are written to the palette by writing their palette index to the port 3C8h and sending their color codes via port 3C9h afterwards.

Since blue is assigned to the zeroth color here, the background becomes blue. In the final part, the 60th palette entry is assigned to the fifth one, clearly highlighting the color difference.



[1]: https://en.wikipedia.org/wiki/Video_Graphics_Array
[2]: https://wiki.osdev.org/VGA_Hardware#Port_0x3C6
[3]: http://www.osdever.net/FreeVGA/vga/colorreg.htm
[4]: http://www.osdever.net/FreeVGA/vga/attrreg.htm
[5]: https://trapgate.blogspot.com/2025/11/programming-vga-smooth-scrolling-in.html