How to shrink an image size?

5.9k views Asked by At

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.

1

There are 1 answers

4
Freeman On

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