到目前BMP內容已經生成在內存
可是不能直接保存爲BMP文件
因爲BMP需要在開頭寫一些header數據 總計54字節
如下圖
BMP header都是些什麼呢
以windows平臺爲例 用途如下
我開始明白raiky的第二個函數在幹嘛了
很多代碼都在生成這個header
接下來我們也要生成BMP Header
要怎麼生成呢 難不成一個字接一個字節的自己寫嗎—NO
WINDOWS.INC已經定義了兩個STRUCT
BITMAPFILEHEADER
BITMAPINFOHEADER
我們直接用這兩個Struct來生成header
BMP header =BMP File Header+BMP Info Header
注意順序file header 在前
先看下面全局變量,這裏僅貼出了接下來用到的部分
.data
SaveFileName db '11.bmp',0 ;必須要跟着0啊
ByteCountPerPixel dd 3;位圖中每個像素所佔字節數
.data?
ImgWidth dd ?
ImgHeight dd ?
dwBMPSize dd ? ; 位圖文件大小(不含文件頭)
dwWritten dd ? ;寫入文件字節數
FileHdr dd ? ;定義文件句柄
bmFileHdr BITMAPFILEHEADER <> ;位圖屬性結構
bmInfoHdr BITMAPINFOHEADER <> ;位圖文件頭結構
先生成BMP INFO HEADER
;-----start to fill BMP INFO HEADER------
mov eax, sizeof BITMAPINFOHEADER
mov bmInfoHdr.biSize,eax ;bmInfoHeader.biSize = sizeof(BITMAPINFOHEADER);
mov eax,ImgWidth ;
mov bmInfoHdr.biWidth,eax
mov eax,ImgHeight
mov bmInfoHdr.biHeight,eax
mov bmInfoHdr.biPlanes,1
mov bmInfoHdr.biBitCount,24
再下來 生成BMP File Header
;------START TO FILL BMP FILE HEADER--------
mov bmFileHdr.bfType,4d42h
mov eax,sizeof BITMAPFILEHEADER
add eax,sizeof BITMAPINFOHEADER
mov bmFileHdr.bfOffBits, eax
;;MUL r/m ;參數是乘數
;如果參數是 r32/m32, 將把 EAX 做乘數, 結果放在 EDX:EAX
mov eax, bmInfoHdr.biWidth ;
mul bmInfoHdr.biHeight
mul ByteCountPerPixel ;其實就是爲了乘以3 可是不能用立即數稱
mov dwBMPSize,eax ;暫存一下
add eax,bmFileHdr.bfOffBits
mov bmFileHdr.bfSize , eax ;屏幕分辨率增大會不會eax不夠存啊
;bmFileHeader.bfSize = bmFileHeader.bfOffBits + ((bmInfoHeader.biWidth * bmInfoHeader.biHeight) * 3); ///3=(24 / 8)
;------------------End of File header
上面dwBMPSize的生成對分辨率是1920*1080的屏幕沒有問題,可以正常使用 因爲1920是4的整數倍
可當你遇到1366*768的分辨率保存的圖就會出錯 無法正常顯示
因爲1366*3無法整除4
其實BMP每行(row)數據要求4字節(32bit)對齊 不足的必須00補齊
bitblt生成的data就已經嚴格按照規則生成了 我們只需要計算出正確的size
https://en.wikipedia.org/wiki/BMP_file_format
這裏是修正過的計算dwBMPSize的code,如果你有下載代碼請自行按照下面的修改下吧哈哈
; RowSize= ((Bitmap.bmWidth *wBitCount+31)/32)* 4 ;每行數據必須爲4字節(32bit)的倍數也就是對齊 必須經過 這樣的運算
; dwBMPSize=RowSize*|bmHeight| ;https://en.wikipedia.org/wiki/BMP_file_format
; wiki頁面說 因爲height值可能爲負 這裏有必要先取絕對值 暫時不處理等到問題再說吧
; 剛開始1920*1080沒有在意32bit對齊 換成1366*768分辨率就出錯了 必須用這個公式計算dwBMPSize
mov eax, bmpInfo.bmiHeader.biWidth ;
mul BitsCountPerPixel ;其實就是爲了乘以24 可是不能用立即數
add eax,31
mov ebx,32
div ebx
mov ebx,4
mul ebx
mul bmpInfo.bmiHeader.biHeight
mov dwBMPSize,eax ;暫存一下
創建文件
注意這裏我在第一個參數(文件名)前加了 offset
;------創建位圖文件
invoke CreateFile,
offset SaveFileName,
GENERIC_WRITE,
0,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL or FILE_FLAG_SEQUENTIAL_SCAN,NULL
mov FileHdr,eax
.if FileHdr==INVALID_HANDLE_VALUE
invoke ExitProcess,NULL
.endif
接下來寫入文件
;寫入位圖文件頭 先寫FILE HEADER 再寫info header
;WriteFile(fh, (LPSTR)&bmfHdr, sizeof(BITMAPFILEHEADER), &dwWritten, NULL)
invoke WriteFile,FileHdr,addr bmFileHdr,sizeof BITMAPFILEHEADER,addr dwWritten,NULL
invoke WriteFile,FileHdr,addr bmInfoHdr,sizeof BITMAPINFOHEADER,addr dwWritten,NULL
;addr dwWritten= lpNumberOfBytesWritten Long,實際寫入文件的字節數量(此變量是用來返回的 )
;寫入位圖文件其餘內容
invoke WriteFile,FileHdr,Data,dwBMPSize,addr dwWritten,NULL ;
注意最後一個WriteFile 和前兩個WriteFile 不同
Data前面沒有addr
因爲data的內容已經算是指針了
到此 BMP文件可以順利的生成了
當然文件的生成也不是一次就成功
中間也遇到了一些問題,比如:
1.文件名變量使用時候前面沒有offset
2.文件名變量後少了 ,0
3.DUMP MEM犯二 把data地址給提前改掉了結果保存輸出的時候超出1027就出錯
通過使用print、dump、OllyDbg 我都順利的一一解決了這些問題
通過使用Ollydbg大大加深了對指針的理解
下面是用到的一些debug code
print ustr$(eax),"return eax",13,10
;DbgDump offset BmpHeader,54;require include debug.inc
DbgDump Data,16;這裏拿掉了offset 就以Data的內容作爲地址 來取RAM的值
add Data,6220797 ;偏移到末尾三個字節 dump之後的16字節
DbgDump Data,16;這裏拿掉了offset 就以Data的內容作爲地址 來取RAM的值
BMP文件中保存的像素信息的順序問題
答案是 :1.從下到上 2.從左到右–>屏幕左下角開始 右上角結束
全篇結束 完整代碼 請到這裏下載