到目前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.从左到右–>屏幕左下角开始 右上角结束
全篇结束 完整代码 请到这里下载