拼一个自己的操作系统 SnailOS 0.03的实现
拼一个自己的操作系统SnailOS0.03源代码-Linux文档类资源-CSDN下载
操作系统SnailOS学习拼一个自己的操作系统-Linux文档类资源-CSDN下载
SnailOS0.00-SnailOS0.00-其它文档类资源-CSDN下载
进入图形模式,画一些简单的图形
https://pan.baidu.com/s/19tBHKyzOSKACX-mGxlvIeA?pwd=i889
进入图形模式
在上一章,我们刻意的把第2个工作环境中的boot.asm设置成了“不开启帧缓冲模式”。这样一来,操作系统就只能使用80*25文本模式的方式显示字符了。这可不是我们操作系统应该有的样子呀!我们是要在一种图形模式下继续展开的。因此,这一节我们要首先打开图形模式,从而进入我们开发的新天地。说到这里,大家也一定想到了,我们的SnailOS开发一定是继承了第2个开发环境的特征,并且是用grub2引导的。没错了,不出所料吧!因为笔者更喜欢图形环境,所以才进行了这样大胆的尝试。这真的是和笔者自己的能力不相匹配,甚至是自不量力。不过好在笔者对自己的要求不高,脸皮也没那么薄,因此,这个操作系统勉强称之为图形的,更甚者勉强称之为操作系统。
节选自boot.asm,并将注释全部去掉,我们就顺利的进入了图形模式。
(上面的代码省略)
; 帧缓冲的信息
framebuffer_tag_start:
;(改动从这里开始)
dw 0x5
dw 0x1
dd framebuffer_tag_end - framebuffer_tag_start
dd 1024
dd 768
dd 32
;(改动到这里结束)
framebuffer_tag_end:
(下面的代码省略)
大家可能会想到,这样一来,我们的显存地址是否也会改变呢?答案是肯定的。而且我们是不能轻易的获得显存地址的,因为我们的系统已经失去了显示字符的能力。至少不能为我们显示显存地址,这样的基本信息了。那么现在我们怎么办呢?对了,既然是grub2为我们引导的内核,说不定它会有办法的。因此这里只要回去翻阅了那个长长的multiboot2规范的网页。还真的找到了呀!
1.1.2 获取显存地址
大家看到图中选中的部分了吗?已经被网页翻译工具翻译成中文了。大概意思就是说,该处存放这显存的物理地址。那么如何找到这里呢?让我们一个萝卜一个萝卜的往前拔好了。想要访问到这个信息,我们就必须知道,grub2在系统引导把控制权交给我们之前,还保留了一个multiboot2的信息区(仔细阅读mulboot2规范可知,这些繁重的工作还是留给有心人吧,我想自己已经够用的了。),据说如果在规范中包含了帧缓冲区这个标签域,则我们可以通过查阅,信息区中的显存物理地址的标签域来获得想要的信息。而我们在boot.asm中,通过将ebx作为参数压栈,传给被调用的kernel_main函数的正是这个值。哈哈获得显存物理地址的工作原来就是这么简单!
这一次,我们在工作目录中添加了multiboot2目录,在该目录中添加了两个文件,分别是multiboot2.h和video_addr.c。并且我们在Makefile中添加了相应的编译语句。
Makefile 节选
(上面省略)
INCLUDE = -I ./kernel/ -I ./multiboot/
OBJ=./create/boot.o ./create/kernel.o ./create/video_addr.o
(中间省略)
./create/video_addr.o: ./multiboot2/video_addr.c
$(CC) $(INCLUDE) -c -o $@ $^
![89c877b2e723743c8136ba3d6016ba95.png](https://i-blog.csdnimg.cn/blog_migrate/ee4d1375c7e44b72f429f55ec3f0343d.png)
multiboot.h是从规范的网站下载的,还是大家自行从网站复制吧。下面的新加的文件video_addr.c。
// video_addr.c 创建者:至强 创建时间:2022年8月
#include "multiboot2.h"
// 从函数的名字可以看出,这就是一个获取显存地址的函数。
void get_video_addr(unsigned int magic, unsigned int addr) {
// 标签域的指针
struct multiboot_tag * tag;
// 这是标签域的基地址
struct multiboot_tag_load_base_addr* load_base_addr_tag;
// 这是显存域的基地址
struct multiboot_tag_framebuffer *tagfb;
// 在内存的2M处,开辟存储空间,准备存储显存基地址。
// 一会你就会发现这个的妙用。
unsigned int *video_phy_addr = (unsigned int*)0x200000;
// 魔数的判别
if (magic != MULTIBOOT2_BOOTLOADER_MAGIC) {
return;
} else {
}
// 对齐的判别
if (addr & 7) {
return;
} else {
}
// 根据规范,第一个双字是信息结构的总长度,第二个
// 双字保留。
unsigned int size = *(unsigned int *)addr;
// 对于初学c语言的同学,这个循环还是挺难的,我只是简单的
// 解释一下,不知道大家能不能够看懂,尽力而为吧!
// 首先如果要想完全理解这段程序,需要区看信息区数据结构的
// 定义。也就是看看上面那个头文件multiboot2.h,根据我的理解
// 每个数结构本身是不难理解的,难的在于这些不等长的数据结构
// 怎么用for遍历,我们发现每个结构,都包含自身长度,哈哈,
// 这样不就有解了。第一个表达式是把tag赋值为第一个标签域的
// 地址,二个表达式当然是遍历的终止条件,也就是信息域中设置
// 了一个结束标签域。怎样获得下一个标签域的地址呢?这则是
// for循环中第三个表达式所有达到的功能。即是tag加上本标签域
// 的长度,就是下一个标签域的地址,并且在长度计算中还做到了
// 8字节的对齐。
for (tag = (struct multiboot_tag *)(addr + 8);
tag->type != MULTIBOOT_TAG_TYPE_END;
tag = (struct multiboot_tag *)((multiboot_uint8_t *)tag
+ ((tag->size + 7) & ~7)))
{
// 根据标签域中的类型信息,做不同的操作。
switch (tag->type) {
case MULTIBOOT_TAG_TYPE_LOAD_BASE_ADDR:
load_base_addr_tag =(struct multiboot_tag_load_base_addr*)tag;
break;
case MULTIBOOT_TAG_TYPE_BASIC_MEMINFO:
break;
case MULTIBOOT_TAG_TYPE_FRAMEBUFFER:
tagfb = (struct multiboot_tag_framebuffer*)tag;
// 这里将显存地址生生的写到了物理内存2M处。
*video_phy_addr = (unsigned int)tagfb->common.framebuffer_addr;
}
}
}
这个文件已经极大地简化了对multiboot2信息区的解析,甚至都对内存容量没有在意。笔者真的是太懒了吧。也许就是这样吧。反正我们现在亟须和必须的即使显存地址,其他的还收今后用到再说。
按照笔者自己的说法,显存地址是有了,可是还是看不到啊!怎么才能看到显存地址呢?现在就让我们用一下virtual box自带的调试功能,来给大家展示一下这个我们辛辛苦苦获得的显存地址吧!让我们来看Makefile的这里。
Make file 节选
(上面省略)
dbg: install
start virtualboxvm --startvm "C:\Users\free2\.VirtualBox\temp\temp.vbox" --debug-command-line
resume:
vboxmanage controlvm "C:\Users\free2\.VirtualBox\temp\temp.vbox" resume
(下面省略)
当我们在命令行中输入“make dbg”时,virtual box进入调试模式(这是停机),再次输入“make resume”,虚拟机开始运行。看到下面的调试窗口了吗?
![afd308dfd35da663c16d521ab842b4a6.png](https://i-blog.csdnimg.cn/blog_migrate/5dca69ab78fd2a946bb72e95b73858df.png)
接下来我们要在命令行窗口中输入“make resume”
在调试窗口的command输入框中输入stop命令,虚拟机暂停运行。
![1fa2b4758f10fa4512dcdf349b8cdec5.png](https://i-blog.csdnimg.cn/blog_migrate/87c4cd73a5d0ad5c82ec87b5d495440b.png)
我们想要看到,显存地址,则这是可以输入dd 0x200000。同时为了验证这不是运气好胡乱得到的,我们还输入了dd 0x100000,那里是我们内核的所在。看看吧,0xe0000000是显存地址,0xe85250d6是我们在boot.asm中使用的魔数。
![4823a833723276f9d4a83d03890e371d.png](https://i-blog.csdnimg.cn/blog_migrate/454920f28f1c1c73df8f298d8995eb0a.png)
顺便说一下,“stop”是停止虚拟机运行,而“dd 【数值】”则是以双字的形式显示内存的内容。
1.1.3 画矩形测试图形模式
有了显存的地址,这一次我们画个简单的图形,那不是信手拈来吗!现在让我们来试试吧!
最好画的图形就是矩形了,因此,我们在内核中直接画了3个不同颜色的矩形,哈哈,是三基色的矩形哦!可能有人会问,video为什么定义为无符号整型指针呢?是这样的,在这个图形模式下,采用的是32位真彩色,一个像素占用的存储空间是4字节,所以我们这样定义。其实画点的函数是最简单,不过放在1024 * 768分辨率的屏幕上啥都不是,所以就改成画矩形的for循环了,这样看起来更过瘾了。
kernel.c 节选
(上面省略)
void kernel_main(unsigned int magic, unsigned int addr) {
pos = 0;
int i, j;
unsigned int* video = (unsigned int*)0xe0000000;
for(j = 20; j < 80; j++) {
for(i = 30; i < 90; i++) {
video[i + j * 1024] = 0x00ff0000;
}
}
for(j = 300; j < 380; j++) {
for(i = 200; i < 290; i++) {
video[i + j * 1024] = 0x0000ff00;
}
}
for(j = 520; j < 580; j++) {
for(i = 130; i < 190; i++) {
video[i + j * 1024] = 0x000000ff;
}
}
put_s(" Hello SnailOS...! ", 0xc);
put_s(" Hello, World!......", 0x9);
get_video_addr(magic, addr);
while(1);
}
(下面省略)
大家看图就完全明白了。
![ea6b8912f6d2ff8bc1012e4d6d69e9e7.png](https://i-blog.csdnimg.cn/blog_migrate/119a2d2c1b015d770f0acc71915a313c.png)
刚才我们直接把显存地址定义成了0xe0000000,显然这是我们从调试器中看到了这样的结果才做出的“鲁莽行为”。在函数get_video_addr()中,我们已经把该地址存储到了内存地址的0x200000处,当然可以从这里获得。可是细想起来,如果是稍微改造一下get_video_addr()函数,让它返回显存地址不就更好了吗!这样我们只要调用函数,显存地址就来了,同时也不用担心显存地址可能不是0xe0000000的情况了。下面便是改造后的函数。
// video_addr.c 创建者:至强 创建时间:2022年8月
#include "multiboot2.h"
#include "global.h"
// 从函数的名字可以看出,这就是一个获取显存地址的函数。
unsigned int* get_video_addr(unsigned int magic, unsigned int addr) {
// 标签域的指针
struct multiboot_tag * tag;
// 这是标签域的基地址
struct multiboot_tag_load_base_addr* load_base_addr_tag;
// 这是显存域的基地址
struct multiboot_tag_framebuffer *tagfb;
unsigned int video_phy_addr;
(中间省略)
case MULTIBOOT_TAG_TYPE_FRAMEBUFFER:
tagfb = (struct multiboot_tag_framebuffer*)tag;
// 保存显存地址
video_phy_addr = (unsigned int)tagfb->common.framebuffer_addr;
}
}
return (unsigned int*)video_phy_addr;
}
1.1.4 最基本的图形绘制函数
画出各种基本图形其实并不是一件很复杂的事情,只要有我们下面介绍的几个简单的函数也就足够了。可是,要想把一件事情做好,就不是这么简单了。概括的说,那其实是一门单独的学问——计算机图形学,大家都听说过吧。不过话又说回来,我们是在拼操作系统了,难道要去学习图形学,那可不是一朝一夕的事情哦!既然说了简单,那就简单着来吧。现在够用就好了。下面便是我们新加的内容。
首先是Makefile的改变。
【Make file 节选】
(上面省略)
INCLUDE = -I ./kernel/ -I ./multiboot/ -I ./x/
OBJ=./create/boot.o ./create/kernel.o ./create/video_addr.o ./create/x.o \
(中间省略)
./create/x.o: ./x/x.c
$(CC) $(INCLUDE) -c -o $@ $^
(下面省略)
可以看出,我们在Makefile文件中分别对路径、目标文件、编译语句进行了添加。相信这个添加新的待编译文件的套路大家也都熟悉了吧。也就是先在工作目录下创建名叫x的目录,在x下创建文件x.h和x.c文件,然后对Makefile文件进行上述修改。这个套路大家既然已经熟悉了,今后的工作我们就不再赘述了,只要按照这个思路添加也就试了。
第二个变化是,为了使获取显存的函数,在任何地方都能用,我们在global.h中添加了两个全局变量用以存储multiboot2传给我们的信息。
【global.h】节选
(上面省略)
unsigned int multiboot2_magic, multiboot2_addr;
(下面省略)
现在轮到我们新添加的x.h和x.c文件上场了。
【x.h】
// x.h 创建者:至强创建时间:2022年8月
#ifndef __X_H
#define __X_H
// 为了缩短画三角形函数的长度,我们定义了一个描述点的数据
// 数据结构。
struct point {
int x;
int y;
};
// 画图形函数的声明。
void draw_point(int* buf, unsigned int win_x_size,
int x, int y, unsigned int colour);
void fill_rectangle(int* buf, unsigned int win_x_size, int x, int y, unsigned int x_size,
unsigned int y_size, unsigned int colour);
void screen_init(void);
void info_area_cls(void);
void fill_circle(int* buf, unsigned int win_x_size, int x0, int y0,
int r, unsigned int colour);
void draw_line(int* buf,unsigned int win_x_size, int start_x, int start_y,
int end_x, int end_y, unsigned int colour);
void draw_circle(int* buf, unsigned int win_x_size, int center_x,
int center_y, int r, unsigned int colour);
void draw_box(int* buf, unsigned int win_x_size, int xs, int ys,
int xsize, int ysize, unsigned int colour);
void draw_triangle(int* buf, unsigned int win_x_size, struct point* a,
struct point* b, struct point* c, unsigned int colour);
void fill_triangle(int* buf, unsigned int win_x_size, struct point* a,
struct point* b, struct point* c, unsigned int colour);
unsigned int get_colour(int* buf, unsigned int win_x_size, int x, int y);
#endif
【x.c】
// x.c 创建者:至强 创建时间:2022年8月
#include "x.h"
#include "global.h"
// 这类函数包含的参数相对多了一些。
// 我们就在这里说些共性吧。比如,都包含buffer
// 参数,这个参数是指示我们要把图形数据写入那个块内存
// 区域。这说明要绘制的图形,一开始不一定就直接绘制到
// 显存中,而是暂存的某一缓存区中备用。还有win_x_size它
// 是指示窗口的横向像素长度。有了这个参数,我们在不同
// 的矩形窗口中画图就可以避免出现图形变形的问题。
// 第一个函数是在屏幕上画点的函数。参数就是点在矩形
// 中的横纵坐标以及点的属性,即是颜色值。
void draw_point(int* buf, unsigned int win_x_size,
int x, int y, unsigned int colour) {
buf[x + y * win_x_size] = colour;
}
// 画矩形的函数需要指示矩形的左上坐标以及矩形的边长,
// 当然每个点的颜色值也要指出。算法就是两层循环,用
// 这种方法遍历矩阵的每个元素。同时调用了画点的函数。
// 调用函数会花费很多时间,因此建议直接写内存。我们
// 在这里使用函数调用只是为了使编程思路更明晰。大家
// 更好理解,因为画任何的图形的实质都是画点的组合。
// 这个函数是画实心的矩形了。
void fill_rectangle(int* buf, unsigned int win_x_size, int x, int y,
unsigned int x_size, unsigned int y_size,
unsigned int colour) {
int i, j;
for(j = y; j < y + y_size; j++) {
for(i = x; i < x + x_size; i++) {
draw_point(buf, win_x_size, i, j, colour);
}
}
}
// 画实心的圆是根据笛卡尔坐标系上圆的公式来的,即是
// 勾股定理得来的x^2 + y^2 = r^2公式。当然这里是画了
// 若干个圆,终止条件是x、y都是0的情况。
void fill_circle(int* buf, unsigned int win_x_size, int x0, int y0,
int r, unsigned int colour) {
int x, y;
for(x = x0 - r; x <= x0 + r; x++) {
for(y = y0 - r; y <= y0 + r; y++) {
if((x - x0) * (x - x0) + (y - y0) * (y - y0) <= r * r) {
draw_point(buf, win_x_size, x, y, colour);
}
}
}
}
// 这两个函数一个是初始化屏幕,一个是初始化信息区,之所以
// 设置信息区主要是方便今后的调试。大家再看看屏幕是不漂亮
// 了不少。同时我们甚至了两个全局变量,从而在任何时候都能够
// 方便的获得显存地址。
void screen_init(void) {
unsigned int* v = get_video_addr(multiboot2_magic, multiboot2_addr);
fill_rectangle(v, 1024, 0, 128, 1024 - 0, 768 - 128, 0x003098df);
}
void info_area_cls(void) {
unsigned int* v = get_video_addr(multiboot2_magic, multiboot2_addr);
fill_rectangle(v, 1024, 0, 0, 1024, 128, 0x00000000);
}
// 下面是画直线的函数,这个函数反而是比较难的。之所以这样说,
// 主要是我们现在这个阶段,所有的画图动作都是由CPU完成,而且
// 以我们现在能力是不能进行浮点运算的。这样一来,恰好直线上
// 的点不是整数而是实数,所以我们只能用整数模拟实数。算法的
// 方面我们就要动动脑筋了。幸好前辈们为我们准备了很多好用的算法,
// 我们只要拿来就好了。其实画图这类的操作属于一门专门的学问,
// 叫做计算机图形学,我们只要参考一些皮毛的东西,就完全可以
// 做到这里,完全没必要深入研究。
// 大部分直线都不会是横线或者竖线,而要想求的直线中下一点的
// 坐标就是计算下一个要画的点相对于当前点坐标的增量,这个增量
// 越接近于实际数值,则直线越逼真。这个算法的关键是根据起点
// 到终点的坐标,横纵坐标的变化幅度是不一样的,变化大的一方
// 增量始终为1(我们把实际值扩大了8192倍,最后在缩小这个倍数),
// 而变化小的一方的增量要用增量加1或者减1除以直线的长度,而
// 这里所谓的直线的长度就是变化大的一方加1了,至于这个算法
// 为什么这样笔者就说不清楚了,总之技巧性挺强的,效果也是
// 杠杠的。
void draw_line(int* buf,unsigned int win_x_size, int start_x, int start_y,
int end_x, int end_y, unsigned int colour) {
int i, x, y, len, dx, dy;
dx = end_x - start_x;
dy = end_y - start_y;
x = start_x << 13;
y = start_y << 13;
if(dx < 0) {
dx = -dx;
}
if(dy < 0) {
dy = -dy;
}
if(dx >= dy) {
len = dx + 1;
if(start_x > end_x) {
dx = -8192;
} else {
dx = 8192;
}
if(start_y <= end_y) {
dy = ((end_y - start_y + 1) << 13) / len;
} else {
dy = ((end_y - start_y - 1) << 13) / len;
}
} else {
len = dy + 1;
if(start_y > end_y) {
dy = -8192;
} else {
dy = 8192;
}
if(start_x <= end_x) {
dx = ((end_x - start_x + 1) << 13) / len;
} else {
dx = ((end_x - start_x - 1) << 13) / len;
}
}
for(i = 0; i < len; i++) {
draw_point(buf, win_x_size, x >> 13, y >> 13, colour);
x += dx;
y += dy;
}
}
// bresenham(译作布雷斯汉姆)算法,是下面画空心圆所用的算法,大家看到了
// 什么时候坐标值该增,什么时候坐标值该减,都是由关键的d的值决定的。
// 至于d是怎么推导的,还是请大家自行研究吧,反正挺难的,笔者只是照模
// 照样的把代码复制了一遍了。
void draw_circle(int* buf, unsigned int win_x_size, int center_x,
int center_y, int r, unsigned int colour) {
int x, y, d;
x = 0;
y = r;
d = 3 - 2 * r;
draw_point(buf, win_x_size, x + center_x, y + center_y, colour);
while(x < y) {
if(d < 0) {
d = d + 4 * x + 6;
} else {
d = d + 4 * (x - y) + 10;
y--;
}
x++;
draw_point(buf, win_x_size, x + center_x, y + center_y, colour);
draw_point(buf, win_x_size, y + center_x, x + center_y, colour);
draw_point(buf, win_x_size, y + center_x, -x + center_y, colour);
draw_point(buf, win_x_size, x + center_x, -y + center_y, colour);
draw_point(buf, win_x_size, -x + center_x, -y + center_y, colour);
draw_point(buf, win_x_size, -y + center_x, -x + center_y, colour);
draw_point(buf, win_x_size, -x + center_x, y + center_y, colour);
draw_point(buf, win_x_size, -y + center_x, x + center_y, colour);
}
}
// 画空心的矩形,没有什么难度吧,这个函数之所以用画点的函数,而没有用
// 画线的函数,是因为它是在画线函数之前就已经写好了。
void draw_box(int* buf, unsigned int win_x_size, int xs, int ys,
int xsize, int ysize, unsigned int colour) {
int i, j;
for(i = xs; i < xs + xsize; i++) {
draw_point(buf, win_x_size, i, ys, colour);
}
for(j = ys; j < ys + ysize; j++) {
draw_point(buf, win_x_size, xs + xsize - 1, j, colour);
}
for(i = xs; i < xs + xsize; i++) {
draw_point(buf, win_x_size, i, ys + ysize - 1, colour);
}
for(j = ys; j < ys + ysize; j++) {
draw_point(buf, win_x_size, xs, j, colour);
}
}
// 画空心三角形的函数,就是在有了画线的函数后,将三角形的三个点
// 首尾相连。怎么样简单吧!这样我们其实可以画出空心的n边形,不过
// 这里并没有对三角形是否成立进行判断,主要靠程序员的自觉了。
void draw_triangle(int* buf, unsigned int win_x_size, struct point* a,
struct point* b, struct point* c, unsigned int colour) {
draw_line(buf, win_x_size, a->x, a->y, b->x, b->y, colour);
draw_line(buf, win_x_size, b->x, b->y, c->x, c->y, colour);
draw_line(buf, win_x_size, c->x, c->y, a->x, a->y, colour);
}
// 实心三角形的函数完全是自己杜撰的算法了,类似于扫描线的算法吧,它是
// 把空心三角形画出来后,判断各个边上点的坐标,横坐标相同的则画一条
// 和边同样颜色的横直线,当然在这之前,要首先判断出三角形三点的上下
// 位置。每天直线的起点和终点的值要拜托一个flag变量来标记,x起点做了
// 减1的操作,是我们根据测试加入的,这很可能是直线算法导致的。特别注意
// 的是这个算法很不完善,很可能出问题,大家还是谨慎使用。
void fill_triangle(int* buf, unsigned int win_x_size, struct point* a,
struct point* b, struct point* c, unsigned int colour)
{
draw_triangle(buf, win_x_size, a, b, c, colour);
struct point p[2];
int t = a->x < b->x ? a->x : b->x;
t = c->x < t ? c->x : t;
p[0].x = t;
t = a->x > b->x ? a->x : b->x;
t = c->x > t ? c->x : t;
p[1].x = t;
t = a->y < b->y ? a->y : b->y;
t = c->y < t ? c->y : t;
p[0].y = t;
t = a->y > b->y ? a->y : b->y;
t = c->y > t ? c->y : t;
p[1].y = t;
int x_len = p[1].x - p[0].x + 1;
int y_len = p[1].y - p[0].y + 1;
int i, j;
struct point f[2];
int flag = 0;
for(j = p[0].y; j < p[0].y + y_len; j++) {
for(i = p[0].x - 1; i < p[0].x + x_len; i++) {
if(get_colour(buf, win_x_size, i, j) == colour) {
if(flag == 0) {
f[0].x = i;
f[0].y = j;
flag++;
} else {
f[1].x = i;
f[1].y = j;
}
}
}
draw_line(buf, win_x_size, f[0].x, f[0].y, f[1].x, f[1].y, colour);
flag = 0;
}
}
// 为配合实心三角形的算法,专门弄了一个从窗口缓冲区获取颜色的函数。
unsigned int get_colour(int* buf, unsigned int win_x_size, int x, int y) {
unsigned int colour = buf[x + y * win_x_size];
return colour;
}
最后的改变当然使内核函数,它改变比较大,大家要仔细看哦。
【kernel.c】 节选
(上面省略)
#include "x.h"
(中间省略)
void kernel_main(unsigned int magic, unsigned int addr) {
multiboot2_magic = magic;
multiboot2_addr = addr;
pos = 0;
int i, j;
unsigned int* video = get_video_addr(magic, addr);;
screen_init();
info_area_cls();
fill_rectangle(video, 1024, 50, 50, 40, 40, 0x00ff0000);
fill_circle(video, 1024, 200, 200, 80, 0x00ffff00);
draw_line(video, 1024, 0, 0, 1023, 767, 0x00ffffff);
draw_circle(video, 1024, 200, 200, 100, 0x0000ffff);
draw_box(video, 1024, 40, 40, 20, 100, 0x0000ff00);
struct point a, b, c;
a.x = 300;
a.y = 300;
b.x = 200;
b.y = 400;
c.x = 500;
c.y = 450;
draw_triangle(video, 1024, &a, &b, &c, 0x00cccccc);
a.x = 600;
a.y = 300;
b.x = 500;
b.y = 400;
c.x = 800;
c.y = 450;
fill_triangle(video, 1024, &a, &b, &c, 0x00bcbcbc);
(下面省略)
各个画图函数我们在代码的注释中,都进行了相应的解释,因此咱们就不再啰嗦了。还是看运行效果图吧。怎么样有点意思吧!
![fe02a39096ed532ee25ad0cad55f149c.png](https://i-blog.csdnimg.cn/blog_migrate/a103f0d6638944d2168e6ac4981cb7d4.png)
这一章,我们不但很平顺的进入了图形模式,而且能够还能够画一些简单的图形了。虽然那些图形看起来很丑,但是它们却不失温柔。对于我们这个刚刚起步系统来说,还是进步不小的。