working with bitmaps in C

  • Thread starter Stephen.Schoenberger
  • Start date
S

Stephen.Schoenberger

Hello,


Sorry if this is not "exactly" a C topic but I thought this would be
the best place to start. I need some guidance on working with bitmap
images in ANSI C. I need to "read" in a bitmap image, then break it up
into pieces (say 32x32) then take those pieces and "save" them,
granted they do not need to be a readable image, then perform some
other operations on the pieces, and then combine the manipulated
pieces back into one image.

Any suggestions on any part of this would be great! I have found the
"EasyBMP" package from http://easybmp.sourceforge.net but apperas this
is not going to work for what I need.

Thanks!
 
R

Richard Heathfield

(e-mail address removed) said:
Hello,


Sorry if this is not "exactly" a C topic but I thought this would be
the best place to start. I need some guidance on working with bitmap
images in ANSI C.

The first thing you need to know is this: YES, it is possible, in ANSI C.
I need to "read" in a bitmap image, then break it up
into pieces (say 32x32) then take those pieces and "save" them,
granted they do not need to be a readable image, then perform some
other operations on the pieces, and then combine the manipulated
pieces back into one image.

This sounds pretty simple. I would recommend the following procedure:

1) decide on a way of representing an image in memory, that has nothing
whatsoever to do with .bmp format; I use a dynamically allocated array of
unsigned long int *, each of which points to the first element in a
dynamically allocated array of unsigned long int. In other words, I have
one unsigned long int per pixel, with the low 24 bits used for
representing the three colour channels, 8 bits each.

2) now write a bitmap loader, a function that can create an appropriately
sized in-memory representation of a bitmap file. Don't hard-code the image
size for your current task! It might sound like it'll make things simpler,
but actually it'll make them harder. That's because your task actually
involves several different sizes already (original, 32x32, 32xrightmargin,
bottommarginx32, and rightmarginxbottommargin).

3) now write a bitmap saver, a function that can create a bitmap file on
disk from an in-memory representation.

4) now write as many graphics functions as you like. On the way, write a
blitter (for copying an image region from an arbitrary part of one image
to an arbitrary part of another).

5) your task itself is now very, very simple - glue together some of the
above, and you're done.

(Although I have never written the program you need, I guess it would take
me about three or four minutes including testing - but only because I've
done all of the above work already.)
 
M

Malcolm McLean

Sorry if this is not "exactly" a C topic but I thought this would be
the best place to start. I need some guidance on working with bitmap
images in ANSI C. I need to "read" in a bitmap image, then break it up
into pieces (say 32x32) then take those pieces and "save" them,
granted they do not need to be a readable image, then perform some
other operations on the pieces, and then combine the manipulated
pieces back into one image.

Any suggestions on any part of this would be great! I have found the
"EasyBMP" package from http://easybmp.sourceforge.net but apperas this
is not going to work for what I need.

Thanks!
A bit long, but then pixels are getting cheaper these days
(for regs who don't want to read this, it is just an ANSI-standard bitmap
loader /saver).
This is incorporated in my book Basic Algorithms, so any bug reports
particularly welcome.

/*********************************************************
* bmp.c - Microsoft bitmap loading functions. *
*********************************************************/
#include <stdio.h>
#include <stdlib.h>

typedef struct
{
int width;
int height;
int bits;
int upside_down; /* set for a bottom-up bitmap */
int core; /* set if bitmap is old version */
int palsize; /* number of palette entries */
int compression; /* type of compression in use */
} BMPHEADER;

int bmpgetinfo(char *fname, int *width, int *height);
unsigned char *loadbmp(char *fname, int *width, int *height);
unsigned char *loadbmp8bit(char *fname, int *width, int *height, unsigned
char *pal);
unsigned char *loadbmp4bit(char *fname, int *width, int *height, unsigned
char *pal);

static void loadpalette(FILE *fp, unsigned char *pal, int entries);
static void loadpalettecore(FILE *fp, unsigned char *pal, int entries);
static void savepalette(FILE *fp, unsigned char *pal, int entries);
static int loadheader(FILE *fp, BMPHEADER *hdr);
static void saveheader(FILE *fp, int width, int height, int bits);
static void loadraster8(FILE *fp, unsigned char *data, int width, int
height);
static void loadraster4(FILE *fp, unsigned char *data, int width, int
height);
static void loadraster1(FILE *fp, unsigned char *data, int width, int
height);
static long getfilesize(int width, int height, int bits);
static void swap(void *x, void *y, int len);
static void fput32le(long x, FILE *fp);
static void fput16le(int x, FILE *fp);
static long fget32le(FILE *fp);
static int fget16le(FILE *fp);


/********************************************************
* bmpgetinfo() - get information about a BMP file. *
* Params: fname - name of the .bmp file. *
* width - return pointer for image width. *
* height - return pointer for image height. *
* Returns: bitmap type. *
* 0 - not a valid bitmap. *
* 1 - monochrome paletted (2 entries). *
* 4 - 4-bit paletted. *
* 8 - 8 bit paletted. *
* 16 - 16 bit rgb. *
* 24 - 24 bit rgb. *
* 32 - 24 bit rgb with 1 byte wasted. *
********************************************************/
int bmpgetinfo(char *fname, int *width, int *height)
{
FILE *fp;
BMPHEADER bmphdr;
fp = fopen(fname, "rb");
if(!fp)
return 0;
if(loadheader(fp, &bmphdr) == -1)
{
fclose(fp);
return 0;
}
fclose(fp);
if(width)
*width = bmphdr.width;
if(height)
*height = bmphdr.height;
return bmphdr.bits;
}

/************************************************************
* loadbmp() - load any bitmap *
* Params: fname - pointer to file path *
* width - return pointer for image width *
* height - return pointer for image height *
* Returns: malloced pointer to image, 0 on fail *
************************************************************/
unsigned char *loadbmp(char *fname, int *width, int *height)
{
FILE *fp;
BMPHEADER bmpheader;
unsigned char *answer;
unsigned char *raster;
unsigned char pal[256 * 3];
int index;
int col;
int target;
int i;
int ii;


fp = fopen(fname, "rb");
if(!fp)
return 0;

if(loadheader(fp, &bmpheader) == -1)
{
fclose(fp);
return 0;
}
if(bmpheader.bits == 0 || bmpheader.compression != 0)
{
fclose(fp);
return 0;
}

answer = malloc(bmpheader.width * bmpheader.height * 3);
if(!answer)
{
fclose(fp);
return 0;
}

if(bmpheader.bits < 16)
{
raster = malloc(bmpheader.width * bmpheader.height);
if(!raster)
{
free(answer);
return 0;
}
}

switch(bmpheader.bits)
{
case 1:
if(bmpheader.core)
loadpalettecore(fp, pal, 2);
else
loadpalette(fp, pal, bmpheader.palsize);

loadraster1(fp, raster, bmpheader.width, bmpheader.height);
break;
case 4:
if(bmpheader.core)
loadpalettecore(fp, pal, 256);
else
loadpalette(fp, pal, bmpheader.palsize);

loadraster4(fp, raster, bmpheader.width, bmpheader.height);
break;

case 8:
if(bmpheader.core)
loadpalettecore(fp, pal, 256);
else
loadpalette(fp, pal, bmpheader.palsize);

loadraster8(fp, raster, bmpheader.width, bmpheader.height);
break;

case 16:
for(i=0;i<bmpheader.height;i++)
{
for(ii=0;ii<bmpheader.width;ii++)
{
target = (i * bmpheader.width * 3) + ii * 3;
col = fget16le(fp);
answer[target] = (col & 0x001F) << 3;
answer[target+1] = (col & 0x03E0) >> 2;
answer[target+2] = (col & 0x7A00) >> 7;
}
while(ii < (bmpheader.width + 1)/2 * 4)
{
fgetc(fp);
ii++;
}
}
break;

case 24:
for(i=0;i<bmpheader.height;i++)
{
for(ii=0;ii<bmpheader.width;ii++)
{
target = (i * bmpheader.width * 3) + ii * 3;
answer[target] = fgetc(fp);
answer[target+1] = fgetc(fp);
answer[target+2] = fgetc(fp);
}
while(ii < (bmpheader.width + 3)/4 * 4)
{
fgetc(fp);
ii++;
}
}
break;

case 32:
for(i=0;i<bmpheader.height;i++)
for(ii=0;ii<bmpheader.width;ii++)
{
target = (i * bmpheader.width * 3) + ii * 3;
answer[target] = fgetc(fp);
answer[target+1] = fgetc(fp);
answer[target+2] = fgetc(fp);
fgetc(fp);
}
break;
}

if(bmpheader.bits < 16)
{
for(i=0;i<bmpheader.height;i++)
for(ii=0;ii<bmpheader.width;ii++)
{
target = (i * bmpheader.width * 3) + ii * 3;
index = raster[i * bmpheader.width + ii] * 3;
answer[target] = pal[ index ];
answer[target+1] = pal[ index + 1 ];
answer[target+2] = pal[ index + 2 ];
}

free(raster);
}

if(bmpheader.upside_down)
{
for(i=0;i<bmpheader.height/2;i++)
swap( answer + i * bmpheader.width * 3,
answer + (bmpheader.height - i - 1) * bmpheader.width * 3,
bmpheader.width * 3);
}

if(ferror(fp))
{
free(answer);
answer = 0;
}

*width = bmpheader.width;
*height = bmpheader.height;

fclose(fp);

return answer;
}

/************************************************************
* loadbmp8bit() - load an 8-bit bitmap. *
* Params: fname - pointer to file path. *
* width - return pointer for image width. *
* height - return pointer for image height. *
* pal - return pointer to 256 rgb palette entries. *
* Returns: malloced pointer to image data, 0 on fail. *
************************************************************/
unsigned char *loadbmp8bit(char *fname, int *width, int *height, unsigned
char *pal)
{
BMPHEADER bmphdr;
FILE *fp;
unsigned char *answer;
int i;

fp = fopen(fname, "rb");
if(!fp)
return 0;

if(loadheader(fp, &bmphdr) == -1)
{
fclose(fp);
return 0;
}
if(bmphdr.bits != 8)
{
fclose(fp);
return 0;
}
if(bmphdr.compression != 0)
{
fclose(fp);
return 0;
}
if(bmphdr.core)
loadpalettecore(fp, pal, 256);
else
loadpalette(fp, pal, bmphdr.palsize);
answer = (unsigned char *) malloc(bmphdr.width * bmphdr.height);
if(!answer)
{
fclose(fp);
return 0;
}

loadraster8(fp, answer, bmphdr.width, bmphdr.height);
if(bmphdr.upside_down)
{
for(i=0;i<bmphdr.height/2;i++)
swap(answer + i * bmphdr.width,
answer + (bmphdr.height - i - 1) * bmphdr.width,
bmphdr.width);
}

if(ferror(fp))
{
free(answer);
answer = 0;
}

fclose(fp);
*width = bmphdr.width;
*height = bmphdr.height;

return answer;
}

/************************************************************
* loadbmp4bit() - load a 4-bit bitmap from disk. *
* Params: fname - pointer to the file path. *
* width - return pointer for image width. *
* height - return pointer for image height. *
* pal - return pointer for 16 rgb palette entries. *
* Returns: malloced pointer to 4-bit image data. *
************************************************************/
unsigned char *loadbmp4bit(char *fname, int *width, int *height, unsigned
char *pal)
{
BMPHEADER bmphdr;
FILE *fp;
unsigned char *answer;
int i;

fp = fopen(fname, "rb");
if(!fp)
return 0;

if(loadheader(fp, &bmphdr) == -1)
{
fclose(fp);
return 0;
}
if(bmphdr.bits != 4)
{
fclose(fp);
return 0;
}
if(bmphdr.compression != 0)
{
fclose(fp);
return 0;
}

if(bmphdr.core)
loadpalettecore(fp, pal, 16);
else
loadpalette(fp, pal, bmphdr.palsize);
answer = (unsigned char *) malloc(bmphdr.width * bmphdr.height);
if(!answer)
{
fclose(fp);
return 0;
}
loadraster4(fp, answer, bmphdr.width, bmphdr.height);

if(bmphdr.upside_down)
{
for(i=0;i<bmphdr.height/2;i++)
{
swap(answer + i * bmphdr.width,
answer + (bmphdr.height - i - 1) * bmphdr.width,
bmphdr.width);
}
}

if(ferror(fp))
{
free(answer);
answer = 0;
}

fclose(fp);
*width = bmphdr.width;
*height = bmphdr.height;
return answer;
}

/***********************************************************
* save a24-bit bmp file. *
* Params: fname - name of file to save. *
* rgb - raster data in rgb format *
* width - image width *
* height - image height *
* Returns: 0 on success, -1 on fail *
***********************************************************/
int savebmp(char *fname, unsigned char *rgb, int width, int height)
{
FILE *fp;
int i;
int ii;

fp = fopen(fname, "wb");
if(!fp)
return -1;

saveheader(fp, width, height, 24);
for(i=0;i<height;i++)
{
for(ii=0;ii<width;ii++)
{
fputc(rgb[2], fp);
fputc(rgb[1], fp);
fputc(rgb[0], fp);
rgb += 3;
}
if(( width * 3) % 4)
{
for(ii=0;ii< 4 - ( (width * 3) % 4); ii++)
{
fputc(0, fp);
}
}
}

if(ferror(fp))
{
fclose(fp);
return -1;
}

return fclose(fp);
}

/**********************************************************
* save an 8-bit palettised bitmap . *
* Params: fname - the name of the file. *
* data - the raster data *
* width - image width *
* height - image height *
* pal - palette (RGB format) *
* Returns: 0 on success, -1 on failure *
**********************************************************/
int savebmp8bit(char *fname, unsigned char *data, int width, int height,
unsigned char *pal)
{
FILE *fp;
int i;
int ii;

fp = fopen(fname, "wb");
if(!fp)
return -1;

saveheader(fp, width, height, 8);
savepalette(fp, pal, 256);

for(i=0;i<height;i++)
for(ii=0;ii< (width + 3)/4;ii++)
{
fputc(data[i*width + ii * 4], fp);
if(ii * 4 + 1 < width)
fputc(data[i*width + ii * 4 + 1], fp);
else
fputc(0, fp);
if(ii * 4 + 2 < width)
fputc(data[i*width + ii * 4 + 2], fp);
else
fputc(0, fp);
if(ii * 4 + 3 < width)
fputc(data[i*width + ii * 4 + 3], fp);
else
fputc(0, fp);
}

if(ferror(fp))
{
fclose(fp);
return -1;
}

return fclose(fp);
}

/*******************************************************
* save a 4-bit palettised bitmap. *
* Params: fname - the name of the file. *
* data - raster data *
* width - image width *
* height - image height *
* pal - the palette (RGB format) *
* Returns: 0 on success, -1 on failure *
*******************************************************/
int savebmp4bit(char *fname, unsigned char *data, int width, int height,
unsigned char *pal)
{
FILE *fp;
int i;
int ii;
int pix;

fp = fopen(fname, "wb");
if(!fp)
return -1;

saveheader(fp, width, height, 4);
savepalette(fp, pal, 16);

for(i=0;i<height;i++)
for(ii=0;ii< (width + 7)/8;ii++)
{
pix = data[i * width + ii * 8] << 4;
if(ii * 8 + 1 < width)
pix |= data[i * width + ii * 8 + 1];
fputc(pix, fp);

pix = 0;
if(ii * 8 + 2 < width)
pix = data[ i * width + ii * 8 + 2] << 4;
if(ii * 8 + 3 < width)
pix |= data[ i * width + ii * 8 + 3];
fputc(pix, fp);

pix = 0;
if(ii * 8 + 4 < width)
pix = data[ i * width + ii * 8 + 4] << 4;
if(ii * 8 + 5 < width)
pix |= data[i * width + ii * 8 + 5];
fputc(pix, fp);

pix = 0;
if(ii * 8 + 6 < width)
pix = data[ i * width + ii * 8 + 6] << 4;
if(ii * 8 + 7 < width)
pix |= data[i * width + ii * 8 + 7];
fputc(pix, fp);
}

if(ferror(fp))
{
fclose(fp);
return -1;
}

return fclose(fp);
}

/***************************************************************
* save a 2-bit palettised bitmap. *
* Params: fname - name of file to write. *
* data - raster data, one byte per pixel. *
* width - image width. *
* height - image height. *
* pal - the palette (0 = black/white) *
* Returns: 0 on success, -1 on fail. *
***************************************************************/
int savebmp2bit(char *fname, unsigned char *data, int width, int height,
unsigned char *pal)
{
FILE *fp;
unsigned char defpal[6] = {0, 0, 0, 255, 255, 255 };
int i;
int ii;
int iii;
int pix;

fp = fopen(fname, "wb");
if(!fp)
return -1;

saveheader(fp, width, height, 1);
if(pal)
savepalette(fp, pal, 2);
else
savepalette(fp, defpal, 2);

for(i=0;i<height;i++)
for(ii=0;ii<width;ii+=32)
{
pix = 0;
for(iii=0;iii<8;iii++)
if(ii + iii < width)
pix |= data[i * width + ii + iii] ? (1 << (7-iii) ) : 0;
fputc(pix, fp);

pix = 0;
for(iii=0;iii<8;iii++)
if(ii + iii + 8 < width)
pix |= data[i * width + ii + iii + 8] ? (1 << (7 - iii)) : 0;
fputc(pix, fp);

pix = 0;
for(iii=0;iii<8;iii++)
if(ii + iii + 16 < width)
pix |= data[i * width + ii + iii + 16] ? (1 << (7 - iii)) : 0;
fputc(pix, fp);

pix = 0;
for(iii=0;iii<8;iii++)
if(ii + iii + 24 < width)
pix |= data[i * width + ii + iii + 24] ? (1 << (7 - iii)) : 0;

fputc(pix, fp);
}

if(ferror(fp))
{
fclose(fp);
return -1;
}

return fclose(fp);
}

/**************************************************************
* loadpalette() - load palette for a new format BMP. *
* Params: fp - pointer to an open file. *
* pal - return pointer for palette entries. *
* entries - number of entries in palette. *
**************************************************************/
static void loadpalette(FILE *fp, unsigned char *pal, int entries)
{
int i;
for(i=0;i<entries;i++)
{
pal[2] = fgetc(fp);
pal[1] = fgetc(fp);
pal[0] = fgetc(fp);
fgetc(fp);
pal += 3;
}
}

/******************************************************
* loadpalettecore() - load a palette for a core BMP *
* Params: fp - pointer to an open file. *
* pal - return pointer for palette entries. *
* entries - number of entries to read. *
******************************************************/
static void loadpalettecore(FILE *fp, unsigned char *pal, int entries)
{
int i;
for(i=0;i<entries;i++)
{
pal[2] = fgetc(fp);
pal[1] = fgetc(fp);
pal[0] = fgetc(fp);
pal += 3;
}
}

/*************************************************************
* saves a palette *
* Params: fp - pointer to an open file *
* pal - the palette *
* entries - number of palette entries. *
*************************************************************/
static void savepalette(FILE *fp, unsigned char *pal, int entries)
{
int i;

for(i=0;i<entries;i++)
{
fputc(pal[2], fp);
fputc(pal[1], fp);
fputc(pal[0], fp);
fputc(0, fp);
pal += 3;
}
}

/*
typedef struct tagBITMAPFILEHEADER { // bmfh
WORD bfType;
DWORD bfSize;
WORD bfReserved1;
WORD bfReserved2;
DWORD bfOffBits;
} BITMAPFILEHEADER;

typedef struct tagBITMAPINFOHEADER{ // bmih
DWORD biSize;
LONG biWidth;
LONG biHeight;
WORD biPlanes;
WORD biBitCount
DWORD biCompression;
DWORD biSizeImage;
LONG biXPelsPerMeter;
LONG biYPelsPerMeter;
DWORD biClrUsed;
DWORD biClrImportant;
} BITMAPINFOHEADER;

*/

/******************************************************
* loadheader() - load the bitmap header information. *
* Params: fp - pinter to an opened file. *
* hdr - return pointer for header information.*
* Returns: 0 on success, -1 on fail. *
******************************************************/
static int loadheader(FILE *fp, BMPHEADER *hdr)
{
int size;
int hdrsize;
int id;
int i;

id = fget16le(fp);
/* is it genuinely a BMP ? */
if(id != 0x4D42)
return -1;
/* skip rubbish */
fget32le(fp);
fget16le(fp);
fget16le(fp);
/* offset to bitmap bits */
size = fget32le(fp);
hdrsize = fget32le(fp);
if(hdrsize == 40)
{
hdr->width = fget32le(fp);
hdr->height = fget32le(fp);
fget16le(fp);
hdr->bits = fget16le(fp);
hdr->compression = fget32le(fp);
/* skip rubbish */
for(i=0;i<12;i++)
fgetc(fp);
hdr->palsize = fget32le(fp);
if(hdr->palsize == 0 && hdr->bits < 16)
hdr->palsize = 1 << hdr->bits;
fget32le(fp);
if(hdr->height < 0)
{
hdr->upside_down = 0;
hdr->height = -hdr->height;
}
else
hdr->upside_down = 1;
hdr->core = 0;
}
else if(hdrsize == 12)
{
hdr->width = fget16le(fp);
hdr->height = fget16le(fp);
fget16le(fp);
hdr->bits = fget16le(fp);
hdr->compression = 0;
hdr->upside_down = 1;
hdr->core = 1;
hdr->palsize = 1 << hdr->bits;
}
else
return 0;
if(ferror(fp))
return -1;
return 0;
}

/****************************************************************
* write a bitmap header. *
* Params: fp - pointer to an open file. *
* width - bitmap width *
* height - bitmap height *
* bit - bit depth (1, 4, 8, 16, 24, 32) *
****************************************************************/
static void saveheader(FILE *fp, int width, int height, int bits)
{
long sz;
long offset;

/* the file header */
/* "BM" */
fputc(0x42, fp);
fputc(0x4D, fp);

/* file size */
sz = getfilesize(width, height, bits) + 40 + 14;
fput32le(sz, fp);

/* reserved */
fput16le(0, fp);
fput16le(0, fp);
/* offset of raster data from header */
if(bits < 16)
offset = 40 + 14 + 4 * (1 << bits);
else
offset = 40 + 14;
fput32le(offset, fp);

/* the infoheader */

/* size of structure */
fput32le(40, fp);
fput32le(width, fp);
/* height negative because top-down */
fput32le(-height, fp);
/* bit planes */
fput16le(1, fp);
fput16le(bits, fp);
/* compression */
fput32le(0, fp);
/* size of image (can be zero) */
fput32le(0, fp);
/* pels per metre */
fput32le(600000, fp);
fput32le(600000, fp);
/* colours used */
fput32le(0, fp);
/* colours important */
fput32le(0, fp);
}

/*********************************************************************
* load 8-bit raster data *
* Params: fp - pointer to an open file. *
* data - return pointer for data (one byte per pixel) *
* width - image width *
* height - image height *
*********************************************************************/
static void loadraster8(FILE *fp, unsigned char *data, int width, int
height)
{
int linewidth;
int i;
int ii;

linewidth = (width + 3)/4 * 4;

for(i=0;i<height;i++)
{
for(ii=0;ii<width;ii++)
*data++ = fgetc(fp);
while(ii < linewidth)
{
fgetc(fp);
ii++;
}
}
}

/**********************************************************************
* load 4-bit raster data *
* Params: fp - pointer to an open file. *
* data - return pointer for data (one byte per pixel) *
* width - image width *
* height - iamge height *
**********************************************************************/
static void loadraster4(FILE *fp, unsigned char *data, int width, int
height)
{
int linewidth;
int i;
int ii;
int pix;

linewidth = (((width + 1)/2) + 3)/4 * 4;

for(i=0;i<height;i++)
{
for(ii=0;ii<linewidth;ii++)
{
pix = fgetc(fp);
if(ii * 2 < width)
*data++ = pix >> 4;
if(ii * 2 + 1 < width)
*data++ = pix & 0x0F;
}
}
}

/*******************************************************************
* load 1 bit raster data *
* Params: fp - pointer to an open file. *
* data - return pointer for data (one byte per pixel) *
* width - image width. *
* height - image height. *
*******************************************************************/
static void loadraster1(FILE *fp, unsigned char *data, int width, int
height)
{
int linewidth;
int i;
int ii;
int iii;
int pix;

linewidth = ((width + 7)/8 + 3)/4 * 4;

for(i=0;i<height;i++)
{
for(ii=0;ii<linewidth;ii++)
{
pix = fgetc(fp);
if(ii * 8 < width)
{
for(iii=0;iii<8;iii++)
if(ii * 8 + iii < width)
*data++ = (pix & (1 << (7 - iii))) ? 1 : 0;
}
}
}

}

/*****************************************************
* get the size of the file to be written. *
* Params: width - image width *
* height - image height *
* bits - image type *
* Returns: size of image data (excluding headers) *
*****************************************************/
static long getfilesize(int width, int height, int bits)
{
long answer = 0;
switch(bits)
{
case 1:
answer = (width + 7)/8;
answer = (answer + 3)/4 * 4;
answer *= height;
answer += 2 * 4;
break;
case 4:
answer = 16 * 4 + (width + 1)/2;
answer = (answer + 3)/4 * 4;
answer *= height;
answer += 16 * 4;
break;
case 8:
answer = (width + 3)/4 * 4;
answer *= height;
answer += 256 * 4;
break;
case 16:
answer = (width * 2 + 3)/4 * 4;
answer *= height;
break;
case 24:
answer = (width * 3 + 3)/4 * 4;
answer *= height;
break;
case 32:
answer = width * height * 4;
break;
default:
return 0;
}

return answer;
}

/***************************************************************
* swap an area of memory *
* Params: x - pointer to first buffer *
* y - pointer to second buffer *
* len - length of memory to swap *
***************************************************************/
static void swap(void *x, void *y, int len)
{
unsigned char *ptr1 = x;
unsigned char *ptr2 = y;
unsigned char temp;
int i;

for(i=0;i<len;i++)
{
temp = ptr1;
ptr1 = ptr2;
ptr2 = temp;
}
}

/***************************************************************
* write a 32-bit little-endian number to a file. *
* Params: x - the number to write *
* fp - pointer to an open file. *
***************************************************************/
static void fput32le(long x, FILE *fp)
{
fputc(x & 0xFF, fp);
fputc( (x >> 8) & 0xFF, fp);
fputc( (x >> 16) & 0xFF, fp);
fputc( (x >> 24) & 0xFF, fp);
}

/***************************************************************
* write a 16-bit little-endian number to a file. *
* Params: x - the nmuber to write *
* fp - pointer to an open file *
***************************************************************/
static void fput16le(int x, FILE *fp)
{
fputc(x & 0xFF, fp);
fputc( (x >> 8) & 0xFF, fp);
}

/***************************************************************
* fget32le() - read a 32 bit little-endian number from a file. *
* Params: fp - pointer to an open file. *
* Returns: value read as a signed integer. *
***************************************************************/
static long fget32le(FILE *fp)
{
long answer;
answer = fgetc(fp);
answer |= (fgetc(fp) << 8);
answer |= (fgetc(fp) << 16);
answer |= (fgetc(fp) << 24);
/* check for negative */
if(answer & 0x80000000)
answer |= ((-1) << 31);
return answer;
}

/***************************************************************
* fget16le() - read a 16 bit little-endian number from a file. *
* Params: fp - pointer to an open file. *
* Returns: value read as a signed integer. *
***************************************************************/
static int fget16le(FILE *fp)
{
int answer;
answer = fgetc(fp);
answer |= (fgetc(fp) << 8);
/* check for negative */
if(answer & 0x8000)
answer |= ((-1) << 16);
return answer;
}
 
M

MisterE

Hello,


Sorry if this is not "exactly" a C topic but I thought this would be
the best place to start. I need some guidance on working with bitmap
images in ANSI C. I need to "read" in a bitmap image, then break it up
into pieces (say 32x32) then take those pieces and "save" them,
granted they do not need to be a readable image, then perform some
other operations on the pieces, and then combine the manipulated
pieces back into one image.

Any suggestions on any part of this would be great! I have found the
"EasyBMP" package from http://easybmp.sourceforge.net but apperas this
is not going to work for what I need.

Thanks!

Here is some dodgy code I found


typedef unsigned short WORD;
typedef unsigned int DWORD;
typedef unsigned char CHAR;


// Bitmap file header
typedef struct tagBITMAPINFOHEADER
{
WORD bfType; //specifies the file type
DWORD bfSize; //specifies the size in bytes of the bitmap file
WORD bfReserved1; //reserved; must be 0
WORD bfReserved2; //reserved; must be 0
DWORD bOffBits; //species the offset in bytes from the bitmapfileheader to
the bitmap bits
DWORD biSize; //specifies the number of bytes required by the struct
DWORD biWidth; //specifies width in pixels
DWORD biHeight; //species height in pixels
WORD biPlanes; //specifies the number of color planes, must be 1
WORD biBitCount; //specifies the number of bit per pixel
DWORD biCompression;//spcifies the type of compression
DWORD biSizeImage; //size of image in bytes
DWORD biXPelsPerMeter; //number of pixels per meter in x axis
DWORD biYPelsPerMeter; //number of pixels per meter in y axis
DWORD biClrUsed; //number of colors used by th ebitmap
DWORD biClrImportant; //number of colors that are important
} BITMAPINFOHEADER;

// Saves picture data to a bitmap file
unsigned char *SaveBitmapFile(char *filename, BITMAPINFOHEADER
*bitmapInfoHeader, unsigned char *bitmapImage)
{
FILE *filePtr;
unsigned int imageIdx=0;
CHAR tempRGB;

//open file in write mode
filePtr = fopen(filename,"wb");
if (filePtr == NULL)
return NULL;

//save the bitmap file header
fwrite(&(bitmapInfoHeader->bfType),
sizeof(bitmapInfoHeader->bfType),1,filePtr);
fwrite(&(bitmapInfoHeader->bfSize),
sizeof(bitmapInfoHeader->bfSize),1,filePtr);
fwrite(&(bitmapInfoHeader->bfReserved1),
sizeof(bitmapInfoHeader->bfReserved1),1,filePtr);
fwrite(&(bitmapInfoHeader->bfReserved2),
sizeof(bitmapInfoHeader->bfReserved2),1,filePtr);
fwrite(&(bitmapInfoHeader->bOffBits),
sizeof(bitmapInfoHeader->bOffBits),1,filePtr);

//write info header
fwrite(&(bitmapInfoHeader->biSize),
sizeof(bitmapInfoHeader->biSize),1,filePtr);
fwrite(&(bitmapInfoHeader->biWidth),
sizeof(bitmapInfoHeader->biWidth),1,filePtr);
fwrite(&(bitmapInfoHeader->biHeight),
sizeof(bitmapInfoHeader->biHeight),1,filePtr);
fwrite(&(bitmapInfoHeader->biPlanes),
sizeof(bitmapInfoHeader->biPlanes),1,filePtr);
fwrite(&(bitmapInfoHeader->biBitCount),
sizeof(bitmapInfoHeader->biBitCount),1,filePtr);
fwrite(&(bitmapInfoHeader->biCompression),
sizeof(bitmapInfoHeader->biCompression),1,filePtr);
fwrite(&(bitmapInfoHeader->biSizeImage),
sizeof(bitmapInfoHeader->biSizeImage),1,filePtr);
fwrite(&(bitmapInfoHeader->biXPelsPerMeter),
sizeof(bitmapInfoHeader->biXPelsPerMeter),1,filePtr);
fwrite(&(bitmapInfoHeader->biYPelsPerMeter),
sizeof(bitmapInfoHeader->biYPelsPerMeter),1,filePtr);
fwrite(&(bitmapInfoHeader->biClrUsed),
sizeof(bitmapInfoHeader->biClrUsed),1,filePtr);
fwrite(&(bitmapInfoHeader->biClrImportant),
sizeof(bitmapInfoHeader->biClrImportant),1,filePtr);

//swap the r and b values to get RGB (bitmap is BGR)
for (imageIdx = 0;imageIdx < bitmapInfoHeader->biSizeImage;imageIdx+=3)
{
tempRGB = bitmapImage[imageIdx];
bitmapImage[imageIdx] = bitmapImage[imageIdx + 2];
bitmapImage[imageIdx + 2] = tempRGB;
}

//write bitmap image values
fwrite(bitmapImage,sizeof(CHAR),bitmapInfoHeader->biSizeImage,filePtr);

//swap the r and b values to get RGB (bitmap is BGR)
for (imageIdx = 0;imageIdx < bitmapInfoHeader->biSizeImage;imageIdx+=3)
{
tempRGB = bitmapImage[imageIdx];
bitmapImage[imageIdx] = bitmapImage[imageIdx + 2];
bitmapImage[imageIdx + 2] = tempRGB;
}


fclose(filePtr);

return 0;
}

// Loads picture data from a bitmap file
unsigned char *LoadBitmapFile(char *filename, BITMAPINFOHEADER
*bitmapInfoHeader)
{
FILE *filePtr; //our file pointer
CHAR *bitmapImage; //store image data
unsigned int imageIdx=0; //image index counter
CHAR tempRGB; //our swap variable

//open filename in read binary mode
filePtr = fopen(filename,"rb");
if (filePtr == NULL)
return NULL;

//read the bitmap file header
//fread(&bitmapFileHeader, sizeof(BITMAPFILEHEADER),1,filePtr);
fread(&bitmapInfoHeader->bfType,
sizeof(bitmapInfoHeader->bfType),1,filePtr);
fread(&bitmapInfoHeader->bfSize,
sizeof(bitmapInfoHeader->bfSize),1,filePtr);
fread(&bitmapInfoHeader->bfReserved1,
sizeof(bitmapInfoHeader->bfReserved1),1,filePtr);
fread(&bitmapInfoHeader->bfReserved2,
sizeof(bitmapInfoHeader->bfReserved2),1,filePtr);
fread(&bitmapInfoHeader->bOffBits,
sizeof(bitmapInfoHeader->bOffBits),1,filePtr);



//read the bitmap info header
//fread(bitmapInfoHeader, sizeof(BITMAPINFOHEADER),1,filePtr);
fread(&bitmapInfoHeader->biSize,
sizeof(bitmapInfoHeader->biSize),1,filePtr);
fread(&bitmapInfoHeader->biWidth,
sizeof(bitmapInfoHeader->biWidth),1,filePtr);
fread(&bitmapInfoHeader->biHeight,
sizeof(bitmapInfoHeader->biHeight),1,filePtr);
fread(&bitmapInfoHeader->biPlanes,
sizeof(bitmapInfoHeader->biPlanes),1,filePtr);
fread(&bitmapInfoHeader->biBitCount,
sizeof(bitmapInfoHeader->biBitCount),1,filePtr);
fread(&bitmapInfoHeader->biCompression,
sizeof(bitmapInfoHeader->biCompression),1,filePtr);
fread(&bitmapInfoHeader->biSizeImage,
sizeof(bitmapInfoHeader->biSizeImage),1,filePtr);
fread(&bitmapInfoHeader->biXPelsPerMeter,
sizeof(bitmapInfoHeader->biXPelsPerMeter),1,filePtr);
fread(&bitmapInfoHeader->biYPelsPerMeter,
sizeof(bitmapInfoHeader->biYPelsPerMeter),1,filePtr);
fread(&bitmapInfoHeader->biClrUsed,
sizeof(bitmapInfoHeader->biClrUsed),1,filePtr);
fread(&bitmapInfoHeader->biClrImportant,
sizeof(bitmapInfoHeader->biClrImportant),1,filePtr);

//verify that this is a bmp file by check bitmap id
if (bitmapInfoHeader->bfType !=0x4D42)
{
fclose(filePtr);
return NULL;
}

//move file point to the begging of bitmap data
fseek(filePtr, bitmapInfoHeader->bOffBits, SEEK_SET);

//allocate enough memory for the bitmap image data
bitmapImage = (CHAR*)malloc(bitmapInfoHeader->biSizeImage);

//verify memory allocation
if (!bitmapImage)
{
free(bitmapImage);
fclose(filePtr);
return NULL;
}

//read in the bitmap image data
fread(bitmapImage,sizeof(CHAR),bitmapInfoHeader->biSizeImage,filePtr);

//make sure bitmap image data was read
if (bitmapImage == NULL)
{
fclose(filePtr);
return NULL;
}

//swap the r and b values to get RGB (bitmap is BGR)
for (imageIdx = 0;imageIdx < bitmapInfoHeader->biSizeImage;imageIdx+=3)
{
tempRGB = bitmapImage[imageIdx];
bitmapImage[imageIdx] = bitmapImage[imageIdx + 2];
bitmapImage[imageIdx + 2] = tempRGB;
}

//close file and return bitmap iamge data
fclose(filePtr);
return bitmapImage;
}
 
B

Bart C

Malcolm McLean said:
....
A bit long, but then pixels are getting cheaper these days
(for regs who don't want to read this, it is just an ANSI-standard bitmap
loader /saver).
This is incorporated in my book Basic Algorithms, so any bug reports
particularly welcome.
* loadbmp() - load any bitmap * ....
unsigned char *loadbmp(char *fname, int *width, int *height) ....
answer = malloc(bmpheader.width * bmpheader.height * 3);

Don't know if this is actually wrong, but: assumes here 24 bits per pixel?
Even though it loads any size? So smaller pixel bitmaps are expanded to 24?
That's why this function doesn't return the pixel size?

Also this allows an odd number of bytes per row although I seem to remember
BMP was padded to 4n bytes per row, so the code presumably compresses the
image by eliminating the padding?

In other words, this function loads any BMP file and returns a pointer to a
string of 3-byte/24-bit pixels in a 'raw' format different from that stored
in the file?

Bart
 
M

Malcolm McLean

Bart C said:
Don't know if this is actually wrong, but: assumes here 24 bits per pixel?
Even though it loads any size? So smaller pixel bitmaps are expanded to
24? That's why this function doesn't return the pixel size?
That's right. If you want the palette and index values for some reason call
getbitmapinfo() to ensure the file is of the right type, then call
loadbmp8bit / 4 bit.
Also this allows an odd number of bytes per row although I seem to
remember BMP was padded to 4n bytes per row, so the code presumably
compresses the image by eliminating the padding?

In other words, this function loads any BMP file and returns a pointer to
a string of 3-byte/24-bit pixels in a 'raw' format different from that
stored in the file?
Yes. The padding is now a quirk of the file format. Originally the idea was
that a slow PC could save a few cycles by loading the image directly into
memory, and then from memory straight to VDU memory. Nowadays that is
unlikely to matter, and probably slows it down as much as it speeds it up.
 
E

Ernie Wright

Malcolm said:
This is incorporated in my book Basic Algorithms, so any bug reports
particularly welcome.

Broken for 24-bit and 32-bit, and for 4-bit old-style. See below.

Since it's intended to be pedagogical, I also have a number of serious
reservations about the style and design, but to be honest I don't have
the energy to discuss them at length.
/*********************************************************
* bmp.c - Microsoft bitmap loading functions. *
*********************************************************/

....and saving functions. It'd probably be a good idea to document which
of these functions is part of the public API of bmp.c, how they are
meant to be used, what data formats they expect or return, and so on.
unsigned char *loadbmp(char *fname, int *width, int *height);
unsigned char *loadbmp8bit(char *fname, int *width, int *height, unsigned char *pal);
unsigned char *loadbmp4bit(char *fname, int *width, int *height, unsigned char *pal);

These get (unnecessary) prototypes, but the corresponding save functions
don't.
int bmpgetinfo(char *fname, int *width, int *height)
{
FILE *fp;
BMPHEADER bmphdr;
fp = fopen(fname, "rb");
if(!fp)
return 0;
if(loadheader(fp, &bmphdr) == -1)
{
fclose(fp);
return 0;
}
fclose(fp);
if(width)
*width = bmphdr.width;
if(height)
*height = bmphdr.height;
return bmphdr.bits;
}

What's up with the indention? Some vertical space would be nice, too.
/************************************************************
* loadbmp() - load any bitmap *
* Params: fname - pointer to file path *
* width - return pointer for image width *
* height - return pointer for image height *
* Returns: malloced pointer to image, 0 on fail *
************************************************************/

This description is inadequate for an API function. What loadbmp()
returns is a 24-bit bitmap written as an array of 3-byte pixels in BGR
order, with row span of 3 * width, and origin in the upper left corner
of the image. Callers need this level of detail in order to know how
to use what loadbmp() returns.
unsigned char *loadbmp(char *fname, int *width, int *height)
{ [...]
switch(bmpheader.bits)
{ [...]
case 4:
if(bmpheader.core)
loadpalettecore(fp, pal, 256);

This'll fail. Should be 16 colors, not 256.
for(i=0;i<bmpheader.height;i++)
{
for(ii=0;ii<bmpheader.width;ii++)
{

How about y and x as the loop variables here and elsewhere?
/************************************************************
* loadbmp4bit() - load a 4-bit bitmap from disk. *
* Params: fname - pointer to the file path. *
* width - return pointer for image width. *
* height - return pointer for image height. *
* pal - return pointer for 16 rgb palette entries. *
* Returns: malloced pointer to 4-bit image data. *
************************************************************/

What this actually returns is 4-bit data expanded into 8-bit pixels.
/***********************************************************
* save a24-bit bmp file. *
* Params: fname - name of file to save. *
* rgb - raster data in rgb format *
* width - image width *
* height - image height *
* Returns: 0 on success, -1 on fail *
***********************************************************/
int savebmp(char *fname, unsigned char *rgb, int width, int height)
{ [...]
for(ii=0;ii<width;ii++)
{
fputc(rgb[2], fp);
fputc(rgb[1], fp);
fputc(rgb[0], fp);
rgb += 3;
}

Oops. Here's where careful documentation of your internal bitmap format
would come in handy. loadbmp() writes pixels to the unsigned char array
in BGR order, but savebmp() assumes that they're written in RGB order.

Did you actually test this code by calling loadbmp() and savebmp() and
looking at the output? Because I think you'll find that your load and
save aren't inverses.
/***************************************************************
* save a 2-bit palettised bitmap. *

Actually, this saves a 1-bit bitmap.
/**************************************************************
* loadpalette() - load palette for a new format BMP. *
* Params: fp - pointer to an open file. *
* pal - return pointer for palette entries. *
* entries - number of entries in palette. *
**************************************************************/
static void loadpalette(FILE *fp, unsigned char *pal, int entries)
{
int i;
for(i=0;i<entries;i++)
{
pal[2] = fgetc(fp);
pal[1] = fgetc(fp);
pal[0] = fgetc(fp);
fgetc(fp);
pal += 3;
}
}

Wait, the plot thickens. The palette channels are inverted here, so
loadbmp() will convert indexed color images to 24-bit in RGB order, but
will store 24-bit and 32-bit BMPs in BGR order.
static int loadheader(FILE *fp, BMPHEADER *hdr)
{ [...]
if(hdrsize == 40)
{ [...]
}
else if(hdrsize == 12)
{ [...]
}
else
return 0;
if(ferror(fp))
return -1;
return 0;
}

This returns 0, your indication of success, if the header size is
neither 40 nor 12 and therefore a header you don't process.
static void saveheader(FILE *fp, int width, int height, int bits)
{ [...]
/* pels per metre */
fput32le(600000, fp);
fput32le(600000, fp);

Why 600000? That's 15240 DPI. Since you don't allow the caller to set
this, the safest choice is 0.
/*****************************************************
* get the size of the file to be written. *
* Params: width - image width *
* height - image height *
* bits - image type *
* Returns: size of image data (excluding headers) *
*****************************************************/
static long getfilesize(int width, int height, int bits)
{
long answer = 0;
switch(bits)
{
case 1:
answer = (width + 7)/8;
answer = (answer + 3)/4 * 4;
answer *= height;
answer += 2 * 4;
break;
case 4:
answer = 16 * 4 + (width + 1)/2;
answer = (answer + 3)/4 * 4;
answer *= height;
answer += 16 * 4;
break;
case 8:
answer = (width + 3)/4 * 4;
answer *= height;
answer += 256 * 4;
break;
case 16:
answer = (width * 2 + 3)/4 * 4;
answer *= height;
break;
case 24:
answer = (width * 3 + 3)/4 * 4;
answer *= height;
break;
case 32:
answer = width * height * 4;
break;
default:
return 0;
}

return answer;
}

All of this could be replaced with

palsize = depth <= 8 ? ( 1 << depth ) * 4 : 0;
rowspan = (( w * depth + 31 ) / 32 ) * 4;
return rowspan * height + palsize;

In fact, since you calculate palsize and rowspan separately at a number
of different points in the code, it might be a good idea to turn each of
the first two lines into a macro or a function, so that you only have to
get them right once.
/***************************************************************
* swap an area of memory *
* Params: x - pointer to first buffer *
* y - pointer to second buffer *
* len - length of memory to swap *
***************************************************************/
static void swap(void *x, void *y, int len)
{
unsigned char *ptr1 = x;
unsigned char *ptr2 = y;
unsigned char temp;
int i;

for(i=0;i<len;i++)
{
temp = ptr1;
ptr1 = ptr2;
ptr2 = temp;
}
}


Rather than the load functions calling this to convert bottom-up
scanline order to top-down, wouldn't it be faster, and not much harder,
simply to load the pixels into your bitmap in the order you want in the
first place?

You could also invert the process so that you can write canonical BMPs
in bottom-up order, with a positive BITMAPINFOHEADER.biHeight.

- Ernie http://home.comcast.net/~erniew
 
M

Malcolm McLean

Ernie Wright said:
Broken for 24-bit and 32-bit, and for 4-bit old-style. See below.
No, it's been tested and basically works. It might not work on everything,
but it will load the vast majority of BMPs OK.
This description is inadequate for an API function. What loadbmp()
returns is a 24-bit bitmap written as an array of 3-byte pixels in BGR
order, with row span of 3 * width, and origin in the upper left corner
of the image. Callers need this level of detail in order to know how
to use what loadbmp() returns.
No, in rgb order. As is the palette. However that proves your point that the
comment is inadequate. Most of the bug reports are based on that
misunderstanding.
The exception is passing a 256 palette size here.

/* 4 bit bitmaps */
if(bmpheader.core)
loadpalettecore(fp, pal, 256);
else
loadpalette(fp, pal, bmpheader.palsize);

I think there's some problem with old BMPs which have core rather than
proper palette sizes. It's a while since I wrote that code, it might have
been that I had a degenerate BMP as the test case . Maybe you need to
calculate the palette size from the raster offset, if you can trust it.

Thanks for taking the time to comment, however.
 
S

Stephen.Schoenberger

No, it's been tested and basically works. It might not work on everything,
but it will load the vast majority of BMPs OK.


No, in rgb order. As is the palette. However that proves your point that the
comment is inadequate. Most of the bug reports are based on that
misunderstanding.
The exception is passing a 256 palette size here.

/* 4 bit bitmaps */
 if(bmpheader.core)
  loadpalettecore(fp, pal, 256);
   else
  loadpalette(fp, pal, bmpheader.palsize);

I think there's some problem with old BMPs which have core rather than
proper palette sizes. It's a while since I wrote that code, it might have
been that I had a degenerate BMP as the test case . Maybe you need to
calculate the palette size from the raster offset, if you can trust it.

Thanks for taking the time to comment, however.

Malcolm,

I had a few questions about your code. The images that I need to load
in I know are 8bit images and are 1280x960. Since I am unfamiliar with
the inner workings of bmp images can you provide further guidance
regarding the use of your code to read in the images? The code you
provided looks like it would work for what I need just not sure how to
approach it.
 
E

Ernie Wright

Malcolm said:
No, it's been tested and basically works. It might not work on
everything, but it will load the vast majority of BMPs OK.

Tested in what way?

If you loadbmp(), then savebmp() a 24-bit BMP and then examine the
output of savebmp(), you'll find that the red and blue channels have
been swapped. After seeing the relevant problem in the source code, I
actually compiled and tested it to confirm that I hadn't missed
something. I wasn't guessing.

Your code to load 24-bit pixels (with indention repaired) is

case 24:
for(i=0;i<bmpheader.height;i++)
{
for(ii=0;ii<bmpheader.width;ii++)
{
target = (i * bmpheader.width * 3) + ii * 3;
answer[target] = fgetc(fp);
answer[target+1] = fgetc(fp);
answer[target+2] = fgetc(fp);

You can see that this stores pixels in answer[] in the same BGR order as
they're written in the BMP.
No, in rgb order. As is the palette. However that proves your point that
the comment is inadequate. Most of the bug reports are based on that
misunderstanding.

At least as important as clarity for users is clarity for *yourself* as
you write the code. It forces you to think more about what you're doing
and to test a lot more thoroughly to prove your code actually implements
your specification.
The exception is passing a 256 palette size here.

/* 4 bit bitmaps */
if(bmpheader.core)
loadpalettecore(fp, pal, 256);
else
loadpalette(fp, pal, bmpheader.palsize);

I think there's some problem with old BMPs which have core rather than
proper palette sizes. It's a while since I wrote that code, it might
have been that I had a degenerate BMP as the test case . Maybe you need
to calculate the palette size from the raster offset, if you can trust it.

The specification claims that 4-bit old-style BMP has a 16-color palette.
It's been so long since I've encountered one of these that I wouldn't
know where to look for one now. Unless you know of users that actually
have to deal with these (they haven't been written by Microsoft code
since Windows 2.x), I'd be tempted to strip all of that cruft out of
your loader.

The other potential problem was your code's handling of header sizes
that weren't one of the two it expects. You're most likely to run into
this if a user tries to load an OS/2 BMP, or a file that's not a BMP at
all but happens to start with the letters 'BM', but it's also a forward
compatibility issue, in the event a new version of BMP comes along with
a different header size.
Thanks for taking the time to comment, however.

I'm sorry I can't delve more deeply, since I think it could illuminate
important design issues. Basically, there are much better ways of doing
this kind of code. But newsgroup posts aren't the best medium for the
lengthy discussion that would need to happen about that.

- Ernie http://home.comcast.net/~erniew
 
M

Malcolm McLean

Ernie Wright said:
Malcolm said:
No, it's been tested and basically works. It might not work on
everything, but it will load the vast majority of BMPs OK.

Tested in what way?

If you loadbmp(), then savebmp() a 24-bit BMP and then examine the
output of savebmp(), you'll find that the red and blue channels have
been swapped. After seeing the relevant problem in the source code, I
actually compiled and tested it to confirm that I hadn't missed
something. I wasn't guessing.

Your code to load 24-bit pixels (with indention repaired) is

case 24:
for(i=0;i<bmpheader.height;i++)
{
for(ii=0;ii<bmpheader.width;ii++)
{
target = (i * bmpheader.width * 3) + ii * 3;
answer[target] = fgetc(fp);
answer[target+1] = fgetc(fp);
answer[target+2] = fgetc(fp);

You can see that this stores pixels in answer[] in the same BGR order as
they're written in the BMP.
You're right.
The file is a different version to the one published in the book, which has
it the right way round. Obviously something has gone wrong with my
versioning.
The specification claims that 4-bit old-style BMP has a 16-color palette.
It's been so long since I've encountered one of these that I wouldn't
know where to look for one now. Unless you know of users that actually
have to deal with these (they haven't been written by Microsoft code
since Windows 2.x), I'd be tempted to strip all of that cruft out of
your loader.
It is a problem testing when you don't have versions of the input. Also if
degenerate files are in circulation. You get this with IFF files quite a
bit - in practise the best thing is to search for the 4-byte tag sequence.
The format says field sizes should be included, and you skip to the next
tag, but actually you are much more likely to get a corrupt field size than
a chance occurence of the tag.
The other potential problem was your code's handling of header sizes
that weren't one of the two it expects. You're most likely to run into
this if a user tries to load an OS/2 BMP, or a file that's not a BMP at
all but happens to start with the letters 'BM', but it's also a forward
compatibility issue, in the event a new version of BMP comes along with
a different header size.
That's a good point. It should be returning -1 instead of zero on an
unrecognised header size, at least. Probably we should try to read it as a
40. However to do a really good job we've got to skip to the raster bits,
which means a total rewrite and lots of complications.
 
F

Flash Gordon

Malcolm McLean wrote, On 27/12/07 17:16:
Ernie Wright said:
Malcolm said:
Broken for 24-bit and 32-bit, and for 4-bit old-style. See below.

No, it's been tested and basically works. It might not work on
everything, but it will load the vast majority of BMPs OK.

Tested in what way?

If you loadbmp(), then savebmp() a 24-bit BMP and then examine the
output of savebmp(), you'll find that the red and blue channels have
been swapped. After seeing the relevant problem in the source code, I
actually compiled and tested it to confirm that I hadn't missed
something. I wasn't guessing.

Your code to load 24-bit pixels (with indention repaired) is

case 24:
for(i=0;i<bmpheader.height;i++)
{
for(ii=0;ii<bmpheader.width;ii++)
{
target = (i * bmpheader.width * 3) + ii * 3;
answer[target] = fgetc(fp);
answer[target+1] = fgetc(fp);
answer[target+2] = fgetc(fp);

You can see that this stores pixels in answer[] in the same BGR order as
they're written in the BMP.
You're right.
The file is a different version to the one published in the book, which
has it the right way round. Obviously something has gone wrong with my
versioning.
The specification claims that 4-bit old-style BMP has a 16-color palette.
It's been so long since I've encountered one of these that I wouldn't
know where to look for one now. Unless you know of users that actually
have to deal with these (they haven't been written by Microsoft code
since Windows 2.x), I'd be tempted to strip all of that cruft out of
your loader.

The version of Paint in Windows Vista claims to be able to save a 16
colour bitmap. I've no idea if the format is the same, but I see no
reason why it would not be.
It is a problem testing when you don't have versions of the input.

You create the test data. Creating test images is not difficult seeing
as you are using an OS that comes with a program able to save in the
relevant format.
Also
if degenerate files are in circulation. You get this with IFF files
quite a bit - in practise the best thing is to search for the 4-byte tag
sequence. The format says field sizes should be included, and you skip
to the next tag, but actually you are much more likely to get a corrupt
field size than a chance occurence of the tag.

Actually, the best thing is normally to start off by checking the length
is within a valid range, then check the data is valid, then check that
you hit a valid tag in the place expected. If any of those things fail
you report the file as being corrupt or a format you can't handle.

I've done a moderate amount of work on serial links where there was real
risk of data corruption (guaranteed to get some within 1KB of data) so I
know what works in the real world.
That's a good point. It should be returning -1 instead of zero on an
unrecognised header size, at least. Probably we should try to read it as
a 40. However to do a really good job we've got to skip to the raster
bits, which means a total rewrite and lots of complications.

Don't advertise your code as being reliable then, advertise it as
supporting some but not all BMP files.
 
M

Malcolm McLean

Flash Gordon said:
Malcolm McLean wrote, On 27/12/07 17:16:
Actually, the best thing is normally to start off by checking the length
is within a valid range, then check the data is valid, then check that you
hit a valid tag in the place expected. If any of those things fail you
report the file as being corrupt or a format you can't handle.
The object of the exercise is to read the data, not to check the file for
adherence to the format.
Don't advertise your code as being reliable then, advertise it as
supporting some but not all BMP files.
The question is what to do with as yet unspecified versions. Presumably MS
will extend the header field, maybe add new chunks. The question is whether
the raster data will still be readable without the new information, and it
is impossible for MS to guarantee that, because they won't change the format
for fun, but because some need arises.
I should be able to read every current format, barring bugs, but some are
obsolete.
 
F

Flash Gordon

Malcolm McLean wrote, On 27/12/07 23:38:
The object of the exercise is to read the data, not to check the file
for adherence to the format.

OK, so I know not to rely on any code you write for reading files. This
is because you can only reliably read data if you validate that it is in
the format that it is meant to be in. Also you were the person who
raised the problem of corrupted files, I just advised you on what works
better in real situations where you do have data corruption to deal with.
The question is what to do with as yet unspecified versions. Presumably
MS will extend the header field, maybe add new chunks. The question is
whether the raster data will still be readable without the new
information, and it is impossible for MS to guarantee that, because they
won't change the format for fun, but because some need arises.
I should be able to read every current format, barring bugs, but some
are obsolete.

You are saying this having just had OS/2 BMPs reported as a possible
problem (in text you snipped) and having admitted to not even testing on
all the formats that MS support. Ernie raised forward compatibility as
an additional problem.
 
E

Ernie Wright

Flash said:
Malcolm McLean wrote, On 27/12/07 17:16:

The version of Paint in Windows Vista claims to be able to save a 16
colour bitmap. I've no idea if the format is the same, but I see no
reason why it would not be.

It's not. Old-style BMP, what Malcolm's code calls "core" BMP, is an
obsolete form of BMP with a different header. This older form was used
by Windows 1.x and 2.x and OS/2 1.x.

- Ernie http://home.comcast.net/~erniew
 
E

Ernie Wright

Malcolm said:
The object of the exercise is to read the data, not to check the file
for adherence to the format.

You have to verify that you're reading a format you understand! If at
any point the file deviates from what you expect, the only *safe* thing
to conclude is that it's not in a format you can handle.
The question is what to do with as yet unspecified versions. Presumably
MS will extend the header field, maybe add new chunks.

They already have. A long time ago, in fact. Google BITMAPV4HEADER
and BITMAPV5HEADER.

It's unlikely you'll see these in files, since they're primarily meant
to enhance aspects of the internal representation of bitmaps in Windows.

But the point is, the right thing to do with variants you don't know
about, which by definition includes future versions, is recognize that
you don't know what they contain, and fail gracefully.

- Ernie http://home.comcast.net/~erniew
 
M

Malcolm McLean

Ernie Wright said:
You have to verify that you're reading a format you understand! If at
any point the file deviates from what you expect, the only *safe* thing
to conclude is that it's not in a format you can handle.
I depends what you are doing.
If we are writing software to prepare a legal case, say a pornography suit,
then any sort of change or corruption of the image would be unacceptable,
and might compromise the case. However I had to think quite hard to come up
with that example. Most of the time an image of a tiger, one pixel out of
register, is better than no image of a tiger at all, or at least no worse,
if we are displaying an excyclopedia article, for example.
But the point is, the right thing to do with variants you don't know
about, which by definition includes future versions, is recognize that
you don't know what they contain, and fail gracefully.
See above.
 
E

Ernie Wright

Malcolm said:
Ernie Wright said:
You have to verify that you're reading a format you understand!

I depends what you are doing. [...]
Most of the time an image of a tiger, one pixel out of register, is
better than no image of a tiger at all, or at least no worse,

Is that really the only consequence you can think of? That the offset
to the pixel data will be slightly off? Can you not imagine the pixel
data itself being different or even absent? If you don't even know
what's in the header, how can you possibly presume to guess what's in
the rest of the file--or even that this is an image file at all?

It's fairly common for a loader like yours to be daisychained with a
number of others, each called in sequence on a file until one of them
recognizes and loads the contents. This approach will break if you fail
to step aside when you encounter something you don't understand. There
may be a loader behind you that *does* understand the file. It's even
possible that your blind meander through the file will lead to a crash.

Your code rejects BMPs with a bits-per-pixel of 0. Why? A 0 bpp is
valid as of Windows NT 4.0, and surely there are tigers in those images
just waiting to be (perhaps imperfectly) revealed, no?

Of course, your code rightly rejects 0-bpp images because it doesn't
know what to do with them. How is an unexpected header size any
different?
See above.

- Ernie http://home.comcast.net/~erniew
 
M

Malcolm McLean

Ernie Wright said:
Malcolm McLean wrote:

Of course, your code rightly rejects 0-bpp images because it doesn't
know what to do with them. How is an unexpected header size any
different?
You need to ask, is a corrupt image better, worse, or no different to no
image? If the answer is "worse" then of course reject anything you can't
read, if the answer is "better" or "no different" you might as well try to
read the data.
However there has got to be a sporting chance. If we guess that pixel data
in the new file format will be in the same rgb order as the old, we've a
pretty good chance of being right. If we simply try to impose a pixel format
on unknown binary data, then the chance of recovering something
humanly-recognisable is so low that it really isn't worth the bother.
You could of course use sophisticated techniques to try to recover images
from unknown data, but that's an entirely different type of program.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,581
Members
45,057
Latest member
KetoBeezACVGummies

Latest Threads

Top