在c中复制bmp

背景:
我想将bmp(未压缩的24 RGB)图像从一个文件名复制到另一个文件名。 我正在使用来自TDM-GCC(版本4.9.2,32位,SJLJ)的mingw编译器,它带有代码块。

问题:
程序适用于黑白图像和简单的彩色图像,但不适用于复杂的彩色图像。 请查看附件图片。 我没有足够的声誉来发布其他图片,所以我尝试发布最相关的2。 该程序无法复制lenna图像。 这种行为的原因是什么?

码:

#include  #include  #include  #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; } 

输入图像:

输入图像

输出图像:

输出图像

我提供了一些提供的数据,我很确定发生了什么:

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

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

以文本模式打开文件。

这是我从微软的C API中所知道的一种特殊行为,它似乎也适用于mingw TDM-GCC(我倾向于相信,直到倾销的数据使我确信我的怀疑是正确的)。

Microsoft C API中的文件I / O区分文本模式和二进制模式:

  • fopen(infile, "rt")fopen(outfile, "wt")以文本模式打开文件。
  • fopen(infile, "rb")fopen(outfile, "wb")以二进制模式打开文件。
  • fopen(infile, "r")fopen(outfile, "w")默认为文本模式。

在文本模式下,文件读取用Unix行结尾( "\n" )替换所有Microsoft行结尾( "\r\n" )以及写入相反( "\n"变为"\r\n" )。

如果内容是纯文本,这是合理的,但它可能破坏二进制内容的输出,只要在数据流中出现字节0x0a (具有任何含义),就会插入字节0x0d

为了certificate这一点,

  1. 我下载了您的示例文件(不幸的是以PNG格式上传)
  2. 将文件(返回)转换为24位BMP(使用GIMP)
  3. 为每个做了一个hex-dump:

     $ hexdump -C IkW6FbN.bmp >IkW6FbN.bmp.txt $ hexdump -C jnxpTwE.bmp >jnxpTwE.bmp.txt 
  4. 最后将IkW6FbN.bmp.txtjnxpTwE.bmp.txt加载到WinMerge中进行比较。

WinMerge的快照显示输出文件中错误内容的开始位置

如快照所示,输入和输出文件具有与前14037(0x36d5)字节相同的内容。 然后,输入文件包含“意外”三个字节0a 0a 0a ,其中输出文件具有0d 0a 0d 0a 0d 0a 。 因此,各个原始像素(以及所有后续像素)被破坏。

(正如您可能已经猜到的那样, 0a是换行符'\n'的hex值, 0d是回车符'\r' 。)

顺便说一句。 输出文件可能比输入文件稍长一些(由于插入的CR字节)。 BMP查看器可能会忽略这一点,因为BMP标头确切地说明了原始图像需要多少字节(并且简单地忽略了额外的字节)。

正如您已经认识到的那样,您应该将fopen()调用更改为

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

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

解决你的问题。

顺便说一句。 * x OS(例如Linux)上的C API没有文本和二进制模式的区别。 相反, b被简单地忽略(这有助于编写可移植代码)。

进一步阅读: fopen,fopen_s在cppreference.com上