作业8-24位bmp图片顺时针旋转90度

昨天上午一次成功写完过于激动,就忘了来写笔记orz

做作业前很有帮助的几篇文章:

百度文库-bmp24位位图格式总结

一个特别厉害的同学知乎写的代码详解(没有用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是新图片

原文地址:https://www.cnblogs.com/fangziyuan/p/12659747.html