显示图片

在1.4中,我们了解了帧缓冲区的起始地址和像素的格式,并绘制了一个矩形。进一步地,我们可以通过类似的方式在屏幕上显示图片。

新建blt函数

首先,我们新建一个从我们指定的缓冲区向帧缓冲区传送内容的函数blt(Block Transfer),如代码7.1所示

void blt(unsigned char img[], unsigned int img_width, unsigned int img_height)
{
    unsigned char *fb;
    unsigned int i, j, k, vr, hr, ofs = 0;

    fb = (unsigned char *)GOP->Mode->FrameBufferBase;
    vr = GOP->Mode->Info->VerticalResolution;
    hr = GOP->Mode->Info->HorizontalResolution;

    for (i = 0; i < vr; i++) {
        if (i >= img_height)
            break;
        for (j = 0; j < hr; j++) {
            if (j >= img_width) {
                fb += (hr - img_width) * 4;
                break;
            }
            for (k = 0; k < 4; k++)
                *fb++ = img[ofs++];
        }
    }
}

代码7.1: sample_poios/graphics.c:blt

上面的代码将缓冲区img中的图像逐字节拷贝到帧缓冲区。图像始终从点(0, 0)开始绘制。

在Shell中加入查看图片的命令view

接下来,我们向Shell中加入一个新的命令view,这个命令使用上面的blt函数来在屏幕上显示图片(代码7.2)。这里的图片文件是以UEFI所兼容的像素格式保存的原始(RAW)图像文件。

/* ...省略... */
#define MAX_IMG_BUF  4194304      /* 4MB, 新增 */

unsigned char img_buf[MAX_IMG_BUF];    /* 新增 */
/* ...省略... */
void view(unsigned short *img_name, unsigned int width, unsigned int height)
{
    unsigned long long buf_size = MAX_IMG_BUF;
    unsigned long long status;
    struct EFI_FILE_PROTOCOL *root;
    struct EFI_FILE_PROTOCOL *file;

    status = SFSP->OpenVolume(SFSP, &root);
    assert(status, L"error: SFSP->OpenVolume");

    status = root->Open(root, &file, img_name, EFI_FILE_MODE_READ,
                EFI_FILE_READ_ONLY);
    assert(status, L"error: root->Open");

    status = file->Read(file, &buf_size, (void *)img_buf);
    if (check_warn_error(status, L"warning:file->Read"))
        blt(img_buf, width, height);

    while (getc() != SC_ESC);

    status = file->Close(file);
    status = root->Close(root);
}

void shell(void)
{
    /* ...省略... */

    while (!is_exit) {
        /* ...省略... */
        else if (!strcmp(L"view", com)) {         /* 新增 */
            view(L"img", IMG_WIDTH, IMG_HEIGHT);  /* 新增 */
            ST->ConOut->ClearScreen(ST->ConOut);  /* 新增 */
        } else
            puts(L"Command not found.\r\n");
    }
}

代码7.2: sample_poios/shell.c1

view命令执行时将会调用view函数来显示文件名为"img"的图片。view函数打开参数中所指定的图片文件,将其原始二进制内容读取并存放至缓冲区img_buf中,再调用blt函数将其传送至帧缓冲区来显示图像,直到按下Esc键退出。与catedit函数一样,我们实现的所有Shell命令函数都不会在退出时清屏,包括这里的view函数。因此,我们需要在shell函数调用view函数之后再调用ClearScreen函数来进行清屏。

转换图像至BGRA32格式的方法

BGRA32是一种每像素32位的像素格式,其中每个通道(Blue、Green、Red和Alpha)各占用8位。

我们可以通过ImageMagick的convert命令来把图片转换为BGRA32格式的原始图像文件(没有文件头(header)等内容的二进制文件)。转换的命令如下:

$ convert hoge.png -depth 8 yux.bgra

由于这里我们实现的view命令不支持滚动或是缩放图片。因此,我们可能需要对图片进行缩放来使它可以在屏幕时显示。这里,作者为了适配他的UEFI固件所识别的分辨率,因而把图片缩放到640像素宽 (在QEMU/OVMF下,这个宽度默认为800像素) ,转换并缩放的命令如下:

$ convert hoge.png -resize 640x -depth 8 yux.bgra

在GUI模式中加入图片查看功能

最后,我们修改gui.c,让它在我们点击图片文件时调用view函数来显示它(代码7.3)。在poiOS中,我们把所有文件名以小写字母"i"开头的文件都当作图片文件。

/* 处理文件图标 */
for (idx = 0; idx < file_num; idx++) {
    if (is_in_rect(px, py, file_list[idx].rect)) {
        /* ...省略... */
        if (prev_lb && !s.LeftButton) {
            if (file_list[idx].name[0] != L'i')                    /* 新增 */
                cat_gui(file_list[idx].name);
            else                                                   /* 新增 */
                view(file_list[idx].name, IMG_WIDTH, IMG_HEIGHT);  /* 新增 */
            file_num = ls_gui();
        }
        /* ...省略... */
    }
}

代码7.3: sample_poios/gui.c:gui

这里我们对gui.c所作的唯一改动是在gui函数的“处理文件图标”的循环中。当我们点击以字母"i"开头的文件时,我们调用view函数来打开它,而在点击其他文件时,还是调用之前的cat_gui函数。

UEFI标准中的Blt函数

事实上,UEFI标准中的EFI_GRAPHICS_OUTPUT_PROTOCOL中存在一个名为Blt的函数(标准文档"11.9 Graphics Output Protocol(P.474)")。UEFI标准中的Blt函数不仅可以将内容传送至帧缓冲区(EfiBltBufferToVideo),还可以保存帧缓冲区中的内容(EfiBltVideoToBltBuffer),或是将帧缓冲区中一个位置的内容移动至另一位置(EfiBltVideoToVideo)。

然而,这一函数并不能在作者的Lenovo ThinkPad E450(UEFI版本2.3.1)上调用1,因此这里作者自己实现了一个简单的blt函数。UEFI标准中的Blt函数仍然可以在QEMU/OVMF(UEFI版本2.3.1)上调用。

1

译者注:为了使非分辨率宽度的图片能够正常显示,中文版的view函数新增了宽度width和高度height两个参数。

2

调用后返回成功的状态,但屏幕上没有任何变化。