读取 & 显示BMP图像

读取

定义DIB类

BMP文件中依次为文件头、位图信息头、调色板

DIB.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#pragma once

// MFC框架
#include "framework.h"

// 保证两字节对齐(否则默认四字节对齐)
#pragma pack(2)
struct BMPFileHeader
{
// 定义BMP文件头
WORD bfType;
DWORD bfSize;
WORD bfReserved1;
WORD bfReserved2;
DWORD bfOffBits;
};

#pragma pack(2)
struct BMPInfoHeader
{
// 定义BMP信息头
DWORD biSize;
DWORD biWidth;
DWORD biHeight;
WORD biPlanes;
WORD biBitCount;
DWORD biCompression;
DWORD biSizeImage;
DWORD biXPelsPerMeter;
DWORD biYPelsPerMeter;
DWORD biClrUsed;
DWORD biClrImportant;
};

#pragma pack(1)
struct RGBQuad
{
// 定义调色板像素格式
BYTE rgbBlue;
BYTE rgbGreen;
BYTE rgbRed;
BYTE rgbReserved;
};

class DIB
{
public:
BMPFileHeader m_BMPfileheader; // 位图的文件头
BMPInfoHeader m_BMPinfoheader; // 位图的信息头
unsigned char* m_BMPdata; // 位图的真实图像数据
RGBQuad* m_palette; // 调色板 if has

int m_width;
int m_height;
int m_colornb; // 当使用调色板时,保存调色板的颜色数量

public:
DIB();
virtual ~DIB();

void Read(const CString& fileName);
void Write(const CString& fileName);
void Clear();
void Grayscale();
};

DIB.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
#include "pch.h"
#include "DIB.h"

DIB::DIB()
{
m_BMPdata = NULL;
m_palette = NULL;
}

DIB::~DIB()
{
Clear();
}

void DIB::Clear()
{
if (m_BMPdata) {
delete[] m_BMPdata;
m_BMPdata = NULL;
}

if (m_palette) {
delete[] m_palette;
m_palette = 0;
}
}

void DIB::Read(const CString& fileName)
{
// 在此处添加你的代码
// 读入新图像前把旧数据清空
Clear();

CFile file;
VERIFY(file.Open(fileName, CFile::modeRead));
file.Read(&m_BMPfileheader, sizeof(BMPFileHeader));//文件头
if (m_BMPfileheader.bfType == 0x4D42) { // 是位图 ("BM")
file.Read(&m_BMPinfoheader, sizeof(BMPInfoHeader));//信息头
m_width = m_BMPinfoheader.biWidth;
m_height = m_BMPinfoheader.biHeight;

int bit = m_BMPinfoheader.biBitCount;

if (bit <= 16) { //小于16位色的有调色板,真彩色没有
AfxMessageBox(_T("Used palette"));
m_colornb = 0;
if (m_BMPinfoheader.biClrUsed == 0)
m_colornb = (1 << bit); // 2^(bitCount)
else
m_colornb = m_BMPinfoheader.biClrUsed;

m_palette = new RGBQuad[m_colornb];
file.Read(m_palette, sizeof(RGBQuad) * m_colornb); //调色板
}

// 读取真实图像数据
file.Seek(m_BMPfileheader.bfOffBits, CFile::begin);
// 有些图像中额size是0,要手动计算大小
/*if (m_BMPinfoheader.biSizeImage == 0) {
AfxMessageBox(_T("size zero!!"));
m_BMPinfoheader.biSizeImage = m_width * m_height * bit / 8;
}*/
m_BMPdata = new unsigned char[m_BMPinfoheader.biSizeImage]; //图像数据
file.Read(m_BMPdata, m_BMPinfoheader.biSizeImage);
file.Close();
}
else {
file.Close();
AfxMessageBox(_T("Not a bmp image!"));
return;
}
}

void DIB::Write(const CString& fileName)
{
// 在此处添加你的代码
CFile BMPFile;
VERIFY(BMPFile.Open(fileName, CFile::modeCreate | CFile::modeWrite));
try {
// 写文件头
BMPFile.Write(&m_BMPfileheader, sizeof(BMPFileHeader));
// 写信息头
BMPFile.Write(&m_BMPinfoheader, sizeof(BMPInfoHeader));
// 如果有调色板,写入调色板
if (m_palette != NULL)
BMPFile.Write(m_palette, sizeof(RGBQuad) * m_colornb);
// 写图像数据
BMPFile.Write(m_BMPdata, m_BMPinfoheader.biSizeImage);
}
catch (CException * pe) {
pe->Delete();
AfxMessageBox(_T("write error"));
BMPFile.Close();
return;
}
BMPFile.Close();
}

void DIB::Grayscale()
{
for (int j = 0; j < m_height; j++) {
for (int i = 0; i < m_width; i++) {
int index = j * m_width + i;
unsigned char r = m_BMPdata[3 * index];
unsigned char g = m_BMPdata[3 * index + 1];
unsigned char b = m_BMPdata[3 * index + 2];

unsigned gray = (r * 0.299 + g * 0.587 + b * 0.114);
m_BMPdata[3 * index] = gray;
m_BMPdata[3 * index + 1] = gray;
m_BMPdata[3 * index + 2] = gray;
}
}
}

{项目名}View.h中添加类成员DIB* m_dib,并using uchar = unsigned char

{项目名}View.cpp中修改OnFileOpen,添加读取代码

1
2
3
4
5
6
CFileDialog dlg(TRUE); 				// 读取任何文件
if (dlg.DoModal() == IDOK) {
bitmapPath = dlg.GetPathName(); // 获取文件完整路径
m_dib = new DIB;
m_dib->Read(bitmapPath);
}

显示

{项目名}View.cpp中修改OnDraw,添加绘制代码

逐像素绘制

这里只考虑24位真彩色位图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
uchar* image = m_dib->m_BMPdata;
int height = m_dib->m_height;
int width = m_dib->m_width;
int width3 = width * 3;
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
// 每个像素依次保存的是b、g、r,各一个字节
uchar* pos = image + width3 * i + j * 3;
uchar b = *pos; pos++;
uchar g = *pos; pos++;
uchar r = *pos;
// 整个位图数据中先保存的是最后一行,要手动翻转
pDC->SetPixelV(10 + j, 10 + height - i, RGB(r, g, b));
}
}

调用MFC接口显示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//定义bitmap指针 调用函数LoadImage装载位图
HBITMAP m_hBitmap;
m_hBitmap = (HBITMAP)LoadImage(NULL, bitMapPath, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE | LR_DEFAULTSIZE | LR_CREATEDIBSECTION);

if (m_bitmap.m_hObject) {
m_bitmap.Detach(); //切断CWnd和窗口联系
}
m_bitmap.Attach(m_hBitmap); //将句柄HBITMAP m_hBitmap与CBitmap m_bitmap关联

//边界
CRect rect;
GetClientRect(&rect);

//图片显示(x,y)起始坐标
int m_showX = 0;
int m_showY = 0;
int m_nWindowWidth = rect.right - rect.left; //计算客户区宽度
int m_nWindowHeight = rect.bottom - rect.top; //计算客户区高度

//定义并创建一个内存设备环境DC
CDC dcBmp;
if (!dcBmp.CreateCompatibleDC(pDC)) //创建兼容性的DC
return;

BITMAP m_bmp; //临时bmp图片变量
m_bitmap.GetBitmap(&m_bmp); //将图片载入位图中

CBitmap* pbmpOld = NULL;
dcBmp.SelectObject(&m_bitmap); //将位图选入临时内存设备环境

//图片显示调用函数stretchBlt
pDC->StretchBlt(0, 0, m_bmp.bmWidth, m_bmp.bmHeight, &dcBmp, 0, 0, m_bmp.bmWidth, m_bmp.bmHeight, SRCCOPY);

dcBmp.SelectObject(pbmpOld); //恢复临时DC的位图
DeleteObject(&m_bitmap); //删除内存中的位图
dcBmp.DeleteDC(); //删除CreateCompatibleDC得到的图片DC