前言:
而今小伙伴们对“c语言建立坐标系绘图”大体比较关切,你们都想要知道一些“c语言建立坐标系绘图”的相关文章。那么小编同时在网摘上网罗了一些关于“c语言建立坐标系绘图””的相关文章,希望同学们能喜欢,看官们一起来学习一下吧!第三天,我们完成了一些基本的设定,实现了操作系统代码bootpack.c的编写。
第三天教程的地址:
以及一些说明:
但是,第三天的bootpack.c内部什么事也没有做,只是个无限等待的循环,同时操作系统的画面是一片黑色,就是黑屏。
结果展示
那么今天,我们就在这黑屏上画一个窗口出来,画这个窗口如下:
这个窗口的所有代码在:
那么这个窗口具体的代码为:
void io_hlt(void);void io_cli(void);void io_out8(int port, int data);int io_load_eflags(void);void io_store_eflags(int eflags);void init_palette(void);void set_palette(int start, int end, unsigned char *rgb);void boxfill8(unsigned char *vram, int xsize, unsigned char c, int x0, int y0, int x1, int y1);#define COL8_000000 0#define COL8_FF0000 1#define COL8_00FF00 2#define COL8_FFFF00 3#define COL8_0000FF 4#define COL8_FF00FF 5#define COL8_00FFFF 6#define COL8_FFFFFF 7#define COL8_C6C6C6 8#define COL8_840000 9#define COL8_008400 10#define COL8_848400 11#define COL8_000084 12#define COL8_840084 13#define COL8_008484 14#define COL8_848484 15void HariMain(void){ char *vram; int xsize, ysize; init_palette(); vram = (char *) 0xa0000; xsize = 320; ysize = 200; boxfill8(vram, xsize, COL8_008484, 0, 0, xsize - 1, ysize - 29); boxfill8(vram, xsize, COL8_C6C6C6, 0, ysize - 28, xsize - 1, ysize - 28); boxfill8(vram, xsize, COL8_FFFFFF, 0, ysize - 27, xsize - 1, ysize - 27); boxfill8(vram, xsize, COL8_C6C6C6, 0, ysize - 26, xsize - 1, ysize - 1); boxfill8(vram, xsize, COL8_FFFFFF, 3, ysize - 24, 59, ysize - 24); boxfill8(vram, xsize, COL8_FFFFFF, 2, ysize - 24, 2, ysize - 4); boxfill8(vram, xsize, COL8_848484, 3, ysize - 4, 59, ysize - 4); boxfill8(vram, xsize, COL8_848484, 59, ysize - 23, 59, ysize - 5); boxfill8(vram, xsize, COL8_000000, 2, ysize - 3, 59, ysize - 3); boxfill8(vram, xsize, COL8_000000, 60, ysize - 24, 60, ysize - 3); boxfill8(vram, xsize, COL8_848484, xsize - 47, ysize - 24, xsize - 4, ysize - 24); boxfill8(vram, xsize, COL8_848484, xsize - 47, ysize - 23, xsize - 47, ysize - 4); boxfill8(vram, xsize, COL8_FFFFFF, xsize - 47, ysize - 3, xsize - 4, ysize - 3); boxfill8(vram, xsize, COL8_FFFFFF, xsize - 3, ysize - 24, xsize - 3, ysize - 3); for (;;) { io_hlt(); }}void init_palette(void){ static unsigned char table_rgb[16 * 3] = { 0x00, 0x00, 0x00, /* 0:黒 */ 0xff, 0x00, 0x00, /* 1:明るい赤 */ 0x00, 0xff, 0x00, /* 2:明るい緑 */ 0xff, 0xff, 0x00, /* 3:明るい黄色 */ 0x00, 0x00, 0xff, /* 4:明るい青 */ 0xff, 0x00, 0xff, /* 5:明るい紫 */ 0x00, 0xff, 0xff, /* 6:明るい水色 */ 0xff, 0xff, 0xff, /* 7:白 */ 0xc6, 0xc6, 0xc6, /* 8:明るい灰色 */ 0x84, 0x00, 0x00, /* 9:暗い赤 */ 0x00, 0x84, 0x00, /* 10:暗い緑 */ 0x84, 0x84, 0x00, /* 11:暗い黄色 */ 0x00, 0x00, 0x84, /* 12:暗い青 */ 0x84, 0x00, 0x84, /* 13:暗い紫 */ 0x00, 0x84, 0x84, /* 14:暗い水色 */ 0x84, 0x84, 0x84 /* 15:暗い灰色 */ }; set_palette(0, 15, table_rgb); return; /* static char 命令は、データにしか使えないけどDB命令相当 */}void set_palette(int start, int end, unsigned char *rgb){ int i, eflags; eflags = io_load_eflags(); /* 割り込み許可フラグの値を記録する */ io_cli(); /* 許可フラグを0にして割り込み禁止にする */ io_out8(0x03c8, start); for (i = start; i <= end; i++) { io_out8(0x03c9, rgb[0] / 4); io_out8(0x03c9, rgb[1] / 4); io_out8(0x03c9, rgb[2] / 4); rgb += 3; } io_store_eflags(eflags); /* 割り込み許可フラグを元に戻す */ return;}void boxfill8(unsigned char *vram, int xsize, unsigned char c, int x0, int y0, int x1, int y1){ int x, y; for (y = y0; y <= y1; y++) { for (x = x0; x <= x1; x++) vram[y * xsize + x] = c; } return;}
可以看到,28行的HariMain函数内,基本上一堆的boxfill8函数,
也就是说,就是这些boxfill8函数构成了最终的窗口画面。
因为今天的目标就是写这样一个画面出来,所以,只要详细解析boxfill8函数,今天的任务就完成了。
那么boxfill8函数实现了怎样的功能?它是如何控制显示屏来显示出了一个窗口的?
控制显示屏
实际上,在昨天的代码中,我们已经对显示屏做了设置:
这里设置了显示屏按照像素的方式来显示画面,画面的大小为320x200,
特别注意最后一行:MOV DWORD [VRAM],0x000a0000;,设置缓冲区的地址为0x000a0000,这意味着,将内存的0x000a 0000出写的值,就是显示屏上每个像素的值。
那我们在c语言中实现这样一个函数就可以了,如下bootpack.c代码:
void io_hlt(void);void write_mem8(int addr, int data);void HariMain(void){ int i; for (i = 0xa0000; i <= 0xaffff; i++) { write_mem8(i, 15); /* MOV BYTE [i],15 */ } for (;;) { io_hlt(); }}
在第8行,我们将15这个值放在了0xa0000到0xaffff地址里,用的是write_mem8函数实现的。write_mem8函数在第2行有声明,注意到它和io_hlt一样,而io_htl是汇编语言写的,所以,write_mem8函数也是汇编语言写的,在naskfunc.nas里,它的实现是这样的:
; naskfunc; TAB=4[FORMAT "WCOFF"] ; 文件格式设定 [INSTRSET "i486p"] ; 486模式[BITS 32] ; 32模式[FILE "naskfunc.nas"] ; GLOBAL _io_hlt,_write_mem8 ; 声明函数[SECTION .text]_io_hlt: ; void io_hlt(void); HLT RET_write_mem8: ; void write_mem8(int addr, int data); MOV ECX,[ESP+4] ; [ESP+4] 就是int addr, MOV AL,[ESP+8] ; [ESP+8] 就是int data MOV [ECX],AL ; 把data 放在addr处 RET
也就是说:write_mem8函数将 data存放到地址addr出。
那么在HariMain函数中,就实现了把15放到了内存地址0xa0000至0xaffff处。
这就实现了改变显示屏内容的作用,把原来的黑屏,变成了白屏,如下:
既然画成白屏了,不如再画个其他的,比如条纹,代码如下:
void io_hlt(void);void write_mem8(int addr, int data);void HariMain(void){ int i; /* 変数宣言。iという変数は、32ビットの整数型 */ for (i = 0xa0000; i <= 0xaffff; i++) { write_mem8(i, i & 0x0f); } for (;;) { io_hlt(); }}
只有第10行有改动: write_mem8(i, 15)改为 write_mem8(I,i&0x0f);
I&0x0f是只取i的最小的四位,由于i本身是从0xa0000增加到0xaffff的,所以,i*0x0f会是一个周期变化的值,不断的从0变化到f=15,然后又从0变化到f=15。
我们设置为15的时候,画面是白色的,现在是0--15之间变化,那么画面会发生怎样的变化呢?
以上程序运行结果如下:
可以看到,这里是一个黑白相间的条纹。
不过这个还是黑白的,那么能否显示彩色呢?
我们知道显示屏一般都是彩色的,肯定是可以显示彩色的,那要如何设定呢?
这涉及到一个概念,调色板。
调色板是啥?
其实我们现在的程序:15对应白色,0对应黑色,就是一个调色板。这种数字和颜色的对应关系,就是调色板。
所以我们只要改变一下数字和颜色的对应关系就行了。在这个代码中实现:
void io_hlt(void);void io_cli(void);void io_out8(int port, int data);int io_load_eflags(void);void io_store_eflags(int eflags);void init_palette(void);void set_palette(int start, int end, unsigned char *rgb);void HariMain(void){ int i; char *p; init_palette(); p = (char *) 0xa0000; # 显示器缓冲区地址 for (i = 0; i <= 0xffff; i++) { p[i] = i & 0x0f; } for (;;) { io_hlt(); }}void init_palette(void){ static unsigned char table_rgb[16 * 3] = { 0x00, 0x00, 0x00, /* 0:黒 */ 0xff, 0x00, 0x00, /* 1:亮红 */ 0x00, 0xff, 0x00, /* 2:亮绿 */ 0xff, 0xff, 0x00, /* 3:亮黄 */ 0x00, 0x00, 0xff, /* 4:明亮蓝 */ 0xff, 0x00, 0xff, /* 5:亮紫 */ 0x00, 0xff, 0xff, /* 6:浅亮蓝 */ 0xff, 0xff, 0xff, /* 7:白 */ 0xc6, 0xc6, 0xc6, /* 8:亮灰 */ 0x84, 0x00, 0x00, /* 9:暗红 */ 0x00, 0x84, 0x00, /* 10:暗緑 */ 0x84, 0x84, 0x00, /* 11:暗色 */ 0x00, 0x00, 0x84, /* 12:暗蓝 */ 0x84, 0x00, 0x84, /* 13:暗紫 */ 0x00, 0x84, 0x84, /* 14:浅暗蓝 */ 0x84, 0x84, 0x84 /* 15:暗灰 */ }; set_palette(0, 15, table_rgb); return; }void set_palette(int start, int end, unsigned char *rgb){ int i, eflags; eflags = io_load_eflags(); /* 记录运行到此代码处时的中断标志到eflags */ io_cli(); /* 禁止中断程序 */ io_out8(0x03c8, start); for (i = start; i <= end; i++) { io_out8(0x03c9, rgb[0] / 4); io_out8(0x03c9, rgb[1] / 4); io_out8(0x03c9, rgb[2] / 4); rgb += 3; } io_store_eflags(eflags); /* 用 eflags恢复中断标志 */ return;}
set_palette(start=0,end=15,table_rgb=颜色表)
这个函数中的start,end就是往数据缓冲区中写的数字的范围。
我们给0,1,2,3,,,,15,每个数字对应一种颜色,一共16种颜色,写在table_rgb中。通过往端口0x03c9写入颜色的红色分量,绿色分量,蓝色分量就可以完成调色板palette的设置,把这些颜色和0,15对应起来。
代码中io_out8是往指定端口写入8位数字。
这里设定显示屏的调色板,要先把start写到0x03c8,然后把具体的颜色写到0x03c9就行了,这都是VGA显示器规定的标准设定方法。
运行一下就可以看到彩色了,如下:
那么以上并不是全部的代码,因为没有给出io_out8的源码,io_store_eflag的源码,下面给出,其实都在nasmfucn里:
; naskfunc; TAB=4[FORMAT "WCOFF"] [INSTRSET "i486p"] [BITS 32] [FILE "naskfunc.nas"] GLOBAL _io_hlt, _io_cli, _io_sti, _io_stihlt GLOBAL _io_in8, _io_in16, _io_in32 GLOBAL _io_out8, _io_out16, _io_out32 GLOBAL _io_load_eflags, _io_store_eflags[SECTION .text]_io_hlt: ; void io_hlt(void); HLT RET_io_cli: ; void io_cli(void); CLI RET_io_sti: ; void io_sti(void); STI RET_io_stihlt: ; void io_stihlt(void); STI HLT RET;端口port的值写入AL,AL是8位寄存器_io_in8: ; int io_in8(int port); MOV EDX,[ESP+4] ; port MOV EAX,0 IN AL,DX RET;端口port的值写入AX,AX是8位寄存器_io_in16: ; int io_in16(int port); MOV EDX,[ESP+4] ; port MOV EAX,0 IN AX,DX RET;端口port的值写入EAX,EAX是32位寄存器_io_in32: ; int io_in32(int port); MOV EDX,[ESP+4] ; port IN EAX,DX RET;把8位的data写到端口port_io_out8: ; void io_out8(int port, int data); MOV EDX,[ESP+4] ; port MOV AL,[ESP+8] ; data OUT DX,AL RET;把16位的data写到端口port_io_out16: ; void io_out16(int port, int data); MOV EDX,[ESP+4] ; port MOV EAX,[ESP+8] ; data OUT DX,AX RET;把32位的data写到端口port_io_out32: ; void io_out32(int port, int data); MOV EDX,[ESP+4] ; port MOV EAX,[ESP+8] ; data OUT DX,EAX RET;把标志寄存器的值放到栈里_io_load_eflags: ; int io_load_eflags(void); PUSHFD ; PUSH EFLAGS POP EAX RET;把 eflags放到栈里,然后再放到标志寄存器里_io_store_eflags: ; void io_store_eflags(int eflags); MOV EAX,[ESP+4] PUSH EAX POPFD ; POP EFLAGS RET
因为设定调色板,需要用到一些汇编代码,所有就又在nasmfunc中加入了一些相关的函数。
绘制矩形
既然可以改变颜色了,说明我们基本上可以比较自由的操作显示屏显示各种颜色了。
那么能否绘制本文开头的窗口呢?
能,不过要先回值一个长方形。
如何绘制呢?
缓冲区从0xa0000开始,一共320x200=64000个值。
如果要绘制举行,单独设置矩形内的像素为特定颜色就行了。
设定一个坐标系,以窗口的左上角为原点,水平向左为x轴正方向,垂直向下为y轴正方向,那么矩阵的左上角点坐标为(x0,y0),右下角点坐标为(x1,y1),
我们只用对横坐标介于[x1,x2],纵坐标介于[y1,y2]的像素设定为特定的值就行了。
函数boxfill8就是对以上思路的实现:
void boxfill8(unsigned char *vram, int xsize, unsigned char c, int x0, int y0, int x1, int y1){ int x, y; for (y = y0; y <= y1; y++) { for (x = x0; x <= x1; x++) vram[y * xsize + x] = c; } return;}
vram就是缓冲区的首地址,
xsize是显示屏每行的像素数,
c是像素的值
x0,y0是所要绘制的左上角点的坐标
x1,y1是所要绘制的右下角点的坐标
这个函数就可以绘制一个颜色为c的,左上角在x0,y0,右下角在x1,y1的长方体。
那么我们使用这个函数来绘制几个长方体来看看,代码如下:
void io_hlt(void);void io_cli(void);void io_out8(int port, int data);int io_load_eflags(void);void io_store_eflags(int eflags);void init_palette(void);void set_palette(int start, int end, unsigned char *rgb);void boxfill8(unsigned char *vram, int xsize, unsigned char c, int x0, int y0, int x1, int y1);#define COL8_000000 0#define COL8_FF0000 1#define COL8_00FF00 2#define COL8_FFFF00 3#define COL8_0000FF 4#define COL8_FF00FF 5#define COL8_00FFFF 6#define COL8_FFFFFF 7#define COL8_C6C6C6 8#define COL8_840000 9#define COL8_008400 10#define COL8_848400 11#define COL8_000084 12#define COL8_840084 13#define COL8_008484 14#define COL8_848484 15void HariMain(void){ char *p; init_palette(); p = (char *) 0xa0000; boxfill8(p, 320, COL8_FF0000, 20, 20, 120, 120); boxfill8(p, 320, COL8_00FF00, 70, 50, 170, 150); boxfill8(p, 320, COL8_0000FF, 120, 80, 220, 180); for (;;) { io_hlt(); }}void init_palette(void){ static unsigned char table_rgb[16 * 3] = { 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc6, 0xc6, 0xc6, 0x84, 0x00, 0x00, 0x00, 0x84, 0x00, 0x84, 0x84, 0x00, 0x00, 0x00, 0x84, 0x84, 0x00, 0x84, 0x00, 0x84, 0x84, 0x84, 0x84, 0x84 }; set_palette(0, 15, table_rgb); return; }void set_palette(int start, int end, unsigned char *rgb){ int i, eflags; eflags = io_load_eflags(); io_cli(); io_out8(0x03c8, start); for (i = start; i <= end; i++) { io_out8(0x03c9, rgb[0] / 4); io_out8(0x03c9, rgb[1] / 4); io_out8(0x03c9, rgb[2] / 4); rgb += 3; } io_store_eflags(eflags); return;}void boxfill8(unsigned char *vram, int xsize, unsigned char c, int x0, int y0, int x1, int y1){ int x, y; for (y = y0; y <= y1; y++) { for (x = x0; x <= x1; x++) vram[y * xsize + x] = c; } return;}
在36,37,38绘制了3个矩形。运行后,启动操作系统后,显示如下:
可以看到,显示了3个不同颜色的矩形。
用矩形绘制窗口
直接上代码:
void io_hlt(void);void io_cli(void);void io_out8(int port, int data);int io_load_eflags(void);void io_store_eflags(int eflags);void init_palette(void);void set_palette(int start, int end, unsigned char *rgb);void boxfill8(unsigned char *vram, int xsize, unsigned char c, int x0, int y0, int x1, int y1);#define COL8_000000 0#define COL8_FF0000 1#define COL8_00FF00 2#define COL8_FFFF00 3#define COL8_0000FF 4#define COL8_FF00FF 5#define COL8_00FFFF 6#define COL8_FFFFFF 7#define COL8_C6C6C6 8#define COL8_840000 9#define COL8_008400 10#define COL8_848400 11#define COL8_000084 12#define COL8_840084 13#define COL8_008484 14#define COL8_848484 15void HariMain(void){ char *vram; int xsize, ysize; init_palette(); vram = (char *) 0xa0000; xsize = 320; ysize = 200; // 绘制很多个长方体来完成一个窗口 // 其他代码跟之前都一样,写在这里只是为了保持一个完整性。 boxfill8(vram, xsize, COL8_008484, 0, 0, xsize - 1, ysize - 29); boxfill8(vram, xsize, COL8_C6C6C6, 0, ysize - 28, xsize - 1, ysize - 28); boxfill8(vram, xsize, COL8_FFFFFF, 0, ysize - 27, xsize - 1, ysize - 27); boxfill8(vram, xsize, COL8_C6C6C6, 0, ysize - 26, xsize - 1, ysize - 1); boxfill8(vram, xsize, COL8_FFFFFF, 3, ysize - 24, 59, ysize - 24); boxfill8(vram, xsize, COL8_FFFFFF, 2, ysize - 24, 2, ysize - 4); boxfill8(vram, xsize, COL8_848484, 3, ysize - 4, 59, ysize - 4); boxfill8(vram, xsize, COL8_848484, 59, ysize - 23, 59, ysize - 5); boxfill8(vram, xsize, COL8_000000, 2, ysize - 3, 59, ysize - 3); boxfill8(vram, xsize, COL8_000000, 60, ysize - 24, 60, ysize - 3); boxfill8(vram, xsize, COL8_848484, xsize - 47, ysize - 24, xsize - 4, ysize - 24); boxfill8(vram, xsize, COL8_848484, xsize - 47, ysize - 23, xsize - 47, ysize - 4); boxfill8(vram, xsize, COL8_FFFFFF, xsize - 47, ysize - 3, xsize - 4, ysize - 3); boxfill8(vram, xsize, COL8_FFFFFF, xsize - 3, ysize - 24, xsize - 3, ysize - 3); for (;;) { io_hlt(); }}void init_palette(void){ static unsigned char table_rgb[16 * 3] = { 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc6, 0xc6, 0xc6, 0x84, 0x00, 0x00, 0x00, 0x84, 0x00, 0x84, 0x84, 0x00, 0x00, 0x00, 0x84, 0x84, 0x00, 0x84, 0x00, 0x84, 0x84, 0x84, 0x84, 0x84 }; set_palette(0, 15, table_rgb); return; }void set_palette(int start, int end, unsigned char *rgb){ int i, eflags; eflags = io_load_eflags(); io_cli(); io_out8(0x03c8, start); for (i = start; i <= end; i++) { io_out8(0x03c9, rgb[0] / 4); io_out8(0x03c9, rgb[1] / 4); io_out8(0x03c9, rgb[2] / 4); rgb += 3; } io_store_eflags(eflags); return;}void boxfill8(unsigned char *vram, int xsize, unsigned char c, int x0, int y0, int x1, int y1){ int x, y; for (y = y0; y <= y1; y++) { for (x = x0; x <= x1; x++) vram[y * xsize + x] = c; } return;}
运行结果如下:
到这里,有点操作系统的样子了,但也只是绘制了一个画面而已。
后面还要完成对鼠标,键盘的相应代码,对内存的管理,以及编写应用程序等功能。
到现在为止,编译完成的映像文件一共1216字节,1.2KB。
下次就显示文字,显示鼠标,以及为操作系统的内存管理做一些准备。
内存管理是操作系统中,重要的内容。我们后期学习C++时,经常要防止内存泄漏。这里的内存管理就是C++中的内存泄漏的基础知识。
总结:
今天,第四天,完成了对显示器的显示内容的控制。我们实现了对显示屏上任一像素的颜色的设定。利用了调色板,以及设定调色板时要关闭中断,往相应端口发送颜色表等。
第五天,我们就显示文字,显示鼠标,再后面控制鼠标,控制键盘,打印文字,保存文件,实现多任务,实现用C语言在操作系统上编写程序等等。
标签: #c语言建立坐标系绘图 #c语言operate函数 #c语言屏幕显示程序 #c语言x0 #图的基本操作c语言