UEFI开发探索24 – 图像显示(BMP)

BMP是微软推荐和支持的标准图像文件格式,图像数据不经过压缩直接存盘,直译过来是位图(bitmap)。

最早的时候,Windows自带的画图程序只支持bmp格式,现在缺省支持PNG了。

这种格式比较简单,数据结构也非常容易看懂。我在开发隔离卡、Option ROM以及嵌入式产品时,如果需要用到图像,只要空间允许,都是用BMP格式。代码简单,很多时候几行循环代码就搞定。

在Foxdisk的博客中,还没来得及对图像格式进行介绍。那款软件中主要使用了BMP和PCX两种图像格式。

我预备用几篇博客,把常用的图像格式在UEFI Shell下如何显示介绍一下。为了方便,没有使用main函数作为入口,而是使用了ShellAppMian。理由比较简单,main函数入口参数不是Unicode的,需要进行转换;另外,我也想尽可能使用UEFI提供的函数来进行处理,尽量少用标准C的库函数。

先从BMP开始。

1 BMP格式介绍

如图,我将\MdePkg\Include\IndustryStandard\Bmp.h中的BMP数据结构截图下来了。一般习惯都是将其数据结构分为两个,UEFI中直接用一个BMP_IMAGE_HEADER表示了。这使得对结构体的有些字段,在理解上有些奇怪。不过问题不大,我把每个字段的含义都标示出来了。

图1 BMP数据结构

简单解释一下,BMP_COLOR_MAP是调色板结构体。这是对那些需要调色板的位图文件而言,24位和32位(UEFI的bmp函数没有支持32位)是不需要调色板的。

调色板结构体个数等于使用的颜色数,即是多少色图就有多少个,4位图16色,就有16个BMP_COLOR_MAP。我常用的有256色位图,也是采用这种索引的方式。总共有256个调色板,分别用0x00~0xff指向。

对256色的显示模式,显卡是直接用一个字节来表示颜色的,也即直接将索引值填入就可以了。比如Legacy BIOS下的0x103模式,就是800×600,256色的,采用的就是这种两级显示的方法。

补充一句,在UEFI下,这些细节都不用在意,只管按照Blt()给定的方式去处理就行了。相比于Vesa标准,UEFI已经封装了很多细节。

2 编程

只要了解了BMP的格式,结合UEFI下图形显示的原理,就可以将图像显示在屏幕上了。

有很多种方法,比如使用bltBuffer的方式,将图片内容读取到blt所规定的矩形区域,直接写屏;或者将图像数据读取到内存,调用画点函数。

我准备把两种方式都实现。当然,也有其他的实现方法,可以根据程序的要求灵活选择。

第一种方式可以参照\MdeModulePkg\Library\DxeCapsuleLibFmp\DxeCapsuleLib.c中的ConvertBmpToGopBlt()。这个函数将*.bmp图像转换到GOP blt缓冲区中,允许用户调用blt将其显示出来。

代码大概120多行,也不复杂。主体逻辑就是将传入的图像(已经拷贝到内存中了),根据bmp结构的信息,转换到GOP blt 缓冲区中。

今天中午花了十几分钟就写完了代码,运行的时候出错。又花了一个下午调试,才发现我准备的图像,不知道怎么回事,在末尾加了两个字节的0x00,无法通过函数内部的检测。

不过还是比Legacy BIOS下的开发容易些,内存申请也终于可以以“兆”为单位了,以前都是省着用,大多是几百字节。

在TianoCore模拟环境中,运行luo2.efi luobing.bmp,运行效果如下:

图2 第一种方式显示

需要注意的是,ConvertBmpToGopBlt()内部的GOP Blt缓冲区申请的内存,必须由调用者释放,否则会导致内存泄漏。

我之所以用这个函数,是因为它内部支持了4色、16色、256色和24位真彩色,省得我自己去写了。

第二种方法则使用之前写好的putPixel()函数,显示图像。我不想去申请内存,所以直接用一个大循环,读一个,写一个。估计显示会比较慢,那也蛮有意思的。

补充一下,对BMP的格式还有两点需要注意。一是数据区开始的像素其实是图像左下角的像素,按照行扫描的方式依次存储的;二是要求每一扫描行的字节数据必须能被4整除,也就是dword对齐(dword是一种数据类型,长度为4个字节)。如果图像的一行字节数不能被4整除,就需要在每行的末尾补齐0以达到规定。

我使用的图像是1024×768的,每行为1024像素,能够整除4。所以在程序中为了简便,没有去处理4字节对齐的问题,应用产品中需要注意。

另外,24位真彩色的BMP图,数据部分是按照3字节为一个像素存储的,依次为Blue、Green、Red。

为了演示画点显示bmp有多慢,我用gif保存了过程。如下:

图2 第二种方式显示

对比一下显示速度就知道了,如果是很小的图片,使用画像素点的方式画图还无所谓;图片大一点的话,还是使用Gop Blt缓冲区的方式较好。

256色的图形显示的原理类似,只是用调色板替代了直接写颜色。提供的代码中已经有相关的示例,带有putBMP256字样的都是为此编写的。

3 One More Thing

这里推测一下第一种方式为什么能这么快。(也即先将图像拷贝到Gop Blt缓冲区后,blt采用EfiBltBufferToVideo的方式输出显示)

图像显示的时候,有所谓位面(Windows Granularity,似乎翻译成窗口粒度更好,这里采用了清华出版的《80×86汇编语言程序设计》中的说法)的概念。参照VESA BIOS EXTENSION Core Functions Standard Version 3.0 page 13,对此有说明。

视频RAM由多个位面组成,每个位面一般为64K。比如256K的视频RAM,必须通过0xA0000-0xAFFFF处的64K字节的窗口,将数据输出显示。

这64K窗口可以通过换页的方式,来对应不同的位面。实际上,BIOS也提供了相应的中断,来实现换页。

换页是很慢的,比内存拷贝慢太多了,在同一窗口的时候,是不需要换页的。

回到问题。putpixel()函数是使用Blt()编写,参数为EfiBltVideoFIll,以及画点位置及颜色。在Blt()中,我没找到如何避免换页的方法。

也就是说,每次调用putpexel(),都会换页。而采用GOP blt 缓冲区的方法,只进行必要的换页。

这就是为何采用第二种方法,会慢成这个样子。在Foxidsk中的putpixel(),是用Legacy BIOS中断来运行的,对换页进行了额外处理,如果不需要的话,不会换页,因此速度还是很快。

UEFI下也许也能实现,只是我还没找到罢了。

百度云链接:https://pan.baidu.com/s/1gccSosw8_UAGTI5gZPnLCA
提取码:dx23
文件在 16 Display BMP 下。
/luobing.bmp: 实验用的图片
/ConvertBMPtoGopBlt: 第一种方式显示24位BMP图
/Putpixel-Demo:第二种方式显示24位BMP图

39 total views, 4 views today

发表评论

电子邮件地址不会被公开。 必填项已用*标注