Resizing a BMP image (making it smaller)
I assume you want to shrink the image by skipping rows and columns using the variables w
, h
, and diff
. For instance, if we set the
scaling factor f
to 0.5, diff
is assigned to 1, and every other
rows/columns will be skipped to scale the image by 0.5x.
Then there are two crutial issues in the loop with i and j:
- You are resetting
w
inif(w==diff){ w=0; }
just afterw++;
. Thenw
keeps being 0 and no columns are skipped. - You are putting the
if(h==0){
condition in outer block. Then the pixels are not read while h==0. In order to shrink the image, you need to keep on reading every pixels regardless of the condition, and write the pixel if the conditions meet.
Then the loop will be improved as:
// iterate over infile's scanlines
for (int i = 0, biHeight = abs(bi.biHeight); i < biHeight; i++) {
// 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);
if (w == 0 && h == 0) {
// write RGB triple to outfile
fwrite(&triple, sizeof(RGBTRIPLE), 1, outptr);
}
w++;
if (w > diff){
w = 0;
}
}
// skip over padding, if any
fseek(inptr, padding, SEEK_CUR);
// padding to the output file, if any
if (h == 0) {
for (int k = 0; k < padding_resize; k++) {
fputc(0x00, outptr);
}
}
h++;
if (h > diff){
h = 0;
}
}
It will work under limited conditions: f=0.5
and the image width and
height are even numbers. But in general conditions it still does not work well.
For instance, if we set f
to 0.4,
bi_resize.biWidth
and bi_resize.biHeight
will be calculated with
multiplying original size by 0.4, while diff
will be calculated to 1.
Then they cause conflicts between the header information and the
actual pixels.
Here is another hint to solve the problems:
- The common approach to manipulate image is to store the entire pixels into the memory at first. Then you can random-access any pixels in the following process and the code will be more straightforward. Basic idea is to iterate over the destination coordinates and retroject back to the coordinates of the source image to pick the pixel values.
- Your posted input image is too small to identify the problem, because thin lines and small dots are easily collapsed just with filtering even if the algorithm is appropriate. Better to use larger images to evaluate.
Here is my rewrite based on your code:
#include <stdio.h>
#include <stdlib.h>
#include "bmp.h"
int main(int argc, char *argv[])
{
// ensure proper usage
if (argc != 4) {
fprintf(stderr, "Usage: resize n infile outfile\n");
return 1;
}
// read the scaling factor
float f = atof(argv[1]);
if (f <= 0 || f > 1) {
fprintf(stderr, "f, the resize factor, must be between 0 and 1.\n");
return 1;
}
char *infile = argv[2];
char *outfile = argv[3];
// open input file
FILE *inptr = fopen(infile, "r");
if (inptr == NULL) {
fprintf(stderr, "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;
fread(&bf, sizeof(BITMAPFILEHEADER), 1, inptr);
// read infile's BITMAPINFOHEADER
BITMAPINFOHEADER bi;
fread(&bi, sizeof(BITMAPINFOHEADER), 1, inptr);
// 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;
}
BITMAPFILEHEADER bf_resize = bf;
BITMAPINFOHEADER bi_resize = bi;
bi_resize.biWidth = bi.biWidth * f;
bi_resize.biHeight = bi.biHeight * f;
int padding = bi.biWidth % 4; // you can simplify the calculation
int padding_resize = bi_resize.biWidth % 4;
bi_resize.biSizeImage = (bi_resize.biWidth * sizeof(RGBTRIPLE) + padding_resize) * bi_resize.biHeight;
bf_resize.bfSize = bi_resize.biSizeImage + sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
// allocate mamory for the rgb triplets of the original (input) image
RGBTRIPLE *pix = malloc(sizeof(RGBTRIPLE) * bi.biWidth * bi.biHeight);
if (pix == NULL) {
fprintf(stderr, "malloc failed.\n");
return 5;
}
// temporary storage
RGBTRIPLE triple;
// read the entire pixels of the original image and store into the memory
for (int i = 0; i < bi.biHeight; i++) {
for (int j = 0; j < bi.biWidth; j++) {
fread(&triple, sizeof(RGBTRIPLE), 1, inptr);
pix[i * bi.biWidth + j] = triple;
}
// skip over padding, if any
fseek(inptr, padding, SEEK_CUR);
}
// write outfile's header
fwrite(&bf_resize, sizeof(BITMAPFILEHEADER), 1, outptr);
fwrite(&bi_resize, sizeof(BITMAPINFOHEADER), 1, outptr);
// write the pixels of destination (resized) image
for (int i = 0; i < bi_resize.biHeight; i++) {
for (int j = 0; j < bi_resize.biWidth; j++) {
// calculate the corresponding coorinates in the original image
int m = (i / f + 0.5); // +0.5 for rounding
if (m > bi.biHeight - 1) { // limit the value
m = bi.biHeight - 1;
}
int n = (j / f + 0.5);
if (n > bi.biWidth - 1) {
n = bi.biWidth - 1;
}
// pick the pixel value at the coordinate
triple = pix[m * bi.biWidth + n];
// write RGB triplet to outfile
fwrite(&triple, sizeof(RGBTRIPLE), 1, outptr);
}
// padding for the output image, if any
for (int j = 0; j < padding_resize; j++) {
fputc(0x00, outptr);
}
}
free(pix);
fclose(inptr);
fclose(outptr);
return 0;
}
Input image: Output image with f=0.4: