拼一个自己的操作系统(SnailOS 0.03的实现)

拼一个自己的操作系统 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

 

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

 

接下来我们要在命令行窗口中输入“make resume”

在调试窗口的command输入框中输入stop命令,虚拟机暂停运行。

1fa2b4758f10fa4512dcdf349b8cdec5.png

 

我们想要看到,显存地址,则这是可以输入dd 0x200000。同时为了验证这不是运气好胡乱得到的,我们还输入了dd 0x100000,那里是我们内核的所在。看看吧,0xe0000000是显存地址,0xe85250d6是我们在boot.asm中使用的魔数。

4823a833723276f9d4a83d03890e371d.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

 

 

刚才我们直接把显存地址定义成了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

 

 

这一章,我们不但很平顺的进入了图形模式,而且能够还能够画一些简单的图形了。虽然那些图形看起来很丑,但是它们却不失温柔。对于我们这个刚刚起步系统来说,还是进步不小的。

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

weixin_39410618

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值