Hello I am working on a program to shrink a 640x480 bit map image into a 320x240 image. I have been researching the issue for a while now but all the good examples i have found have been for increasing the size of an image.
(See here:http://cboard.cprogramming.com/c-programming/154737-help-program-resize-image.html)
I am having difficulty translating what was done in that program to what needs to be done in mine. Here is my code sofar:
include stdio.h
include stdlib.h
include string.h
include math.h
pragma pack(push, 1)
typedef struct tagBITMAPFILEHEADER
{
unsigned short bfType; //specifies the file type
unsigned int bfSize; //specifies the size in bytes of the bitmap file
unsigned short bfReserved1; //reserved; must be 0
unsigned short bfReserved2; //reserved; must be 0
unsigned int bfOffBits; //species the offset in bytes from the bitmapfileheader to the bitmap bits
} BITMAPFILEHEADER;
pragma pack(pop)
pragma pack(push, 1)
typedef struct tagBITMAPDIBHEADER
{
unsigned int biSize; //specifies the number of bytes required by the struct
int biWidth; //specifies width in pixels
int biHeight; //species height in pixels
unsigned short biPlanes; //specifies the number of color planes, must be 1
unsigned short biBitCount; //specifies the number of bit per pixel
unsigned int biCompression;//spcifies the type of compression
unsigned int biSizeImage; //size of image in bytes
int biXPelsPerMeter; //number of pixels per meter in x axis
int biYPelsPerMeter; //number of pixels per meter in y axis
unsigned int biClrUsed; //number of colors used by th ebitmap
unsigned int biClrImportant; //number of colors that are important
} BITMAPDIBHEADER;
pragma pack(pop)
pragma pack(push, 1)
typedef struct
{
int rgbtBlue;
int rgbtGreen;
int rgbtRed;
}
RGBTRIPLE;
pragma pack(pop)
int main()
{
FILE *input, *output;
BITMAPDIBHEADER inputdibHeader;
BITMAPFILEHEADER inputfileHeader;
BITMAPDIBHEADER outputdibHeader;
BITMAPFILEHEADER outputfileHeader;
int greenValue = 0;
int blueValue = 0;
int redValue = 0;
fopen_s(&output, "test.bmp", "wb");
if (output == NULL){
return NULL;
}
fopen_s(&input, "lolcat.bmp", "rb");
if (input == NULL)
return NULL;
rewind(input); // rewind the file before reading it again
fread(&(inputfileHeader), sizeof(BITMAPFILEHEADER), 1, input);
fread(&(inputdibHeader), sizeof(BITMAPDIBHEADER), 1, input);
rewind(input); // rewind the file before reading it again
fread(&(outputfileHeader), sizeof(BITMAPFILEHEADER), 1, input);
fread(&(outputdibHeader), sizeof(BITMAPDIBHEADER), 1, input);
outputdibHeader.biWidth = inputdibHeader.biWidth *.5;
outputdibHeader.biHeight = inputdibHeader.biHeight *.5;
outputfileHeader.bfSize = outputdibHeader.biWidth * outputdibHeader.biHeight;
outputdibHeader.biSizeImage = inputdibHeader.biSizeImage *.5;
fwrite(&(outputfileHeader), sizeof(BITMAPFILEHEADER), 1, output);
fwrite(&(outputdibHeader), sizeof(BITMAPDIBHEADER), 1, output);
rewind(input);
fseek(input, inputfileHeader.bfOffBits, SEEK_SET);
fseek(output, outputfileHeader.bfOffBits, SEEK_SET);
int oldheight = inputdibHeader.biHeight;
int oldwidth = inputdibHeader.biWidth;
int i;
int timeswriten = 0;
int oldPad = (4 - ((inputdibHeader.biWidth * sizeof(RGBTRIPLE)) % 4)) % 4;
int newPad = (4 - ((outputdibHeader.biWidth * sizeof(RGBTRIPLE)) % 4)) % 4;
// iterate over infile's scanlines
for (int i = 0; i < abs(oldheight); i++)
{
if (i % 2){
// iterate over pixels in scanline
for (int j = 0; j < oldwidth; j++)
{
// temporary storage
RGBTRIPLE triple;
fread(&triple, sizeof(RGBTRIPLE), 1, input);
if (j % 2){
fwrite(&triple, sizeof(RGBTRIPLE), 1, output);
}
// skip over any input padding
fseek(input, oldPad, SEEK_CUR);
}
}
}
fclose(input);
fclose(output);
}
Currently this code produces a valid bitmap image, however the image created is a very distorted version of the original. Im fairly sure this is due to the way that i am omitting pixels from my new image but I am not sure on what a proper approach for this should be. Over all my question is can anyone help explain to me where and how I should be omitting pixels?
Update
I now know that what I am aiming to do is to average 2x2 pixels into one pixels but I can not find a good example on how to do this. Can any please explain this process?
Update 2 Thanks to PeterT I know have the following code that appears to be correct by my output is not.
RGBTRIPLE *line_a = (RGBTRIPLE*)malloc(inputdibHeader.biWidth * sizeof(RGBTRIPLE)); /* check malloc() */
RGBTRIPLE *line_b = (RGBTRIPLE*)malloc(inputdibHeader.biWidth *sizeof(RGBTRIPLE)); /* check malloc() */
RGBTRIPLE *dest_line = (RGBTRIPLE*)malloc(outputdibHeader.biWidth * sizeof(RGBTRIPLE));
/* move through the target array line by line, consuming two lines from the source
image at a time */
/* also assuming you verified the source image is exactly 2x the size of the dest
malloc() */
for (i = 0; i < outputdibHeader.biHeight; ++i)
{
fread(&(line_a), sizeof(RGBTRIPLE), inputdibHeader.biWidth, input); /* read scanline & advance file pointer, err check in func */
fread(&(line_b), sizeof(RGBTRIPLE), inputdibHeader.biWidth, input);/* read scanline & advance file pointer, err check in func */
for (j = 0; j < outputdibHeader.biWidth; ++j)
{
bilinear_filter(&(dest_line[j]), &(line_a[j * 2]), &(line_a[(j * 2) + 1]), &(line_b[j * 2]), &(line_b[(j * 2) + 1]));
}
fwrite(&(dest_line), sizeof(RGBTRIPLE), outputdibHeader.biWidth, output);
/* or something... point is we're creeping through the files scaline by scanline,
and letting another function handle it to keep this code more intelligble */
}
fclose(input);
fclose(output);
}
void bilinear_filter(RGBTRIPLE *dest, RGBTRIPLE *A, RGBTRIPLE *B, RGBTRIPLE *C, RGBTRIPLE *D)
{
/* assuming 0888 ARGB */
dest->Red = (A->Red + B->Red + C->Red + D->Red) / 4;
dest->Green = (A->Green + B->Green + C->Green + D->Green) / 4;
dest->Blue = (A->Blue + B->Blue + C->Blue + D->Blue) / 4;
}
I think this issue may lie in my header creation so here is that
fread(&(inputHeader), sizeof(TwoHeader), 1, input);
inputfileHeader = inputHeader.fileHeader;
inputdibHeader = inputHeader.dibHeader;
rewind(input); // rewind the file before reading it again
fread(&(outputHeader), sizeof(TwoHeader), 1, input);
outputfileHeader = outputHeader.fileHeader;
outputdibHeader = outputHeader.dibHeader;
outputdibHeader.biWidth = inputdibHeader.biWidth *.5;
outputdibHeader.biHeight = inputdibHeader.biHeight *.5;
//outputfileHeader.bfSize = inputfileHeader.bfSize - (inputdibHeader.biWidth*inputdibHeader.biHeight) + outputdibHeader.biWidth*outputdibHeader.biHeight;
outputfileHeader.bfSize = sizeof(BITMAPFILEHEADER)+sizeof(BITMAPDIBHEADER)+outputdibHeader.biSizeImage;
//outputdibHeader.biSizeImage = inputdibHeader.biSizeImage * .25;
//outputdibHeader.biXPelsPerMeter = inputdibHeader.biXPelsPerMeter * .5;
//outputdibHeader.biYPelsPerMeter = inputdibHeader.biYPelsPerMeter * .5;
//fwrite(&(outputfileHeader), sizeof(BITMAPFILEHEADER), 1, output);
//fwrite(&(outputdibHeader), sizeof(BITMAPDIBHEADER), 1, output);
fwrite(&(outputHeader), sizeof(TwoHeader), 1, output);
rewind(input);
fseek(input, inputfileHeader.bfOffBits, SEEK_SET);
fseek(output, outputfileHeader.bfOffBits, SEEK_SET);
Pardon all the comments its mostly old code or code that I am unsure of.
"I have been researching the issue for a while now"... really? /raises eyebrow/ ;)
Sounds like you're looking to do a bilinear filter: the new pixel is really the average of four pixels and located in the center of the previous four pixels.
Do you really need to re-invent the wheel? I'd just use a solid library for this, and focus my energy on solving other problems:
https://github.com/nothings/stb
There's a great wikipedia article on the basic methods of interpolation:
http://en.wikipedia.org/wiki/Image_scaling
Downsampling a 2D image is a 40+ year old problem in computer science. Foley covers it in his seminal book "Computer Graphics", it's a great book.
Regarding your code:
For a simple bilinear downsampling you'll have to store at least two scanlines. I'd recommend breaking your code up into something more modular, e.g. here's a really simple implementation:
Now, there are many ways to interpolate color channels. There are theories that take into account energy emissions of visible spectra for the eye, or gamut curves of print/film, etc. Most do NOT scale r/g/b independently like I did, since it doesn't preserve the relationship of the three values to the sensitivity of the eye. The method I show above is just a hack to point out that a bilinear filter requires reading four pixel's worth of data to produce one new pixel.
I hope this helped.