打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
VFP下位图格式与低级操作
在VFP编程中我们经常需要对BMP位图文件进行操作,然而我相信90%以上的foxer们仅仅是停留在位图文件的显示上面,在碰到需要编程绘制一张属于自己的位图的时候往往不知所措,在这里我给大家简单介绍下如何用VFP绘制属于自己的位图文件,首先简单介绍下BMP文件的基本格式:
BMP是bitmap的缩写形式,bitmap顾名思义,就是位图也即Windows位图。它一般由4部分组成:文件头信息块、图像描述信息块、颜色表(在真彩色模式无颜色表)和图像数据区组成。在系统中以BMP为扩展名保存。
打开Windows的画图程序,在保存图像时,可以看到三个选项:2色位图(黑白)、16色位图、256色位图和24位位图。这是最普通的生成位图的工具,在这里讲解的BMP位图形式,主要就是指用画图生成的位图。
现在讲解BMP的4个组成部分:

1.文件头信息块
0000-0001:文件标识,为字母ASCII码"BM"。
0002-0005:文件大小。
0006-0009:保留,每字节以"00"填写。
000A-000D:记录图像数据区的起始位置。各字节的信息依次含义为:文件头信息块大小,图像描述信息块的大小,图像颜色表的大小,保留(为01)。
2.图像描述信息块
000E-0011:图像描述信息块的大小,常为28H。
0012-0015:图像宽度。
0016-0019:图像高度。
001A-001B:图像的plane总数(恒为1)。
001C-001D:记录像素的位数,很重要的数值,图像的颜色数由该值决定。
001E-0021:数据压缩方式(数值位0:不压缩;1:8位压缩;2:4位压缩)。
0022-0025:图像区数据的大小。
0026-0029:水平每米有多少像素,在设备无关位图(.DIB)中,每字节以00H填写。
002A-002D:垂直每米有多少像素,在设备无关位图(.DIB)中,每字节以00H填写。
002E-0031:此图像所用的颜色数,如值为0,表示所有颜色一样重要。
3.颜色表
颜色表的大小根据所使用的颜色模式而定:2色图像为8字节;16色图像位64字节;256色图像为1024字节。其中,每4字节表示一种颜色,并以B (蓝色)、G(绿色)、R(红色)、alpha(32位位图的透明度值,一般不需要)。即首先4字节表示颜色号1的颜色,接下来表示颜色号2的颜色,依此类推。
4.图像数据区
颜色表接下来位为位图文件的图像数据区,在此部分记录着每点像素对应的颜色号,其记录方式也随颜色模式而定,既 2色图像每点占1位(8位为1字节);16色图像每点占4位(半字节);256色图像每点占8位(1字节);真彩色图像每点占24位(3字节)。所以,整个数据区的大小也会随之变化。究其规律而言,可的出如下计算公式:图像数据信息大小=(图像宽度*图像高度*记录像素的位数)/8。
然而,未压缩的图像信息区的大小。除了真彩色模式外,其余的均大于或等于数据信息的大小。这是为什么呢?原因有两个:
1.BMP文件记录一行图像是以字节为单位的。因此,就不存在一个字节中的数据位信息表示的点在不同的两行中。也就是说,设显示模式位16色,在每个字节分配两个点信息时,如果图像的宽度位奇数,那么最后一个像素点的信息将独占一个字节,这个字节的后4位将没有意义。接下来的一个字节将开始记录下一行的信息。
2.为了显示的方便,除了真彩色外,其他的每种颜色模式的行字节数要用数据"00"补齐为4的整数倍。如果显示模式为16色,当图像宽为 19时,存储时每行则要补充4-(19/2+1)%4=2个字节(加1是因为里面有一个像素点独占了一字节)。如果显示模式为256色,当图像宽为19 时,每行也要补充4-19%4=1个字节。还有一点我要申明,当屏幕初始化为16或256色模式时,一定要设置调色板或修正颜色值,否则无法得到正确的图像颜色。
以上是BMP位图文件的基本组成结构,简单的说一张BMP文件就是 文件头+图像数据 组成的,所以首先我们先用VFP来创建一个BMP文件头:
假定BMP文件的高度为y、宽度为x,(姑且把位图比作XY坐标轴的一个区域),我们按照最简单的文件头来设定:
在定义这个文件头之前我们先自己写个十进制数据转换为二进制字符的函数:

FUNCTION bin2str(bin as long)
For i=1 To 4
k1='0x'+Subs(Tran(bin,"@0"),9,2)
k2='0x'+ Subs(Tran(bin,"@0"),7,2)
k3='0x'+ Subs(Tran(bin,"@0"),5,2)
k4='0x'+ Subs(Tran(bin,"@0"),3,2)
retu Chr(&k1)+Chr(&k2)+Chr(&k3)+Chr(&k4)
Endf
ENDFUNC
在二进制文件里,数值的存储是按照从低位到高位的顺序来填充的,这个函数很简单,就是把一个数字比如123先转换为十六进制Tran(123,"@0")= 0x0000007B,然后得到的字串就是
Chr(0x7B)+chr(0x00) +chr(0x00) +chr(0x00) ,引入这个函数的目的是为了简化下面的一些程序书写。
现在开始创建BMP文件头:
dime BmpHeader[12] &&为了使这个文件头更加清晰,我这里用数组来定义
BmpHeader[1]='BM' &&2字节,BMP文件标识,固定值。
BmpHeader[2]= bin2str(Int((x*3+3)/4)*4*y+54) & &4字节,存储图像大小信息。实际上就是文件头的54个字节加上图像数据区的字节数,按道理,一个像素是RGB 3字节组成,所以图像大小是x*y*3+54,但是BMP位图图像规定数据区每行的像素的个数必须是4的倍数,如果图像宽度刚好是4的倍数,那么这个数值就是x*y*3+54,如果不是4的倍数那么要用chr(0)来补齐,所以这里要写个通用的表达式:Int((x*3+3)/4)*4*y+54,例如x =100,y=200,那么Tran(Int((x*3+3)/4)*4*y+54,"@0")=0x0000EA96。
BmpHeader[3]=Chr(0)+Chr(0)+Chr(0)+Chr(0) &&4字节,固定值,为保留字节,每字节以"00"填写即可。
BmpHeader[4]=Chr(54))+Chr(0)+Chr(0)+Chr(0) &&4字节,图像信息的开始位置,我这里定义的这个文件头的大小是54字节,从第54字节开始就是图像信息了,所以这里是Chr(54).
BmpHeader[5]=chr(0x28)+chr(0)+chr(0)+chr(0) &&4字节,图像描述信息块的大小,常为28H。
BmpHeader[6]=bin2str(x)  &&4字节,位图宽度。
BmpHeader[7]= bin2str(y) && 4字节,位图高度。
BmpHeader[8]=chr(1)+chr(0) &&2字节,图像的plane总数(恒为1)。
BmpHeader[9]=chr(24)+chr(0) &&2字节,记录像素的位数,很重要的数值,图像的颜色数由该值决定,这里定义的是24位位图。
BmpHeader[10]=Chr(0)+Chr(0)+Chr(0)+Chr(0) &&4字节,数据压缩方式(数值位0:不压缩;1:8位压缩;2:4位压缩。
BmpHeader[11]=bin2str(Int((x*3+3)/4)*4*y) &&4字节,图像区数据的大小,计算方式上面已经说了。
BmpHeader[12]= Chrt(Spac(16), Chr(32),Chr(0))  &&剩下的16字节全部以CHR(0)来填写就可以了。
至此,一个24位位图的基本文件头我们就定义结束了.

BmpHeader=''
For i=1 to 12
BmpHeader= BmpHeader+ BmpHeader[i]
Endf
接下来是数据区了,数据区有个特别需要注意的地方就是:
位图保存时扫描顺序是从下至上,从左至右,即在位图数据区,位图第一行保存在位图数据区的最后,而位图最后一行保存在位图数据区的开始。
比如我们现在制作一个宽210高110的空白位图文件,那么就是这样了:
BmpHwnd=fcrea('位图.bmp')
Fwrite(BmpHwnd ,BmpHeader) &&写入文件头
Fwrite(BmpHwnd ,BmpBody) &&写入图像数据
Fclose(BmpHwnd)
现在再说下这里的BmpBody有一点需要注意:Windows要求每一行的数据的长度必须是4Bytes的整数倍,如果不是,要以值为0的字节补充,如果读取的时候不处理,会得到一个倾斜的图像。举例:一张479*360的24位色BMP图片,宽度是479个像素,每一行的数据长度是479*3(个字节用于RGB三色) =1437,不是4的倍数,所以,在这行数据的后面,要补上3个字节,值为0,凑成1440,达到4的倍数,然后,才是下一行的数据。在做程序的时候,需要把这些冗余点的值舍去。
至此,我们就可以写一个创建一个宽度为x,高度为y的空白位图的自定义函数了。(附后)
现在我们需要做的是在一张空白位图上画点了,我们知道了位图的图像数据是按照从下到上、从左到右的方式来排列的,刚好符合数学里的XY轴的数据模型,而且我们在做一些数据图的时候也正式按照XY轴的数据模型来实现的(我想微软之所以按照这样的方式来排列数据也正是因为此种道理吧,呵呵)。
首先我们先自己写一个可以在位图任意行任意列画一个点的函数,从文件头结束位置开始就是图像数据了,
每个点占RGB三字节,所以我们只要根据行和列就能确定这个点在图像数据区所在的开始位置,然后改写这个点为指定的颜色,那么我们相当于画点了。
   图像的基本要素是点和线,画线的的算法好坏是直接影响图像格栅化质量的根本,我们在这里引入比较适合BMP位图的DDA算法, DDA称为数值微分画线算法,是直线生成算法中最简单的一种,其它还有breshenham算法、中点算法等等。原理相当简单,就是最直观的根据斜率的偏移程度,决定是以x为步进方向还是以y为步进方向。然后在相应的步进方向上,步进变量每次增加一个像素,而另一个相关坐标变量则为Yk_1=Yk+m(以 x为步进变量为例,m为斜率),代码附后。
至此我们就了解了BMP位图的三个最基本的低级操作,创建位图、画点、画线。


附:本文中的三个函数
****制作:行者孙 VFP应用程式算法群(12787940)****
FUNCTION bin2str(bin as long)
For i=1 To 4
k1='0x'+Subs(Tran(bin,"@0"),9,2)
k2='0x'+ Subs(Tran(bin,"@0"),7,2)
k3='0x'+ Subs(Tran(bin,"@0"),5,2)
k4='0x'+ Subs(Tran(bin,"@0"),3,2)
retu Chr(&k1)+Chr(&k2)+Chr(&k3)+Chr(&k4)
Endf
ENDFUNC
在二进制文件里,数值的存储是按照从低位到高位的顺序来填充的,这个函数很简单,就是把一个数字比如123先转换为十六进制Tran(123,"@0")= 0x0000007B,然后得到的字串就是
Chr(0x7B)+chr(0x00) +chr(0x00) +chr(0x00) ,引入这个函数的目的是为了简化下面的一些程序书写。
现在开始创建BMP文件头:
dime BmpHeader[12] &&为了使这个文件头更加清晰,我这里用数组来定义
BmpHeader[1]='BM' &&2字节,BMP文件标识,固定值。
BmpHeader[2]= bin2str(Int((x*3+3)/4)*4*y+54) & &4字节,存储图像大小信息。实际上就是文件头的54个字节加上图像数据区的字节数,按道理,一个像素是RGB 3字节组成,所以图像大小是x*y*3+54,但是BMP位图图像规定数据区每行的像素的个数必须是4的倍数,如果图像宽度刚好是4的倍数,那么这个数值就是x*y*3+54,如果不是4的倍数那么要用chr(0)来补齐,所以这里要写个通用的表达式:Int((x*3+3)/4)*4*y+54,例如x =100,y=200,那么Tran(Int((x*3+3)/4)*4*y+54,"@0")=0x0000EA96。
BmpHeader[3]=Chr(0)+Chr(0)+Chr(0)+Chr(0) &&4字节,固定值,为保留字节,每字节以"00"填写即可。
BmpHeader[4]=Chr(54))+Chr(0)+Chr(0)+Chr(0) &&4字节,图像信息的开始位置,我这里定义的这个文件头的大小是54字节,从第54字节开始就是图像信息了,所以这里是Chr(54).
BmpHeader[5]=chr(0x28)+chr(0)+chr(0)+chr(0) &&4字节,图像描述信息块的大小,常为28H。
BmpHeader[6]=bin2str(x)  &&4字节,位图宽度。
BmpHeader[7]= bin2str(y) && 4字节,位图高度。
BmpHeader[8]=chr(1)+chr(0) &&2字节,图像的plane总数(恒为1)。
BmpHeader[9]=chr(24)+chr(0) &&2字节,记录像素的位数,很重要的数值,图像的颜色数由该值决定,这里定义的是24位位图。
BmpHeader[10]=Chr(0)+Chr(0)+Chr(0)+Chr(0) &&4字节,数据压缩方式(数值位0:不压缩;1:8位压缩;2:4位压缩。
BmpHeader[11]=bin2str(Int((x*3+3)/4)*4*y) &&4字节,图像区数据的大小,计算方式上面已经说了。
BmpHeader[12]= Chrt(Spac(16), Chr(32),Chr(0))  &&剩下的16字节全部以CHR(0)来填写就可以了。
至此,一个24位位图的基本文件头我们就定义结束了.

BmpHeader=''
For i=1 to 12
BmpHeader= BmpHeader+ BmpHeader[i]
Endf
接下来是数据区了,数据区有个特别需要注意的地方就是:
位图保存时扫描顺序是从下至上,从左至右,即在位图数据区,位图第一行保存在位图数据区的最后,而位图最后一行保存在位图数据区的开始。
比如我们现在制作一个宽210高110的空白位图文件,那么就是这样了:
BmpHwnd=fcrea('位图.bmp')
Fwrite(BmpHwnd ,BmpHeader) &&写入文件头
Fwrite(BmpHwnd ,BmpBody) &&写入图像数据
Fclose(BmpHwnd)
现在再说下这里的BmpBody有一点需要注意:Windows要求每一行的数据的长度必须是4Bytes的整数倍,如果不是,要以值为0的字节补充,如果读取的时候不处理,会得到一个倾斜的图像。举例:一张479*360的24位色BMP图片,宽度是479个像素,每一行的数据长度是479*3(个字节用于RGB三色) =1437,不是4的倍数,所以,在这行数据的后面,要补上3个字节,值为0,凑成1440,达到4的倍数,然后,才是下一行的数据。在做程序的时候,需要把这些冗余点的值舍去。
至此,我们就可以写一个创建一个宽度为x,高度为y的空白位图的自定义函数了。(附后)
现在我们需要做的是在一张空白位图上画点了,我们知道了位图的图像数据是按照从下到上、从左到右的方式来排列的,刚好符合数学里的XY轴的数据模型,而且我们在做一些数据图的时候也正式按照XY轴的数据模型来实现的(我想微软之所以按照这样的方式来排列数据也正是因为此种道理吧,呵呵)。
首先我们先自己写一个可以在位图任意行任意列画一个点的函数,从文件头结束位置开始就是图像数据了,
每个点占RGB三字节,所以我们只要根据行和列就能确定这个点在图像数据区所在的开始位置,然后改写这个点为指定的颜色,那么我们相当于画点了。
   图像的基本要素是点和线,画线的的算法好坏是直接影响图像格栅化质量的根本,我们在这里引入比较适合BMP位图的DDA算法, DDA称为数值微分画线算法,是直线生成算法中最简单的一种,其它还有breshenham算法、中点算法等等。原理相当简单,就是最直观的根据斜率的偏移程度,决定是以x为步进方向还是以y为步进方向。然后在相应的步进方向上,步进变量每次增加一个像素,而另一个相关坐标变量则为Yk_1=Yk+m(以 x为步进变量为例,m为斜率),代码附后。
至此我们就了解了BMP位图的三个最基本的低级操作,创建位图、画点、画线。


附:本文中的三个函数
****制作:行者孙 VFP应用程式算法群(12787940)****
FUNCTION CreateBmp(x as long,y as long,filename as string)
*x:图像宽 y:图像高 filename:文件名
*功能:创建一个任意尺寸的空白24位位图图像(分辨率为:96X96)
BmpHwnd=fcreat(filename)
Fwrite(BmpHwnd,'BM')
Fwrite(BmpHwnd, bin2str(Int((x*3+3)/4)*4*y+54))
Fwrite(BmpHwnd, Chr(0)+Chr(0)+Chr(0)+Chr(0))
Fwrite(BmpHwnd, Chr(54)+Chr(0)+Chr(0)+Chr(0))
Fwrite(BmpHwnd, chr(0x28)+chr(0)+chr(0)+chr(0))
Fwrite(BmpHwnd, bin2str(x))
Fwrite(BmpHwnd, bin2str(y))
Fwrite(BmpHwnd, chr(1)+chr(0))
Fwrite(BmpHwnd, chr(24)+chr(0))
Fwrite(BmpHwnd, Chr(0)+Chr(0)+Chr(0)+Chr(0))
Fwrite(BmpHwnd, bin2str(Int((x*3+3)/4)*4*y))
Fwrite(BmpHwnd, REPL(CHR(0),16))
For Bmp_i=1 to y
Fwrite(BmpHwnd, REPL(CHR(255),x*3)+REPL(CHR(0),(4-(x*3)%4)%4))
Endf
RETURN BmpHwnd
ENDFUNC

FUNCTION writebmp(BmpHwnd,x as Long,y as Long ,R as long,G as long,B as long)
*按照xy轴正向实现改写位图的一个点
*fname:位图文件名 X:x轴坐标 Y:y轴坐标 RGB:颜色值
FSEEK(BmpHwnd,0x0A)
bStar=str2bin(FREAD(hwnd,4))&&图像数据的开始位置
FSEEK(hwnd,0x12)
bWidth=str2bin(FREAD(hwnd,4))&&获取图像宽度(列)
FSEEK(hwnd,0x16)
bHeight=str2bin(FREAD(hwnd,4))&&获取图像高度(行)
FSEEK(hwnd,bStar+(y-1)*(3*bWidth+(4-(3*bWidth)%4)%4)+(x-1)*3)&&(4-(3*bWidth)%4)%4为每行的补位数
FWRITE(hwnd,CHR(B)+CHR(G)+CHR(R))
ENDFUNC

FUNCTION draw_line(BmpHwnd,x1 as Integer,y1 as Integer,x2 as Integer,y2 as Integer,R as long,G as long,B as long)
*基于DDA算法实现在两点之间画一条直线
dx=y2-y1
dy=x2-x1
length=0
IF abs(x2-x1) > abs(y2-y1)
length = abs(x2-x1)
ELSE
length = abs(y2-y1)
ENDIF
Dx = (x2-x1)/length
Dy = (y2-y1)/length
       x0 = INT(x1+0.5*Sign(Dx))
       y0 = INT(y1 + 0.5*Sign(Dy))
       Bmp_mi = 1
DO WHILE Bmp_mi<=length
writebmp(hwnd,INT(x0)+Sign(x0),INT(y0)+Sign(y0),R,G,B)
x0= x0 + Dx
y0= y0 + Dy
Bmp_mi=Bmp_mi+1
endd
ENDFUNC

function bin2str(bin as long)
k1='0x'+Subs(Tran(bin,"@0"),9,2)
k2='0x'+ Subs(Tran(bin,"@0"),7,2)
k3='0x'+ Subs(Tran(bin,"@0"),5,2)
k4='0x'+ Subs(Tran(bin,"@0"),3,2)
retu Chr(&k1)+Chr(&k2)+Chr(&k3)+Chr(&k4)
ENDFUNC

FUNCTION str2bin(bStr as string)
bstr=STRCONV(bstr,15)
bin='0x'
FOR i=LEN(bstr)-1 TO 1 STEP -2
bin=bin+SUBSTR(bstr,i,2)
endf
retu &bin
endfunc

应用举例:
1、   创建一个宽为300,高210的24位位图文件,并在x=10 y=30,x=200,y=100的两点间画线
hwnd=CreateBmp(300,210,'111.bmp')
draw_line(hwnd,10,30,200,100,0,0,255)
FCLOSE(hwnd)&&记得在写完文件后关闭哦
2、   根据传入的股票数据画走势图
代码略,也是在根据股票数据所确定的各个点之间画线

我们这里都是空白图,如果你想酷一点,可以自己先制作一张背景图,然后在背景图的基础上进行操作,
这里我只说了画图的点线的基本操作,至于画区域图、BOX图、圆形图等等其他复杂的图像,大家可自己去发挥聪明才智,这里就不多说了,至于其他图形的算法如果不清除的话可以找我。也许有人说根本没必要这么麻烦,使用一些现成的控件或者函数库不就行了,当然是如此,但知其然不知其所依然是一个编程人员的最大禁忌!如果大家有什么不明白或者在制作图形的时候碰到了什么问题可以到 VFP应用程式算法群(12787940) 里找我一起探讨!
                                                           
                                                                                          --- 行者孙---
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
用C语言写BMP图像文件
BMP图片知识
BMP图像信息隐藏
BMP文件详解
JAVA实现对BMP图片的读取
转贴:BMP位图格式详解 一
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服