在图像处理的过程中,我们必然要对位图文件进行像素级的操作。一种办法是使用windows位图操作api或者是MFC中的CBitmap类;另外的办法是调用第三方的图像类,如CxImage类。
(一)目的
在图像处理的过程中,我们必然要对位图文件进行像素级的操作。一种办法是使用windows位图操作api或者是MFC中的CBitmap类;另外的办法是调用第三方的图像类,如CxImage类。作者在使用上述这些方案时,发现了如下的问题,首先,他们的功能要么过强(提供了许多一般很少用的功能,例如封装了很多我们不想要的图像格式处理函数),要么过弱(很多常用功能没有提供,比如对像素级的操作接口)。为此,作者根据自己的使用需要,建立了一个针对windows 24位真彩色位图的处理函数。功能目前还不是很完善,但是已经能够满足作者的使用需要。主要功能有:位图的读写、位图的建立和保存、两幅位图的比较、位图中像素颜色的获取和设置、位图中指定区域的获取和设置等等。如果有需要的话,可以进行进一步的的扩充。
(二)位图处理概述
1 位图在windows中以位图文件格式存放(bmp文件)。
文件格式如下:
[ 位图文件头 + 位图信息头 + 颜色表(24位真彩色没有这项) + 位图数据 ]
2 位图文件头:位图文件头主要用于识别位图文件。
以下是位图文件头结构的定义和含义:
typedef struct tagBITMAPFILEHEADER
{
WORD bfType; //必须是“BM”(0x4d42),标志该文件是位图文件
DWORD bfSize; //位图文件的总的大小
WORD bfReserved1; //必须为0
WORD bfReserved2; //必须为0
DWORD bfOffBits; //到位图数据的偏移量(字节数)
} BITMAPFILEHEADER;
3 位图信息头 + 颜色表
以下结构体中定义了位图信息头和颜色表。
typedef struct tagBITMAPINFO {
BITMAPINFOHEADER bmiHeader;//信息头
RGBQUAD bmiColors[1];//颜色表
} BITMAPINFO;
本文中只讨论24位真彩色位图,因此上述结构体不予讨论,只关注信息头就可以了。
信息头定义如下:
typedef struct tagBITMAPINFOHEADER
{
DWORD biSize; //等于sizeof(BITMAPINFOHEADER)
LONG biWidth; //图像的宽度
LONG biHeight; //高度
WORD biPlanes; //必须为1
WORD biBitCount //每个像素的比特数,24位真彩色图应该设成24
DWORD biCompression; //压缩
DWORD biSizeImage; //图像数据的大小
LONG biXPelsPerMeter; //水平方向上的每米的像素个数
LONG biYPelsPerMeter; //垂直方向上的每米的像素个数
DWORD biClrUsed; //调色板中实际使用的颜色数
DWORD biClrImportant; //现实位图时必须的颜色数
} BITMAPINFOHEADER;
4 位图数据
根据不同的位图,位图数据所占据的字节数也是不同的,比如,对于8位位图,每个字节代表了一个像素,对于16位位图,每两个字节代表了一个像素,对于24位位图,每三个字节代表了一个像素,对于32位位图,每四个字节代表了一个像素。本文中的位图采用的是24位位图(3字节)。
这里需要说明的是:位图数据中的数据排列不是按照我们习惯的从左到右,从上到下的顺序。而是采用了从下到上,从左到右的顺序排列每个像素,且,每个像素的RGB颜色值的排列顺序是B,G,R,恰好相反。因此,我们在处理的时候需要特别的注意。
(三)MyColor类定义和实现
MyColor类代表了一个像素点(的颜色),为了方便后续的处理,使用double类型保存RGB值。
struct MyColor
{
public:
// default constructor
MyColor()
{
}
// double initialization constructor
MyColor(double x, double y, double z)
{
X = x;
Y = y;
Z = z;
}
// int initialization constructor
MyColor(int x, int y, int z)
{
X = x;
Y = y;
Z = z;
}
// Equal operator
bool operator==(MyColor& c)
{
return ( (X == c.X) && (Y == c.Y) && (Z == c.Z));
}
// Assignment operator
MyColor& operator=(MyColor& c)
{
X = c.X;
Y = c.Y;
Z = c.Z;
return (*this);
}
// operator /
MyColor operator/(int i)
{
return MyColor(this->X / i, this->Y / i, this->Z / i);
}
MyColor operator/(double d)
{
return MyColor(this->X / d, this->Y / d, this->Z / d);
}
// operator *
MyColor operator*(int i)
{
return MyColor(this->X * i, this->Y * i, this->Z * i);
}
MyColor operator*(double d)
{
return MyColor(this->X * d, this->Y * d, this->Z * d);
}
// operator +
MyColor operator+(MyColor& c)
{
return MyColor(this->X + c.X, this->Y + c.Y, this->Z + c.Z);
}
// operator -
double operator-(MyColor& c)
{
return _Euclidean3D(c);
}
// 3-D 欧式距离(squared)
double _Euclidean3D(MyColor& c)
{
static double tmp;
tmp = (this->X - c.X) * (this->X - c.X) +
(this->Y - c.Y) * (this->Y - c.Y) +
(this->Z - c.Z) * (this->Z - c.Z);
return tmp;
}
public:
// 3-dimension data, eg. R, G, B
double X;
double Y;
double Z;
};
上面的“减号”操作符是求两个颜色值的距离,它直接调用 _Euclidean3D计算欧式距离;也可以替换成别的距离函数(如海明距离)。
X,Y,Z中保存的是颜色值,double类型。
还有一些别的操作,如*,/等等,主要是对颜色值进行线性运算。
(四)MyBitmap类的定义
MyBitmap类实现了基本的常用的位图操作。
一些成员变量:
LPBITMAPFILEHEADER m_lpFileHeader;//文件头的指针
LPBITMAPINFOHEADER m_lpInfoHead;//信息头指针
MyColor * m_Image;//位图数据指针
一些成员函数:
void Open(char* Filename);//打开文件(24位真彩色)
void Close();//关闭文件,释放所有资源
void Write2File(char* Filename);//写回bmp文件
void Create(int width, int height, MyColor c = MyColor(0,0,0));//建立指定大小和颜色的位图对象
MyBitmap& operator=(MyBitmap& bitmap);//复制本对象
bool operator==(MyBitmap& bitmap);//比较两个位图是否相同
void GetAt(int x, int y, MyColor& c);//获取坐标x,y的颜色值(左上角为0,0)
void SetAt(int x, int y, MyColor& c);//设置颜色
MyColor& GetRef(int x, int y);//获取某个像素对象的引用
void GetImageSection(int x, int y, int width, int height, MyBitmap& bitmap);//获取指定区域
void SetImageSection(int x, int y, int width, int height, MyColor& c);//设置指定区域
void SetImageSection(int x, int y, int width, int height, MyBitmap& bitmap);//设置指定区域
long GetWidth();//获取宽度
long GetHeight();//获取高度
bool IsBitmapValid();//测试对象是否有效
// 24-bit bitmap class
class MyBitmap
{
public:
// default constructor
MyBitmap();
// initialization constructor
MyBitmap(int width, int height, MyColor c = MyColor(0,0,0));
// default deconstructor
~MyBitmap();
public:
// open a 24-bit bitmap file
void Open(char* Filename);
// close a bitmap object and release all resource it used
void Close();
// write this object to a bitmap file
void Write2File(char* Filename);
// create a new bitmap object with prefixed width and height and color
void Create(int width, int height, MyColor c = MyColor(0,0,0));
// assignment operator
MyBitmap& operator=(MyBitmap& bitmap);
// equal operator
bool operator==(MyBitmap& bitmap);
// get color element at pos(x,y) x: width y: height (0,0) at left-top
void GetAt(int x, int y, MyColor& c);
// set color element at pos(x,y)
void SetAt(int x, int y, MyColor& c);
// get reference of the m_Image
MyColor& GetRef(int x, int y);
// get section of an image
void GetImageSection(int x, int y, int width, int height, MyBitmap& bitmap);
// set section of an image to color
void SetImageSection(int x, int y, int width, int height, MyColor& c);
// set section of an image to another image
void SetImageSection(int x, int y, int width, int height, MyBitmap& bitmap);
// get bitmap width
long GetWidth();
// get bitmap height
long GetHeight();
// test if a valid bitmap is read in memory
bool IsBitmapValid();
private:
// get bytes used per line in the bitmap
int GetBytesPerLine();
private:
// true if an object is valid
bool m_bValid;
// store the image file head
LPBITMAPFILEHEADER m_lpFileHeader;
// store the image info head
LPBITMAPINFOHEADER m_lpInfoHead;
// store the image data
MyColor * m_Image;
};
(五)MyBitmap类的定义
以下是实现代码:
MyBitmap::MyBitmap()
{
m_bValid = false;
m_lpFileHeader = NULL;
m_lpInfoHead = NULL;
m_Image = NULL;
}
MyBitmap::MyBitmap(int width, int height, MyColor c /* = MyColor */)
{
if(!(width > 0 && height > 0))
throw "MyBitmap::MyBitmap - width / height non-positive";
m_bValid = false;
m_lpFileHeader = NULL;
m_lpInfoHead = NULL;
m_Image = NULL;
Create(width, height, c);
}
MyBitmap::~MyBitmap()
{
delete m_lpFileHeader;
delete m_lpInfoHead;
delete []m_Image;
m_bValid = false;
m_lpFileHeader = NULL;
m_lpInfoHead = NULL;
m_Image = NULL;
}
bool MyBitmap::IsBitmapValid()
{
return m_bValid;
}
void MyBitmap::Open(char* Filename)
{
if(IsBitmapValid())
Close();
FILE *fp = fopen(Filename, "rb");
if(!fp)
throw "MyBitmap::Open - file open error";
m_lpFileHeader = new BITMAPFILEHEADER;
if(m_lpFileHeader == NULL)
throw "MyBitmap::Open - out of memory";
m_lpInfoHead = new BITMAPINFOHEADER;
if(m_lpInfoHead == NULL)
throw "MyBitmap::Open - out of memory";
fread(m_lpFileHeader, sizeof(BITMAPFILEHEADER), 1, fp);
fread(m_lpInfoHead, sizeof(BITMAPINFOHEADER), 1, fp);
byte *image = new byte[m_lpInfoHead->biSizeImage];
if(image == NULL)
throw "MyBitmap::Open - out of memory";
fread(image, 1, m_lpInfoHead->biSizeImage, fp);
m_Image = new MyColor[m_lpInfoHead->biWidth * m_lpInfoHead->biHeight];
if(m_Image == NULL)
throw "MyBitmap::Open - out of memory";
double thex, they, thez;
long addr;
long bytePerline = m_lpInfoHead->biSizeImage / m_lpInfoHead->biHeight;
for(int x = 0 ; x < m_lpInfoHead->biWidth ; x++)
{
for(int y = 0 ; y < m_lpInfoHead->biHeight ; y++)
{
addr = x * 3 + (m_lpInfoHead->biHeight - y - 1) * bytePerline;
thez = image[addr];
they = image[addr+1];
thex = image[addr+2];
m_Image[y * m_lpInfoHead->biWidth + x].X = thex;
m_Image[y * m_lpInfoHead->biWidth + x].Y = they;
m_Image[y * m_lpInfoHead->biWidth + x].Z = thez;
}
}
delete []image;
m_bValid = true;
}
void MyBitmap::Close()
{
if(m_bValid)
{
delete m_lpFileHeader;
m_lpFileHeader = NULL;
delete m_lpInfoHead;
m_lpInfoHead = NULL;
delete []m_Image;
m_Image = NULL;
m_bValid = false;
}
}
int MyBitmap::GetBytesPerLine()
{
if(!IsBitmapValid())
throw "MyBitmap::GetBytesPerLine - open first";
int bytePerline = m_lpInfoHead->biSizeImage / m_lpInfoHead->biHeight;
return bytePerline;
}
long MyBitmap::GetWidth()
{
if(!IsBitmapValid())
throw "MyBitmap::GetWidth - open first";
return m_lpInfoHead->biWidth;
}
long MyBitmap::GetHeight()
{
if(!IsBitmapValid())
throw "MyBitmap::GetHeight - open first";
return m_lpInfoHead->biHeight;
}
void MyBitmap::Write2File(char* Filename)
{
if(!IsBitmapValid())
throw "MyBitmap::Write2File - no image exists";
FILE *fp = fopen(Filename, "w+b");
if(!fp)
throw "MyBitmap::Write2File - file open error";
fwrite(m_lpFileHeader, sizeof(BITMAPFILEHEADER), 1, fp);
fwrite(m_lpInfoHead, sizeof(BITMAPINFOHEADER), 1, fp);
byte *image = new byte[m_lpInfoHead->biSizeImage];
if(image == NULL)
throw "MyBitmap::Write2File - out of memory";
memset(image, 0, m_lpInfoHead->biSizeImage);
double thex, they, thez;
long addr;
long bytePerline = this->GetBytesPerLine();
for(int x = 0 ; x < m_lpInfoHead->biWidth ; x++)
{
for(int y = 0 ; y < m_lpInfoHead->biHeight ; y++)
{
addr = x * 3 + (m_lpInfoHead->biHeight - y - 1) * bytePerline;
thex = m_Image[y * m_lpInfoHead->biWidth + x].X;
they = m_Image[y * m_lpInfoHead->biWidth + x].Y;
thez = m_Image[y * m_lpInfoHead->biWidth + x].Z;
image[addr] = thez;
image[addr+1] = they;
image[addr+2] = thex;
}
}
fwrite(image, 1, m_lpInfoHead->biSizeImage, fp);
delete []image;
fclose(fp);
}
void MyBitmap::Create(int width, int height, MyColor c)
{
if(!(width > 0 && height > 0))
throw "MyBitmap::Create - width / height non-positive";
if(this->IsBitmapValid())
Close();
m_lpFileHeader = new BITMAPFILEHEADER;
if(m_lpFileHeader == NULL)
throw "MyBitmap::Create - out of memory";
memset(m_lpFileHeader, 0, sizeof(BITMAPFILEHEADER));
m_lpInfoHead = new BITMAPINFOHEADER;
if(m_lpInfoHead == NULL)
throw "MyBitmap::Create - out of memory";
memset(m_lpInfoHead, 0, sizeof(BITMAPINFOHEADER));
m_Image = new MyColor[width * height];
if(m_Image == NULL)
throw "MyBitmap::Create - out of memory";
int linebytes = width * 3;
while(linebytes % 4)
linebytes ++;
//位图文件大小
int ImageSize = linebytes * height +
sizeof(BITMAPFILEHEADER) +
sizeof(BITMAPINFOHEADER);
m_lpFileHeader->bfType = (WORD)0x4d42;
m_lpFileHeader->bfSize = ImageSize;
m_lpFileHeader->bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
m_lpInfoHead->biBitCount = 24;
m_lpInfoHead->biWidth = width;
m_lpInfoHead->biHeight = height;
m_lpInfoHead->biPlanes = 1;
m_lpInfoHead->biCompression = BI_RGB;
m_lpInfoHead->biSize = sizeof(BITMAPINFOHEADER);
m_lpInfoHead->biSizeImage = linebytes * height;
for(int x = 0 ; x < width ; x ++)
{
for(int y = 0 ; y < height ; y ++)
{
m_Image[width * y + x] = c;
}
}
m_bValid = true;
}
MyBitmap& MyBitmap::operator=(MyBitmap& bitmap)
{
if(bitmap.IsBitmapValid()) //valid
{
this->Close();
m_lpFileHeader = new BITMAPFILEHEADER;
if(m_lpFileHeader == NULL)
throw "MyBitmap::operator= - out of memory";
memcpy(m_lpFileHeader, bitmap.m_lpFileHeader, sizeof(BITMAPFILEHEADER));
m_lpInfoHead = new BITMAPINFOHEADER;
if(m_lpInfoHead == NULL)
throw "MyBitmap::operator= - out of memory";
memcpy(m_lpInfoHead, bitmap.m_lpInfoHead, sizeof(BITMAPINFOHEADER));
m_Image = new MyColor[bitmap.GetWidth() * bitmap.GetHeight()];
if(m_Image == NULL)
throw "MyBitmap::operator= - out of memory";
memcpy(m_Image, bitmap.m_Image, bitmap.GetWidth() * bitmap.GetHeight() * sizeof(MyColor));
m_bValid = true;
}
else //not valid
{
this->Close();
}
return (*this);
}
bool MyBitmap::operator==(MyBitmap& bitmap)
{
if(this->m_bValid == false && bitmap.m_bValid == false)
return true;
if( (this->m_bValid == true && bitmap.m_bValid == false) ||
(this->m_bValid == false && bitmap.m_bValid == true) )
return false;
//all valid
int ret;
ret = memcmp(this->m_lpFileHeader, bitmap.m_lpFileHeader, sizeof(BITMAPFILEHEADER));
if(ret != 0)
return false;
ret = memcmp(this->m_lpInfoHead, bitmap.m_lpInfoHead, sizeof(BITMAPINFOHEADER));
if(ret != 0)
return false;
ret = memcmp(this->m_Image,
bitmap.m_Image, sizeof(MyColor) * bitmap.GetWidth() * bitmap.GetHeight());
if(ret != 0)
return false;
return true;
}
void MyBitmap::GetAt(int x, int y, MyColor& c)
{
if( ! (x >= 0 && y >= 0 && x < this->GetWidth() && y < this->GetHeight()) )
throw "MyBitmap::GetAt - x / y beyond the bound";
if(!IsBitmapValid())
throw "MyBitmap::GetAt - no image exists";
c = m_Image[this->GetWidth() * y + x];
}
void MyBitmap::SetAt(int x, int y, MyColor& c)
{
if( ! (x >= 0 && y >= 0 && x < this->GetWidth() && y < this->GetHeight()) )
throw "MyBitmap::SetAt - x / y beyond the bound";
if(!IsBitmapValid())
throw "MyBitmap::SetAt - no image exists";
m_Image[this->GetWidth() * y + x] = c;
}
MyColor& MyBitmap::GetRef(int x, int y)
{
if( ! (x >= 0 && y >= 0 && x < this->GetWidth() && y < this->GetHeight()) )
throw "MyBitmap::GetRef - x / y beyond the bound";
if(!IsBitmapValid())
throw "MyBitmap::GetRef - no image exists";
return m_Image[this->GetWidth() * y + x];
}
void MyBitmap::GetImageSection(int x, int y, int width, int height, MyBitmap& bitmap)
{
if( ! (x >= 0 && y >= 0 && x+width-1 < this->GetWidth() && y+height-1 < this->GetHeight() &&
width > 0 && height > 0) )
throw "MyBitmap::GetImageSection - x / y / widht / height beyond the bound";
bitmap.Create(width, height);
MyColor c;
for(int i = 0 ; i < width ; i ++)
{
for(int j = 0 ; j < height ; j ++)
{
this->GetAt(i + x, j + y, c);
bitmap.SetAt(i, j, c);
}
}
}
void MyBitmap::SetImageSection(int x, int y, int width, int height, MyColor& c)
{
if( ! (x >= 0 && y >= 0 && x+width-1 < this->GetWidth() && y+height-1 < this->GetHeight() &&
width > 0 && height > 0) )
throw "MyBitmap::SetImageSection - x / y / widht / height beyond the bound";
for(int i = 0 ; i < width ; i ++)
{
for(int j = 0 ; j < height ; j ++)
{
this->GetRef(i + x, j + y) = c;
}
}
}
void MyBitmap::SetImageSection(int x, int y, int width, int height, MyBitmap& bitmap)
{
if( ! (x >= 0 && y >= 0 && x+width-1 < this->GetWidth() && y+height-1 < this->GetHeight() &&
width > 0 && height > 0 && width <= bitmap.GetWidth() && height <= bitmap.GetHeight()) )
throw "MyBitmap::SetImageSection - x / y / widht / height beyond the bound";
MyColor c;
for(int j = 0 ; j < height ; j ++)
{
for(int i = 0 ; i < width ; i ++)
{
bitmap.GetAt(i, j, c);
this->GetRef(i + x, j + y) = c;
}
}
}
(六)测试
主函数:
void main(void)
{
MyBitmap b;
b.Open("a.bmp");
b.Write2File("b.bmp");
b.Close();
}
(七)说明
本文实现了一个简单易用的24位真彩色位图类,避免了过多的接口和不必要的功能。通过对这个类进行扩充,还可以满足未来更多的需要。