分类:
2008-10-15 16:41:03
注:该程序最好在win98下运行,否则看不到鼠标。
一、算法设计及流程1.点不管什么样的图形归根结底到屏幕,都是由一些颜色不一的象素点组成,为提高图形的显示效率,没有用到系统的画点函数,而采用了640*480*16模式下的直接写屏技术画点,该模式下的直接写屏比320*200*256的直接写屏要复杂的多,后者的视频缓冲区是线性编址的,一个字节对应屏幕上一点的颜色,而前者的视频缓冲区被分成四个位面,屏幕上一点的颜色由每个位面上的一位组成的四位二进制数决定,由于四位二进制数的最大表示范围是16,所以在这种模式下最多可同时显示16种颜色。
在对视频缓冲区的读写时,是一个字节一个字节写的,只需要其中某一位时,就要用到位掩码屏蔽掉其他的位,位掩码就在图形控制寄存器的位屏蔽寄存器里设置。先通过向端口号为0x3ce的索引寄存器写入位屏蔽寄存器的索引号8,然后向端口号为0x3cf的寄存器写入位掩码值。设置好位掩码后,直接向视频缓冲区相应位置写入颜色即为画点函数。
取屏幕上一点颜色其实也就是画点的反操作,画点是将一个四位的二进制颜色值分配到四个位面,而取一点的颜色是将四个位面的颜色组合成一个四位二进制数。
2.线线其实就是由点组成的,通过连续的改变点的坐标,也就可以得到一条线。所以画线的函数归根结底也就是如何改变画点的坐标。
画线通常有DDA算法,BRESENHAM算法,这里采用的是BRESENHAM算法。这种算法从根本上讲,是通过横线与竖线的倍数关系来实现的。每次循环画点后x、y中位移大的坐标变化一个单位,而当循环次数是两者倍数关系时,x、y中位移小的坐标变化一个单位。为了使画线函数能在八个方向都能画,将增量为负的都转化正的增量。
画图程序经常要用到moveto、lineto等函数,用于把点移动到某点或从当前点画线到某点,为了模拟这些函数,设置了SX,SY两个全局变量用来作为Moveto()的目的地和Drawlineto()的起始坐标。
3.矩形及填充矩形矩形是通过两点确定的对角线来画的,直接连上各点的坐标就可以了。填充矩形是通过一条线一条线画的。这种算法速度比较慢,较好的方法是用Fillscreen()里面用到的方法,那是一个字节一个字节处理的。就因为画矩形并不一定是整字节开始整字节结束的,开始处和结尾处就有了问题。我也把它们单独拿出来处理,但还是没做好,等有时间了再改吧。
4.圆及弧画圆采用的是参数方程的方法。为了提高速度,角度的sin、cos值建立了一个表,要用时直接查表。这是因为在用BRESENHAM算法画椭圆时,效果不是很好,画的椭圆不但厚薄不一,而且不闭合。估计是程序哪里出问题了,干脆都用参数方程的方法,又简单,又省事。我这人通常是比较懒的。
弧主要是用来画圆角矩形的,且只能画圆弧。这也是用参数方程来画的,跟画圆不同的只是有了角度的限制。
5.贝塞尔曲线贝塞尔曲线其实主要也就是一个公式,跟参数方程画圆是一回事,只是要传入的参数要多一些。在用鼠标控制时,因为有四个点,而用鼠标对多点的采集有点不好做,于是先就初始化了一个四点的数组,用不按键、按左键、按右键分别控制三个点,左右键同时按时,确定下一条曲线。
其实用鼠标画贝塞尔曲线因该把一次、二次、三次的处理函数都写出来,在按下左键时,标记下这点,再一次按下左键时,用画一次贝塞尔曲线的函数画,按下第三次时,用画二次曲线的函数画,按下第四次时才用画三次曲线的函数画。这样就不用为每个点分配一个鼠标按键了。
6.填充填充也是采用的种子填充法,不过不是直接从种子开始填色,而是先从种子出发,找到种子沿x最小的边界,然后填上一点,判断这一点的上边一点和下边一点,如果不为边界并且未被填色,则压入栈中,然后x增1后判断是否到达这一行x的最大值(既边界),如不是,则循环处理这一行,直到到达边界,再弹出一个点继续循环处理。由于是一个点一个点处理,所以速度比较慢。
为了对一个图形的不同颜色边界也能填充,填色时没有传入图形的边界颜色,而是采用先取出种子的颜色,在填充下一个点时,把下一点的颜色和种子颜色比较,如果相同则填色,不同则作为边界。
7.汉字及字符图形模式下显示汉字其实是一件比较麻烦的事,一般要用到汉字字库,生成的可执行文件也得在有字库的环境下运行。我这里采用了汉字无字库技术,生成的文件不依赖任何其他文件。
无字库技术是通过从字库文件提取需要的中文点阵字模建立一个类似字库的字库数组。它可以大大提高汉字的显示速度。在建立字库数组时,将字模数组进行了排序,所以在显示汉字时,通过二分查找来得到要显示的汉字字模。我用的是从hzk16里面提取的16*16点阵字模。试一试将一个字模展开成二进制,每十六个二进制位为一行,你是不是看出这是一个什么字了。这就对了,汉字的显示其实就是通过画点的函数将这些为1的位显示出来,这就画出了一个汉字。因为我们是一个点一个点画的,所以要求在处理一个点时,用位掩码屏蔽掉其他位。
字符的显示原理跟汉字的显示原理是一回事,只不过ROM里面已经有了8*8的字符字模,它的首地址为0xF000FA6E.将要显示的字符的ASCII码值乘8再加上首地址就是这个字符字模的首地址。
8.窗口、工具栏及颜色选择框有了前面的这些基本函数,要画一个窗口就不是什么难事了。直接仿造WINDOWS窗口,用Drawbar()及画线函数直接就可以画出来。工具栏之类的也一样的画。为了使按钮看起来有点立体感,我们仔细看一下WINDOWS下的按钮就会发现:按钮左边、上边有两条高亮的线;按钮右边、下边有两条加深的线;而按钮本身的颜色就介于两者之间的填充矩形。所以我们在画立体按钮时,就画三个填充矩形,先画高亮的或加深的,使它们错开一点,再将按钮画在前面两个矩形框上。在画颜色选择框时,其坐标全部采用相对于颜色选择框左上角坐标的方法画的,以便于在扩展时将颜色框设为可用鼠标拖动的。
9.主过程及其实现有了前面的窗口,主过程只需要实现判断鼠标的位置,并对鼠标的动作作出检测,实现各种绘图功能 .在对鼠标位置进行判断时,为提高效率,将整个屏幕分成标题菜单栏、绘图工具栏、颜色选择框和绘图区。 如果鼠标不在哪块区域,就不必对里面的小区域进行判断了。这些判断的代码看起来比较多,其实都是大同小异的,主要也就是实现当鼠标移动到这些工具选择框时,这些框看起来能有凹下去的感觉。在按下按钮后这种凹下去的状态能保存。实现方法就是在鼠标还未按下时,鼠标移动到哪块就在这区域用画填充矩形的函数重画这块区域,由于在异或模式下,看到的就像被按下去了一样。在鼠标移动到下一块时,前面凹下去的那块还的再画一次,使其恢复。这就要把前面是哪块被按下的标志保存,好在下一块恢复时用到。到现在,这个功能实现的还不是很好。在颜色选择框上选颜色时,在限定的区域内用Getpixel()函数取出颜色,并用Drawbar()函数重画左边的前景色标志框。
在实现绘图功能时,由于用到了橡皮条技术,所以整个绘图模式是在异或模式下进行的,这样在清除前一条线的痕迹时就比较容易了。其实还有一种好的方法是采用双缓冲技术,这样就不用在异或模式下实现各种绘图功能了。有一个问题就是这样大块的内存在常规模式下很难申请到。况且开始没有想到用这种方法,现在也不深究了。
为了能更精确绘图,及时显示了鼠标光标位置。这主要是用sprintf()这个函数将鼠标位置坐标格式化输入到一个字符串,然后用图形模式下显示字符串的函数将其显示出来。在显示移动后的坐标前,要先把前一次显示的内容清除掉,不然多次重复到一起就看不清了。全屏清除重画显然不可能,我用的是一个与背景相同的矩形块,这样就快多了。