4 画直线(harib20d)
现在我们已经可以显示文字、描绘方块,还能够画点,接下来我们应该实现画直线的功能了。
画直线的基本方法概括如下。
for (i = 0; i < len; i++) {
api_point(win, x, y, col);
x += dx;
y += dy;
}
len表示直线的长度,x和y表示直线的起点坐标,dx和dy表示直线延伸的方向。不过这样用起来很麻烦,最好是只要指定直线两端的坐标,就可以自动计算出dx、dy和len并画出直线,那么我们就按这个思路来编写API吧。
dx和dy这两个值如果太大的话,点与点之间的间隔就会空得很大,看上去就变成虚线了;反过来说,如果dx和dy取值太小,画点的坐标就无法前进,会导致多次在同一个坐标上画点,浪费CPU的处理能力。
■■■■■
此外,x和y,以及dx和dy如果不支持小数的话,就画不出漂亮的直线(假如只支持整数,某些情况下画出来的就是虚线了)。不过现在我们还没有做好使用小数的准备,不能使用小数,因此我们将这4个整数预先扩大1000倍,即下面这样:
for (i = 0; i < len; i++) {
api_point(win, x / 1000, y / 1000, col);
x += dx;
y += dy;
}
这样一来,如果x为100000,dx为123,就相当于用整数实现了将100每次累加0.123这样的运算。
实际上,除以1000的运算速度不怎么快,所以我们改成除以1024。再进一步说,其实我们使用的根本不是除法运算,而是右移10比特的方法,这等效于除以1024,而之所以使用移位运算,当然是因为它比除法运算速度要快。因为除数改成了1024,所以如果要表示0.5的话应该使用512而不是500了。
■■■■■
关于 dx和dy等值详细的计算方法请看下面的程序。
本次的API式样如下。
在窗口上画直线
EDX = 13
EBX = 窗口句柄
EAX = x0
ECX = y0
ESI = x1
EDI = y1
EBP = 色号
本次的console.c节选
int *hrb_api(int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int eax)
{
(中略)
} else if (edx == 13) {
sht = (struct SHEET *) (ebx & 0xfffffffe);
hrb_api_linewin(sht, eax, ecx, esi, edi, ebp);
if ((ebx & 1) == 0) {
sheet_refresh(sht, eax, ecx, esi + 1, edi + 1);
}
}
return 0;
}
void hrb_api_linewin(struct SHEET *sht, int x0, int y0, int x1, int y1, int col)
{
int i, x, y, len, dx, dy;
dx = x1 - x0;
dy = y1 - y0;
x = x0 << 10;
y = y0 << 10;
if (dx < 0) {
dx = - dx;
}
if (dy < 0) {
dy = - dy;
}
if (dx >= dy) {
len = dx + 1;
if (x0 > x1) {
dx = -1024;
} else {
dx = 1024;
}
if (y0 <= y1) {
dy = ((y1 - y0 + 1) << 10) / len;
} else {
dy = ((y1 - y0 - 1) << 10) / len;
}
} else {
len = dy + 1;
if (y0 > y1) {
dy = -1024;
} else {
dy = 1024;
}
if (x0 <= x1) {
dx = ((x1 - x0 + 1) << 10) / len;
} else {
dx = ((x1 - x0 - 1) << 10) / len;
}
}
for (i = 0; i < len; i++) {
sht->buf[(y >> 10) * sht->bxsize + (x >> 10)] = col;
x += dx;
y += dy;
}
return;
}
我们来讲解一下这段程序。程序首先要做的是计算len,通过比较直线起点和终点的坐标,将变化比较大的作为len(实际上还需要加上1)。为什么要加上1呢?因为如果不这样做的话,画到最后就会差1个像素。举个例子,当起点和终点完全相同时,应该在画面上画出1个点才对,但此时dx和dy都为0,如果不加1就什么都画不出来了。
len计算出来以后,接着计算dx和dy。将变化比较大的一方设为1024或者-1024(即1或-1),变化较小的一方用变化量去除以len。在做除法的时候我们还是会进行加1和减1的运算,这有点像烧菜用的秘方,是个很微妙的小技巧,下面我们来讲一讲这个秘方的效果吧。
如果不用这个小技巧的话,假设我们需要画一条从(100, 100)到(159, 102)的直线,则dy为2 / 60,约为0.0333。
不用小技巧的情况(x = 100, y= 100, dx = 1, dy = 0.0333, len = 60)
这样不行,画这条直线的时候y明明需要增加2,但这里只增加了1。如果我们加上这个小技巧的话,dy的计算就变成了3 / 60,即0.05。
用了小技巧的情况(x = 100, y= 100, dx = 1, dy = 0.05, len = 60)
■■■■■
好,我们来编写测试用的应用程序吧。
本次的a_nask.nas节选
_api_linewin: ; void api_linewin(int win, int x0, int y0, int x1, int y1, int col);
PUSH EDI
PUSH ESI
PUSH EBP
PUSH EBX
MOV EDX,13
MOV EBX,[ESP+20] ; win
MOV EAX,[ESP+24] ; x0
MOV ECX,[ESP+28] ; y0
MOV ESI,[ESP+32] ; x1
MOV EDI,[ESP+36] ; y1
MOV EBP,[ESP+40] ; col
INT 0x40
POP EBX
POP EBP
POP ESI
POP EDI
RET
本次的lines.c
int api_openwin(char *buf, int xsiz, int ysiz, int col_inv, char *title);
void api_initmalloc(void);
char *api_malloc(int size);
void api_refreshwin(int win, int x0, int y0, int x1, int y1);
void api_linewin(int win, int x0, int y0, int x1, int y1, int col);
void api_end(void);
void HariMain(void)
{
char *buf;
int win, i;
api_initmalloc();
buf = api_malloc(160 * 100);
win = api_openwin(buf, 160, 100, -1, "lines");
for (i = 0; i < 8; i++) {
api_linewin(win + 1, 8, 26, 77, i * 9 + 26, i);
api_linewin(win + 1, 88, 26, i * 9 + 88, 89, i);
}
api_refreshwin(win, 6, 26, 154, 90);
api_end();
}
我们来“make run”,希望一切顺利。成功了!
画出直线了哦
美丽的星空……哦不对,这不是星空,不过感觉比stars看上去要高级多了,难道是笔者自我感觉良好吗?