作业要求: 将24位真彩色.bmp文件顺时针旋转90度。
可执行程序名:rotatebmp
用法:rotatebmp src.bmp dest.bmp (文件名可变)
生成的 dest.bmp 是从 src.bmp 顺时针旋转 90 度得到的。
后文中:
typedef unsigned char BYTE;
typedef unsigned short WORD;
typedef unsigned int DWORD;
typedef long LONG;
结构边界对齐
C++编译有“结构边界对齐”的概念:
struct A {
int a;
char c;
};
那么 sizeof(A)未必是 5,很有可能是 8,因为 C++编译器为了提高代码执行效 率,往往在生成结构变量的时候,自动将其大小调整为 4 的整数倍,或 8 的整数 倍。程序员可以在编译选项中指定对齐的方式是 1 字节,2 字节,4 字节或 8 字 节(即每个结构变量的大小一定是 1 的倍数,2 的倍数,4 的倍数,8 的倍数),也 可以在程序开头直接用:#pragma pack(2)
指定对齐边界是 2 字节,或 1 字节,4 字节…
BMP图像文件格式
BMP 是 bitmap 的缩写形式,bitmap 顾名思义,就是位图也即 Windows 位图。 它一般由 4 部分组成:文件头信息块、图像描述信息块、颜色表(在真彩色模式 无颜色表)和图像数据区组成。在系统中以 BMP 为扩展名保存。
·文件头: 主要包含文件的大小、文件类型、图像数据偏离文件头的长度等信息;
·位图信息头: 包含图象的尺寸信息、图像用几个比特数值来表示一个像素、图像是否压缩、图像所用的颜色数等信息。
·颜色信息: 包含图像所用到的颜色表,显示图像时需用到这个颜色表来生成调色板,但如果图像为真彩色,既图像的每个像素用 24 个比特来表示,文件中就没有这一块信息,也就不需要操作调色板。
·文件中的数据块: 表示图像的相应的像素值,需要注意的是:图像的像素值在文件中的存放顺序为从左到右,从下到上,也就是说,在 BMP 文件中首先存放的是图像的最后一行像素,最后才存储图像的第一行像素,但对与同一行的像素,则是按照先左边后右边的的顺序存储的;
·结构边界对齐: 文件存储图像的每一行像素值时,如果存储该行像素值所占的字节数为 4 的倍数,则正常存储,否则,需要在后端补 0,凑足 4 的倍数。
文件头信息块
位(Bit):“位”或“比特”,是计算机运算的基础;
字节(Byte):“字节”是通过网络传输信息(或在硬盘或内存中存储信 息)的单位。字节是计算机信息技术用于计量存储容量和传输容量的一种计量单位,1个字节等于8位二进制,一个一般int占用4个字节。
在ASCII码中,一个英文字母(不分大小写)占一个字节的空间,一个中文汉字占两个字节的空间。
符号:英文标点占一个字节,中文标点占两个字节。举例:英文句号“.”占1个字节的大小,中文句号“。”占2个字节的大小
一个二进制数字序列,在计算机中作为一个数字单元,一般为8位二进制数,如一个ASCII码就是一个字节.
字(WORD): 2个字节组成一个字。-16bit- short是一个WORD
双字(DWORD): 4个字节组成一个双字,-32bit- int是一个DWORD。 -64bit- long是 2个DWORD。
typedef struct tagBITMAPFILEHEADER {
WORD bfType; // 位图文件的类型,必须为“BM”
DWORD bfSize; // 位图文件的大小,以字节为单位
WORD bfReserved1; // 位图文件保留字,必须为 0
WORD bfReserved2; // 位图文件保留字,必须为 0
DWORD bfOffBits; // 位图数据的起始位置,以相对于位图文件头的偏移量表示,以字节为单位,即前三个部分的长度之和
} BITMAPFILEHEADER;//该结构占据 14 个字节。
位图信息头
BMP 位图信息头数据用于说明位图的尺寸等信息。其结构如下:
typedef struct tagBITMAPINFOHEADER{
DWORD biSize; // 本结构所占用字节数
DWORD biWidth; // 位图的宽度,以像素为单位
DWORD biHeight; // 位图的高度,以像素为单位
WORD biPlanes; // 目标设备的平面数不清,必须为 1 ,不用考虑
WORD biBitCount// 每个像素所需的位数,必须是 1(黑白), 4(16 色),8(256 色)或 24(真彩色)之一
DWORD biCompression; // 位图压缩类型,必须是 0(BI_RGB 不压缩),1(BI_RLE8 压缩类型)或 2(BI_RLE4 压缩类型)之一
DWORD biSizeImage; // 位图的大小,以字节为单位
DWORD biXPelsPerMeter; // 位图水平分辨率,每米像素数
DWORD biYPelsPerMeter; // 位图垂直分辨率,每米像素数
DWORD biClrUsed;// 位图实际使用的颜色表中的颜色数 如果该值为零,则用到的颜色数为2^(biBitCount)
DWORD biClrImportant;// 位图显示过程中重要的颜色数 如果该值为零,则认为所有的颜色都是重要的。
} BITMAPINFOHEADER;//该结构占据 40 个字节。
颜色表
这里是对那些需要调色板的位图文件而言的。有些位图,如真彩色图,前面已经讲过,是不需要调色板的,BITMAPINFOHEADER后直接是位图数据。
调色板实际上是一个数组,共有biClrUsed个元素(如果该值为零,则有2biBitCount个元素)。数组中每个元素的类型是一个RGBQUAD结构,占4个字节,其定义如下:
typedef struct tagRGBQUAD {
BYTE rgbBlue; //该颜色的蓝色分量 0-255
BYTE rgbGreen; //该颜色的绿色分量 0-255
BYTE rgbRed; //该颜色的红色分量 0-255
BYTE rgbReserved; //保留值 0
} RGBQUAD;
对于用到调色板的位图,图象数据就是该象素颜在调色板中的索引值。对于真彩色图,图象数据就是实际的R、G、B值。
RGBQUAD 结构中定义的颜色值中,红色、绿色和蓝色的排列顺序与一般真彩色图像文件的颜色数据排列顺序恰好相反,既:若某个位图中的一个像素点的颜色的描述为“00,00,ff,00”,则 表示该点为红色,而不是蓝色。
位图信息头和颜色表组成位图 信息,BITMAPINFO 结构定义如下:
typedef struct tagBITMAPINFO {
BITMAPINFOHEADER bmiHeader; // 位图信息头
RGBQUAD bmiColors[1]; // 颜色表
} BITMAPINFO;
位图数据
位图数据记录了位图的每一个像素值或该对应像素的颜色表的索引值,图像记录顺序是在扫描行内是从左 到右,扫描行之间是从下到上。
当 biBitCount=1 时,8 个像素占 1 个字节;
当 biBitCount=4 时,2 个像素占 1 个字节;
当 biBitCount=8 时,1 个像素占 1 个字节;
当 biBitCount=24 时,1 个像素占 3 个字节,此时图像为真彩色图像。
当图像不是为真彩色时,图像文件中包含颜色表,位图的数据表示对应像素点在颜色表中相应的索引值。
当为真彩色时,每一个像素用三个字节表示图像相应像素点彩色值,每个字节分别对应 R、G、 B 分量的值,这时候图像文件中没有颜色表。
Windows 规定图像文件中一个扫描行所占的字节数必须是 4 的倍数(即以字为单位),不足的以 0 填充,图像文件中一个扫描行所占的字节数计算方法:
DataSizePerLine=(biWidth*biBitCount+31)/8;
DataSizePerLine=DataSizePerLine/4*4;
位图数据的大小按下式计算(不压缩情况下):
DataSize= DataSizePerLine*biHeight;
上述是 BMP 文件格式的说明,搞清楚了以上的结构,就可以正确的操作图像文件,对它进行读或写操作了。
代码
下面是完整代码:
#include<stdio.h>
#include<iostream>
#include<math.h>
#include<stdlib.h>
using namespace std;
typedef unsigned char BYTE;
typedef unsigned short WORD;
typedef unsigned int DWORD;
typedef long LONG;
//位图文件头定义;
//其中不包含文件类型信息(由于结构体的内存结构决定,要是加了的话将不能正确读取文件信息)4的倍数
typedef struct tagBITMAPFILEHEADER{
//WORD bfType;//文件类型,必须是0x424D,即字符“BM”
DWORD bfSize;//文件大小
WORD bfReserved1;//保留字
WORD bfReserved2;//保留字
DWORD bfOffBits;//从文件头到实际位图数据的偏移字节数
}BITMAPFILEHEADER;
typedef struct tagBITMAPINFOHEADER{
DWORD biSize;//信息头大小
DWORD biWidth;//图像宽度
DWORD biHeight;//图像高度
WORD biPlanes;//位平面数,必须为1
WORD biBitCount;//每像素位数
DWORD biCompression; //压缩类型
DWORD biSizeImage; //压缩图像大小字节数
DWORD biXPelsPerMeter; //水平分辨率
DWORD biYPelsPerMeter; //垂直分辨率
DWORD biClrUsed; //位图实际用到的色彩数
DWORD biClrImportant; //本位图中重要的色彩数
}BITMAPINFOHEADER; //位图信息头定义
typedef struct tagRGBQUAD{
BYTE rgbBlue; //该颜色的蓝色分量
BYTE rgbGreen; //该颜色的绿色分量
BYTE rgbRed; //该颜色的红色分量
BYTE rgbReserved; //保留值
}RGBQUAD;//调色板定义
//像素信息
typedef struct tagIMAGEDATA
{
BYTE red;
BYTE green;
BYTE blue;
}IMAGEDATA;
BITMAPFILEHEADER head;
BITMAPINFOHEADER info;
void SizeCheck(){
printf("BYTE:%lu\n",sizeof(BYTE));
printf("WORD:%lu\n",sizeof(WORD));
printf("DWORD:%lu\n",sizeof(DWORD));
printf("LONG:%lu\n",sizeof(LONG));
printf("BITMAPFILEHEADER:%lu\n",sizeof(BITMAPFILEHEADER));
printf("BITMAPINFOHEADER:%lu\n",sizeof(BITMAPINFOHEADER));
}
void showBmpHead(BITMAPFILEHEADER BmpHead){
printf("---BmpHead---\n");
printf("Type:BM\n");
printf("FileSize(perByte):%d\n",BmpHead.bfSize);
printf("Reverse_1:%d\n",BmpHead.bfReserved1);
printf("Reverse_2:%d\n",BmpHead.bfReserved2);
printf("OffBits(perByte):%d\n\n",BmpHead.bfOffBits);
}
void showBmpInfoHead(BITMAPINFOHEADER BmpInfo){
printf("---BmpInfo---\n");
printf("InfoSize:%d\n",BmpInfo.biSize);
printf("Width(perPixel):%d\n",BmpInfo.biWidth);
printf("Height(perPixel):%d\n",BmpInfo.biHeight);
printf("Planes:%d\n",BmpInfo.biPlanes);
printf("BitCount:%d\n",BmpInfo.biBitCount);
printf("Compression:%d\n",BmpInfo.biCompression);
printf("ImageSize(perByte):%d\n",BmpInfo.biSizeImage);
printf("XpelsPerMeter(perPixel):%d\n",BmpInfo.biXPelsPerMeter);
printf("YpelsPerMeter(perPixel):%d\n",BmpInfo.biYPelsPerMeter);
printf("ColorUsed:%d\n",BmpInfo.biClrUsed);
printf("ColorImportant:%d\n\n",BmpInfo.biClrImportant);
}
void GetHead(BITMAPFILEHEADER& Head,FILE* input){
fread(&Head.bfSize,sizeof(DWORD),1,input);
fread(&Head.bfReserved1,sizeof(WORD),1,input);
fread(&Head.bfReserved2,sizeof(WORD),1,input);
fread(&Head.bfOffBits,sizeof(DWORD),1,input);
}
void OutHead(BITMAPFILEHEADER& Head,FILE* output){
WORD bfType=0x4d42;
fwrite(&bfType,sizeof(WORD),1,output);
fwrite(&Head.bfSize,sizeof(DWORD),1,output);
fwrite(&Head.bfReserved1,sizeof(WORD),1,output);
fwrite(&Head.bfReserved2,sizeof(WORD),1,output);
fwrite(&Head.bfOffBits,sizeof(DWORD),1,output);
}
void GetInfo(BITMAPINFOHEADER& Info,FILE* input){
fread(&Info.biSize,sizeof(DWORD),1,input);
fread(&Info.biWidth,sizeof(DWORD),1,input);
fread(&Info.biHeight,sizeof(DWORD),1,input);
fread(&Info.biPlanes,sizeof(WORD),1,input);
fread(&Info.biBitCount,sizeof(WORD),1,input);
fread(&Info.biCompression,sizeof(DWORD),1,input);
fread(&Info.biSizeImage,sizeof(DWORD),1,input);
fread(&Info.biXPelsPerMeter,sizeof(DWORD),1,input);
fread(&Info.biYPelsPerMeter,sizeof(DWORD),1,input);
fread(&Info.biClrUsed,sizeof(DWORD),1,input);
fread(&Info.biClrImportant,sizeof(DWORD),1,input);
}
void OutInfo(BITMAPINFOHEADER& Info,FILE* output){
fwrite(&Info.biSize,sizeof(DWORD),1,output);
fwrite(&Info.biWidth,sizeof(DWORD),1,output);
fwrite(&Info.biHeight,sizeof(DWORD),1,output);
fwrite(&Info.biPlanes,sizeof(WORD),1,output);
fwrite(&Info.biBitCount,sizeof(WORD),1,output);
fwrite(&Info.biCompression,sizeof(DWORD),1,output);
fwrite(&Info.biSizeImage,sizeof(DWORD),1,output);
fwrite(&Info.biXPelsPerMeter,sizeof(DWORD),1,output);
fwrite(&Info.biYPelsPerMeter,sizeof(DWORD),1,output);
fwrite(&Info.biClrUsed,sizeof(DWORD),1,output);
fwrite(&Info.biClrImportant,sizeof(DWORD),1,output);
}
int main(int argc,char* argv[]){
//SizeCheck();
//char opFile[30];
//char aimFile[30];
//scanf("%s %s",opFile,aimFile);
FILE* input;
FILE* output;
//input=fopen(opFile,"rb");
input=fopen(argv[1],"rb");
if (!input){
printf("Wrong File!\n");
return 0;
}
WORD bfType;
fread(&bfType,sizeof(WORD),1,input);
//printf("%x\n",bfType);
if (bfType!=0x4d42){
printf("Wrong Type!\n");
return 0;
}
BITMAPFILEHEADER Head;
BITMAPINFOHEADER Info;
//fread(&Head,sizeof(BITMAPFILEHEADER),1,input);
//fread(&Info,sizeof(BITMAPFILEHEADER),1,input);
GetHead(Head,input);
GetInfo(Info,input);
//showBmpHead(Head);
//showBmpInfoHead(Info);
int PerLine=(Info.biWidth*3+3)/4*4;
Info.biSizeImage=PerLine*Info.biHeight;
Head.bfSize=Info.biSizeImage+Head.bfOffBits;
BYTE* MAP;
MAP=new BYTE[Info.biSizeImage+10];
for (int i=0;i<Info.biSizeImage;i++){
fread(&MAP[i],sizeof(BYTE),1,input);
}
IMAGEDATA* Image;
Image=new IMAGEDATA[Info.biHeight*Info.biWidth+10];
for (int i=0;i<Info.biHeight;i++){
for (int j=0;j<Info.biWidth;j++){
Image[i*Info.biWidth+j].red=MAP[i*PerLine+j*3];
Image[i*Info.biWidth+j].green=MAP[i*PerLine+j*3+1];
Image[i*Info.biWidth+j].blue=MAP[i*PerLine+j*3+2];
}
}
IMAGEDATA* newImage;
newImage=new IMAGEDATA[Info.biHeight*Info.biWidth+10];
for (int i=0;i<Info.biWidth;i++){
for (int j=0;j<Info.biHeight;j++){
newImage[i*Info.biHeight+j]=
Image[(Info.biWidth-1-i)+j*Info.biWidth];
}
}
int newPerLine=(Info.biHeight*3+3)/4*4;
int newSize=newPerLine*Info.biWidth;
BYTE* newMAP;
newMAP=new BYTE[newSize+10];
for (int i=0;i<Info.biWidth;i++){
for (int j=0;j<Info.biHeight;j++){
newMAP[i*newPerLine+j*3]=newImage[i*Info.biHeight+j].red;
newMAP[i*newPerLine+j*3+1]=newImage[i*Info.biHeight+j].green;
newMAP[i*newPerLine+j*3+2]=newImage[i*Info.biHeight+j].blue;
}
}
Info.biSizeImage=newSize;
Head.bfSize=Info.biSizeImage+Head.bfOffBits;
swap(Info.biHeight,Info.biWidth);
//output=fopen(aimFile,"wb");
output=fopen(argv[2],"wb");
OutHead(Head,output);
OutInfo(Info,output);
for (int i=0;i<Info.biSizeImage;i++){
fwrite(&newMAP[i],sizeof(BYTE),1,output);
}
return 0;
}