2

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.

Noob
  • 145
  • 2
  • 9
  • 1
    Probably not related to your problem (which you may want to explain in more detail), but if you halve both the width and height of your image, the new `biSizeImage` is not going to be 1/2, it's actually 1/2*1/2 = 1/4 size. Mainly, I guess your RGB Triplets should be `unsigned char`, not `int`. – Jongware Nov 29 '14 at 01:15
  • 1
    There are quite a few algorithms with varying levels of efficiency and quality. It's a pretty broad subject. This might give you some things to think about: http://stackoverflow.com/questions/6133957/image-downsampling-algorithms – Retired Ninja Nov 29 '14 at 01:15
  • @RetiredNinja Thanks that helps a lot, at least now I have an idea on what to look for. Unfortunately that page doesn't really show any good examples on what the algorithm looks like in use. I will look for one. If you happen to know of one could you link that as well please. – Noob Nov 29 '14 at 01:30
  • https://code.google.com/p/nvidia-texture-tools/wiki/MipmapGeneration has some articles about the theory behind some of the downsampling algorithms/filters as well as being a pretty good library if you choose not to reinvent the wheel. – Retired Ninja Nov 29 '14 at 09:01

1 Answers1

5

"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:

triple *line_a = (triple*)malloc(...) /* check malloc() */
triple *line_b = (triple*)malloc(...) /* check malloc() */
triple *dest_line = etc...

/* 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 < dest_height; ++i) 
{
    read_line(line_a); /* read scanline & advance file pointer, err check in func */
    read_line(line_b); /* read scanline & advance file pointer, err check in func */
    for (j = 0; j < dest_width; ++j)
    {
        bilinear_filter(&(dest_line[j]), &(line_a[j*2]), &(line_a[(j*2)+1]), &(line_b[j*2]), &(line_b[(j*2)+1]));
    }
    write_line_to_file(dest_line, fp); /* 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 */
}
:
:
void bilinear_filter(triple *dest, triple *A, triple *B, triple *C, triple *D) 
{

    /* assuming 0888 ARGB */
    dest->r = (A->r + B->r + C->r + D->r) / 4;
    dest->g = (A->g + B->g + C->g + D->g) / 4;
    dest->b = (A->b + B->b + C->b + D->b) / 4;

}

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.

PeterT
  • 920
  • 8
  • 20
  • Thank you for the response. Sorry for not commenting on it sooner. I haven't been able to really look at your post yet but i dose look very helpful at first glance. Ill will make a second post once i can look at it more. Thanks again for the post. – Noob Dec 01 '14 at 19:17
  • I have just now implemented you code in to my project. After a little thought I got it to run a create a functional bmp file. However the picture produced is not correct nor is it even the correct size. I have updated my question with the current code if you see anything glaringly wrong please let me know. Even if not thank you for the help so far. – Noob Dec 02 '14 at 02:26
  • It has been a long time since I worked with bitmaps, but I do remember the headers being finicky. You commented out many of the fields, which means they still have the input file's attributes. That's part of the problem. If I were you I would write a debugPrintHeader() function that prints all of the bitmap fields so you can verify if you are writing it out properly. You might even want to use a hex editor to make sure what you write to the file matches what's in memory, until you're confident the headers are correct. Good luck! – PeterT Dec 02 '14 at 16:09
  • Thank you. If I have time I will differently look into that more. Just to make sure though, my implementation of the code you posted appears to be correct to you right? Just trying to isolate the error. – Noob Dec 02 '14 at 20:50