C++:实现BMP图像的旋转操作

BMP是英文Bitmap(位图)的简写,也被称为DIB(与设备无关的位图),是一种独立于显示器的位图数字图像文件格式,它是Windows操作系统中的标准图像文件格式,能够被多种Windows应用程序所支持。

通过本文,我们将学习如何通过编程语言C++对BMP格式存储的图像进行基础的旋转操作。

在上一篇文章:C++:实现BMP图像的读入与复制操作  中,我们介绍了BMP类型图片的存储格式,讲解了如何使用C++对BMP图片进行读入和复制操作,在这篇文章中,我们将进一步深入,学习如何使用C++对BMP图片进行旋转操作。

实际上,通过C++对BMP图像进行旋转操作于对BMP图像进行赋值操作是大同小异的。

首先,我们需要正确读入BMP图像,这里不过多赘述,直接上代码:

#include <iostream>
#include<windows.h>
#include<fstream>
#pragma pack(2)//设置对齐边界为2

using namespace std;

struct RGB_data
{
	BYTE blue;
	BYTE red;
	BYTE green;
};
struct RGB_data2
{
	BYTE blue;
	BYTE red;
	BYTE green;
	BYTE rgbReserved;
};
struct Plus//32位图会额外有一段
{
	DWORD        bV5RedMask;
	DWORD        bV5GreenMask;
	DWORD        bV5BlueMask;
	DWORD        bV5AlphaMask;
	DWORD        bV5CSType;
	CIEXYZTRIPLE bV5Endpoints;
	DWORD        bV5GammaRed;
	DWORD        bV5GammaGreen;
	DWORD        bV5GammaBlue;
	DWORD        bV5Intent;
	DWORD        bV5ProfileData;
	DWORD        bV5ProfileSize;
	DWORD        bV5Reserved;
};


bool is32_flag = false;

string bmp_in_address;
string bmp_out_address;

BITMAPFILEHEADER former_bmp_FILE_Head;
BITMAPINFOHEADER former_bmp_DIB_Head;
RGB_data* former_rgb_data1;//用于24位BMP图像
RGB_data2* former_rgb_data2;//用于32位BMP图像

Plus former_bmp_Plus;
int former_width;//原图宽度
int former_height;//原图高度




bool bmp_read(string s)
{
	//跟随地址以二进制读入bmp文件
	ifstream former_bmp_info(s, ios::in | ios::binary);
	if (!former_bmp_info)//若无法打开对应文件
	{
		cout << "Open File Failed!" << endl;
		former_bmp_info.close();
		return false;
	}
	else
	{
		cout << "Open File Successfully!" << endl << endl;
	}

	former_bmp_info.read((char*)&former_bmp_FILE_Head, sizeof(BITMAPFILEHEADER));//读文件头
	//判断是否正确读入bmp文件
	if (former_bmp_FILE_Head.bfType != 0x4D42)
	{
		cout << "该文件非BMP格式!" << endl;
		return false;
	}
	else
	{
		cout << "该文件为BMP格式!" << endl << endl;
	}

	former_bmp_info.read((char*)&former_bmp_DIB_Head, sizeof(BITMAPINFOHEADER));//读DIB头


	cout << "位图文件头:" << endl;
	cout << "DIB头占用字节数:" << "14 bytes" << endl;
	cout << "位图文件类型: 0x" << hex << former_bmp_FILE_Head.bfType << endl;//16进制
	cout << "位图文件大小: " << dec << former_bmp_FILE_Head.bfSize << " bytes" << endl;
	cout << "偏移字节数: " << former_bmp_FILE_Head.bfOffBits << endl;
	cout << endl;


	cout << "DIB头占用字节数:" << former_bmp_DIB_Head.biSize << " bytes" << endl;
	cout << "位图宽度: " << former_bmp_DIB_Head.biWidth << endl;
	cout << "位图高度: " << former_bmp_DIB_Head.biHeight << endl;
	cout << "位图压缩类型: " << former_bmp_DIB_Head.biCompression << endl;
	cout << "位图像素位数: " << former_bmp_DIB_Head.biBitCount << endl;
	cout << "位图数据总占用字节数: " << former_bmp_DIB_Head.biSizeImage << " bytes" << endl;

	if (former_bmp_DIB_Head.biBitCount == 32)
	{
		is32_flag = true;
		cout << "输入的图片为32位" << endl;
		former_bmp_info.read((char*)&former_bmp_Plus, sizeof(Plus));//32位图像会多一个Plus部分
	}

	former_width = former_bmp_DIB_Head.biWidth;
	former_height = former_bmp_DIB_Head.biHeight;

	//计算扫描一行所需要的字节数
	int former_line_byte = (former_bmp_DIB_Head.biBitCount * former_width / 8 + 3) / 4 * 4;
	int need_zeros = former_line_byte - former_bmp_DIB_Head.biBitCount * former_width / 8;

	if (is32_flag)
	{
		former_rgb_data2 = new  RGB_data2[former_width * former_height];
		for (int i = 0; i < former_height; i++)
		{
			former_bmp_info.read((char*)former_rgb_data2 + i * former_width * former_bmp_DIB_Head.biBitCount / 8, former_bmp_DIB_Head.biBitCount / 8 * former_width);
			former_bmp_info.seekg(need_zeros, ios::cur);//忽略掉补位
		}
	}
	else
	{
		former_rgb_data1 = new  RGB_data[former_width * former_height];
		for (int i = 0; i < former_height; i++)
		{
			former_bmp_info.read((char*)former_rgb_data1 + i * former_width * former_bmp_DIB_Head.biBitCount / 8, former_bmp_DIB_Head.biBitCount / 8 * former_width);
			former_bmp_info.seekg(need_zeros, ios::cur);//忽略掉补位
		}
	}
	former_bmp_info.close();
	return true;
}

int main()
{
	cout << "请输入你要旋转的BMP图像的地址:" << endl;
	cin >> bmp_in_address;
	cout << endl;
	if (!bmp_read(bmp_in_address))return 0;

	cout << endl;
	return 0;
}

分别用24位图和32位图进行检验:

经检验,代码正确,均实现了正确的读入,接下来我们开始实现对bmp图像进行旋转的函数。

首先完成旋转后FILE头和DIB头的信息编辑,旋转后与旋转前的BMP图像的信息头区别仅在于——旋转后的图像宽=旋转前图像的高,旋转后图像的高=旋转前图像的宽。

然后要注意若BMP图像为32位,需要在信息头后额外加上一段(我这边定义为PLUS结构体)

因此还要加上一行代码来判断原图像是否为32位BMP图

接下来就需要考虑旋转操作了,我们这里考虑90°顺时针旋转。

BMP格式编码像素区的存储方式为从左往右,从下往上。(旋转坐标对应示例图如下)

所以可以得到旋转后的像素区:

最后把像素区也写入到待编辑的BMP图像即可,注意补零操作

(注:在写入部分我们预先定义了两种像素格式,一种是RGB_data,只有RGB三个通道,是24位BMP图的像素格式,另一种是RGB_data2,除了RGB三个通道之外还存在一个rgbreserved(保留通道),在进行旋转操作的时候,二者的区别仅在于此)

完整的像素区编码操作:

最后我们来验证一下是否能够顺利对24位图和32位图完成旋转操作:

顺利完成顺时针旋转操作。

最后附上完整代码:

#include <iostream>
#include<windows.h>
#include<fstream>
#pragma pack(2)//设置对齐边界为2

using namespace std;

struct RGB_data
{
	BYTE blue;
	BYTE red;
	BYTE green;
};
struct RGB_data2
{
	BYTE blue;
	BYTE red;
	BYTE green;
	BYTE rgbReserved;
};
struct Plus//32位图会额外有一段
{
	DWORD        bV5RedMask;
	DWORD        bV5GreenMask;
	DWORD        bV5BlueMask;
	DWORD        bV5AlphaMask;
	DWORD        bV5CSType;
	CIEXYZTRIPLE bV5Endpoints;
	DWORD        bV5GammaRed;
	DWORD        bV5GammaGreen;
	DWORD        bV5GammaBlue;
	DWORD        bV5Intent;
	DWORD        bV5ProfileData;
	DWORD        bV5ProfileSize;
	DWORD        bV5Reserved;
};


bool is32_flag = false;

string bmp_in_address;
string bmp_out_address;

BITMAPFILEHEADER former_bmp_FILE_Head;
BITMAPINFOHEADER former_bmp_DIB_Head;
RGB_data* former_rgb_data1;//用于24位BMP图像
RGB_data2* former_rgb_data2;//用于32位BMP图像

Plus former_bmp_Plus;
int former_width;//原图宽度
int former_height;//原图高度




bool bmp_read(string s)
{
	//跟随地址以二进制读入bmp文件
	ifstream former_bmp_info(s, ios::in | ios::binary);
	if (!former_bmp_info)//若无法打开对应文件
	{
		cout << "Open File Failed!" << endl;
		former_bmp_info.close();
		return false;
	}
	else
	{
		cout << "Open File Successfully!" << endl << endl;
	}

	former_bmp_info.read((char*)&former_bmp_FILE_Head, sizeof(BITMAPFILEHEADER));//读文件头
	//判断是否正确读入bmp文件
	if (former_bmp_FILE_Head.bfType != 0x4D42)
	{
		cout << "该文件非BMP格式!" << endl;
		return false;
	}
	else
	{
		cout << "该文件为BMP格式!" << endl << endl;
	}

	former_bmp_info.read((char*)&former_bmp_DIB_Head, sizeof(BITMAPINFOHEADER));//读DIB头


	cout << "位图文件头:" << endl;
	cout << "DIB头占用字节数:" << "14 bytes" << endl;
	cout << "位图文件类型: 0x" << hex << former_bmp_FILE_Head.bfType << endl;//16进制
	cout << "位图文件大小: " << dec << former_bmp_FILE_Head.bfSize << " bytes" << endl;
	cout << "偏移字节数: " << former_bmp_FILE_Head.bfOffBits << endl;
	cout << endl;


	cout << "DIB头占用字节数:" << former_bmp_DIB_Head.biSize << " bytes" << endl;
	cout << "位图宽度: " << former_bmp_DIB_Head.biWidth << endl;
	cout << "位图高度: " << former_bmp_DIB_Head.biHeight << endl;
	cout << "位图压缩类型: " << former_bmp_DIB_Head.biCompression << endl;
	cout << "位图像素位数: " << former_bmp_DIB_Head.biBitCount << endl;
	cout << "位图数据总占用字节数: " << former_bmp_DIB_Head.biSizeImage << " bytes" << endl;

	if (former_bmp_DIB_Head.biBitCount == 32)
	{
		is32_flag = true;
		cout << "输入的图片为32位" << endl;
		former_bmp_info.read((char*)&former_bmp_Plus, sizeof(Plus));//32位图像会多一个Plus部分
	}

	former_width = former_bmp_DIB_Head.biWidth;
	former_height = former_bmp_DIB_Head.biHeight;

	//计算扫描一行所需要的字节数
	int former_line_byte = (former_bmp_DIB_Head.biBitCount * former_width / 8 + 3) / 4 * 4;
	int need_zeros = former_line_byte - former_bmp_DIB_Head.biBitCount * former_width / 8;

	if (is32_flag)
	{
		former_rgb_data2 = new  RGB_data2[former_width * former_height];
		for (int i = 0; i < former_height; i++)
		{
			former_bmp_info.read((char*)former_rgb_data2 + i * former_width * former_bmp_DIB_Head.biBitCount / 8, former_bmp_DIB_Head.biBitCount / 8 * former_width);
			former_bmp_info.seekg(need_zeros, ios::cur);//忽略掉补位
		}
	}
	else
	{
		former_rgb_data1 = new  RGB_data[former_width * former_height];
		for (int i = 0; i < former_height; i++)
		{
			former_bmp_info.read((char*)former_rgb_data1 + i * former_width * former_bmp_DIB_Head.biBitCount / 8, former_bmp_DIB_Head.biBitCount / 8 * former_width);
			former_bmp_info.seekg(need_zeros, ios::cur);//忽略掉补位
		}
	}
	former_bmp_info.close();
	return true;
}

bool bmp_rotate(string s)
{
	BITMAPFILEHEADER rotate_bmp_FILE_Head = former_bmp_FILE_Head;
	BITMAPINFOHEADER rotate_bmp_DIB_Head = former_bmp_DIB_Head;
	ofstream bmp_out(s, ios::out | ios::binary);
	if (!bmp_out)
	{
		cout << "Creat File Failed!" << endl;
		bmp_out.close();
		return false;
	}
	else
	{
		cout << "Creat File Successfully!" << endl;
	}

	rotate_bmp_DIB_Head.biHeight = former_width;
	rotate_bmp_DIB_Head.biWidth = former_height;

	int rotate_width = former_height;
	int rotate_height = former_width;

	bmp_out.write((char*)&rotate_bmp_FILE_Head, sizeof(BITMAPFILEHEADER));
	bmp_out.write((char*)&rotate_bmp_DIB_Head, sizeof(BITMAPINFOHEADER));
	if (is32_flag)bmp_out.write((char*)&former_bmp_Plus, sizeof(Plus));

	int rotate_line_byte = (rotate_bmp_DIB_Head.biBitCount * rotate_bmp_DIB_Head.biWidth / 8 + 3) / 4 * 4;
	int need_zeros = rotate_line_byte - rotate_bmp_DIB_Head.biBitCount * rotate_bmp_DIB_Head.biWidth / 8;
	string temp_zero = "00000";//用0补齐

	if (is32_flag)
	{
		RGB_data2* rotate_rgb_data = new RGB_data2[former_width * former_height];
		for (int i = 0; i < rotate_height; i++)//翻转后的高
		{
			for (int j = 0; j < rotate_width; j++)//翻转后的宽
			{
				//做九十度顺时针旋转 w,h 原 [i,j] 变为[j,h-i]  
				//原[i,j]内存 former_rgb_data + i * former_height + j 变为 rotate_rgb_data + j * former_height + h-i
				//现[i,j] 对应原来 [h-j,i] rotate_rgb_data + i * former_height + j 对应 former_rgb_data + (h-j) * former_width + i
				//warning:BMP格式编码像素区是从左下往右上,下方的反而先读入,所以顺逆会反一个方向
				//现[i,j] 对应原来 [j,w-i] rotate_rgb_data + i * former_height + j 对应 former_rgb_data + j * former_width +w-i-1
				*(rotate_rgb_data + i * rotate_width + j) = *(former_rgb_data2 + j * former_width + former_width - 1 - i);
			}
		}

		//写入bmp文件
		for (int i = 0; i < rotate_height; i++) {
			bmp_out.write((char*)rotate_rgb_data + i * rotate_width * rotate_bmp_DIB_Head.biBitCount / 8, rotate_bmp_DIB_Head.biBitCount / 8 * rotate_width);
			bmp_out.write((char*)&temp_zero, need_zeros);
		}
		delete[] rotate_rgb_data;
	}
	else
	{
		RGB_data* rotate_rgb_data = new RGB_data[former_width * former_height];
		for (int i = 0; i < rotate_height; i++)//翻转后的高
		{
			for (int j = 0; j < rotate_width; j++)//翻转后的宽
			{
				*(rotate_rgb_data + i * rotate_width + j) = *(former_rgb_data1 + j * former_width + former_width - 1 - i);
			}
		}

		//写入bmp文件
		for (int i = 0; i < rotate_height; i++) {
			bmp_out.write((char*)rotate_rgb_data + i * rotate_width * rotate_bmp_DIB_Head.biBitCount / 8, rotate_bmp_DIB_Head.biBitCount / 8 * rotate_width);
			bmp_out.write((char*)&temp_zero, need_zeros);
		}
		delete[] rotate_rgb_data;
	}
	bmp_out.close();
	return true;
}

int main()
{
	cout << "请输入你要旋转的BMP图像的地址:" << endl;
	cin >> bmp_in_address;
	cout << endl;
	if (!bmp_read(bmp_in_address))return 0;

	cout << endl;

	cout << "请输入你要输出的BMP图像的地址:" << endl;
	cin >> bmp_out_address;
	if (!bmp_rotate(bmp_out_address))cout << "旋转文件失败" << endl;
	else cout << "旋转文件成功" << endl;


	return 0;
}

  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值