4 画直线(harib20d)

现在我们已经可以显示文字、描绘方块,还能够画点,接下来我们应该实现画直线的功能了。

画直线的基本方法概括如下。

  1. for (i = 0; i < len; i++) {
  2. api_point(win, x, y, col);
  3. x += dx;
  4. y += dy;
  5. }

len表示直线的长度,x和y表示直线的起点坐标,dx和dy表示直线延伸的方向。不过这样用起来很麻烦,最好是只要指定直线两端的坐标,就可以自动计算出dx、dy和len并画出直线,那么我们就按这个思路来编写API吧。

dx和dy这两个值如果太大的话,点与点之间的间隔就会空得很大,看上去就变成虚线了;反过来说,如果dx和dy取值太小,画点的坐标就无法前进,会导致多次在同一个坐标上画点,浪费CPU的处理能力。

■■■■■

此外,x和y,以及dx和dy如果不支持小数的话,就画不出漂亮的直线(假如只支持整数,某些情况下画出来的就是虚线了)。不过现在我们还没有做好使用小数的准备,不能使用小数,因此我们将这4个整数预先扩大1000倍,即下面这样:

  1. for (i = 0; i < len; i++) {
  2. api_point(win, x / 1000, y / 1000, col);
  3. x += dx;
  4. y += dy;
  5. }

这样一来,如果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节选

  1. int *hrb_api(int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int eax)
  2. {
  3. (中略)
  4. } else if (edx == 13) {
  5. sht = (struct SHEET *) (ebx & 0xfffffffe);
  6. hrb_api_linewin(sht, eax, ecx, esi, edi, ebp);
  7. if ((ebx & 1) == 0) {
  8. sheet_refresh(sht, eax, ecx, esi + 1, edi + 1);
  9. }
  10. }
  11. return 0;
  12. }
  13. void hrb_api_linewin(struct SHEET *sht, int x0, int y0, int x1, int y1, int col)
  14. {
  15. int i, x, y, len, dx, dy;
  16. dx = x1 - x0;
  17. dy = y1 - y0;
  18. x = x0 << 10;
  19. y = y0 << 10;
  20. if (dx < 0) {
  21. dx = - dx;
  22. }
  23. if (dy < 0) {
  24. dy = - dy;
  25. }
  26. if (dx >= dy) {
  27. len = dx + 1;
  28. if (x0 > x1) {
  29. dx = -1024;
  30. } else {
  31. dx = 1024;
  32. }
  33. if (y0 <= y1) {
  34. dy = ((y1 - y0 + 1) << 10) / len;
  35. } else {
  36. dy = ((y1 - y0 - 1) << 10) / len;
  37. }
  38. } else {
  39. len = dy + 1;
  40. if (y0 > y1) {
  41. dy = -1024;
  42. } else {
  43. dy = 1024;
  44. }
  45. if (x0 <= x1) {
  46. dx = ((x1 - x0 + 1) << 10) / len;
  47. } else {
  48. dx = ((x1 - x0 - 1) << 10) / len;
  49. }
  50. }
  51. for (i = 0; i < len; i++) {
  52. sht->buf[(y >> 10) * sht->bxsize + (x >> 10)] = col;
  53. x += dx;
  54. y += dy;
  55. }
  56. return;
  57. }

我们来讲解一下这段程序。程序首先要做的是计算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。

4 画直线(harib20d) - 图1

不用小技巧的情况(x = 100, y= 100, dx = 1, dy = 0.0333, len = 60)

这样不行,画这条直线的时候y明明需要增加2,但这里只增加了1。如果我们加上这个小技巧的话,dy的计算就变成了3 / 60,即0.05。

4 画直线(harib20d) - 图2

用了小技巧的情况(x = 100, y= 100, dx = 1, dy = 0.05, len = 60)

■■■■■

好,我们来编写测试用的应用程序吧。

本次的a_nask.nas节选

  1. _api_linewin: ; void api_linewin(int win, int x0, int y0, int x1, int y1, int col);
  2. PUSH EDI
  3. PUSH ESI
  4. PUSH EBP
  5. PUSH EBX
  6. MOV EDX,13
  7. MOV EBX,[ESP+20] ; win
  8. MOV EAX,[ESP+24] ; x0
  9. MOV ECX,[ESP+28] ; y0
  10. MOV ESI,[ESP+32] ; x1
  11. MOV EDI,[ESP+36] ; y1
  12. MOV EBP,[ESP+40] ; col
  13. INT 0x40
  14. POP EBX
  15. POP EBP
  16. POP ESI
  17. POP EDI
  18. RET

本次的lines.c

  1. int api_openwin(char *buf, int xsiz, int ysiz, int col_inv, char *title);
  2. void api_initmalloc(void);
  3. char *api_malloc(int size);
  4. void api_refreshwin(int win, int x0, int y0, int x1, int y1);
  5. void api_linewin(int win, int x0, int y0, int x1, int y1, int col);
  6. void api_end(void);
  7. void HariMain(void)
  8. {
  9. char *buf;
  10. int win, i;
  11. api_initmalloc();
  12. buf = api_malloc(160 * 100);
  13. win = api_openwin(buf, 160, 100, -1, "lines");
  14. for (i = 0; i < 8; i++) {
  15. api_linewin(win + 1, 8, 26, 77, i * 9 + 26, i);
  16. api_linewin(win + 1, 88, 26, i * 9 + 88, 89, i);
  17. }
  18. api_refreshwin(win, 6, 26, 154, 90);
  19. api_end();
  20. }

我们来“make run”,希望一切顺利。成功了!

4 画直线(harib20d) - 图3

画出直线了哦

美丽的星空……哦不对,这不是星空,不过感觉比stars看上去要高级多了,难道是笔者自我感觉良好吗?