[Rx86OS-VI] 像素点阵字符串和鼠标图标

平台

处理器:Intel Celeron(R) Dual-Core CPU

操作系统:Windows7 专业版 x86

阅读书籍:《30天自制操作系统》—川合秀实[2015.03.18 ]

将《30天自制操作系统》简称为“书”。对于书中的工具,可以专门对其笔记学习。

工具:../toolset/


1 用结构体获取画面(屏幕)模式

设置画面模式是在asmhead.nas文件即进入32位模式前的准备。当时在asmhead.nas中调用BIOS程序设置画面后将画面的模式保存在内存中:

 

1.        CYLS        EQU          0x0ff0                          ;设定启动区

2.        LEDS        EQU          0x0ff1

3.        VMODE    EQU          0x0ff2                          ;颜色数目信息。颜色的位数。

4.        SCRNX      EQU          0x0ff4                          ;分辨率的X(screen x)

5.        SCRNY      EQU          0x0ff6                          ;分辨率的Y(screen y)

6.        VRAM       EQU          0x0ff8                          ;图像缓冲区的开始地址

7.       ……

8.                           MOV                  BYTE  [VMODE],8  ;记录调用int 0x10后设置的画面模式

9.                           MOV                  WORD [SCRNX],320

10.                       MOV                  WORD [SCRNY],200

11.                   MOV                  DWORD  [VRAM],0x000a0000  ;内存地址空间0xa0000 ~ 0xaffff 64kb为VRAM
 

虽然在asmhead.nas文件中保存了画面的模式,但还一直没用过(在bootpack.c中都是直接用320,200,0xa0000之类的常数)。当画面模式改变时,就不能再正确运行这个bootpack.c。所以我们应该在程序中获取在asmhead.nas文件中保存下来画面模式值。


观察用来保存画面设置的这些地址(将其它的考虑在内),可以设计一个结构体来对应:

typedef struct  _BOOTINFO {
	char cyls, leds, vmode, reserve;
	short scrnx, scrny;
	char *vram;
} BOOTINFO;

BOOTINFO  *binfo;
binfo = (BOOTINFO *) 0x0ff0;


那么“binfo-->vmode”、“binfo--> scrnx”、“binfo-->scrny”、“binfo--> vram”就分别表示“0x0ff2”、“0x0ff4”、“0x0ff6”、“0x0ff8”。之所以能保证这一点是因为以下两点:

[1] 结构体内的元素的地址是相对于结构体变量地址的偏移值,结构体第一个元素的地址跟结构体变量地址相同。

[2] 内存对齐(若编译器有内存对齐的代码,则可无reserve元素)。


2 显示字符串

因为此时在32位模式下,就不能调用BIOS程序来显示字符串。笔记“书”中在32位模式下显示字符串的方式。


2.1 像素点阵字符

可用像素点阵(如8x 16)来表示字符。在屏幕上将要点亮的点用1表示,不点亮的点用0表示。然后将每行(16)中所有要点亮的点在像素点阵中的VRAM内存地址算出来并赋予点亮的颜色。这样就得到了一个显示字符的方法,但这样就需要为每个字符的显示编写一个函数。


在8x 16点阵中,字符’A’在点阵中的信息为:

static char font_A[16] = {
		0x00, 0x18, 0x18, 0x18, 0x18, 0x24, 0x24, 0x24,
		0x24, 0x7e, 0x42, 0x42, 0x42, 0xe7, 0x00, 0x00
};

那么根据font_A[16]显示字符的程序为:

/*varm为此次画面模式下显存的地址,
* xsize为画面模式X方向像素大小,
* (x,y)为字符在屏幕上显示的位置,
*c是调色板号,
*font是经编码的字符(编码过程如上描述)*/
void putfont8(char *vram, int xsize, int x, int y, char c, char *font)
{
	int i;
	char *p, d;
	for (i = 0; i < 16; i++) {
		p = vram + (y + i) * xsize + x;
		d = font[i];
		if ((d & 0x80) != 0) { p[0] = c; }
		if ((d & 0x40) != 0) { p[1] = c; }
		if ((d & 0x20) != 0) { p[2] = c; }
		if ((d & 0x10) != 0) { p[3] = c; }
		if ((d & 0x08) != 0) { p[4] = c; }
		if ((d & 0x04) != 0) { p[5] = c; }
		if ((d & 0x02) != 0) { p[6] = c; }
		if ((d & 0x01) != 0) { p[7] = c; }
	}
	return;
}

p的下标对应字符’A’的每一行。


2.2 引用字体数据

此时,“书”中各程序的合并将比在“[OS-IV]导入C语言”中的“C程序与汇编程序的合并(机器码)”多一部分:

[Rx86OS-VI] 像素点阵字符串和鼠标图标_第1张图片

Figure 1. 加入字体

上图中的绿色部分即为往操作系统中加字体的过程,这个过程被编写在Makefile中。


按照以上方式导入hankaku字体以后,在C程序中引用字体的方式为:

extern char hankaku[4096];  //声明外部数据
hankaku + ‘a’ * 16;//得字符’a’,编码过程符合2.1中所描述过程
hankaku + ‘1’ * 16;//得字符’1’

2.3 显示字符串

用2.1中的putfont8函数就能hankaku字符,因为hankaku就是根据2.1中那样对每个字符进行编码的。显示字符串也是调用这个函数来实现的:

/*varm为此次画面模式下显存的地址,
* xsize为画面模式X方向像素大小,
* (x,y)为字符在屏幕上显示的位置,
*c是调色板号,
*s是要显示的字符串*/
void putfonts8_asc(char *vram, int xsize, int x, int y, char c, unsigned char *s)
{
	extern char hankaku[4096];
	for (; *s != 0x00; s++) {
		putfont8(vram, xsize, x, y, c, hankaku + *s * 16);
		x += 8;
	}
	return;
}

整理显示字符串所需要的所有文件:ipl20.nas,asmhead.nas,naskfunc.nas,hankaku.txt,bootpack.c。前3个文件都未曾发生改变,hankaku.txt是makefont.exe工具下的字体文件,增添这个文件只需要在Makefile中按照2.2中生成字体过程添加相应的命令即可。另外,bootpack.c中的内容增加了“用结构体获取画面参数”和“显示字符串”的代码:

//naskfun.nas汇编编写的函数声明
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);

//bootpack.c中C函数声明
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);
void init_screen(char *vram, int x, int y);
void putfont8(char *vram, int xsize, int x, int y, char c, char *font);
void putfonts8_asc(char *vram, int xsize, int x, int y, char c, unsigned char *s);

//调色板号模式,RGB和调色板号
#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       //暗灰

//与asmhead.nas画面设置参数保存地址相对应的结构体
struct BOOTINFO {
	char cyls, leds, vmode, reserve;
	short scrnx, scrny;
	char *vram;
};

//C程序入口
void HariMain(void)
{
	struct BOOTINFO *binfo = (struct BOOTINFO *) 0x0ff0;

	init_palette();
	init_screen(binfo->vram, binfo->scrnx, binfo->scrny);
	putfonts8_asc(binfo->vram, binfo->scrnx, 8, 8, COL8_FFFFFF, "ABC 123");
	putfonts8_asc(binfo->vram, binfo->scrnx, 30, 30, COL8_FFFFFF, "Still Haribote OS.");
	putfonts8_asc(binfo->vram, binfo->scrnx, 50, 50, COL8_FFFFFF, "oh Still Haribote OS.");

	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;
}

/* 设置调色板号对应的颜色
 * start, end表示8位模式下的调色板号码的开始和结束值
 * rgb为RGB颜色数组,每个元素表一个RGB颜色*/
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;
}

/* 用颜色绘制矩形
 * vram 表示画面对应的显存地址空间的首地址
 * xsize表示所设置的画面模式的X方向的像素点
 * c表示调色板号
 * (x0, y0)表示屏幕上矩阵像素点的起始坐标(左上)
 * (x1, y1)表示屏幕上像素点的末坐标(右下)
 */
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为屏幕模式对应的显存地址空间的首地址
 * x和y分别表示屏幕X和Y方向的像素点值*/
void init_screen(char *vram, int x, int y)
{
	boxfill8(vram, x, COL8_000000, 0, 0, x, y);
	return;
}

/* 显示按照 8 x 16 像素点阵编码的字符
 * vram表示所设置屏幕对应的显存地址空间的首地址
 * xsize为屏幕X方向上的像素点数
 * (x, y)为屏幕显示字符的坐标
 * font表示要显示字符的编码数组
 */
void putfont8(char *vram, int xsize, int x, int y, char c, char *font)
{
	int i;
	char *p, d;
	for (i = 0; i < 16; i++) {
		p = vram + (y + i) * xsize + x;
		d = font[i];
		if ((d & 0x80) != 0) { p[0] = c; }
		if ((d & 0x40) != 0) { p[1] = c; }
		if ((d & 0x20) != 0) { p[2] = c; }
		if ((d & 0x10) != 0) { p[3] = c; }
		if ((d & 0x08) != 0) { p[4] = c; }
		if ((d & 0x04) != 0) { p[5] = c; }
		if ((d & 0x02) != 0) { p[6] = c; }
		if ((d & 0x01) != 0) { p[7] = c; }
	}
	return;
}

/*根据引入的字体hankaku显示字符串
 * * vram表示所设置屏幕对应的显存地址空间的首地址
 * xsize为屏幕X方向上的像素点数
 * (x, y)为屏幕显示字符的坐标
 * s为要显示的字符串
 */
void putfonts8_asc(char *vram, int xsize, int x, int y, char c, unsigned char *s)
{
	extern char hankaku[4096];
	for (; *s != 0x00; s++) {
		putfont8(vram, xsize, x, y, c, hankaku + *s * 16);
		x += 8;
	}
	return;
}

运行“!cons_nt.bat”,与各文件相同目录下编写好Makefile,运行“makeintall”命令就可以将这些文件件形成的二进制文件“.img”下载到软盘中了。重启计算机即可执行操作系统程序。用“makerun”命令得到的运行结果如下:

Figure2. 32位模式下在图形界面中显示字符串的操作系统程序

3 显示鼠标图标

显示鼠标图标最直接的方法是根据屏幕像素坐标跟显存地址空间的关系而让屏幕显示颜色,使其在屏幕上呈现出鼠标的形状。要怎么决定显存的内容才能够在屏幕上显示鼠标图样呢?跟显示字符’A’的过程一样:

  • 在屏幕上选择一块区域(以像素点为单位,如16 x 16像素阵);
  • 在这块区域上把鼠标图标形状描述出来;
  • 计算这块区域各像素点对应的显存地址并标记该像素点将显示的颜色


3.1 选择像素点阵描绘形状

“书”中为鼠标选择一个16 x 16的像素点阵,并打算在此像素点阵中这样显示鼠标:

[Rx86OS-VI] 像素点阵字符串和鼠标图标_第2张图片

Figure1. 鼠标形状

3.2 保存像素点将显示的颜色

为鼠标形状开辟一个16x 16的内存mouse [16 x 16],根据Figure1中的字符在mouse中保存对应的颜色。’*’所在的像素点标黑色;’o’所在的像素点标白色;’.’所在的点标背景色(区分与鼠标区域的背景色)。这样就能够比较明显的突出一个鼠标的形状。

/*保存鼠标形状
* mouse为保存鼠标形状(颜色)的缓冲区
* bc鼠标形状的为背景色*/
void init_mouse_cursor8(char *mouse, char bc)
{
	static char cursor[16][16] = {
		"**************..",
		"*OOOOOOOOOOO*...",
		"*OOOOOOOOOO*....",
		"*OOOOOOOOO*.....",
		"*OOOOOOOO*......",
		"*OOOOOOO*.......",
		"*OOOOOOO*.......",
		"*OOOOOOOO*......",
		"*OOOO**OOO*.....",
		"*OOO*..*OOO*....",
		"*OO*....*OOO*...",
		"*O*......*OOO*..",
		"**........*OOO*.",
		"*..........*OOO*",
		"............*OO*",
		".............***"
	};
	int x, y;
	for (y = 0; y < 16; y++) {
		for (x = 0; x < 16; x++) {
			if (cursor[y][x] == '*') {
				mouse[y * 16 + x] = COL8_000000;
			}
			if (cursor[y][x] == 'O') {
				mouse[y * 16 + x] = COL8_FFFFFF;
			}
			if (cursor[y][x] == '.') {
				mouse[y * 16 + x] = bc;
			}
		}
	}
	return;
}

3.3 根据图标在屏幕上显示的坐标计算图标显示的显存地址

将保存鼠标图标形状(颜色)的mcursor拷贝到显卡内存地址空间中就可以显示了。将此功能做成一个函数,使得想往屏幕哪里显示就往哪里显示。

/* 将保存鼠标形状的缓冲区的内容拷贝到显存,在屏幕上显示鼠标形状
 * vram为显存地址空间首地址
 * vxsize指屏幕在X方向像素点数
 * pxsize和pysize指显示图标编码的像素点阵大小
 *(px0, py0)表示在屏幕上的显示坐标
 * buf表示将要显示的形状(颜色)
 *bxsize为buf代表的在X方向上的像素点阵数
 **/
void putblock8_8(char *vram, int vxsize, int pxsize,
	int pysize, int px0, int py0, char *buf, int bxsize)
{
	int x, y;
	for (y = 0; y < pysize; y++) {
		for (x = 0; x < pxsize; x++) {
			vram[(py0 + y) * vxsize + (px0 + x)] = buf[y * bxsize + x];
		}
	}
	return;
}

3.4 显示鼠标图标

在[OS-V]的基础上,在bootpack.c文件中定义并声明“保存像素点将显示的颜色”和“根据图标在屏幕上显示的坐标计算图标显示的显存地址”俩函数。并在HariMain函数中调用它们:

//C程序入口
void HariMain(void)
{
	struct BOOTINFO *binfo = (struct BOOTINFO *) 0x0ff0;
	char mcursor[256];
	
	
	init_palette();
	
	init_screen(binfo->vram, binfo->scrnx, binfo->scrny);
	
	putfonts8_asc(binfo->vram, binfo->scrnx,  160,  20, COL8_FFFFFF, "ABC 123");
	putfonts8_asc(binfo->vram, binfo->scrnx, 120, 40, COL8_FFFFFF, "Still Haribote OS.");
	putfonts8_asc(binfo->vram, binfo->scrnx, 110, 60, COL8_FFFFFF, "oh Still Haribote OS.");
	
	init_mouse_cursor8(mcursor, COL8_000000);
	putblock8_8(binfo->vram, binfo->scrnx, 16, 16, 110 + 21 * 8, 60 + 16, mcursor, 16);
	
	for (;;) {
		io_hlt();
	}
}

其它文件保持不变。打开“!cons_nt.bat”执行“make install”命令得到如下结果:

[Rx86OS-VI] 像素点阵字符串和鼠标图标_第3张图片

Figure2. 操作系统程序显示鼠标程序运行效果

笔记将鼠标指向最后一串字符串的末尾。


总结

[1] 字符、汉字、图标都是通过像素点阵显示的。

  • 在屏幕上选择一块区域(以像素点为单位,如8 x 16像素阵);
  • 在这块区域上把鼠标图标形状描述出来;
  • 计算这块区域各像素点对应的显存地址并标记该像素点将显示的颜色

[2] 开发操作系统时不能用其实现方式依靠操作系统的函数。


[x86OS] Note Over.

[2015.04.17]

你可能感兴趣的:([Rx86OS-VI] 像素点阵字符串和鼠标图标)