我的一個MFC小項目

雖然是小項目,但是還是有點挑戰性的,因爲從來沒有做過一個比較正式的項目;之前在編程過程當中,都是比較粗糙,想到什麼就寫什麼,在沒有一個系統的架構前提之下,雖可謂倚馬可待,但bug很多,多到自己想法都沒有了,最後不得不丟下個“四不像”的一堆代碼。

找個藉口原諒自己就是自覺閱讀之前寫下的代碼,然後認真總結和分析,談談自己的收穫。

其實也沒什麼,就一個處理圖像的東西,很多的東西已經被sdk封裝了,其實你能使用sdk到遊刃有餘的地步,那也是一種強的表現了,別被別人的閒言冷語冷落到“認爲sdk沒出息”。

重要的不是你學會了sdk什麼的,重要的是你在完成一個任務的過程當中的收穫,more or less。

功能列表

文件

獲取圖片信息

圖像操作

打開

像素寬度

移動

*重新加載

像素高度

旋轉

另存爲

兩點距離

放大

保存

每行象素所佔字節數

縮小

退出

當然還有一些具體的要求。

 

因爲需要用到顯示位圖,所以我也突發奇想要自定義一個控件,專門用來對付位圖的處理,包括移動旋轉之類的,感受到了吧,OO。開始的時候不去借助網絡資源,自己操手幹起來,但是遇到的問題還是蠻多的。

離開了win32一段時間了,來到MFC就忘本了。現在背背,

image

 

 

大概的過程就是這個(其實還是翻了書)。

自定義控件的思路也是這樣的,只是createwindow之後的東西(消息處理過程還是要我們操手)IDE幫我們做好了,注意就算在win32下我們還是要對控件createwindow的。上面說消息處理過程還是要自己動手,就是標準控件的消息處理過程已經被包裝好了,但是我們自定義的控件會有我們自己想要處理的消息。

在對話框資源窗口添加了Custom Control之後,在屬性對話框中要增加Class,在這裏要注意填寫的是你的註冊窗口類而不是你的窗口類,明白人懂的。

添加了一個窗口類(繼承自CWnd)之後,裏邊什麼都沒有,當然除了動態創建,消息映射,以及CWnd的一些函數還是有的,因爲這是CWnd自有的,“爸爸有,兒子也要有”(C++裏邊的“遺傳”好蛋疼)。任何一個窗口都要註冊窗口類,所以一定要先在構造函數裏面註冊好。代碼如下:

BOOL CBMPViewer::RegisterWndClass()
{
    WNDCLASS windowclass; 
    HINSTANCE hInst = AfxGetInstanceHandle();
 
    windowclass.style =  CS_HREDRAW | CS_VREDRAW |CS_OWNDC; 
    windowclass.lpfnWndProc = ::DefWindowProc; 
    windowclass.cbClsExtra = windowclass.cbWndExtra = 0; 
    windowclass.hInstance = hInst; 
    windowclass.hIcon = NULL; 
    windowclass.hCursor = AfxGetApp()->LoadStandardCursor(IDC_ARROW); 
    windowclass.hbrBackground =(HBRUSH)::CreateSolidBrush(#808a87);
    //::GetSysColorBrush(COLOR_WINDOW); 
    windowclass.lpszMenuName = NULL; 
    windowclass.lpszClassName = L"MYCLASS";
 
    if (!AfxRegisterClass(&windowclass)) 
    
        AfxThrowResourceException(); 
        return FALSE; 
    
    return TRUE;
}

之後的話就不廢話了,想幹嘛就幹嘛。

在自定義控件當中只有在onpaint()中的設備環境纔是有效的,在其他的方法當中你也無法get到,這讓我很疑惑,調試了一下,發現連hwnd都是unused的(好奇怪)。如果你發現了,就告訴我。

BitmapSize=bmp.bmWidthBytes*bmp.bmHeight; 在這一次實驗當中,涉及到了比較多的BMP圖片的學習,包括基本的組成和操作(放大縮小等)。下面是DIB的文件格式:

 

 

image

這就是BMP圖片的組成,可以當成是他的數據結構,這裏面就存放了顯示圖片需要的數據。結構體內的數據比較複雜,但是在這裏講幾點。

offsetbit 爲文件頭到像素爲的位移;

imagesize爲像素位的大小;

size爲整個DIB文件的大小。

文件頭當中,offsetbit 的計算其實很簡單,就是文件頭和信息頭的大小加起來(當然默認是24位的位圖),但是因爲他們是固定大小的的所以比較簡單。

imagesize計算的方式看起來很複雜:

((((m_bdBmpdata.width*32)+31)/32)*4)*m_bdBmpdata.height,因爲所佔字節數不足4的倍數的話要用0來補全,慢慢理解一下。

如果你準備的信息比較完善的話還可以用下面的公式來計算:

BitmapSize=bmp.bmWidthBytes*bmp.bmHeight;

第一個是BITMAP結構中的字段,表示每行所佔的字節數。

8位位圖除了可以索引彩色圖像外,也可以是灰階圖像,相信更多的是用於灰度的圖像,既然有8位的灰階圖像,也就是說從白到黑分成256種漸變,那16位灰階圖像也是存在的,只不過從白到黑分成2^16種漸變;但這是一種很大的浪費我覺得,因爲灰階圖像應用不是非常廣,在一些專業領域或許會用到。

 

而如今PC下的更多的是24位的,32位的,16位的也有。一開始的時候還不知道什麼是RGB,其實簡單來說就是Red,Green,Blue分別用一定的位數來存儲他們的值。16位以上多見RGB格式的圖,16位位圖圖片還可分爲R5G6B5,R5G5B5X1(有1位不攜帶信息,其實就是最高位),R5G5B5A1,R4G4B4A4 等等。

其中RGB555,BITMAPINFOHEADER信息頭字段biCompression成員的值是BI_RGB,它的存儲格式是:

XRRRRRGG GGGBBBBB,注意它跟24位位圖沒有顏色表。

24位位圖更簡單,它的存儲格式是:

RRRRRRRR GGGGGGGG BBBBBBBB

而32位的位圖,新增了一個透明度,這在XP下很常見了:

AAAAAAAA RRRRRRRR GGGGGGGG BBBBBBBB

 

圖形處理中,通常把RGB三種顏色信息稱爲紅通道、綠通道和藍通道,相應的把透明度稱爲Alpha通道。由RGB形成的圖像均稱做真彩色


OK,差不多就囉嗦到這裏吧。真正在寫程序中遇到的問題是位圖的轉換:將24位真彩位圖轉換爲8位灰階位圖。而沒有上面的東西,下面的東西看起來會很吃力

 

問題描述的清楚一點吧:原圖片是24位位圖,而如今項目當中的絕多數操作都是基於8位位圖,不可能讓客戶多一手準備8位的位圖,自然而然轉換的任務就落在了程序的頭上了。

 

我的解決方法是這樣:就用一個方法來實現這個轉換的操作,而函數的參數就一個路徑(讓客戶只提供路徑,這方便很多了),在轉換之後再寫爲另一個8位位圖文件。

 

或許一開始拿到這樣的任務,還是摸不着頭腦,不知道從何下手;我畫了下面的一張圖,

image

 

Y是什麼,Y就是明度,在灰階位圖當中,只有明度(灰階值),而沒有色度和濃度,它和RGB是有區別的,是兩種不同的顏色編碼方法,RGB注重的是色彩,而YUV注重的亮度。幸運的是,兩者之間可以進行轉換,

 

\begin{array}{rll}Y &= 0.299 * R + 0.587 * G + 0.114 * B \\U &= 0.436 * (B - Y) / (1 - 0.114) \\V &= 0.615 * (R - Y) / (1 - 0.299)\end{array}

 

對,上面的第一個公式正是我們想要的,熟悉DIB格式的童鞋都應該有思路了吧,像素位裏面就存着RGB,它與上面的公式能夠幫助我們將24位位圖轉換爲8位灰階位圖。

 

其中有個問題,就是涉及到位圖的每行所佔字節數的問題,重申一次,Windows規定一個掃描行所佔的字節數必須是4的倍數(即以long爲單位),不足的以0填充,每行所佔字節數的計算公式:((((width*32)+31)/32)*4)。

 

舉一個簡單的例子:假如一個8位灰階位圖,它的寬是31,則每一行需要31個字節存儲,因爲字節數必須是4的整倍數,所以應該是32字節,此時BITMAPINFOHEADER字段當中,biWidth=31,biBitCount=8,但要清楚,每行所佔字節是32字節。因此在做每行轉換爲8位位圖的時候,我們需要每個掃描行+32,而不是每個掃描行+31。

所以下面所展示的程序,看到寬度轉換可以回到這裏找答案。上代碼吧,

void Convertto8Bit()
{
    HANDLE hFile;                   //文件句柄
    DWORD dwWritten;            //記錄以寫入的字節數
    hFile   =   CreateFile(L"F:\\1.bmp",GENERIC_READ,
    FILE_SHARE_READ,
    NULL,
    OPEN_EXISTING,
    FILE_FLAG_SEQUENTIAL_SCAN,
    NULL);
 
    BITMAPFILEHEADER  bmfh;                 //文件頭
    ReadFile(hFile,&bmfh,sizeof(BITMAPFILEHEADER),&dwWritten,NULL);
 
    BITMAPINFOHEADER   bmif;                    //信息頭
    ReadFile(hFile,&bmif,40,&dwWritten,NULL); 
 
    DWORD   dwSizeImage;                        //源文件像素位大小
    dwSizeImage   =                                 //計算 像素位 的大小
        bmfh.bfSize -
        sizeof(BITMAPFILEHEADER) -
        sizeof(BITMAPINFOHEADER);
 
    BYTE * pBits   =   new BYTE[dwSizeImage];  
    ReadFile(hFile,pBits,dwSizeImage,&dwWritten,NULL);
 
    ::CloseHandle(hFile);
 
    long  lSrcWidth = bmif.biWidth;                 //原圖長與寬
    long lSrcHeight = bmif.biHeight;
 
    long lLineBytes;                            //原圖每行總字節數
    long lScanWidth;                        //轉換爲8位圖之後的寬度,必須是大於原圖且爲4的倍數
 
    lLineBytes = ((lSrcWidth*3)/4)*4;           //爲了與8位位圖數據對齊,原圖每行總字節數也必須爲4的倍數,
    if(lLineBytes<lSrcWidth*3)                   //在這裏轉換需要比原圖每行總字節數大
        lLineBytes += 4;
 
    lScanWidth = (lSrcWidth/4)*4;           //8位位圖的寬度必須爲4的倍數,在這裏轉換需要比原圖寬度大
    if(lScanWidth<lSrcWidth)
        lSrcWidth += 4;
 
    DWORD dwSizeNewImage = lSrcWidth * lSrcHeight + 2;          //爲什麼要預留兩位呢
    BYTE * bits = new BYTE[dwSizeNewImage];
 
    for(int i=0; i<lSrcHeight; i++)
    {
        for(int j=0; j<lSrcWidth; j++)
        {
            BYTE color[3];                      //對應RGB的紅綠藍值
            DWORD dwColorTemp;          //Y值,RGB轉換爲Y之後的值Y=0.299*R+0.587*G+0.114*B
            for(int s=0;s<3;s++)         //一個RGB對應一個Y值
                color[s]=pBits[i*lLineBytes+j*3+s];
 
            dwColorTemp=unsigned int(color[2]*0.299+color[1]*0.587+color[0]*0.114);    
 
            if(dwColorTemp>255)
                dwColorTemp = 255;
 
            if(dwColorTemp<0)
                dwColorTemp = 0;
            bits[i*lScanWidth+j]=(unsigned char)dwColorTemp;
        }
    }
    bits[dwSizeNewImage-1] = 0;
    bits[dwSizeNewImage-2] = 0;
 
    RGBQUAD   *rgbQuad   =   new   RGBQUAD[256];   //顏色表
    for(int i=0;i<256;i++)  
    {  
        rgbQuad[i].rgbBlue   =   i;    
        rgbQuad[i].rgbGreen   =   i;    
        rgbQuad[i].rgbRed   =   i;    
        rgbQuad[i].rgbReserved   =   0;    
    }  
 
    //完善8位位圖的文件頭和信息頭字段
 
    bmfh.bfOffBits = sizeof(BITMAPFILEHEADER) +
        sizeof(BITMAPINFOHEADER) +
        256*sizeof(RGBQUAD);                //顏色表有256個
 
    bmfh.bfReserved1 = 0;
    bmfh.bfReserved2 = 0;
     
    bmfh.bfSize = bmfh.bfOffBits + dwSizeNewImage;          //size in byte of the file
 
    bmif.biBitCount = 8;                                                            //信息頭中bitcounts改爲8
    bmif.biSizeImage =  dwSizeNewImage;
 
    //寫入轉換後得到的8位位圖
    hFile=CreateFile(L"F://2.bmp",GENERIC_WRITE,
        FILE_SHARE_WRITE,NULL,
        CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
    ::WriteFile(hFile,&bmfh,sizeof(BITMAPFILEHEADER),&dwWritten,NULL);
    ::WriteFile(hFile,&bmif,sizeof(BITMAPINFOHEADER),&dwWritten,NULL);
    ::WriteFile(hFile,rgbQuad,256*sizeof(RGBQUAD),&dwWritten,NULL);
    ::WriteFile(hFile,bits,dwSizeNewImage,&dwWritten,NULL);
    ::CloseHandle(hFile);
}

童鞋們,在這裏缺了很多的檢測,文件大小的檢測,如果位圖超過4M,就不被允許了,所以一開始應該結合getfilesize來檢測,還有讀入數據的時候是否已經讀入正確的字節數,特別是文件頭和信息頭;BITMAPFILEHEADER中bftype字段應該爲‘BM’;文件句柄的檢測等等,因爲程序長,我忽略了。一個優秀程序員都不應該忘記這些,很明顯菜鳥一個啊....

代碼有修改,主要是:

  1. 計算8位位圖像素位使用了更高效的整點運算;
  2. 去除多餘的變量檢測。

求網友們支招:在32位位圖中有Alpha值,在轉換成8位灰階位圖的時候可以直接省略嗎?                                                                                            

很容易可以得到32位位圖彩圖轉換爲8位的灰階位圖,看圖:

image 

 

 32位位圖多出一個Alpha字節,用來描述圖片的透明度,根據這一特性,可以將Alpha特意忽略,然後將緊跟其後的RGB按照24位位圖轉8位灰階位圖的方法就可以很輕易地達到我們的目的。這一次的位圖轉換接口我優化了一下,下面給出代碼

void Convertto8Bit(LPWSTR lpSrcFileName,LPWSTR lpDestFileName)
{
    HANDLE hFile;                   //文件句柄
    DWORD dwWritten;            //記錄以寫入的字節數
    hFile   =   CreateFile(lpSrcFileName,GENERIC_READ,
    FILE_SHARE_READ,
    NULL,
    OPEN_EXISTING,
    FILE_FLAG_SEQUENTIAL_SCAN,
    NULL);
 
    BITMAPFILEHEADER  bmfh;                 //文件頭
    ReadFile(hFile,&bmfh,sizeof(BITMAPFILEHEADER),&dwWritten,NULL);
 
    BITMAPINFOHEADER   bmif;                    //信息頭
    ReadFile(hFile,&bmif,40,&dwWritten,NULL); 
 
    DWORD   dwSizeImage;                        //源文件像素位大小
    dwSizeImage   =                                 //獲取 像素位 的大小,分配空間
        bmif.biSizeImage;
 
    BYTE * pBits   =   new BYTE[dwSizeImage];  
    ReadFile(hFile,pBits,dwSizeImage,&dwWritten,NULL);
 
    ::CloseHandle(hFile);
 
    long  lSrcWidth = bmif.biWidth;                 //原圖長與寬
    long lSrcHeight = bmif.biHeight;
 
    long lLineBytes;                            //原圖每行總字節數
    long lScanWidth;                        //轉換爲8位圖之後的寬度,必須是大於原圖且爲4的倍數
 
    lLineBytes = (lSrcWidth*4);        
    /*
    if(lLineBytes<lSrcWidth*4)                   //在這裏轉換需要比原圖每行總字節數大
        lLineBytes += 4;
    */
 
    lScanWidth = (lSrcWidth/4)*4;           //8位位圖的寬度必須爲4的倍數,在這裏轉換需要比原圖寬度大
    if(lScanWidth<lSrcWidth)
        lSrcWidth += 4;
 
    DWORD dwSizeNewImage = lSrcWidth * lSrcHeight + 2;          //爲什麼要預留兩位呢
    BYTE * bits = new BYTE[dwSizeNewImage];
 
    for(int i=0; i<lSrcHeight; i++)
    {
        for(int j=0; j<lSrcWidth; j++)
        {
            BYTE color[3];                      //對應RGB的紅綠藍值
            DWORD dwColorTemp;          //Y值,RGB轉換爲Y之後的值Y=0.299*R+0.587*G+0.114*B
            for(int s=0;s<3; s++)            //一個RGB對應一個Y值
                color[s]=pBits[i*lLineBytes+j*4+s+1];
 
            /*
            dwColorTemp=unsigned int(color[2]*0.299+color[1]*0.587+color[0]*0.114);    
            */
            //換成更高效的計算,確實快很多
            dwColorTemp=unsigned int(color[2]*30+color[1]*59+color[0]*11+50)/100;    
 
            //多餘的變量檢測
            /*
            if(dwColorTemp>255)
                dwColorTemp = 255;
            if(dwColorTemp<0)
                dwColorTemp = 0;
            */
 
            bits[i*lScanWidth+j]=(unsigned char)dwColorTemp;
        }
    }
    bits[dwSizeNewImage-1] = 0;
    bits[dwSizeNewImage-2] = 0;
 
    RGBQUAD   *rgbQuad   =   new   RGBQUAD[256];   //顏色表
    for(int i=0;i<256;i++)  
    {  
        rgbQuad[i].rgbBlue   =   i;    
        rgbQuad[i].rgbGreen   =   i;    
        rgbQuad[i].rgbRed   =   i;    
        rgbQuad[i].rgbReserved   =   0;    
    }  
 
    //完善8位位圖的文件頭和信息頭字段
 
    bmfh.bfOffBits = sizeof(BITMAPFILEHEADER) +
        sizeof(BITMAPINFOHEADER) +
        256*sizeof(RGBQUAD);                //顏色表有256個
 
    bmfh.bfReserved1 = 0;
    bmfh.bfReserved2 = 0;
     
    bmfh.bfSize = bmfh.bfOffBits + dwSizeNewImage;          //size in byte of the file
 
    bmif.biBitCount = 8;                                                            //信息頭中bitcounts改爲8
    bmif.biSizeImage =  dwSizeNewImage;
 
    //寫入轉換後得到的8位位圖
    hFile=CreateFile(lpDestFileName,GENERIC_WRITE,
        FILE_SHARE_WRITE,NULL,
        CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
    ::WriteFile(hFile,&bmfh,sizeof(BITMAPFILEHEADER),&dwWritten,NULL);
    ::WriteFile(hFile,&bmif,sizeof(BITMAPINFOHEADER),&dwWritten,NULL);
    ::WriteFile(hFile,rgbQuad,256*sizeof(RGBQUAD),&dwWritten,NULL);
    ::WriteFile(hFile,bits,dwSizeNewImage,&dwWritten,NULL);
    ::CloseHandle(hFile);
}

換湯不換藥的。另外在這裏問問大牛們,用MFC做圖像處理的工程,是不是最好用單文檔或者單文檔之類的框架?



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章