win32彙編 屏幕截圖保存BMP 學習筆記之生成BMP文件

到目前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 ;必須要跟着0ByteCountPerPixel 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.從左到右–>屏幕左下角開始 右上角結束

全篇結束 完整代碼 請到這裏下載

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