作业8-24位bmp图片顺时针旋转90度
昨天上午一次成功写完过于激动,就忘了来写笔记orz
做作业前很有帮助的几篇文章:
一个特别厉害的同学知乎写的代码详解(没有用windows.h)
有一个特别厉害的同学四十几行就写完的代码(用了windows.h)
然后我的代码就是综合了上面的两者qwq 利用了前者的补足字节函数(稍微修改了一下)和旋转公式(文章里有很形象的图示说明),参考了后者如何应有windows.h,C++二进制文件的读写操作(第一篇用的是C的fopen来读),以及如何实现命令行调用exe文件!(神奇的argc和argv[]原来是这么用的!)
1 //只能读24位的bmp图片 2 #include<iostream> 3 #include<cstdio> 4 #include<windows.h> 5 #include<fstream> 6 #include<cstring> 7 using namespace std; 8 9 int addByte(BITMAPINFOHEADER & info){ //计算每一行要补多少字节,1个字节是8bit,字节数必须是4的整倍数 /32的单位是4byte 10 int DataSizePerline = (info.biWidth*info.biBitCount+31)/32*4; //+31是为了除完以后取整 11 return DataSizePerline-info.biWidth*info.biBitCount/8; 12 } 13 14 int main(int argc,char*argv[]) 15 { 16 ifstream fin(argv[1], ios::binary|ios::in);//argv1是原文件 17 if (!fin){ 18 cout<<"Source file open error."<<endl; 19 return 0; 20 } 21 ofstream fout(argv[2], ios::binary|ios::out|ios::trunc); 22 if (!fout){ 23 cout<<"New file open error. "; 24 return 0; 25 } 26 27 /*处理文件头和位图信息头*/ 28 BITMAPFILEHEADER fileheader;//文件头 29 BITMAPINFOHEADER infoheader;//信息头 30 fin.read((char*)&fileheader, sizeof(BITMAPFILEHEADER)); 31 fin.read((char*)&infoheader, sizeof(BITMAPINFOHEADER)); 32 fout.write((char*)&fileheader, sizeof(fileheader));//文件头没变化 33 int addb = addByte(infoheader); //计算原图片补充字节 34 int w = infoheader.biWidth; 35 int h = infoheader.biHeight; //原图片的宽和高 36 37 int tmp; 38 tmp = infoheader.biWidth; 39 infoheader.biWidth = infoheader.biHeight; 40 infoheader.biHeight = tmp;//交换宽高 41 42 tmp = infoheader.biXPelsPerMeter; 43 infoheader.biXPelsPerMeter = infoheader.biYPelsPerMeter; 44 infoheader.biYPelsPerMeter = tmp;//交换xy分辨率 45 46 int newaddb = addByte(infoheader); //计算新图片补充字节 47 fout.write((char*)&infoheader, sizeof(infoheader));//写入描述信息块 48 49 /*读入原图片像素至imgdata*/ 50 int Size = w*h; //大小 51 RGBTRIPLE *imgdata = new RGBTRIPLE[Size]; //读入原图片像素信息 52 53 for(int i = 0; i < h; i++){ 54 fin.read((char*)imgdata+i*w*3,w*3); //读入一行 55 fin.seekg(addb, ios::cur); //跳过补齐的字节 56 } 57 58 /*旋转,将旋转完像素存入target*/ 59 RGBTRIPLE * target = new RGBTRIPLE[Size]; 60 int newH = w, newW = h; 61 for (int i=0; i<newH; i++){ 62 for (int j=0; j<newW; j++){ 63 *(target+i*newW+j) = *(imgdata+j*w+newH-i-1); 64 } 65 } 66 67 /*将target写入文件*/ 68 char * ab = new char[newaddb]; 69 memset(ab, 0, sizeof(ab));//用来补入0 70 for (int i=0; i<newH; i++){ 71 fout.write((char*)target+i*newW*3, 3*newW); 72 fout.write(ab, newaddb); 73 } 74 delete[]imgdata; 75 delete[]target; 76 delete[]ab; 77 fin.close(); 78 fout.close(); 79 cout<<"Rotate successfully!"<<endl; 80 return 0; 81 }
其实注释很详尽了。其实只要先了解一下bmp文件的格式,就发现它只包含三部分:文件头、图片信息头、图片像素信息。
我们要做的只有三件事,把文件头原封不动地写到新文件里、把图片信息头里的有关宽和高的信息交换一下直接写到新文件里,然后读入图片像素信息(注意跳过用来补足的字节),旋转之后写入新文件(要补足字节)。
关于补足字节再说明一下吧,公式看起来不是很容易理解,我也是在网上的犄角旮旯找到了一个很好的说明:
DataSizePerline = (info.biWidth*info.biBitCount+31)/32*4;
这一行info.biWidth*info.biBitCount是计算图片一行要占多少位(biWidth的单位是RGPTRIPLE)每一行的字节数需要是4的整数倍,所以最小单位是4*8是32bit,那么一行如果不能整除32,最多是差31bit,/是向下取整,所以这样算出来的是这一行需要有几个【四字节】,但单位是字节,所以再乘个4,这样算出来的就是这一行需要有多少字节,并且这样算出来的肯定是4的整数倍。那么要补充的字节就是DataSizePerline-info.biWidth*info.biBitCount/8
还有一个问题就是怎么通过命令行调用这个exe文件,就是通过main函数的参数argc和argv。具体调用方法就是cmd进入命令行然后【进入到exe文件所在的文件夹】,然后rotatebmp 1.bmp myout.bmp,其中rotatebmp是exe文件的文件名,1.bmp是源文件,myout.bmp是新图片