两种方式 :一种为按像素点画图;另一种为将图片转换到GOP blt缓冲区中,允许用户调用blt将其显示出来。 画像素点方式极慢,可以看到一行一行的绘画过程;而使用缓冲区则会立刻显示出一张图片。
先了解BMP文件的组成,BMP文件组成有三部分:
位图头文件
位图颜色表(24位真彩色没有)
位图点阵数据
文件头包括:
typedef struct {
CHAR8 CharB; //'B'
CHAR8 CharM; //'M'表示为BMP文件
UINT32 Size;
UINT16 Reserved[2];
UINT32 ImageOffset; //位图数据距离文件头的偏移量
UINT32 HeaderSize;
UINT32 PixelWidth;
UINT32 PixelHeight;
UINT16 Planes; ///< Must be 1
UINT16 BitPerPixel; ///< 1, 4, 8, or 24
UINT32 CompressionType; //压缩方式 可不压缩
UINT32 ImageSize; //位图实际占用字节数
UINT32 XPixelsPerMeter;
UINT32 YPixelsPerMeter;
UINT32 NumberOfColors;
UINT32 ImportantColors;
} BMP_IMAGE_HEADER;
位图点阵数据是按照扫描行的像素来存储的,而且所存储的数据是四字节对齐的,如果不齐则补零。位图像素值为1,4,8,24:为1时每个字节可以记录8个像素的颜色索引值;为4时每个字节记录两个像素;为8时每个字节记录一个像素;为24时每三个字节记录一个像素。
一般的,uefi进行图形开发需要以下步骤:
1.获取EFI_GRAPHICS_OUTPUT_PROTOCOL实例。
2.查询系统支持的显示模式,特别是分辨率,以确定画布大小。画过大的图片会导致设备错误
3.选择显示模式。
4.使用Blt()函数绘制图形。
EFI_GRAPHICS_OUTPUT_PROTOCOL函数接口
typedef struct EFI_GRAPHICS_OUTPUT_PROTCOL {
EFI_GRAPHICS_OUTPUT_PROTOCOL_QUERY_MODE QueryMode; //查询系统显示模式
EFI_GRAPHICS_OUTPUT_PROTOCOL_SET_MODE SetMode; //设置显示模式
EFI_GRAPHICS_OUTPUT_PROTOCOL_BLT Blt; //图形显示接口函数
EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE *Mode; //当前显示模式
} EFI_GRAPHICS_OUTPUT_PROTOCOL;
先通过QueryMode查询显示模式,queryMode接口函数及信息结构体
typedef
EFI_STATUS
(EFIAPI *EFI_GRAPHICS_OUTPUT_PROTOCOL_QUERY_MODE) (
IN EFI_GRAPHICS_OUTPUT_PROTOCOL *This,
IN UINT32 ModeNumber, //模式序号
OUT UINTN *SizeOfInfo //指向显示模式信息结构体缓存区的指针
OUT EFI_GRAPHICS_OUTPUT_MODE_INFORMATION **Info //显示模式信息
);
typedef struct { //显示模式信息结构体
UINT32 Version; //版本号
UINT32 HorizontalResolution; //水平分辨率
UINT32 VerticalResolution; //垂直分辨率
EFI_GRAPHICS_PIXEL_FORMAT PixelFormat; //像素格式
EFI_PIXEL_BITMASK PixelInformation; //像素格式为PixelBitMask时有效
UINT32 PixelsPerScanLine; //每行扫描的像素数
} EFI_GRAPHICS_OUTPUT_MODE_INFORMATION;
其中参数info的大小只能通过SizeOfInfo获取,因为不同UEFI版本中结构体可能会有不同。
而要通过QueryMode获得指定的显示模式的信息,需要显示模式的序号,这就需要EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE来获得。
typedef struct {
UINT32 MaxMode; //设备支持的模式数量
UINT32 Mode; //当前的显示模式
EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *Info; //当前显示模式下的模式信息
UINTN SizeOfInfo; //Info数据结构大小
EFI_PHYSICAL_ADDRESS FrameBufferBase; //帧缓冲起始物理地址
UINTN FrameBufferSize; //帧缓冲大小
} EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE;
1.得到画图的PROTOCOL
//找到图形输出Protocol
EFI_STATUS LocateGraphicsOutput (void)
{
EFI_STATUS Status;
EFI_HANDLE *GraphicsOutputControllerHandles = NULL;
UINTN HandleIndex = 0;
UINTN HandleCount = 0;
//找出支持GraphicsOutputProtocol的设备
Status = gBS->LocateHandleBuffer(
ByProtocol,
&gEfiGraphicsOutputProtocolGuid,
NULL,
&HandleCount,
&GraphicsOutputControllerHandles
);//从系统Handle数据库中找出支持此Protocol的Handle
if (EFI_ERROR(Status)) return Status; //没有支持
for (HandleIndex = 0; HandleIndex < HandleCount; HandleIndex++)
{
//得到PROTOCOL实例
Status = gBS->HandleProtocol(
GraphicsOutputControllerHandles[HandleIndex],
&gEfiGraphicsOutputProtocolGuid,
(VOID**)&gGraphicsOutput);
if (EFI_ERROR(Status)) continue;
else
{
return EFI_SUCCESS;
}
}
return Status;
}
2.得到实例后就可以查询模式信息:
//输出分辨率
VOID showInfo(){
//显示模式信息结构体
EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *info=NULL;
UINTN infosize;//模式信息大小
//使用QueryMode查询
gGraphicsOutput->QueryMode(gGraphicsOutput,gGraphicsOutput->Mode->Mode,&infosize,&info);
Print(L"Horizontal:%x\n",info->HorizontalResolution);
Print(L"Vertical:%x\n",info->VerticalResolution);
}
3.可以通过SetMode自行选择显示模式
4.通过Blt()绘图
在处理图像时都需要转换为UEFI的四字节颜色,BMP有1,2,8,24四种表示颜色的图片,数字表示的是像素的位数。
Blt四种方式
typedef enum {
EfiBltVideoFill, //将屏幕指定区域填充为单一颜色
EfiBltVideoToBltBuffer, //屏幕某个区域的数据复制到缓冲区
EfiBltBufferToVideo, //缓冲区复制到屏幕某个区域
EfiBltVideoToVideo, //屏幕某个区域复制到另一个区域
EfiGraphicsOutputBltOperationMax //枚举变量最大值,其余值必须小于它
} EFI_GRAPHICS_OUTPUT_BLT_OPERATION;
typedef
EFI_STATUS
(EFIAPI *EFI_GRAPHICS_OUTPUT_PROTOCOL_BLT) (
IN EFI_GRAPHICS_OUTPUT_PROTOCOL *This,
IN OUT EFI_GRAPHICS_OUTPUT_BLT_PIXEL *BltBuffer, OPTIONAL //图像缓冲区
IN EFI_GRAPHICS_OUTPUT_BLT_OPERATION BltOperation, //操作方式
IN UINTN SourceX,
IN UINTN SourceY,
IN UINTN DestinationX,
IN UINTN DestinationY,
IN UINTN Width,
IN UINTN Height,
IN UINTN Delta OPTIONAL //BltBuffer每行的字节数
);
Delta不能用在EfiBltVideoFill和EfiBltVideoToVideo之中。值为0时表示整个BltBuffer都被占用。
方式1.写像素点方式 使用罗冰范例 写像素点较麻烦且慢,其中内存的复制关系较复杂。
VOID putpixel(UINTN x,UINTN y,EFI_GRAPHICS_OUTPUT_BLT_PIXEL *color){
gGraphicsOutput->Blt(gGraphicsOutput,color, //存储颜色的缓冲区
EfiBltVideoFill,
0,0, //源坐标(缓冲区)
x,y, //目标坐标(屏幕)
1,1, //图像大小
0);
}
EFI_GRAPHICS_OUTPUT_PROTOCOL *gGraphicsOutput;//定义一个全局的PROTOCOL
//得到目录句柄
EFI_STATUS
GetFileIo(EFI_FILE_PROTOCOL** Root)
{
EFI_STATUS Status = 0;
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *SimpleFileSystem;//用于操作FAT文件系统,建立在DiskIO之上
Status = gBS->LocateProtocol(
&gEfiSimpleFileSystemProtocolGuid,
NULL,
(VOID**)&SimpleFileSystem
);
if (EFI_ERROR(Status)) {
return Status;
}
Status = SimpleFileSystem->OpenVolume(SimpleFileSystem, Root);//打开卷,获得FAT系统上的根目录句柄
Print(L"OpenVolume status: %d\n",Status);
return Status;
}
//得到画图的PROTOCOL之后
//实现256色BMP图像显示的函数
VOID putBMP256(UINTN x,UINTN y,UINTN Width,UINTN Heigth,//位置和图像宽高
EFI_GRAPHICS_OUTPUT_BLT_PIXEL *ColorTable,//BMP颜色表数据
UINT8 *Picture,//位图点阵数据
UINT8 MaskColor//掩码颜色,指定不显示的索引值
){
UINTN i,j;
UINT32 index=0;
UINT8 color_number;
for(j=y;j<Heigth+y;j++){
for(i=x;i<Width+x;i++){
color_number=Picture[index];
if(color_number!=MaskColor)
putpixel(i,j,&(ColorTable[color_number]));
++index;
}
}
}
EFI_STATUS ShowBMP256(CHAR16 *fileName,UINTN x,UINTN y){
EFI_FILE_PROTOCOL *root,*bmpFile;
EFI_STATUS Status;
//
Status = GetFileIo(&root);
Status = root->Open(root,&bmpFile,(CHAR16*)fileName,EFI_FILE_MODE_READ,0);
VOID *buffBMP=NULL;//存储图像的缓冲器
BMP_IMAGE_HEADER imageHeader;//位图头文件
UINTN bufLength=sizeof(BMP_IMAGE_HEADER);//文件头长度
UINTN index,j,middleValue;//交换数据时使用的变量
UINT8 *middleBuffer,*bmpdata,*bmpplatte;
Status = bmpFile->Read(bmpFile,&bufLength,&imageHeader);//读取BMP文件头
buffBMP = AllocateZeroPool(imageHeader.Size);//分配存储BMP文件所需要的内存
bmpFile->SetPosition(bmpFile,0);//设置位置到文件最开始处
bufLength = (UINTN)(imageHeader.Size);
Status = bmpFile->Read(bmpFile,&bufLength,buffBMP);//全部读取BMP文件
middleValue = imageHeader.PixelHeight/2;
bmpdata = (UINT8 *)buffBMP + imageHeader.ImageOffset;//BMP图像点阵数据位置
bmpplatte = (UINT8 *)buffBMP + sizeof(BMP_IMAGE_HEADER);//BMP位图颜色表位置
middleBuffer = AllocateZeroPool(imageHeader.PixelWidth);
for(j=0;j<middleValue;j++){
index = imageHeader.PixelHeight -1 -j;
CopyMem(middleBuffer,bmpdata+j*imageHeader.PixelWidth,imageHeader.PixelWidth);
CopyMem(bmpdata+j*imageHeader.PixelWidth,bmpdata+index*imageHeader.PixelWidth,imageHeader.PixelWidth);
CopyMem(bmpdata+index*imageHeader.PixelWidth,middleBuffer,imageHeader.PixelWidth);
}
LocateGraphicsOutput();
//给定颜色表,点阵数据,进行256色BMP图像显示
putBMP256(x,y,imageHeader.PixelWidth,imageHeader.PixelHeight,(EFI_GRAPHICS_OUTPUT_BLT_PIXEL *)bmpplatte,bmpdata,0);
FreePool(buffBMP);
FreePool(middleBuffer);
bmpFile->Close(bmpFile);
return EFI_SUCCESS;
}
方法二:直接缓冲区写屏方法
EFI_STATUS BuffBlt(CHAR16 *fileName){
EFI_GRAPHICS_OUTPUT_BLT_PIXEL *GopBlt=NULL; //颜色结构体
EFI_FILE_PROTOCOL *root,*bmpFile; //文件PROTOCOL
EFI_FILE_INFO *ReadFileInfo = NULL; //文件信息
EFI_STATUS Status;
Status = GetFileIo(&root); //得到文件句柄
Status = root->Open(root,&bmpFile,(CHAR16*)fileName,EFI_FILE_MODE_READ,0);//打开指定的file
Print(L"file status: %d\n",Status);
VOID *DataBuffer=NULL; //保存图片的缓冲区
UINT64 DataLength; //图片缓冲区长度
UINTN BltSize,Height,Width; //GOPBlt的大小,图片长,图片宽
ReadFileInfo = ShellGetFileInfo((SHELL_FILE_HANDLE)bmpFile); //检索有关指定句柄的文件的信息并将其存储在分配的池内存中。
DataLength=ReadFileInfo->FileSize; //将图片缓冲区长度设为文件大小
DataBuffer = (UINT8 *)AllocateZeroPool(DataLength); //给图片缓冲区分配内存
Status = bmpFile->Read(bmpFile,&DataLength,DataBuffer); //读取图片文件到DataBuffer
Print(L"Read file status: %d\n",Status);
Status = TranslateBmpToGopBlt(DataBuffer,DataLength,&GopBlt,&BltSize,&Height,&Width); //将图片转换到GOP blt 缓冲区中。
Print(L"Translate status: %d\n",Status);
LocateGraphicsOutput(); //得到画图PROTOCOL
showInfo(); //输出信息
//画图
Status = gGraphicsOutput->Blt(gGraphicsOutput,GopBlt,EfiBltBufferToVideo,0,0,0x0,0x0,Width,Height,Width*sizeof(EFI_GRAPHICS_OUTPUT_BLT_PIXEL));
Print(L"Blt status: %d\n",Status);
Status = gST->ConOut->EnableCursor(gST->ConOut,TRUE);
FreePool(GopBlt);
bmpFile->Close(bmpFile);
FreePool(DataBuffer);
return Status;
}
在使用第二种方式画图时,在TranslateBmpToGopBlt函数中的
if ((BmpHeader->Size != BmpImageSize) ||
(BmpHeader->Size < BmpHeader->ImageOffset) ||
(BmpHeader->Size - BmpHeader->ImageOffset-2 != DataSize))
{
DEBUG ((DEBUG_ERROR, "TranslateBmpToGopBlt: invalid BmpImage... \n"));
DEBUG ((DEBUG_ERROR, " DataSizePerLine: 0x%x\n", DataSizePerLine));
DEBUG ((DEBUG_ERROR, " BmpHeader->PixelWidth: 0x%x\n", BmpHeader->PixelWidth));
DEBUG ((DEBUG_ERROR, " BmpHeader->PixelHeight: 0x%x\n", BmpHeader->PixelHeight));
DEBUG ((DEBUG_ERROR, " BmpHeader->Size: 0x%x\n", BmpHeader->Size));
DEBUG ((DEBUG_ERROR, " BmpHeader->ImageOffset: 0x%x\n", BmpHeader->ImageOffset));
DEBUG ((DEBUG_ERROR, " BmpImageSize: 0x%x\n", BmpHeader->ImageSize));
DEBUG ((DEBUG_ERROR, " DataSize: 0x%lx\n", (UINTN)DataSize));
return RETURN_UNSUPPORTED;
}
出错,在画图时经过计算发现,图片大小减去偏移量比实际大小多2,所以再次添加了一个减2.实在不行可以注释掉这一块之后再次编译。而且在画图时出现了设备错误,将图片缩小之后可以画出,所以可能是因为图片过大导致。
TranslateBmpToGopBlt适用于每种BMP图片,因为它对每种BMP文件做了相应的处理。