C语言与画面显示的练习
昨天完成了黑屏的显示,今天主要完成白屏,花屏,方块屏,基本背景的显示等等。先用汇编直接操作内存,后用C语言中的指针再写一遍同样功能的函数;其中牵扯到自定义调色板的方法和EFLAGS的使用。以后编程基本上都是C语言,总算能脱离汇编了。
汇编实现颜色显示
老规矩,复制代码
把 \30天自制操作系统\projects\04_day\harib01a 复制到 \30天自制操作系统\tolset。
naskfunc.nas文件
; naskfunc
; TAB=4
[FORMAT "WCOFF"] ; 指定生成的目标文件格式为WCOFF
[INSTRSET "i486p"] ; 指定代码使用i486的指令集
[BITS 32] ; 声明代码是为32位模式编写
; 制作目标文件的信息
[FILE "naskfunc.nas"] ; 指定源文件名信息
GLOBAL _io_hlt,_write_mem8 ;函数的声明
[SECTION .text] ; 定义接下来的代码属于 .text 段
_io_hlt: ; void io_hlt(void);
HLT
RET
;新函数:把data送入指定的addr中
_write_mem8: ; void write_mem8(int addr, int data);
MOV ECX,[ESP+4] ; [ESP+4]中存放的是地址,读入ECX
MOV AL,[ESP+8] ; [ESP+8]中存放的是数据,读入AL
MOV [ECX],AL
RET
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();
}
}
asmhead.nas
;部分代码。
VMODE EQU 0x0ff2 ; 颜色位数
SCRNX EQU 0x0ff4 ; x分辨率
SCRNY EQU 0x0ff6 ; y分辨率
VRAM EQU 0x0ff8 ; 图像缓冲区(显存)开始地址
ORG 0xc200 ; 程序装载地址
MOV AL,0x13 ; VGA显卡调用、320x200x8bit
MOV AH,0x00
INT 0x10
MOV BYTE [VMODE],8 ; 记录画面模式
MOV WORD [SCRNX],320 ; 记录分辨率
MOV WORD [SCRNY],200
MOV DWORD [VRAM],0x000a0000
这三段代码主要实现了调用bios中断,使屏幕显示纯白。
首先是asmhead.nas中设置BIOS中断调用和各种参数
;设置为VGA显卡模式(320x200x8bit)
MOV AL,0x13
MOV AH,0x00
INT 0x10
之后把图像分辨率、颜色位数、显存地址存入所指定的内存单元中
VMODE EQU 0x0ff2 ; 颜色位数
SCRNX EQU 0x0ff4 ; x分辨率
SCRNY EQU 0x0ff6 ; y分辨率
VRAM EQU 0x0ff8 ; 图像缓冲区(显存)开始地址
;……………………
;8存入0x0ff2单元
MOV BYTE [VMODE],8 ; 记录画面模式
;320存入0x0ff4单元,后面以此类推。
MOV WORD [SCRNX],320 ; 记录分辨率
MOV WORD [SCRNY],200
MOV DWORD [VRAM],0x000a0000
提醒下,内存地址和软盘中数据存放的地址是不同的。软盘中的文件要加载到内存中才能运行,ORG 0xc200、VMODE EQU 0x0ff2
……这些地址均为内存地址。又因为ipl10.nas中DS寄存器初始化为0,所以这些地址是实际上的物理地址。
新函数:把data送入指定的addr中
_write_mem8: ; void write_mem8(int addr, int data);
MOV ECX,[ESP+4] ; [ESP+4]中存放的是地址,读入ECX
MOV AL,[ESP+8] ; [ESP+8]中存放的是数据,读入AL
MOV [ECX],AL
RET
C语言函数调用中,传入的参数是从右向左压入栈的,具体而言void write_mem8(int addr, int data)
函数中data先入栈,addr后入栈,执行完毕后函数的返回地址入栈。ESP是栈指针寄存器,指向栈顶的内存单元。栈是一种保存在内存中的数据结构,栈中保存的数据具有后进先出的特点。
其实就是一个桶,最先放进去的东西会被压在最下面。然而在计算机里这个桶是倒扣状态,存数据时要向上塞。
int类型变量在32位系统中占用4个字节,所以addr、data占用4个存储单元,所以MOV ECX,[ESP+4]
就是把addr
的数据存入ECX;之后通过AL,把data的数据存入ECX所指的内存单元中。
最后在C语言程序中
int i;
for (i = 0xa0000; i <= 0xaffff; i++) {
write_mem8(i, 15); /* MOV BYTE [i],15 */
}
15是在 8 位彩色VGA 模式中白色的意思,是调色板索引。每个像素用一个8位数值表示0x00——0xFF,这个数值不储存颜色,而是一个指向颜色查找表的索引。总之就是这个数0——255随便填,每个数都表示不同颜色。
for循环中有两个问题,为什么内存从0xa0000到0xaffff?为什么传入int类型的15时,内存地址不+4?
- 0xA0000是规定好的标准。显示的分辨率为320x200x8bit=64000byte。64000=0xFA00,取个整数0xA0000+0xFFFF=0xAFFFF。(其实这里改成i <= 0xafa00也完全不受影响)
- 32位模式下,没有声明的数值默认都是int类型,除非超过int类型的范围,所以函数
write_mem8(i, 15)
中理论上来说地址应该+4而不是+1。这是因为MOV AL,[ESP+8]
。AL是个8位寄存器,32位int传入8位寄存器时高24位被直接舍弃了。
最后,模拟运行。显示纯白
harib01b显示花屏这里就改了一行代码,write_mem8(i, i & 0x0f)
。没什么好说的。
C语言实现颜色显示
harib01c、harib01d、harib01e三篇都是使用C语言的指针重写void write_mem8(int addr, int data)函数。并在naskfunc.nas中删除了write_mem8(int addr, int data)
/* harib01c */
char *p;
for (i = 0xa0000; i <= 0xaffff; i++) {
p = i;
*p = i & 0x0f;
}
/* harib01d */
char *p;
p = (char *) 0xa0000;
for (i = 0; i <= 0xffff; i++) {
*(p + i) = i & 0x0f;
}
/* harib01e */
char *p;
p = (char *) 0xa0000;
for (i = 0; i <= 0xffff; i++) {
p[i] = i & 0x0f;
}
以上三段代码的功能都是实现花屏。
提一下指针的概念char *p;作者把指针称之为地址变量我觉得总结的很好。
- p = 3; 3作为地址赋值给p。
- p = i; i中的数据作为地址赋值给p。
- *p = 3; 指针的解引用,将3写入p所指的内存单元中。
- i = *p; 指针的解引用将p地址中所存的值赋值给i。
- *(p + i)和p[i]是等效的写法。此时p[i]不是数组,没有声明。
色号设置
由于颜色输出采用256色模式,号只有8位。而如今RGB采用16进制24位颜色码,所以需要对其进行映射,例如把#ffffff映射成15号索引。这些映射的索引编号和颜色均可自定义设置。
注意!!调色板中每个RGB每个通道只有6位,不是8位,这是硬件设计决定的
#000000:黑 #ffffff:白 #008484:浅暗蓝 #0000ff:亮蓝 #00ffff:浅亮蓝 #840084:暗紫 #ffff00:亮黄 #008400:暗绿 #000084:暗蓝 #00ff00:亮绿 #840000:暗红 #ff00ff:亮紫 #ff0000:亮红 #c6c6c6:亮灰 #848484:暗灰 #848400:暗黄
复制代码
把 \30天自制操作系统\projects\04_day\harib01f 复制到 \30天自制操作系统\tolset。
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 HariMain(void)
{
int i;
char *p;
init_palette();
p = (char *) 0xa0000;
for (i = 0; i <= 0xffff; i++) {
p[i] = i & 0x0f;
}
for (;;) {
io_hlt();
}
}
/* init_palette函数主要是定义和输出颜色 */
/* 颜色只有24位,若定义成int类型(32位)会浪费空间 */
/* 所以用3个连续的char类型定义颜色 */
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(); /* 中断许可标志的值置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;
}
这段代码很简单,几个新加的函数无法用C语言,只能使用汇编编写。所以新的naskfunc.nas文件中多了很多汇编指令。
naskfunc.nas文件
; naskfunc
; TAB=4
[FORMAT "WCOFF"] ; 指定生成的目标文件格式为WCOFF
[INSTRSET "i486p"] ; 使用了486的指令集
[BITS 32] ; 声明代码是为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] ; 定义接下来的代码属于 .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
_io_in8: ; int io_in8(int port);
MOV EDX,[ESP+4] ; port
MOV EAX,0
IN AL,DX
RET
_io_in16: ; int io_in16(int port);
MOV EDX,[ESP+4] ; port
MOV EAX,0
IN AX,DX
RET
_io_in32: ; int io_in32(int port);
MOV EDX,[ESP+4] ; port
IN EAX,DX
RET
_io_out8: ; void io_out8(int port, int data);
MOV EDX,[ESP+4] ; port
MOV AL,[ESP+8] ; data
OUT DX,AL
RET
_io_out16: ; void io_out16(int port, int data);
MOV EDX,[ESP+4] ; port
MOV EAX,[ESP+8] ; data
OUT DX,AX
RET
_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
_io_store_eflags: ; void io_store_eflags(int eflags);
MOV EAX,[ESP+4]
PUSH EAX
POPFD ; 指POP EFLAGS
RET
首先bootpack.c文件中io_out8(0x03c8, start)中的0x03c8、0x03c9等解释。
硬件指令IO
简单的说,计算机中CPU与各种设备相连,为了进行数据输入输出,对各种设备进行了编号——端口号。0x03c8、0x03c9就是某个设备的编号而已。汇编语言中向设备发送电信号的是OUT指令;从设备取得电气信号的是IN指令。端口号不能随意指定。
- IN AL, 21H ; 从21H端口读取一个字节到AL寄存器
- OUT 21H,AL;将AL的值发送的21H端口
中断操作
io_load_eflags、io_store_eflags、io_cli、io_sti函数
新汇编指令:
- CLT 关中断,中断标志位(IF)置0
- STL 开中断,中断标志位(IF)置1
- PUSH 入栈
- POP 出栈
- PUSHFD 将EFLAGS(标志位) 寄存器的值按双字长入栈
- POPFD 将栈顶数据按双字长出栈并存入EFLAGS(标志位) 寄存器
调色板设置是一个原子操作,必须要关中断。这几个函数都是开关中断相关的函数。中断标志位(IF)表示是否允许中断,保存在EFLAGS寄存器中。EFLAGS寄存器是从16位的FLAGS寄存器扩展而来的32位寄存器。只能用PUSHFD和POPFD命令读写,也称标志寄存器或程序状态字(PSW)记录了各种标志信息。结构如下图。
开关中断前要保存EFLAGS寄存器的值。作者直接把它保存在eflags变量中。恢复时直接把eflags变量的值存入EFLAGS寄存器中,没有使用STL指令。
自定义调色板
VGA端口号详情可自行阅读https://wiki.osdev.org/VGA_Hardware#Port_0x3C8
其中记录了调色板的访问步骤
- 关中断
- 将想设定的调色板索引写入0x03c8,然后,按RGB的顺序写入0x03c9。如果还想继续设定下一个调色板,则省略调色板索引号码(此时索引会自动+1),再按照RGB的顺序写入0x03c9
- 如果想要读出当前调色板的状态,首先要将调色板的号码写入0x03c7,再从0x03c9读取3次。读出的顺序就是R,G,B。如果要继续读出下一个调色板,同样省略调色板号码的设置,按RGB的顺序读出。
- 开中断
解释下init_palette();这个函数
- 首先用static unsigned char table_rgb[16 * 3]定义了一个数组,数组中每三个单元保存一个颜色的RGB值。其中的颜色顺序可自定义。
- set_palette(0, 15, table_rgb);函数中0,15是自定义的调色板索引号。table_rgb是table_rgb数组的首地址。
- set_palette函数中首先记录中断许可标志,之后关中断,然后通过io_out8(0x03c8, start);把自定义的索引号写入0x03c8的内存单元。之后用for循环,把RGB的值依次写入0x03c9。rgb[0] / 4的目的是RGB中每个颜色只有6位,table_rgb是char,每个值是8位。所以要右移2位。
- 之后rgb += 3;即rgb=rgb+3。rgb指向了下一组RGB
这一段曾卡了我几天,主要还是书上介绍的不是很详细。举例来说set_palette(0, 15, table_rgb);若改成set_palette(10, 15, table_rgb);那么黑色的索引值就变成10,亮红的索引值就变成11……
运行结果
画图
这一节也很简单,就是单纯指定内存单元中保存什么值
复制代码
把 \30天自制操作系统\projects\04_day\harib01g 复制到 \30天自制操作系统\tolsetg。
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 15
void 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, /* 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(); /* 中断许可标志的值置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;
}
程序中多了个boxfill8函数
首先,画面上有320×200(=64 000)个像素假设左上点的坐标是(0,0),右下点的
坐标是(319,199),那么像素坐标,由于像素以行优先方式存入显存单元。(x,y)对应的VRAM地址应按下式计算
VRAM地址 = 0xa0000 + x + y * 320
于是void boxfill8(unsigned char *vram, int xsize, unsigned char c, int x0, int y0, int x1, int y1);函数中
- char *vram 显存地址
- xsize x轴分辨率
- c 颜色索引
- x0 矩形左上角坐标(x轴)
- y0 举行左上角坐标(y轴)
- x1 矩形右下角坐标(x轴)
- y1 矩形右下角坐标(y轴)
注意,要确保 x0, y0, x1, y1的值在320×200之中,否则会发生显存越界。
运行如下
今天最后开始画一个真正的界面吧
复制代码
把 \30天自制操作系统\projects\04_day\harib01h 复制到 \30天自制操作系统\tolset。
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 15
void 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;
}
void set_palette(int start, int end, unsigned char *rgb)
{
int i, eflags;
eflags = io_load_eflags(); /* 用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;
}
运行结果,只是张纯粹的图片。