雖然是小項目,但是還是有點挑戰性的,因爲從來沒有做過一個比較正式的項目;之前在編程過程當中,都是比較粗糙,想到什麼就寫什麼,在沒有一個系統的架構前提之下,雖可謂倚馬可待,但bug很多,多到自己想法都沒有了,最後不得不丟下個“四不像”的一堆代碼。
找個藉口原諒自己就是自覺閱讀之前寫下的代碼,然後認真總結和分析,談談自己的收穫。
其實也沒什麼,就一個處理圖像的東西,很多的東西已經被sdk封裝了,其實你能使用sdk到遊刃有餘的地步,那也是一種強的表現了,別被別人的閒言冷語冷落到“認爲sdk沒出息”。
重要的不是你學會了sdk什麼的,重要的是你在完成一個任務的過程當中的收穫,more or less。
功能列表
文件 |
獲取圖片信息 |
圖像操作 |
打開 |
像素寬度 |
移動 |
*重新加載 |
像素高度 |
旋轉 |
另存爲 |
兩點距離 |
放大 |
保存 |
每行象素所佔字節數 |
縮小 |
退出 |
當然還有一些具體的要求。
因爲需要用到顯示位圖,所以我也突發奇想要自定義一個控件,專門用來對付位圖的處理,包括移動旋轉之類的,感受到了吧,OO。開始的時候不去借助網絡資源,自己操手幹起來,但是遇到的問題還是蠻多的。
離開了win32一段時間了,來到MFC就忘本了。現在背背,
大概的過程就是這個(其實還是翻了書)。
自定義控件的思路也是這樣的,只是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的文件格式:
這就是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位位圖文件。
或許一開始拿到這樣的任務,還是摸不着頭腦,不知道從何下手;我畫了下面的一張圖,
Y是什麼,Y就是明度,在灰階位圖當中,只有明度(灰階值),而沒有色度和濃度,它和RGB是有區別的,是兩種不同的顏色編碼方法,RGB注重的是色彩,而YUV注重的亮度。幸運的是,兩者之間可以進行轉換,
對,上面的第一個公式正是我們想要的,熟悉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位位圖 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’;文件句柄的檢測等等,因爲程序長,我忽略了。一個優秀程序員都不應該忘記這些,很明顯菜鳥一個啊....
代碼有修改,主要是:
- 計算8位位圖像素位使用了更高效的整點運算;
- 去除多餘的變量檢測。
求網友們支招:在32位位圖中有Alpha值,在轉換成8位灰階位圖的時候可以直接省略嗎?
很容易可以得到32位位圖彩圖轉換爲8位的灰階位圖,看圖:
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做圖像處理的工程,是不是最好用單文檔或者單文檔之類的框架?