在c中复制一个bmp
Copying a bmp in c
背景:
我想将 bmp(未压缩的 24 RGB)图像从一个文件名复制到另一个文件名。我正在使用代码块附带的 TDM-GCC(版本 4.9.2,32 位,SJLJ)的 mingw 编译器。
问题:
程序适用于黑白图像和简单的彩色图像,但不适用于复杂的彩色图像。请查看所附图片。我没有足够的声誉 post 其他图像,所以我尝试 post 最相关的 2 张图像。该程序无法复制 lenna 图像。这种行为的原因是什么?
代码:
#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;
}
输入图像:
输出图像:
我对提供的数据做了一些改动,我很确定发生了什么:
FILE *inptr = fopen(infile, "r");
和
FILE *outptr = fopen(outfile, "w");
以文本模式打开文件。
这是我从 Microsoft 的 C API 中了解到的一种特殊行为,似乎这也适用于 mingw TDM-GCC(我一直难以相信,直到转储数据使我相信我的怀疑是对的)。
微软的CAPI中的文件I/O区分文本模式和二进制模式:
fopen(infile, "rt")
和 fopen(outfile, "wt")
以文本模式打开文件。
fopen(infile, "rb")
和 fopen(outfile, "wb")
以二进制模式打开文件。
fopen(infile, "r")
和 fopen(outfile, "w")
默认为文本模式。
在文本模式下,文件读取将所有 Microsoft line-endings ("\r\n"
) 替换为 Unix line-endings ("\n"
),而写入则相反 ("\n"
变成 "\r\n"
).
如果内容是纯文本,这是合理的,但它可能会破坏二进制内容的输出,只要字节 0x0a
(具有任何含义)出现在数据流中,就会插入一个字节 0x0d
.
为了证明这一点,
- 我下载了你的样本文件(不幸的是以PNG格式上传的)
- 将文件(返回)转换为 24 位 BMP(使用 GIMP)
为每个hex-dump做了一个:
$ hexdump -C IkW6FbN.bmp >IkW6FbN.bmp.txt
$ hexdump -C jnxpTwE.bmp >jnxpTwE.bmp.txt
最后将IkW6FbN.bmp.txt
和jnxpTwE.bmp.txt
载入WinMerge进行比较
如快照所示,输入和输出文件的前 14037 (0x36d5) 字节内容相同。然后,输入文件包含 "accidentally" 三个字节 0a 0a 0a
,而输出文件包含 0d 0a 0d 0a 0d 0a
。因此,相应的原始像素(以及所有后续像素)已损坏。
(您可能已经猜到,0a
是 line-feed 字符 '\n'
的十六进制值,0d
是 carriage-return '\r'
.)
顺便说一句。输出文件可能比输入文件长一点(由于插入了 CR 字节)。这可能会被 BMP 查看器忽略,因为 BMP header 准确说明了原始图像需要多少字节(多余的字节将被忽略)。
正如您可能已经认识到的那样,您应该将 fopen()
调用更改为
FILE *inptr = fopen(infile, "rb");
和
FILE *outptr = fopen(outfile, "wb");
解决您的问题。
顺便说一句。 *x 操作系统上的 C APIs(例如 Linux)没有文本和二进制模式的区别。相反,b
被简单地忽略(这有助于编写可移植代码)。
背景:
我想将 bmp(未压缩的 24 RGB)图像从一个文件名复制到另一个文件名。我正在使用代码块附带的 TDM-GCC(版本 4.9.2,32 位,SJLJ)的 mingw 编译器。
问题:
程序适用于黑白图像和简单的彩色图像,但不适用于复杂的彩色图像。请查看所附图片。我没有足够的声誉 post 其他图像,所以我尝试 post 最相关的 2 张图像。该程序无法复制 lenna 图像。这种行为的原因是什么?
代码:
#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;
}
输入图像:
输出图像:
我对提供的数据做了一些改动,我很确定发生了什么:
FILE *inptr = fopen(infile, "r");
和
FILE *outptr = fopen(outfile, "w");
以文本模式打开文件。
这是我从 Microsoft 的 C API 中了解到的一种特殊行为,似乎这也适用于 mingw TDM-GCC(我一直难以相信,直到转储数据使我相信我的怀疑是对的)。
微软的CAPI中的文件I/O区分文本模式和二进制模式:
fopen(infile, "rt")
和fopen(outfile, "wt")
以文本模式打开文件。fopen(infile, "rb")
和fopen(outfile, "wb")
以二进制模式打开文件。fopen(infile, "r")
和fopen(outfile, "w")
默认为文本模式。
在文本模式下,文件读取将所有 Microsoft line-endings ("\r\n"
) 替换为 Unix line-endings ("\n"
),而写入则相反 ("\n"
变成 "\r\n"
).
如果内容是纯文本,这是合理的,但它可能会破坏二进制内容的输出,只要字节 0x0a
(具有任何含义)出现在数据流中,就会插入一个字节 0x0d
.
为了证明这一点,
- 我下载了你的样本文件(不幸的是以PNG格式上传的)
- 将文件(返回)转换为 24 位 BMP(使用 GIMP)
为每个hex-dump做了一个:
$ hexdump -C IkW6FbN.bmp >IkW6FbN.bmp.txt $ hexdump -C jnxpTwE.bmp >jnxpTwE.bmp.txt
最后将
IkW6FbN.bmp.txt
和jnxpTwE.bmp.txt
载入WinMerge进行比较
如快照所示,输入和输出文件的前 14037 (0x36d5) 字节内容相同。然后,输入文件包含 "accidentally" 三个字节 0a 0a 0a
,而输出文件包含 0d 0a 0d 0a 0d 0a
。因此,相应的原始像素(以及所有后续像素)已损坏。
(您可能已经猜到,0a
是 line-feed 字符 '\n'
的十六进制值,0d
是 carriage-return '\r'
.)
顺便说一句。输出文件可能比输入文件长一点(由于插入了 CR 字节)。这可能会被 BMP 查看器忽略,因为 BMP header 准确说明了原始图像需要多少字节(多余的字节将被忽略)。
正如您可能已经认识到的那样,您应该将 fopen()
调用更改为
FILE *inptr = fopen(infile, "rb");
和
FILE *outptr = fopen(outfile, "wb");
解决您的问题。
顺便说一句。 *x 操作系统上的 C APIs(例如 Linux)没有文本和二进制模式的区别。相反,b
被简单地忽略(这有助于编写可移植代码)。