C programming: Who wants to help me improve my code for resizing bitmaps?
This program takes in a float in the range (0.0, 100] as a command line argument as a scaling factor, and infile, and outfile. As I wrote in the comments, this works for when size is an integer, but still doesn't work on floating point numbers. I tried to avoid changing the buffer for resizing factors in (0, 1) but the image still gets warped when I resize by, say, 0.5, and for floats above 1.0 the image simply won't render. I couldn't think what to alter in the bitmap when given a float expect pixel density, since I can't write a fractional pixel. Maybe I should be utilizing the decimal part of my input differently.
/** * resize.c * * Resizes a bitmap image by a user input factor. * * Currently this code is still problematic. Does not handle * floating point inputs pixel by pixel. */ #include <stdio.h> #include <stdlib.h> #include "bmp.h" int main(int argc, char* argv[]) { // ensure proper usage if (argc != 4) { printf("Usage: ./resize factor infile outfile\n"); return 1; } // change first command line argument to a float float size = atof(argv[1]); // remember filenames char* infile = argv[2]; char* outfile = argv[3]; // get decimal and integer part of scaling factor float decimal = size; while (decimal > 1) decimal--; // if size less than one, set isize to 1 int isize = (size > 1) ? (int) size : 1; // open input file FILE* inptr = fopen(infile, "r"); if (inptr == NULL) { printf("Could not open %s.\n", infile); return 2; } // open output file FILE* outptr = fopen(outfile, "w"); if (outptr == NULL) { fclose(inptr); fprintf(stderr, "Could not create %s.\n", outfile); return 3; } // read infile's BITMAPFILEHEADER BITMAPFILEHEADER bf, bfo; fread(&bfo, sizeof(BITMAPFILEHEADER), 1, inptr); // read infile's BITMAPINFOHEADER BITMAPINFOHEADER bi, bio; fread(&bio, sizeof(BITMAPINFOHEADER), 1, inptr); // save attributes of original image headers in bi and bf bi = bio, bf = bfo; // adjust dimensions of out bitmap accordingly if (size < 1) { bio.biXPelsPerMeter /= decimal; bio.biYPelsPerMeter /= decimal; } else { bio.biXPelsPerMeter *= 1 + decimal; bio.biYPelsPerMeter *= 1 + decimal; } bfo.bfSize *= size; bio.biWidth *= size; bio.biHeight *= size; bio.biSizeImage *= size; // ensure infile is (likely) a 24-bit uncompressed BMP 4.0 if (bf.bfType != 0x4d42 || bf.bfOffBits != 54 || bi.biSize != 40 || bi.biBitCount != 24 || bi.biCompression != 0) { fclose(outptr); fclose(inptr); fprintf(stderr, "Unsupported file format.\n"); return 4; } // write outfile's BITMAPFILEHEADER fwrite(&bfo, sizeof(BITMAPFILEHEADER), 1, outptr); // write outfile's BITMAPINFOHEADER fwrite(&bio, sizeof(BITMAPINFOHEADER), 1, outptr); // determine padding for scanlines int oldPad = (4 - ((bi.biWidth * sizeof(RGBTRIPLE)) % 4)) % 4; int newPad = (4 - ((bio.biWidth * sizeof(RGBTRIPLE)) % 4)) % 4; // iterate over infile's scanlines for (int i = 0, biHeight = abs(bi.biHeight); i < biHeight; i++) { // mark the beginning of the scanline int start = ftell(inptr); // repeating size times for (int m = 0; m < isize; m++) { // iterate over pixels in scanline for (int j = 0; j < bi.biWidth; j++) { // temporary storage RGBTRIPLE triple; // read RGB triple from infile fread(&triple, sizeof(RGBTRIPLE), 1, inptr); // write RGB triple to outfile size times for (int n = 0; n < isize; n++) fwrite(&triple, sizeof(RGBTRIPLE), 1, outptr); } // if size > 1 skip old padding and put new padding if (size > 1) { // skip over old padding, if any fseek(inptr, oldPad, SEEK_CUR); // then add back new padding, if any for (int k = 0; k < newPad; k++) fputc(0x00, outptr); } // if size < 1 keep old padding else for (int k = 0; k < oldPad; k++) fputc(0x00, outptr); // on each scan but the last if (m < isize - 1) // then go back to beginning of scanline fseek(inptr, start - ftell(inptr), SEEK_CUR); } } // close infile fclose(inptr); // close outfile fclose(outptr); // that's all folks return 0; }
and in case you wanted to see bmp.h here it is: /** * bmp.h * * * BMP-related data types based on Microsoft's own. */ #include <stdint.h> /** * Common Data Types * * The data types in this section are essentially aliases for C/C++ * primitive data types. * * Adapted from http://msdn.microsoft.com/en-us/library/cc230309.aspx. * See http://en.wikipedia.org/wiki/Stdint.h for more on stdint.h. */ typedef uint8_t BYTE; typedef uint32_t DWORD; typedef int32_t LONG; typedef uint16_t WORD; /** * BITMAPFILEHEADER * * The BITMAPFILEHEADER structure contains information about the type, size, * and layout of a file that contains a DIB [device-independent bitmap]. * * Adapted from http://msdn.microsoft.com/en-us/library/dd183374(VS.85).aspx. */ typedef struct { WORD bfType; DWORD bfSize; WORD bfReserved1; WORD bfReserved2; DWORD bfOffBits; } __attribute__((__packed__)) BITMAPFILEHEADER; /** * BITMAPINFOHEADER * * The BITMAPINFOHEADER structure contains information about the * dimensions and color format of a DIB [device-independent bitmap]. * * Adapted from http://msdn.microsoft.com/en-us/library/dd183376(VS.85).aspx. */ typedef struct { DWORD biSize; LONG biWidth; LONG biHeight; WORD biPlanes; WORD biBitCount; DWORD biCompression; DWORD biSizeImage; LONG biXPelsPerMeter; LONG biYPelsPerMeter; DWORD biClrUsed; DWORD biClrImportant; } __attribute__((__packed__)) BITMAPINFOHEADER; /** * RGBTRIPLE * * This structure describes a color consisting of relative intensities of * red, green, and blue. * * Adapted from http://msdn.microsoft.com/en-us/library/aa922590.aspx. */ typedef struct { BYTE rgbtBlue; BYTE rgbtGreen; BYTE rgbtRed; } __attribute__((__packed__)) RGBTRIPLE;
I don't know C, but I still think I might be of some help with your fractional pixel issue. Then again, probably not, but I'll throw out some garbage anyways. So if I understand you correctly, you would like to resize a picture to any size, regardless of the number of pixels. Any image you have will have a definite number of pixels, as a length and width. And so every time you look at the picture, you can start at one end, walk down the row, and then start again on the next column. So this is essentially a loop in a loop. Now suppose you want to go from a 100x100 picture to a 100x66 pixel picture, what we can now do is see that 100/66 = 1.5152... is our ratio, so we make about one and a half steps our original picture for every 1 step on our new picture. Since we're focusing on the picture to be made, a step size of 1 is perfect, we will go from 1 to 66 without ever encountering a fraction on this end. So we make a loop that counts from 0 to 66 and I'll call that our index number. Multiply the index number by the ratio (100/66) and round it to an integer. That integer will tell you exactly where on the original picture you need to sample for the color. So for example, fifth step would result in (5*100/66), and would color the 5th pixel of the picture by referencing the 7th or 8th pixel on the original picture, depending on how you decide to set the program to round numbers. Of course, then all that would really be inside another similar loop that counts the pixels in the other dimension and has its own scaling factor after you've gone from 0 to 66 you'd start at the next row and go again. But I'm assuming you have a euclidean monitor, so x and y axes are linearly independent, shouldn't be a problem there ;P I hope that's actually useful to you and makes sense.
Yeah I'm just completely new to programming, so what I wrote might just be completely childish.
No, that makes a lot of sense to me (though I am not a seasoned programmer either), however there are two things I probably should have pointed out. Firstly, the program scales equally in width and height by the factor "size" that is input, so I don't have to worry about going from a 100x100 image to 100x66, only 100x100 to 66x66. But from that I don't determine the scaling factor, that is what's input. i.e. if I type ./resize 0.5 inimage.bmp outimage.bmp at the command line, and inimage.bmp is 100x100, then outimage.bmp should, by your approach, be 50x50 (either that or what I did, which is try to make the pixels 50% closer together so that when my image is just, say, 3x3 pixels, I can still reduce its size). Second, bitmaps are such that each horizontal line of the image (scanline) must have its bytes in multiples of 4, hence the need for the "padding" bytes of 0x00 that are calculated in the program, so if I resize by, say, 1.66, if I change the number of pixels based on the 0.66 part, how do I then determine the padding? Maybe if I multiply the whole scaling factor by 100 and find the padding of that.... As far as the double loop, my program basically does that with the two for loops with variable m and n for (n is for the number of pixels printed and m is for the scanlines), and the horizontal and vertical dimensions are being evenly changed when I alter the header, bio. Thanks for the the input, though, that may lead me to something :)
Interesting, I definitely don't know anything about how bmps are saved, so that's new to me. Sounds like a fun to play around with though, good luck! Also, after I posted, I was able to recognize the little bit of double loop and was afraid I might have suggested something you already did, hence my second smaller post shortly after it above haha.
Join our real-time social learning platform and learn together with your friends!