1

Background:
I want to copy a bmp (uncompressed 24 RGB) image from one file name to another. I am using the mingw compiler from TDM-GCC (version 4.9.2, 32 bit, SJLJ) that comes with codeblocks.

Problem:
Program works for black and white images and simple color images but not complicated color images. Please review attached images. I did not have enough reputation to post the other images so I tried posting the 2 most relevant. The program is unable to copy the lenna image. What is the cause for this behavior?

Code:

 #include <stdio.h>
 #include <stdlib.h>
 #include <stdint.h>
 #pragma pack(1)

/*  The following is to access the DIB information
https://msdn.microsoft.com/en-us/library/cc230309.aspx
https://msdn.microsoft.com/en-us/library/dd183374(v=vs.85).aspx
https://msdn.microsoft.com/en-us/library/dd183376(v=vs.85).aspx */

typedef uint8_t  BYTE;
typedef uint32_t DWORD;
typedef int32_t  LONG;
typedef uint16_t WORD;

typedef struct
{
    WORD bfType;
    DWORD bfSize;
    WORD bfReserved1;
    WORD bfReserved2;
    DWORD bfOffBits;
}BITMAPFILEHEADER;

typedef struct
{
    DWORD biSize;
    LONG biWidth;
    LONG biHeight;
    WORD biPlanes;
    WORD biBitCount;
    DWORD biCompression;
    DWORD biSizeImage;
    LONG biXPelsPerMeter;
    LONG biYPelsPerMeter;
    DWORD biClrUsed;
    DWORD biClrImportant;
}BITMAPINFOHEADER;


typedef struct
{
    BYTE rgbtBlue;
    BYTE rgbtGreen;
    BYTE rgbtRed;
}RGBTRIPLE;

int main(void)
{
    char *infile = "testin.bmp";
    char *outfile = "testout.bmp";

    FILE *inptr = fopen(infile, "r");
    if (inptr == NULL)
    {
        fprintf(stderr, "Could not open %s.\n", infile);
        return 2;
    }

    FILE *outptr = fopen(outfile, "w");
    if (outptr == NULL)
    {
        fclose(inptr);
        fprintf(stderr, "Could not create %s.\n", outfile);
        return 3;
    }

    BITMAPFILEHEADER bf;
    fread(&bf, sizeof(BITMAPFILEHEADER), 1, inptr);

    BITMAPINFOHEADER bi;
    fread(&bi, sizeof(BITMAPINFOHEADER), 1, inptr);

    fwrite(&bf, sizeof(BITMAPFILEHEADER), 1, outptr);

    fwrite(&bi, sizeof(BITMAPINFOHEADER), 1, outptr);

    int padding = (4 - (bi.biWidth * sizeof(RGBTRIPLE)) % 4) % 4;

    int i, j, k, biHeight;

    for(i = 0, biHeight = abs(bi.biHeight); i < biHeight; i++)
    {
        for(j = 0; j < bi.biWidth; j++)
        {
        RGBTRIPLE triple;

        fread(&triple, sizeof(RGBTRIPLE), 1, inptr);

        fwrite(&triple, sizeof(RGBTRIPLE), 1, outptr);
        }
    }

    fseek(inptr, padding, SEEK_CUR);

    for(k = 0; k < padding; k++)
    {
        fputc(0x00, outptr);
    }

fclose(inptr);

fclose(outptr);

return 0;

}

input image:

input image

output image:

output image

Scheff's Cat
  • 19,528
  • 6
  • 28
  • 56
ebmadrio
  • 13
  • 1
  • 4
  • It would be a problem in MSC - about gcc on Windows I'm not sure. However, please, try `fopen(infile, "rb")` and `fopen(outfile, "wb")` and report whether this changes anything. (At best, it fixes the problem, at least it doesn't change anything.) – Scheff's Cat Sep 28 '17 at 11:46
  • There are numerous problems with this code. You only got it working by a coincidence. Is your goal simply to copy the bitmap? or extract information bitmap information? If you just want to copy, there is a much simpler solution. – Barmak Shemirani Oct 03 '17 at 07:08

1 Answers1

1

I fiddled a little bit with the data provided, and I'm quite sure what happened:

FILE *inptr = fopen(infile, "r");

and

FILE *outptr = fopen(outfile, "w");

open the files in text mode.

This is a special behavior I know from Microsoft's C API and it seems that this applies to mingw TDM-GCC as well (which I struggled to believe until the dumped data convinced me that my suspicion was right).

The file I/O in Microsoft's C API distinguishs between text mode and binary mode:

  • fopen(infile, "rt") and fopen(outfile, "wt") open the files in text mode.
  • fopen(infile, "rb") and fopen(outfile, "wb") open the files in binary mode.
  • fopen(infile, "r") and fopen(outfile, "w") default to text mode.

In text mode the file reading replaces all Microsoft line-endings ("\r\n") by Unix line-endings ("\n") as well as the writing does the opposite ("\n" becomes "\r\n").

This is reasonable if the contents is plain text but it probably corrupts output of binary contents where a byte 0x0d is inserted whenever a byte 0x0a (with any meaning) occurs in the data stream.

To prove this,

  1. I downloaded your sample files (unfortunately uploaded in PNG format)
  2. converted the files (back) to 24 bit BMP (using GIMP)
  3. made a hex-dump for each:

    $ hexdump -C IkW6FbN.bmp >IkW6FbN.bmp.txt
    
    $ hexdump -C jnxpTwE.bmp >jnxpTwE.bmp.txt
    
  4. and finally loaded IkW6FbN.bmp.txt and jnxpTwE.bmp.txt into WinMerge for comparison.

Snapshot of WinMerge showing where wrong contents start in output file

As the snapshot illustrates, the input and output file have identical contents for the first 14037 (0x36d5) bytes. Then, the input file contains "accidentally" three bytes 0a 0a 0a where the output file has instead 0d 0a 0d 0a 0d 0a. Thus, the respective original pixels (and all following) are corrupted.

(As you might already guess, 0a is the hexadecimal value of the line-feed character '\n', 0d the one of the carriage-return '\r'.)

Btw. the output file is probably a little bit longer than the input file (due to the inserted CR bytes). This might be ignored by a BMP viewer as the BMP header states exactly how many bytes are needed for the raw image (and the extra bytes are simply ignored).

As you already might've recognized you should change the fopen() calls to

FILE *inptr = fopen(infile, "rb");

and

FILE *outptr = fopen(outfile, "wb");

to fix your issue.

Btw. the C APIs on *x OSes (e.g. Linux) doesn't have such a distinction of text and binary mode. Instead, the b is simply ignored (which is helpful to write portable code).

Further reading: fopen, fopen_s on cppreference.com

Scheff's Cat
  • 19,528
  • 6
  • 28
  • 56