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.从左到右–>屏幕左下角开始 右上角结束

全篇结束 完整代码 请到这里下载

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