UEFI显示BMP图片

两种方式 :一种为按像素点画图;另一种为将图片转换到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文件做了相应的处理。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值