查看“VGA Fonts”的源代码
←
VGA Fonts
跳到导航
跳到搜索
因为以下原因,您没有权限编辑本页:
您请求的操作仅限属于该用户组的用户执行:
用户
您可以查看和复制此页面的源代码。
{{TutorialTone}} 你已知道如何在文本模式下显示字符,现在你想要在图形模式下执行此操作。(译者注:内核中文本模式的显示方式是使用BIOS软中断INT指令,调用BIOS功能) 这并不复杂,但绝对比在内存中的特定偏移量下编写ASCII代码更复杂。 你必须逐像素地绘制。 但是你怎么知道该画什么呢? 它存储在称为位图字体(bitmap fonts)的数据矩阵中。 == 位图字体的解码 == 一个字符是如何存储在内存中的? 很简单,0编码代表背景,1编码代表前景色。 VGA字体总是8位宽,因此每个字节只包含一行。 对于典型8x16字体的字母‘A’,它将是(二进制,共16字节): <pre> 00000000b byte 0 00000000b byte 1 00000000b byte 2 00010000b byte 3 00111000b byte 4 01101100b byte 5 11000110b byte 6 11000110b byte 7 11111110b byte 8 11000110b byte 9 11000110b byte 10 11000110b byte 11 11000110b byte 12 00000000b byte 13 00000000b byte 14 00000000b byte 15 </pre> 完整的位图包含每个ASCII码字符的位图,因此它是256*16字节,4096字节长。 如果你想得到一个特定字符的位图,你必须将ASCII码乘以16(字符中的行数),再加上位图的偏移量,你就可以开始了。 存储这些内容的一个非常简单的文件格式是Linux控制台使用的[[PC Screen Font]]。 它以上面描述的方式存储字体,并带有一个小标头。 另一个解决方案是[[Scalable Screen Font|可缩放屏幕字体]]格式,它附带一个非常小的免费呈现ANSI C库。 ==如何获取字体?== 有几种方法。 你可以将其保存在文件系统中的文件中。 你可以在一个数组中对其进行硬编码。 但有时4k占用内存太多了,读取文件不是一个可行的选项(尤其在引导加载程序中),在这种情况下,你必须从VGA RAM中读取设备卡使用的字符(原用于显示文本模式)。 === 将其存储在数组中 === 最简单的方法,但会增加你的代码4k大小。 有几个源代码以二进制或源文件格式提供整个字体,因此您无需手动将其写出。 === 将其存储在文件中 === 最模块化的方式。 如果你愿意,你可以使用不同的字体。 缺点你需要一个有效的文件系统实现。 至于文件格式,我建议使用前面提到的[[PC Screen Font|PC屏幕字体]](.psfu)或[[Scalable Screen Font|可缩放屏幕字体]](.sfn)。 === 获取存储在VGA BIOS中的副本 === 这是一个标准的BIOS调用 (不需要检查它的持久性)。 如果你仍然处于实模式,它很容易使用。 <source lang="asm"> ;输入: es:di=4k缓冲区 ;输出: 用字体填充的缓冲区 push ds push es ;要求BIOS返回VGA位图字体 mov ax, 1130h mov bh, 6 int 10h ;复制字符映射 push es pop ds pop es mov si, bp mov cx, 256*16/4 rep movsd pop ds </source> === 直接从VGA RAM获取 === 也许你已经处于保护模式,因此无法访问BIOS功能。 在这种情况下,你仍然可以通过编程VGA寄存器获得位图。 请注意,VGA始终以8x32字体保留空间,因此你需要在复制过程中修剪每个字符的底部16个字节: <source lang="asm"> ; 输入: edi = 4k缓冲区 ; 输出:用字体填充的缓冲区 ;清除偶数/奇数模式 mov dx, 03ceh mov ax, 5 out dx, ax ; 将VGA内存映射到0A0000h mov ax, 0406h out dx, ax ; 设置bitplane 2 mov dx, 03c4h mov ax, 0402h out dx, ax ; 清除偶数/奇数模式(另一种方式,不要问为什么) mov ax, 0604h out dx, ax ; 复制charmap mov esi, 0A0000h mov ecx, 256 ; 将16个字节复制到位图 @@: movsd movsd movsd movsd ;再跳过16个字节 add esi, 16 loop @b ; 将VGA状态恢复为正常运行 mov ax, 0302h out dx, ax mov ax, 0204h out dx, ax mov dx, 03ceh mov ax, 1005h out dx, ax mov ax, 0E06h out dx, ax </source> 值得一提的是,切换到VBE图形模式'''之前'''必须先完成,因为VGA寄存器之后通常无法访问。 这意味着你将无法将VGA卡的字体内存映射到屏幕内存,并且只能读取到垃圾文件。 == 设置VGA字体 == 如果你仍处于文本模式,并且希望VGA卡绘制不同的字形,还可以设置VGA字体。 在图形模式下它毫无价值(因为字符是通过代码显示的,而不是卡显示的),我写这一部分只是为了描述完整。 如果你仔细阅读了到目前为止所写的内容,那么修改VGA RAM中的字体位图并不困难。 我会把它作为作业留给你。(译者注:注意以下内容仍属于这个大纲内,作者还是做了一些提示。) === 通过BIOS设置字体 === 提示:检查Ralph Brown中断列表Int 10/AX = 1110h。 === 直接设置字体 === 提示:使用与上面相同的代码,但将源代码和目标代码交换为“movsd”。 == 显示字符 == 最后,我们到了可以显示字符的地步。 我想你已经准备好了。 我们必须绘制8x16像素,位图中的每一位都需要一个像素。 <source lang="c"> // 这是您加载的位图字体 unsigned char *font; void drawchar(unsigned char c, int x, int y, int fgcolor, int bgcolor) { int cx,cy; int mask[8]={1,2,4,8,16,32,64,128}; unsigned char *gylph=font+(int)c*16; for(cy=0;cy<16;cy++){ for(cx=0;cx<8;cx++){ putpixel(glyph[cy]&mask[cx]?fgcolor:bgcolor,x+cx,y+cy-12); } } } </source> 参数很简单。 你可能想知道为什么要从y中减去12。 它用于对齐基线(baseline):认为你指定的y坐标作为字符的底部,未将向下的字形中的“猪尾(piggy tail)”计算在内 (例如在 “p”,“g”,“q” 等中)。 换言之,就是字母“A”的最底有已设置1的bit位的那行。 虽然直接擦除字形下的屏幕像素也够用,但在某些情况下可能不好(例如:在闪亮的渐变按钮上写字)。 这是一个稍微修改的版本,考虑了透明背景。 <source lang="c"> // 这是您加载的位图字体 unsigned char *font; void drawchar_transparent(unsigned char c, int x, int y, int fgcolor) { int cx,cy; int mask[8]={1,2,4,8,16,32,64,128}; unsigned char *gylph=font+(int)c*16; for(cy=0;cy<16;cy++){ for(cx=0;cx<8;cx++){ if(glyph[cy]&mask[cx]) putpixel(fgcolor,x+cx,y+cy-12); } } } </source> 正如你所见,我们这次只有前景色,putpixel调用有一个条件:仅当位图中的相应位被设置时调用。 当然,上面的代码会非常慢(主要是因为一次只处理一个像素,并且反复重新计算“putPixel()”函数中每个像素的地址)。 为了获得更好的性能,上面的代码可以优化为使用布尔运算和 “掩码查找表”。 例如(对于8-bpp模式): <source lang="c"> // 这是您加载的位图字体 unsigned char *font; void drawchar_8BPP(unsigned char c, int x, int y, int fgcolor, int bgcolor) { void *dest; uint32_t *dest32; unsigned char *src; int row; uint32_t fgcolor32; uint32_t bgcolor32; fgcolor32 = fgcolor | (fgcolor << 8) | (fgcolor << 16) | (fgcolor << 24); bgcolor32 = bgcolor | (bgcolor << 8) | (bgcolor << 16) | (bgcolor << 24); src = font + c * 16; dest = videoBuffer + y * bytes_per_line + x; for(row = 0; row < 16; row++) { if(*src != 0) { mask_low = mask_table[*src][0]; mask_high = mask_table[*src][1]; dest32 = dest; dest32[0] = (bgcolor32 & ~mask_low) | (fgcolor32 & mask_low); dest32[1] = (bgcolor32 & ~mask_high) | (fgcolor32 & mask_high); } src++; dest += bytes_per_line; } } void drawchar_transparent_8BPP(unsigned char c, int x, int y, int fgcolor) { void *dest; uint32_t *dest32; unsigned char *src; int row; uint32_t fgcolor32; fgcolor32 = fgcolor | (fgcolor << 8) | (fgcolor << 16) | (fgcolor << 24); src = font + c * 16; dest = videoBuffer + y * bytes_per_line + x; for(row = 0; row < 16; row++) { if(*src != 0) { mask_low = mask_table[*src][0]; mask_high = mask_table[*src][1]; dest32 = dest; dest32[0] = (dest[0] & ~mask_low) | (fgcolor32 & mask_low); dest32[1] = (dest[1] & ~mask_high) | (fgcolor32 & mask_high); } src++; dest += bytes_per_line; } } </source> 在这种情况下,显示内存(display memory)中的地址只计算一次(而不是最多128次),并且并行计算8个像素(这完全消除了内部循环)。 这种方法的主要缺点是,你需要为每个 “每像素位” 使用不同的功能,除了15-bpp和16-bpp可以使用相同的代码。 在最坏的情况下(32-bpp),查找表的成本为8kib。 用于32-bpp的查找表可以重新用于24-bpp,而对于4-BPP根本不需要查找表。 为了支持VBE能够实现的所有标准位深度; 给出了每个 “绘制字符” 功能的总共5个版本 (4-bpp,8-bpp,15-bpp和16-bpp,24-bpp,32-bpp) 和3个查找表 (8-bpp,15-bpp和16-bpp,24-bpp和32-bpp),如果使用静态表,则总共花费14 KiB的数据 (不在需要时动态生成所需的查找表的话)。 ==另见== * [[VGA Hardware]] - 如果你想自己实现 * [[PC Screen Font]] - wiki中有一个关于如何显示它们的教程 * [[Scalable Screen Font]] - 附带一个小的、免费的ANSI C渲染库 * [[TrueType Fonts]] - 一种非常复杂的矢量字体格式,部分专有的 ==外部链接== * [http://www.inp.nsk.su./~bolkhov/files/fonts/univga/ UNI-VGA] - 免费的Unicode VGA字体(.bdf) * [http://sourceforge.net/projects/bdf2c/ bdf2c] - .bdf字体到C源代码转换器。 [[Category:VGA]] [[Category:Video]]
本页使用的模板:
模板:EditThis
(
查看源代码
)
模板:TutorialTone
(
查看源代码
)
返回至“
VGA Fonts
”。
导航菜单
个人工具
登录
命名空间
页面
讨论
变体
已展开
已折叠
查看
阅读
查看源代码
查看历史
更多
已展开
已折叠
搜索
导航
首页
最近更改
随机页面
MediaWiki帮助
工具
链入页面
相关更改
特殊页面
页面信息