Chinaunix首页 | 论坛 | 博客
  • 博客访问: 18568621
  • 博文数量: 7460
  • 博客积分: 10434
  • 博客等级: 上将
  • 技术积分: 78178
  • 用 户 组: 普通用户
  • 注册时间: 2008-03-02 22:54
文章分类

全部博文(7460)

文章存档

2011年(1)

2009年(669)

2008年(6790)

分类: C/C++

2008-05-28 16:05:59

在图像处理的过程中,我们必然要对位图文件进行像素级的操作。一种办法是使用windows位图操作api或者是MFC中的CBitmap类;另外的办法是调用第三方的图像类,如CxImage类。

[原创] 一个简单的windows位图文件类的实现

(一)目的
在图像处理的过程中,我们必然要对位图文件进行像素级的操作。一种办法是使用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位真彩色位图类,避免了过多的接口和不必要的功能。通过对这个类进行扩充,还可以满足未来更多的需要。

阅读(554) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~