Sunday, October 25, 2026

VGA Palette #2


Hi there. I've dedicated this post of the series to the mode 13h palette. In my opinion, the mode deserves to be explored in its own. Or maybe, I'm just being nostalgic and therefore biased when it comes to mode 13h. I'll handle the topics in this post by viewing some files using the BMP Viewer, I mentioned in my previous post. I'll additionally highlight some more details about the palette here.


First of all, almost everything I explained so far about the palette of other display modes in the first article also applies to mode 13h. To summarize: The VGA DAC can display only 16 of 262 144 colors in text mode as well as mode 12h, and 256 of them in mode 13h simultaneously. Each palette color has an index and an 18-bit RGB value consisting of 6-bit per channel. After the palette index is written to the port 3C8h, the color values of each channel are written to the port 3C9h sequentially with byte-length. Here is the loop of the BMP Viewer, which loads the palette.

  for(i = 0; i < ColorCount; i++)    {
    outportb(0x3C8, i);    // Palette index
    outportb(0x3C9, VGApalette.color[i].B >> 2);  // Blue
    outportb(0x3C9, VGApalette.color[i].G >> 2);  // Green
    outportb(0x3C9, VGApalette.color[i].R >> 2);  // Red
  }


The palette of mode 13h is simpler than other modes because, even though it is still possible to assign values to attribute register in this mode, there is no point in assigning a color to another index which can already be displayed on the screen, since all colors in the DAC (256 colors) can be displayed anyway without any trick. Therefore the attribute register has no use in this mode.

As a base example, I'll use the three parrots image from the VGA article on Wikipedia. As this image contains all 256 colors, it's quite useful for demonstrating VGA's capabilities.

Source: Wikipedia [2]

I first resized this image to 320x200. The original image is 1280x960, therefore the aspect ratio changed, but apart from that, I did not change anything on the existing colors or the palette. Here is a screenshot of the image from DOSBox.


There is no noticeable difference when comparing the two images visually. To show the effect of the palette better, let's comment out the setPalette function on line 208, and see how it would look with the default mode 13h palette:

As you can clearly see, the absence of the palette makes a significant difference.

Back in the 90s, when RAM in computers was measured in MBs and therefore software optimization was the primary concern, I wondered, "(How) Can I rearrange the colors in the image palette to fit the default VGA palette?". That way, I could get rid of both setPalette(...) function, which is around 400 bytes, and the palette, which is 1 KB per image. I'll briefly explain the theory behind this, using the image below, which I cropped and scaled a bit down from the original [1], which has 97 328 colors.


The problem of creating a palette by decreasing the number of colors had been studied long before the question above even came to my mind. First check point is the median cut algorithm from 1982. In this algorithm, the median of all colors in the color space is calculated, and it is assumed that this color could represent all others with the least average error. Depending on the target color depth,  the number of colors is decreased to a 2n (usually 16 or 256) colors by recursively calculating the medians of the color space. However, since this algorithm ignores small color deviations in images with a wide range of colors, it causes unnatural color islands. To show this effect, I decreased the number of colors to 16 using median cut (below). The color islands in the background and on the birds belly are clearly noticeable.


For this reason, techniques such as dithering and/or error diffusion are applied to insert pixels with similar colors from the palette, thus adding a small amount of noise to large color islands. This results in an image that is less unpleasant to the human eye.


Good old days 🥲

Although median cut is only used for decreasing the number of colors, error diffusion is also used in palette transformations. I won't go into the details of the algorithms in this post. I'll save them for the next post, if there is enough space. Back in the days, I tried and learned these conversions using Paint Shop Pro (PSP) and got quite nice results. Below, I put the image, whose palette I converted, at the top and the original image at the bottom for comparison. Although there are some shiny, flashy colors on the green bird and the road in the background due to error diffusion, the result is still not too bad.


When I display the converted image in DOSBox without setPalette(...):

I also displayed this image without modifying the viewer, and when I overlapped both in GIMP and subtracted the layers, I was left with a black image as expected.

To convert the palette, as I explained in my previous post, first I sent the palette index via port 0x3C7, read the color values from port 0x3C9, then multiplied them by four, and wrote them to a file in JASC (PSP) format. This format is a simple text file consisting of a three line header followed by R, G, B values separated by spaces. And the good thing is, GIMP can also read this format.

OPEN "13.PAL" FOR OUTPUT AS #1
PRINT #1, "JASC-PAL"
PRINT #1, "0100"
PRINT #1, "256"

SCREEN 13
FOR I% = 0 TO 255
    OUT &H3C7, I%
    R% = INP(&H3C9)
    G% = INP(&H3C9)
    B% = INP(&H3C9)
    PRINT #1, LTRIM$(STR$(R% * 4));
    PRINT #1, RTRIM$(STR$(G% * 4));
    PRINT #1, RTRIM$(STR$(B% * 4))
NEXT I%

CLOSE #1

I downloaded Paint Shop Pro from oldversion.com and could run it with Wine under Linux. I opened the file I wanted to get its palette converted and went to Colors -> Load Palette. Here, I loaded the 13.PAL, I had created in the previous step and selected "Error diffusion dithering" as the conversion algorithm. You can try out different algorithms and compare their results here.

In GIMP, you first need to open Palettes window (Windows -> Dockable Dialogs -> Palettes), then right click on an empty area and import 13.PAL. Then I opened the image, I wanted to convert. In the window, that appeared when I selected Image -> Mode -> Indexed, I checked "Use custom palette" and selected 13.PAL from the list below. No matter which dithering method I chose, GIMP unfortunately couldn't do this conversion any better than PSP.




[1]: https://commons.wikimedia.org/wiki/File:Perched_blue_and_orange_bird_%28Unsplash%29.jpg
[2]: https://en.wikipedia.org/wiki/Video_Graphics_Array#/media/File:Birds_VGA256.png

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.