View Full Version : About BMP files

05-18-2002, 09:13 AM
People have mercy upon a poor and desolate C programmer.
Tell me how to load and display simple 8bit BMP files with a borland compiler. Thanks.
Your help will be GREATLY apreciated =)


Discover the XBlue:

05-18-2002, 10:12 AM
Base bitmap header for 256 color - other formats derive from this

//256 color Bitmap class
#ifndef _BITMAP_
#define _BITMAP_

#include <mem.h>
#include <io.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <process.h>
#include <stdio.h>

//Makes calls to fast VGA assembly functions for blitting images
#include "vga.h"

//Generic 256 color bitmap class
class Bitmap256
BYTE far *Image;
BYTE Palette[768];
WORD Width;
WORD Height;
int XPos;
int YPos;
int ZOrder;
Bitmap256(Bitmap256 &B);

virtual void Load(char far *filename);
virtual void Draw(void);
virtual void Draw(int x,int y);

virtual void SetDimensions(WORD w,WORD h)
virtual void SetPalette(void) {::SetPalette(Palette);};

Bitmap256 operator=(Bitmap256 B);


Header for bmp.h - derives from Bitmap - see above

//class BMP - derived from Bitmap
#ifndef _BMP_
#define _BMP

#include "vga.h"
#include "bitmap.h"

//struct for BMP header

#define SRC_COPY 0x01
#define SRC_TRANS 0x02

//BMP header
WORD Type;
DWORD Reserved;
DWORD Offset;
DWORD HeaderSize;
DWORD Width;
DWORD Height;
WORD Planes;
WORD BitsPerPixel;
DWORD Compression;
DWORD SizeImage;
DWORD XPixelsPerMeter;
DWORD YPixelsPerMeter;
DWORD ColorsUsed;
DWORD ColorsImportant;

//BMP class - implements Windows BMPs
class BMP:public Bitmap256
int Padding;
BYTE Transparent;
BMP(void) {};
virtual void Load(char far *filename);
void SetTransparent(BYTE color) {Transparent=color;};
BYTE GetTransparent(void) {return Transparent;};
void Draw(int x,int y,BYTE mode);

void ConvertPalette(BYTE nPAl[1024]);


Code for bmp.h

#include "bmp.h"
#include <conio.h>

//Load the BMP from disk
void BMP::Load(char far *filename)
int handle=0;
if ((handle=open(filename,O_BINARY|O_RDONLY,S_IREAD)) ==-1)
printf("Cannot open file %s\n",filename);

read (handle,&bmInfo,sizeof(bmInfo));
Image=new BYTE[Size];

BYTE tempPal[1024];
read (handle,&tempPal,1024);
read (handle,Image,bmInfo.SizeImage);


//Convert the palette to match the BMP
void BMP::ConvertPalette(BYTE nPal[1024])
int palentry=0;

//Delete 4th byte in BMP palette
//Change format to RGB in prep for changing palette
//Divide values by 4 to convert from 24-bit to 256 color
for (int i=0;i<1024;i+=4)
BYTE blu=nPal[i];
BYTE grn=nPal[i+1];
BYTE red=nPal[i+2];


I did not include bitmap.cpp, this post is already long enough. Remember that each BMP is zero padded to the nearest 4 byte boundary (4+(width%4)). If you don't take this into account, BMPs that are not multiples of 4 will not display correctly. This code will automatically ignore the 4th byte in the palette. BMP palettes are stored as 4 byte entries - BGRA, and in the reverse order the hardware needs. This code ignores the A byte and reverses the BGR so calls to palette functions are easy.

Format of the BMP file.

Header (see the BITMAPINFO struct in bmp.h)

Palette (1024 bytes - BGRA)

Raw data(BYTES).

So read in the header.
Read in the palette.
Read in the raw data.

I recommend using assembly to blit the images. I have code in assembly that will blit a rectangular image to the screen and will also allow you to set transparent pixels. If you are interested in it, PM me or look for the post titled Bitmap Blues on this board. The full sources for these functions from my VGA library are there.

Hope this helps.

05-19-2002, 04:26 AM
A few glitches. When i use the 'load' function and try to read a bmp the bmInfo.Size is set to 0, and teh function returns without reading the image.
Could you just attach the bitmap.cpp, or make a precompiled version of it and attach it. The code is pretty interesting.

Thanks a lot.

05-19-2002, 12:55 PM
It works on my machine. Not sure what is going on. The size is extracted from the header in the file by the first read.

The only thing I can think of is in the read. I always get confused about the sizeof(), whether it should be the object or the actual structure. If its not using the correct size, then it would not read the right amount of bytes in and the entire process would be messed up.

But again, this code works on my system so I'm not sure what's wrong. I pasted this code from wordpad so all I did here was remove all the ugly 40 space tabs and reformatted the text so it is readable.

05-19-2002, 09:51 PM
there is BMP code in the FAQ...it worked for me...maybe you could try it and it might work for you. its worth a shot.

05-21-2002, 09:40 PM
Thanks DaivdP, but unfortunately it seems the thread was removed from the FAQ.
Anyway, this might be a clue to the Size reading problem:
I had to add these typedefs, and they're based on logical deduction:

typedef unsigned char BYTE;
typedef unsigned int WORD;
typedef long int DWORD;

perhaps i am mistaken on some of these types. Please, if you could elaborate it would be very helpfull. Perhaps you could try adding the Draw(int ,int ) function too so i can guide myself to something. I don't think it's a matter of which compiler I'm using at the moment, right?

05-22-2002, 05:36 AM
typedef unsigned long DWORD;

05-22-2002, 06:01 AM
One more thing, these are 16-bit data types only. They are different if you are using 32-bit DJGPP or Windows. I only use these datatypes in 16-bit and 32-bit DOS code because it helps me logically (not computer-wise) mix C with assembly when the datatypes are alike.

But note that even in 32-bit assembly, a DWORD is still only 32-bits long (unsigned long). The C data types change, but not the assembly ones. This can be very confusing, but if you plan on writing low-level assembly functions you must be aware of this.

In general you should avoid using assembly data types in C code since they are system dependent. Notice that even though I've defined all these types as unsigned, in assembly an unsigned integer and a signed integer are both WORDs. It's just that one is using one bit as the sign bit. And because Intel/AMD CPUs use two's complement mantissa to represent negative numbers, the range of signed integer is -32767 to +32767. But in assembly, this is still a WORD.

MyFunction proc
ARG value:WORD

push bp
mov bp,sp

mov ax,value
add ax,1

pop bp
MyFunction endp

In this function you could pass a signed integer but be careful. If you add 1 to 32767 on a signed integer, it will wrap around to -32768. So even though the sign of the data type is not evident from looking at the assembly source, you could prototype this in C in a couple of ways.

extern "C"
unsigned int MyFunction(unsigned int value);
int MyFunction(int value); //implied signed int

Both of these are valid prototypes for the assembly function. You could also write 2 more and switch the parameters around, but passing a signed int to a function that returns a unsigned int is useless since the sign will be removed in the result, and likewise passing an unsigned int to a function that returns a signed int is useless in most cases.

My bitmap drawing code is in assembly so if you do not know assembly, it would not help much to look at it.

Drawing a bmp in C is very slow and even slower if you must check for transparent pixels. But it would go something like this.

unsigned int row=0,col=0;
unsigned int x=0,y=0;

unsigned int sx=startx,sy=starty;
while (row<bitmap.height)
while (col<bitmap.width+bitmap.padding)
Retrieve pixel color or RGB from bitmap at row*width+col
Check for transparency
If not transparent
Draw pixel at x,y
Do nothing
increment x
increment row
Reset x to bitmap startx on screen (x=sx;)
Increment Y
Reset row (row=0)

This does not check to make sure the bitmap is actually on screen and does not account for the fact
that the bitmap could be leaving/entering the screen on either or both the x,y axes. There are other methods such as blitting one entire line of pixels using functions from mem.h.

05-22-2002, 06:04 AM
increment row
Reset x to bitmap startx on screen (x=sx
Increment Y
Reset row (row=0)

should be:

increment col
Reset x to bitmap startx on screen (x=sx)
Increment Y
Increment Row

05-23-2002, 11:22 PM
Ok guys, thanks for your support and all

Here is the code from the FAQ (not that Bubba's code wan't helpfull). The only trouble is the writing
to video memory. It usees a pointer instead. I would have preferred it
used alternate system for outputing the graphics, because like this
i can't use the function in a higher resolution, and I can't place the
bitmap at custom coordonates.

Here is some of the code from the FAQ:

// This is where the headeache begins...

offset = (100 + (Header.biHeight >> 1)) * 320 + 160 - (Header.biWidth >> 1);

lines = 0;

paddedWidth = Header.biWidth & 0xFFFC;
if (Header.biWidth != paddedWidth) paddedWidth += 4;

// Here, instead of using the Screen pointer,
// it would be nicer to be able to plot the pixels
// using something like PutPixel(x,y,c)

//Loop for reading lines
while (lines < Header.biHeight) {
// This code can be annoying //
fread (Screen + offset, paddedWidth, 1, BMPFile);
offset -= 320;
fclose (BMPFile);
return 0;

int main (int argcount, char *argvalue[]){
//Set up a pointer in vga memory

Screen = (char far *)0xA0000000L;

VGAScreen ();
getch ();
TextScreen ();
return 0;

05-25-2002, 10:52 PM
Well you can actually use that code for higher resolutions, but you will lose speed.

The main reason they are not calling PlotPixel() for every pixel in the bitmap is because this is extremely slow. Each time you call a function a new stack frame is set up. Then the parameters have to pushed onto the stack prior to the call, and popped off during the call. Then the stack is cleaned up and the function returns to the caller. As you can see to do this for every pixel would be slow.

It's much faster to computer y*width+x than it is to call PlotPixel. Even faster yet is to pre-compute the starting offset of the bitmap and then just increment the offset from there. This eliminates the multiply per pixel and gives good results.

So instead of:

You have:

unsigned int offset=y*width+x;
unsigned int startoffset=offset;

for ....
for ..
startoffset+=Screenwidth; //move start offset down 1 line

So now you have 0 multiplies in your main loop. Of course if it were me I would do this in assembly anyways and not C.

The way that I write to the screen in my library is this. I have three pointers. One to a back buffer, one to the screen, and one pointer that can either point to the back buffer or the screen.

Let's say we are in 320x200x256.

unsigned char far *Buffer=new unsigned char[ScreenSize];
unsigned char far *Screen=(unsigned char far*)MK_FP(0xa000,0);
unsigned char far *ScreenPtr=Screen;

void SetBufferMode(bool mode)
switch (mode)
case TRUE: ScreenPtr=Buffer;break;
default: ScreenPtr=Screen;break;

Now all your primtive drawing functions will use ScreenPtr to access the memory instead of Screen or Buffer which would lock you into one or the other. This way each one of your drawing functions can also draw to the back buffer, blit to the back buffer, blit from the back buffer, etc.,etc. and do the same to the screen.
You can also use this pointer to do screen captures which is basically blitting the screen to the buffer instead of the buffer to the screen.

05-26-2002, 12:53 AM
Thanks. Ok, but will it work on higher resolutions? Speed is not sucha big issue actually. Will i able to allocate a buffer for a 640x480 res ( or higher) or is it too much?


05-26-2002, 08:56 AM
First if you are not in protected mode there are some issues that you must deal with to get higher resolutions. This is because of the inherent limitations places upon real mode - not because of DOS as some erroneously claim. This limitation was put in place by Intel when they developed real mode. DOS, since it is a real mode operating system, would naturally inherit the limits placed upon real mode.

If you do the math, 320x200x256 fits into 64000 bytes exactly. This is about the size of one segment of memory in real mode. So in order to access a screen like 640x480x256 which is well over the one segment limit, you would think that you would have to use 2 pointers and write to two segments. But there is not enough room in the HMA to allow the video more than one segment.

To fix this they invented the idea of bank switching which basically changes the window for the A000:0000 address. So if your address does not reside within the first segment, you switch the bank of memory on the card so that all writes to A000:0000 will now be in a different memory bank on the card.

There are two ways to do this.

1. Use interrupt 10 and the VESA VBE extensions to execute a bank switch - check the RBIL and the web for more info - tons of info and code samples relating to this.

2. Retrieve the far address of the bank switch function. Create a far pointer to this address, place the correct values in the registers prior to the call and call the function via the pointer.

The first method is significantly slower than the first, but is well documented on the web.

The second method is very fast, but not well documented on the web. I've done both and I prefer the second method as it relates to real mode.

Note that even thogh you can gain access to the video memory in this way, this does not solve the problem of creating a back buffer to be used in animation or simulations. Of course you could cache this to EMS, but that would require an interrupt per 16KB of data - very slow.

Best way to deal with hi res modes is to switch to protected mode and use DJGPP. If you run some of your older games you will notice that DOS4GW comes up first - this was the DOS extender used by the old Watcom C/C++. Those hi res games are written in protected mode, not real mode and most of them only use 256 colors cuz most cards did not support hi color modes.

For instance in DJGPP the following will compile fine as long as you have the free memory to do it.

unsigned char *buffer=new unsigned char[500000];

Now you can access the memory just like any other memory which is very nice and gives you a lot more power for graphics and other things.

05-26-2002, 11:16 AM
thanks a lot for taking your time to help. You seem to be a Huge resource of information.

This seems to be to complicated at the moment, as i haven't yet succeeded to get some parts of DJGPP to work properly - but I'm trying!

For writing to video memory, couldn't we resort to the old system for the moment, trough good old PutPixel(X,Y,C). At the moment I really need to use Real mode- unfortunately. However, i didn't encounter such difficulties with the PCX files. I used to think they where the same.

Well, thanks a lot again.


05-27-2002, 11:30 AM
void PlotPixel(int x,int y,unsigned char color)




05-27-2002, 09:22 PM
Thanks a lot again!

Take care everyone :D


Discover the XBlue enviornment:

06-05-2002, 01:46 PM
YOu might need the file format itself too. http://www.wotsit.org has all the common file formats.