本博文内容是博文基于MFC框架的图像缩放算法示例的一部分(返回目录)。
上一篇博文02. 基于MFC读取并显示一幅BMP图像介绍了介绍有关BMP格式图像的相关内容,编写C++函数读取并在屏幕上显示一个BMP图像。为简单起见,该篇博文并没有介绍C++类的概念,而是将相关代码直接写在CView类中,这样使得核心算法代码和GUI界面有关代码混杂在一起,不方便管理。为了降低耦合度,本博文引入C++类的概念,将所用BMP处理相关的代码,包括读取BMP,分离R,G,B通道和绘制BMP等用一个CBmp类进行封装,而在CView的OnDraw()内调用CBmp类对象的方法即可。
新建一个名为MFC_SD_03的工程,依次点击菜单项【项目】->【添加类】,会出现下面的添加新的界面,如图所示填好类名和文件名点击【确定】按钮即可Bmp.h
和Bmp.cpp
两个文件。
生成的两个文件是类名框架。Bmp.h
内容包含一个类声明。
#pragma once
class CBmp
{
};
Bmp.cpp
内只有两个包含文件。
#include "pch.h"
#include "Bmp.h"
将上一篇博文中有关BMP处理相关的函数全部复制到Bmp.cpp
中,并为每个函数添加类限定符CBmp::
。另外,在为CBmp添加一个构造函数CBmp::CBmp()
和析构函数CBmp::~CBmp()
。
下面是Bmp.cpp的内容。
#include "pch.h"
#include "Bmp.h"
//构造函数
CBmp::CBmp() {}
//析构函数
CBmp::~CBmp(){}
//读取BMP文件
bool CBmp::readBmp(char* bmpName,
unsigned char* img_data,
int* bmpWidth, int* bmpHeight,
int* biBitCount, int* lineByte) {
//二进制读方式打开指定的图像文件
FILE* fp;
fopen_s(&fp, bmpName, "rb");
if (fp == NULL)
return false;
//读取位图文件头结构BITMAPFILEHEADER
BITMAPFILEHEADER file_header;
fread(&file_header, sizeof(BITMAPFILEHEADER), 1, fp);
//定义位图信息头结构变量,读取位图信息头进内存,存放在变量head中
BITMAPINFOHEADER info_header;
fread(&info_header, sizeof(BITMAPINFOHEADER), 1, fp);
//获取图像的宽、高、每像素所占比特数等信息
*bmpWidth = info_header.biWidth;
*bmpHeight = info_header.biHeight;
*biBitCount = info_header.biBitCount;
//计算图像每行像素所占的字节数(必须是4的倍数)
*lineByte = (*bmpWidth * *biBitCount / 8 + 3) / 4 * 4;
//读位图数据进内存
fread(img_data, 1, (*lineByte) * (*bmpHeight), fp);
fclose(fp);//关闭文件
return true;//读取文件成功
}
//绘制BMP图像
void CBmp::drawBmp(CDC* pDC,
unsigned char* img_data,
int bmpWidth, int bmpHeight, int lineByte,
int offset_left, int offset_top)
{
unsigned char red, green, blue = 0;
unsigned char* img_line = img_data;
for (int i = 0; i< bmpHeight; i++) {
for (int k = 0; k < bmpWidth; k++)
{
blue = img_line[3 * k + 0];
green = img_line[3 * k + 1];
red = img_line[3 * k + 2];
pDC->SetPixel(k + offset_left,
bmpHeight - 1 - i + offset_top,
RGB(red, green, blue));
}
img_line += lineByte;
}
}
//读取并绘制BMP图像
void CBmp::readAndDrawBMP(CDC* pDC,
char* bmpName,
int offset_left, int offset_top)
{
//申请保存图像数据的内存区,最大图像可以有1920*1080像素
unsigned char* img_data = new unsigned char[1920 * 1080 * 3];
//图像相关信息变量
int bmpWidth, bmpHeight, biBitCount, lineByte;
//读取图像数据到内存
bool result = readBmp(bmpName, img_data,
&bmpWidth, &bmpHeight,
&biBitCount, &lineByte);
if (!result) {
delete[] img_data;
return;
}
//绘制读取的BMP图像
drawBmp(pDC, img_data,
bmpWidth, bmpHeight, lineByte,
offset_left, offset_top);
delete[] img_data;
}
//将真彩色BMP分离为独立的R,G,B三个通道
bool CBmp::separateRGB(unsigned char* img_data,
unsigned char* R, unsigned char* G, unsigned char* B,
int bmpWidth, int bmpHeight, int lineByte)
{
for (int i = 0; i < bmpHeight; i++) {
for (int k = 0; k < bmpWidth; k++)
{
*R++ = img_data[3 * k + 0];
*G++ = img_data[3 * k + 1];
*B++ = img_data[3 * k + 2];
}
img_data += lineByte;
}
return true;
}
void CBmp::print_matrix(CDC* pDC,
unsigned char* img_R,
unsigned char* img_G,
unsigned char* img_B,
int width, int height,
int offset_left, int offset_top)
{
unsigned char r, g, b = 0;
for (int i = height-1; i >=0; i--)
for (int k = 0; k < width; k++) {
r = (unsigned char)img_R[i * width + k];
g = (unsigned char)img_G[i * width + k];
b = (unsigned char)img_B[i * width + k];
pDC->SetPixel(k + offset_left,
height - 1 - i + offset_top,
RGB(b, g, r));
}
}
void CBmp::readAndDrawBMP_seperate(CDC* pDC,
char* bmpName,
int offset_left, int offset_top)
{
//申请保存图像数据的内存区,最大图像可以有1920*1080像素
unsigned char* img_data = new unsigned char[1920 * 1080 * 3];
//图像相关信息变量
int bmpWidth, bmpHeight, biBitCount, lineByte;
//读取图像数据到内存
bool result = readBmp(bmpName, img_data,
&bmpWidth, &bmpHeight,
&biBitCount, &lineByte);
if (!result) {
delete[] img_data;
return;
}
unsigned char* R = new unsigned char[1920 * 1080];
unsigned char* G = new unsigned char[1920 * 1080];
unsigned char* B = new unsigned char[1920 * 1080];
separateRGB(img_data, R, G, B, bmpWidth, bmpHeight, lineByte);
//绘制读取的BMP图像
print_matrix(pDC, R, G, B, bmpWidth, bmpHeight, offset_left, offset_top);
delete[] img_data;
delete[] R;
delete[] G;
delete[] B;
}
将Bmp.cpp
文件中所有函数的声明行复制到Bmp.h
, 并为函数添加访问修饰符public:
,表示可以被其他对方访问。下面是Bmp.h
的内容。
#pragma once
class CBmp
{
public:
CBmp();
~CBmp();
bool readBmp(char* bmpName,
unsigned char* img_data,
int* bmpWidth, int* bmpHeight,
int* biBitCount, int* lineByte);
void drawBmp(CDC* pDC,
unsigned char* img_data,
int bmpWidth, int bmpHeight, int lineByte,
int offset_left, int offset_top);
void readAndDrawBMP(CDC* pDC,
char* bmpName,
int offset_left, int offset_top);
bool separateRGB(unsigned char* img_data,
unsigned char* R, unsigned char* G, unsigned char* B,
int bmpWidth, int bmpHeight, int lineByte);
void print_matrix(CDC* pDC,
unsigned char* img_R,
unsigned char* img_G,
unsigned char* img_B,
int width, int height,
int offset_left, int offset_top);
void readAndDrawBMP_seperate(CDC* pDC,
char* bmpName,
int offset_left, int offset_top);
};
设计好CBmp类后就可以在CView类对象OnDraw()中例化CBmp对象并调用相关方法即可实现与上一篇博文相同的功能。
为了更好的控制图像绘制的时机,而不是一运行就直接显示,我们需要为MFC程序添加对应某个事件的消息响应函数(也称为回调函数)。比如我们希望在用鼠标左键点击界面的时候才开始画图,其事件就是LButtonDown
。
在MFC工程依次点击菜单项【项目】->【类向导】,会出现下面的界面,我们可以添加一些事件的消息响应函数。
点击【编辑代码(E)】按钮就会直接跳到OnLButtonDown()
函数。
void CMFCSD03View::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
CView::OnLButtonDown(nFlags, point);
}
在这里添加图像绘制代码就可以显示图像了。
有两点需要提醒一下,一是OnLButtonDown()的参数没有绘图用的CDC* pDC
,下面的代码展示了如何主动获取系统的DC进行绘图的方法。
CClientDC dc(this);
CDC* pDC = &dc;
二是要在CMFCSD03View类里面使用CBmp类的对象,需要包含CBmp类的头文件#include "Bmp.h"
。
下面是完整的绘制代码
#include "Bmp.h"
void CMFCSD03View::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
CBmp* pBMP = new CBmp();
//要绘制的图像文件名
char bmpName[] = "1.bmp";
//左上角坐标
int offset_left = point.x;
int offset_top = point.y;
//读取并绘制图像
CClientDC dc(this);
CDC* pDC = &dc;
pBMP->readAndDrawBMP_seperate(pDC, bmpName, offset_left, offset_top);
CView::OnLButtonDown(nFlags, point);
}
编译执行MFC工程,会发现每次点击鼠标左键都会以点击的位置为左上角绘制指定的图像。
基于以上代码,实现在鼠标移动过程中BMP图像跟随移动的效果,即每次移动到一个新的位置会清除之前绘制的图像,而在新位置再重新绘制一遍。