简介:本文介绍了如何在Windows应用程序开发中使用MFC读取和处理BMP位图文件。详细说明了从BMP文件中读取像素值到二维数组的过程,以及如何将数组中的像素值重新显示为位图。我们通过CBitmap类和CDC类等MFC组件来完成这些任务,包括加载位图、创建内存DC、选择位图到DC、获取位图信息、动态分配数组存储像素值、读取像素到数组、验证和显示像素以及释放相关资源等步骤。这些技术要点对于图像处理和图形界面编程非常重要。
1. MFC读取BMP文件的机制与实现
当我们谈论如何在MFC(Microsoft Foundation Class)中读取和操作BMP文件时,我们首先需要了解BMP文件的存储格式以及MFC是如何提供接口来处理这些位图图像。本章将探讨MFC读取BMP文件的基本原理和实现方法,为后续章节深入学习CBitmap类和CDC类操作位图打下基础。
BMP文件是由微软开发的一种图像文件格式,用于存储Windows平台上的位图图像。它包含了图像数据和头信息,其中头信息包括了图像的宽度、高度、颜色深度等重要信息。在MFC中,我们主要通过CBitmap类来操作位图数据,包括加载、保存、绘制等。而CDC(设备上下文)类则为我们提供了一个设备无关的绘图接口,让我们可以在不同的设备上绘制相同的结果。
在后续的章节中,我们将深入分析CBitmap类和CDC类的具体操作,并通过示例代码展示如何在MFC应用中实现对BMP文件的有效读取和处理。
2. CBitmap类与位图操作
2.1 CBitmap类基础操作
CBitmap类是MFC中的一个封装了Windows GDI位图功能的类,用于在内存中处理图像数据。它与设备无关位图(DIB)不同,后者可以在不同的设备之间交换,而CBitmap主要针对内存中的图像数据进行操作。
2.1.1 CBitmap类的创建和销毁
创建一个CBitmap对象非常简单,通常有两种方法:
CBitmap bitmap;
或者:
CBitmap* pBitmap = new CBitmap;
创建对象后,你可以使用 CreateBitmap
、 CreateCompatibleBitmap
、 CreateDiscardableBitmap
或者 LoadBitmap
等成员函数进行初始化。
销毁一个CBitmap对象,如果它是局部对象,会在其作用域结束时自动调用析构函数。如果是通过 new
创建的动态对象,需要在适当的时候使用 delete
进行显式销毁:
delete pBitmap;
2.1.2 CBitmap类与设备无关位图(DIB)
与设备无关位图(DIB)相比,CBitmap提供了更高级的抽象。DIB通常存储在一个文件中,而CBitmap则可以存储在内存中,并且提供了直接在MFC应用程序中进行处理的接口。
CBitmap可以与DIB一起使用,例如,你可以使用 GetBitmapBits
和 SetBitmapBits
函数来读写DIB数据:
BITMAPINFOHEADER bmih;
BYTE* pBitmapBits;
// 假定bmih已经被适当填充
// 获取位图数据
pBitmapBits = new BYTE[bmih.biSizeImage];
pBitmap.GetBitmapBits(bmih.biSizeImage, pBitmapBits);
// 设置位图数据
pBitmap.SetBitmapBits(bmih.biSizeImage, pBitmapBits);
2.2 CBitmap类的高级功能
2.2.1 加载外部BMP文件
加载外部BMP文件到CBitmap对象中,可以使用MFC的CFile类或者标准的文件操作API来读取文件,然后使用CBitmap的 LoadBitmap
或 LoadMappedLuminanceBitmap
函数加载位图数据。
以下是使用 LoadBitmap
函数加载位图的一个例子:
CBitmap bitmap;
if (bitmap.LoadBitmap(IDB_MYBITMAP))
{
// 加载成功后的操作
}
如果需要更细致的控制,比如需要读取一个非资源类型的BMP文件,你可能需要手动加载文件头信息,位图信息头,然后分配内存读取像素数据:
CFile file;
BITMAPFILEHEADER fileHeader;
BITMAPINFOHEADER infoHeader;
BYTE* pBits = NULL;
if (file.Open(_T("path_to_bitmap.bmp"), CFile::modeRead))
{
file.Read(&fileHeader, sizeof(BITMAPFILEHEADER));
file.Read(&infoHeader, sizeof(BITMAPINFOHEADER));
// 分配内存
pBits = new BYTE[infoHeader.biSizeImage];
// 读取像素数据
file.Read(pBits, infoHeader.biSizeImage);
// 加载位图到CBitmap对象
bitmap.Attach(CreateDIBits(pDC, pBits, infoHeader.biWidth,
infoHeader.biHeight, pBits,
(BITMAPINFO*)&infoHeader, DIB_RGB_COLORS));
delete[] pBits;
file.Close();
}
2.2.2 位图的创建与保存
创建一个位图,可以使用 CreateBitmap
系列函数创建一个空白位图,或者创建一个与指定设备兼容的位图。此外,你还可以创建一个可丢弃的位图,这样的位图如果当前不可用,会从系统中删除,以节省资源。
保存位图到文件,你需要将位图数据从内存中导出到一个文件。可以使用 GetBitmapBits
来获取位图的数据,并将其写入到一个BMP文件中。这需要我们自己实现文件头、信息头的构建和像素数据的写入。
void SaveBitmap(const CBitmap& bitmap, const CString& filename)
{
BITMAP bmp;
bitmap.GetBitmap(&bmp);
CFile file;
if (file.Open(filename, CFile::modeCreate | CFile::modeWrite))
{
BITMAPFILEHEADER fileHeader;
BITMAPINFOHEADER infoHeader;
BYTE* pBits = NULL;
// 初始化位图文件头
memset(&fileHeader, 0, sizeof(fileHeader));
fileHeader.bfType = 0x4D42; // BM
fileHeader.bfSize = sizeof(fileHeader) + sizeof(infoHeader) + bmp.bmWidth * bmp.bmHeight * 3;
fileHeader.bfReserved1 = 0;
fileHeader.bfReserved2 = 0;
fileHeader.bfOffBits = sizeof(fileHeader) + sizeof(infoHeader);
// 初始化位图信息头
infoHeader.biSize = sizeof(infoHeader);
infoHeader.biWidth = bmp.bmWidth;
infoHeader.biHeight = bmp.bmHeight;
infoHeader.biPlanes = 1;
infoHeader.biBitCount = 24;
infoHeader.biCompression = BI_RGB;
infoHeader.biSizeImage = 0;
infoHeader.biXPelsPerMeter = 0;
infoHeader.biYPelsPerMeter = 0;
infoHeader.biClrUsed = 0;
infoHeader.biClrImportant = 0;
// 写入位图文件头
file.Write(&fileHeader, sizeof(fileHeader));
// 写入位图信息头
file.Write(&infoHeader, sizeof(infoHeader));
// 写入像素数据
pBits = new BYTE[bmp.bmWidth * bmp.bmHeight * 3];
bitmap.GetBitmapBits(bmp.bmWidth * bmp.bmHeight * 3, pBits);
file.Write(pBits, bmp.bmWidth * bmp.bmHeight * 3);
delete[] pBits;
file.Close();
}
}
这个函数首先获取位图的宽度和高度,然后创建文件头和信息头,并将像素数据写入到文件中。请确保文件名正确且路径可写。
需要注意的是,上述代码是一个简化的例子,它假定位图是24位的RGB图像。在实际应用中,你可能需要对不同类型的位图做不同处理,比如调色板的处理、压缩格式的解压等。
以上是CBitmap类基础操作和高级功能的介绍,CBitmap类是MFC中非常重要的一个类,它提供了丰富的接口进行位图的创建、操作、保存等操作,是进行图形编程不可或缺的工具。
3. CDC类与位图设备上下文
在MFC(Microsoft Foundation Classes)开发中,CDC类扮演着非常重要的角色。CDC是设备上下文(Device Context)的缩写,负责描述与设备有关的绘图属性和操作。在处理位图时,CDC类可以用来在不同的设备上下文中绘制、选择和处理位图。
3.1 CDC类基础概念
3.1.1 设备上下文的理解
设备上下文是一个包含了输出设备信息的结构体,包括了绘图的属性、字体、颜色等。在MFC中,CDC类对GDI(图形设备接口)的封装使得程序员能够以面向对象的方式来处理各种图形操作。设备上下文类似于一个画布,所有的绘图操作都在这个画布上完成。
3.1.2 CDC类与图形设备接口
CDC类抽象了Windows的GDI,提供了大量的绘图和文本输出函数。例如,可以使用CDC类的 MoveTo
和 LineTo
函数来绘制线条, Rectangle
来绘制矩形,以及 DrawText
来在设备上下文中输出文本。
3.2 CDC类操作位图
3.2.1 选择位图到设备上下文
要在一个设备上下文中操作位图,首先需要将位图选入到这个上下文中。CDC类提供了 CBitmap
对象的 SelectObject
函数,可以将一个 CBitmap
对象选入到CDC对象中。这一步骤是进行位图操作的前提。
void CYourView::OnDraw(CDC* pDC)
{
// 创建一个CBitmap对象
CBitmap bitmap;
// 加载BMP文件
bitmap.LoadBitmap(IDB_YOUR_BITMAP);
// 选择位图到设备上下文中
CBitmap* pOldBitmap = pDC->SelectObject(&bitmap);
// 绘制位图
BITMAP bm;
bitmap.GetBitmap(&bm);
pDC->BitBlt(0, 0, bm.bmWidth, bm.bmHeight, pDC, 0, 0, SRCCOPY);
// 将旧位图恢复
pDC->SelectObject(pOldBitmap);
}
在上述代码中, LoadBitmap
函数用于加载一个位图资源。 SelectObject
函数将位图选入到设备上下文 pDC
中。 BitBlt
函数用于将位图内容从 CBitmap
对象中绘制到屏幕上。
3.2.2 位图的绘制与处理
绘制位图只是位图操作的一部分,我们还可能需要对位图进行各种处理,例如位图的旋转、缩放等。这些操作可以在CDC类上进行,因为CDC类提供了相关的GDI函数来实现这些图形变换。
// 位图旋转示例
void CYourView::RotateBitmap(CDC* pDC, CBitmap* pBitmap, float angle)
{
// 这里我们仅提供代码概念,并未实现具体的GDI旋转函数调用
// 实际上,需要使用CDC类的Transform和SetWorldTransform函数
// 进行坐标变换实现位图的旋转
}
// 位图缩放示例
void CYourView::ScaleBitmap(CDC* pDC, CBitmap* pBitmap, float scaleX, float scaleY)
{
// 同样地,CDC类提供了StretchBlt函数来实现位图的缩放
pDC->StretchBlt(0, 0, scaleX * bitmap.GetWidth(), scaleY * bitmap.GetHeight(), pDC, 0, 0, bitmap.GetWidth(), bitmap.GetHeight(), SRCCOPY);
}
在上述代码中, RotateBitmap
函数展示了位图旋转的概念,而 ScaleBitmap
函数则展示了位图缩放的代码。值得注意的是,实际的旋转操作涉及到更复杂的GDI变换操作,例如使用 SetWorldTransform
函数设置世界变换来实现旋转。
本章节中,我们了解了CDC类的基础概念和位图的基本操作。下一章节,我们将深入探讨位图信息的解析与像素值的存储,从而进一步掌握位图处理的底层细节。
4. 位图信息的解析与像素值的存储
在本章节中,我们将深入探讨MFC中位图信息的解析过程和像素值的存储方法。这包括对位图文件结构的理解,特别是BITMAPINFOHEADER结构的解析,以及如何处理RGBQUAD结构体以及像素数据。通过本章节的介绍,您将获得从BMP文件中提取位图信息并将其像素值准确存储到内存中的能力。
4.1 BITMAPINFOHEADER结构解析
4.1.1 BITMAPINFOHEADER结构详解
BITMAPINFOHEADER是描述位图信息的一个关键结构体,在读取BMP文件时,首先需要解析的就是这个结构。它包含了位图的尺寸、颜色深度、压缩类型等关键信息。结构定义如下:
typedef struct tagBITMAPINFOHEADER {
DWORD biSize;
LONG biWidth;
LONG biHeight;
WORD biPlanes;
WORD biBitCount;
DWORD biCompression;
DWORD biSizeImage;
LONG biXPelsPerMeter;
LONG biYPelsPerMeter;
DWORD biClrUsed;
DWORD biClrImportant;
} BITMAPINFOHEADER;
每个字段都有其特定的意义:
-
biSize
:BITMAPINFOHEADER结构体的大小(以字节为单位)。 -
biWidth
和biHeight
:位图的宽度和高度,以像素为单位。 -
biPlanes
:颜色平面数,在大多数情况下,此值为1。 -
biBitCount
:每个像素的位数,比如24位表示真彩色。 -
biCompression
:位图压缩方式,如BI_RGB表示无压缩。 -
biSizeImage
:位图图像的大小(以字节为单位)。 - 其余字段描述了图像的水平和垂直分辨率以及使用的颜色数和重要颜色数。
4.1.2 从BMP文件中读取位图信息
从BMP文件中读取位图信息的步骤通常如下:
- 打开BMP文件。
- 跳过文件头(BITMAPFILEHEADER)直接定位到BITMAPINFOHEADER结构体的位置。
- 读取BITMAPINFOHEADER的各个字段,并进行适当的处理。
下面是一个简单的示例代码:
#include <windows.h>
BITMAPINFOHEADER GetBitmapInfoHeader(const char* filename) {
HANDLE hFile = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
throw std::runtime_error("无法打开文件");
}
BITMAPFILEHEADER fileHeader;
DWORD bytesRead;
ReadFile(hFile, &fileHeader, sizeof(BITMAPFILEHEADER), &bytesRead, NULL);
if (fileHeader.bfType != 0x4D42) {
CloseHandle(hFile);
throw std::runtime_error("不是有效的BMP文件");
}
BITMAPINFOHEADER infoHeader;
SetFilePointer(hFile, fileHeader.bfOffBits, NULL, FILE_BEGIN);
ReadFile(hFile, &infoHeader, sizeof(BITMAPINFOHEADER), &bytesRead, NULL);
CloseHandle(hFile);
return infoHeader;
}
4.2 RGBQUAD与像素数据处理
4.2.1 RGBQUAD结构体介绍
RGBQUAD结构体描述了一个像素的颜色。它包含四个字节,分别对应红色、绿色、蓝色的值和一个保留字节(通常设为0)。结构定义如下:
typedef struct tagRGBQUAD {
BYTE rgbBlue;
BYTE rgbGreen;
BYTE rgbRed;
BYTE rgbReserved;
} RGBQUAD;
4.2.2 像素数据的解析与存储
位图的像素数据在文件中通常是以从下到上的顺序存储的,即扫描线从文件末尾开始。每个像素的颜色数据由多个字节组成,这些字节的数据类型和存储顺序由BITMAPINFOHEADER中的 biBitCount
字段决定。
以下是一个处理像素数据的示例代码,假设位图是24位深度的真彩色图像:
void ProcessBitmapPixels(HBITMAP hBitmap) {
BITMAP bitmap;
GetObject(hBitmap, sizeof(BITMAP), &bitmap);
// 确保位图是24位深度
if (bitmap.bmBitsPixel != 24) {
throw std::runtime_error("位图不是24位深度");
}
// 获取位图的宽度和高度
int width = bitmap.bmWidth;
int height = bitmap.bmHeight;
// 从设备无关位图中获取位图信息头
BITMAPINFOHEADER infoHeader = { sizeof(BITMAPINFOHEADER) };
GetDIBits(hBitmap, 0, height, NULL, (BITMAPINFO*)&infoHeader, DIB_RGB_COLORS);
// 定义指向像素数据的指针
RGBQUAD* pixels = new RGBQUAD[width * height];
// 从位图中获取像素数据
GetDIBits(hBitmap, 0, height, pixels, (BITMAPINFO*)&infoHeader, DIB_RGB_COLORS);
// 处理像素数据...
delete[] pixels;
}
在上述代码中,我们首先检查位图是否为24位深度的真彩色图像。然后,我们使用 GetObject
函数获取位图的尺寸,并使用 GetDIBits
函数获取像素数据。这里 GetDIBits
的第三个参数为0,表示我们希望从设备无关位图的第0行开始读取。
请注意,像素数据的处理需要根据实际的位图信息进行相应的调整。例如,如果位图使用的是其他颜色深度(如32位),那么像素数据的结构和处理方式会有所不同。因此,本章节提供了处理24位位图像素数据的基本方法,针对不同的应用场景和需求,可能需要进一步的调整和优化。
5. 像素数据的读取与显示
5.1 动态二维数组的创建与管理
5.1.1 动态二维数组的作用
在处理图像像素数据时,我们通常需要一个能够存储大量数据的容器。动态二维数组正是这样的一个结构,它能动态地根据需要的像素数量进行分配,并且能够方便地对这些数据进行访问和操作。这在对图像数据进行处理,如滤波、缩放等操作时非常有用。
5.1.2 在MFC中实现动态二维数组
在MFC中,我们可以通过new运算符来动态分配内存。下面的代码展示了如何创建一个动态二维数组:
BYTE** CreateDynamic2DArray(int width, int height) {
BYTE** array = new BYTE*[height];
for (int i = 0; i < height; i++) {
array[i] = new BYTE[width]; // 分配一行的内存空间
}
return array;
}
在这个函数中,我们首先创建了一个指向指针的指针,即一个指向BYTE类型的一维数组指针的数组,这样就可以访问一个二维数组中的每个元素。
5.2 像素值的读取与存储
5.2.1 GetDIBits函数的使用
GetDIBits
函数是GDI中用于从设备上下文中读取像素数据的函数。它可以将位图数据从设备上下文中拷贝到一个指定的内存缓冲区中。下面是使用 GetDIBits
的基本步骤:
BOOL GetDIBits(CDC* pDC, CBitmap* pBitmap, LPBITMAPINFOHEADER lpbi, LPVOID lpBits)
{
// 1. 获取位图的宽度和高度
BITMAP bmp;
pBitmap->GetBitmap(&bmp);
// 2. 调用GetDIBits函数
return pDC->GetDIBits(pBitmap->m_hObject, 0, bmp.bmHeight, lpBits, (BITMAPINFO*)lpbi, DIB_RGB_COLORS);
}
这个函数需要一个设备上下文指针、位图对象指针、位图信息头指针以及用于存储像素数据的指针作为参数。
5.2.2 将像素值存储到二维数组中
当我们从位图中读取了像素数据后,可以将这些数据存储到动态创建的二维数组中,以便后续处理。下面的代码展示了如何将位图数据拷贝到二维数组中:
void CopyBitmapDataTo2DArray(CDC* pDC, CBitmap* pBitmap, BYTE*** ppArray, int width, int height)
{
BITMAPINFOHEADER bmi;
memset(&bmi, 0, sizeof(BITMAPINFOHEADER));
bmi.biSize = sizeof(BITMAPINFOHEADER);
bmi.biWidth = width;
bmi.biHeight = height;
bmi.biPlanes = 1;
bmi.biBitCount = 24;
bmi.biCompression = BI_RGB;
BYTE* pPixels = new BYTE[width * height * 3];
if (GetDIBits(pDC, pBitmap, &bmi, pPixels)) {
// 分配二维数组空间
*ppArray = CreateDynamic2DArray(width, height);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int index = (y * width + x) * 3;
(*ppArray)[y][x] = pPixels[index]; // 假设是24位RGB图像
}
}
}
delete[] pPixels;
}
在这个函数中,我们首先初始化了一个 BITMAPINFOHEADER
结构体,然后创建了一个足够大的一维数组来存储图像数据。之后,我们调用 GetDIBits
函数将数据从位图中读出,并将其存放到二维数组中。
5.3 从数组到位图的显示
5.3.1 SetBitmapBits函数的应用
SetBitmapBits
函数用于将内存中的图像数据直接设置到位图中。这对于将我们处理过的像素数据更新到位图中非常有用。这个函数通常与 GetBitmapBits
联合使用,后者用于从位图中获取原始数据。
5.3.2 二维数组中像素值的显示方法
一旦我们在二维数组中处理完了像素数据,我们可以使用 SetBitmapBits
函数将这些数据更新到位图中,然后将其显示在屏幕上。下面是一个例子:
void UpdateDisplayFrom2DArray(CDC* pDC, CBitmap* pBitmap, BYTE** pArray, int width, int height)
{
BITMAPINFOHEADER bmi;
memset(&bmi, 0, sizeof(BITMAPINFOHEADER));
bmi.biSize = sizeof(BITMAPINFOHEADER);
bmi.biWidth = width;
bmi.biHeight = height;
bmi.biPlanes = 1;
bmi.biBitCount = 24;
bmi.biCompression = BI_RGB;
BYTE* pPixels = new BYTE[width * height * 3];
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int index = (y * width + x) * 3;
pPixels[index] = pArray[y][x];
}
}
// 假设pBitmap已经有了一个有效的位图句柄
pBitmap->SetBitmapBits((width * height * 3), pPixels);
delete[] pPixels;
// 使用pDC来显示更新后的位图
// ...
}
在这个函数中,我们从二维数组中读取像素数据并拷贝到一个一维数组中。然后,我们使用 SetBitmapBits
函数将这些数据设置到位图对象中。最后,你可以使用CDC对象来在窗口或其他GDI对象中绘制这个更新后的位图。
通过以上的步骤,我们可以读取BMP图像文件中的像素数据,对其进行处理,并将处理后的结果显示在应用程序界面上。这为图像处理提供了一个基础的框架,也为后续更复杂的图像处理操作打下了坚实的基础。
简介:本文介绍了如何在Windows应用程序开发中使用MFC读取和处理BMP位图文件。详细说明了从BMP文件中读取像素值到二维数组的过程,以及如何将数组中的像素值重新显示为位图。我们通过CBitmap类和CDC类等MFC组件来完成这些任务,包括加载位图、创建内存DC、选择位图到DC、获取位图信息、动态分配数组存储像素值、读取像素到数组、验证和显示像素以及释放相关资源等步骤。这些技术要点对于图像处理和图形界面编程非常重要。