GUI實作 - Lesson 2 - 顯示BMP圖檔
先用自己在書上查的部份把code打出來, 但是沒有功能, Debug 很久.
後來用Gemini幫忙做 Vibe coding , 幾次調整之後就搞定了(用Pro模式)
主要要注意三個部分
1. 每個像素, BMP 是3 個Byte , UEFI 的 VGA 是 4 byte
2. BMP的寬是Align 似的倍數, 而他每一個元素是3 byte , 所以結尾需要處理(code裡面的Padding)
3. BMP 開始是從右下 , UEFI VGA 是從左上開始算
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/BaseMemoryLib.h>
#include <Protocol/GraphicsOutput.h>
#include <Protocol/SimpleFileSystem.h>
#include <Guid/FileInfo.h>
#include <IndustryStandard/Bmp.h>
/**
Read the BMP file from the Simple File System.
**/
EFI_STATUS
ReadLogo (
OUT VOID **LogoFile,
OUT UINTN *FileSize
)
{
EFI_STATUS Status;
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *Fs;
EFI_FILE_PROTOCOL *Root;
EFI_FILE_PROTOCOL *File;
EFI_FILE_INFO *FileInfo;
UINTN InfoSize;
Root = NULL;
File = NULL;
FileInfo = NULL;
InfoSize = 0;
Status = gBS->LocateProtocol(&gEfiSimpleFileSystemProtocolGuid, NULL, (VOID**)&Fs);
if (EFI_ERROR(Status)) return Status;
Status = Fs->OpenVolume(Fs, &Root);
if (EFI_ERROR(Status)) return Status;
Status = Root->Open(Root, &File, L"logo.bmp", EFI_FILE_MODE_READ, 0);
if (EFI_ERROR(Status)) {
Print(L"Error: File 'logo.bmp' not found.\n");
Root->Close(Root);
return Status;
}
Status = File->GetInfo(File, &gEfiFileInfoGuid, &InfoSize, NULL);
if (Status == EFI_BUFFER_TOO_SMALL) {
FileInfo = AllocatePool(InfoSize);
if (FileInfo == NULL) return EFI_OUT_OF_RESOURCES;
Status = File->GetInfo(File, &gEfiFileInfoGuid, &InfoSize, FileInfo);
}
if (EFI_ERROR(Status)) {
File->Close(File);
Root->Close(Root);
if (FileInfo) FreePool(FileInfo);
return Status;
}
*FileSize = (UINTN)FileInfo->FileSize;
*LogoFile = AllocateZeroPool(*FileSize);
if (*LogoFile == NULL) {
FreePool(FileInfo);
File->Close(File);
Root->Close(Root);
return EFI_OUT_OF_RESOURCES;
}
Status = File->Read(File, FileSize, *LogoFile);
File->Close(File);
Root->Close(Root);
FreePool(FileInfo);
return Status;
}
/**
Convert BMP to GOP Blt format. Handles 24-bit depth and row padding.
**/
EFI_STATUS
TransferBmpToBlt (
IN VOID *BmpBuffer,
OUT VOID **BltOutput,
OUT UINT32 *OutWidth,
OUT UINT32 *OutHeight
)
{
BMP_IMAGE_HEADER *BmpHeader;
EFI_GRAPHICS_OUTPUT_BLT_PIXEL *BltBuffer;
UINTN BmpStride;
UINT8 *ImageData;
UINT32 x, y;
UINTN BmpIndex, BltIndex;
BmpHeader = (BMP_IMAGE_HEADER *)BmpBuffer;
if (BmpHeader->CharB != 'B' || BmpHeader->CharM != 'M') {
Print(L"Error: Not a valid BMP file.\n");
return EFI_UNSUPPORTED;
}
if (BmpHeader->BitPerPixel != 24) {
Print(L"Error: Only 24-bit BMP is supported. Found: %d\n", BmpHeader->BitPerPixel);
return EFI_UNSUPPORTED;
}
*OutWidth = BmpHeader->PixelWidth;
*OutHeight = BmpHeader->PixelHeight;
// BMP rows are 4-byte aligned
BmpStride = (*OutWidth * 3 + 3) & ~3;
ImageData = (UINT8 *)BmpBuffer + BmpHeader->ImageOffset;
BltBuffer = AllocateZeroPool((*OutWidth) * (*OutHeight) * sizeof(EFI_GRAPHICS_OUTPUT_BLT_PIXEL));
if (BltBuffer == NULL) return EFI_OUT_OF_RESOURCES;
for (y = 0; y < *OutHeight; y++) {
for (x = 0; x < *OutWidth; x++) {
// Flip vertical axis (BMP is bottom-up)
BmpIndex = ((*OutHeight - 1 - y) * BmpStride) + (x * 3);
BltIndex = (y * (*OutWidth)) + x;
BltBuffer[BltIndex].Blue = ImageData[BmpIndex];
BltBuffer[BltIndex].Green = ImageData[BmpIndex + 1];
BltBuffer[BltIndex].Red = ImageData[BmpIndex + 2];
BltBuffer[BltIndex].Reserved = 0x00;
}
}
*BltOutput = BltBuffer;
return EFI_SUCCESS;
}
/**
Draw the image to the screen with boundary clipping to avoid Invalid Parameter.
**/
EFI_STATUS
DrawLogo (
IN VOID *LogoFile
)
{
EFI_STATUS Status;
EFI_GRAPHICS_OUTPUT_PROTOCOL *Gop;
VOID *BltOutput;
UINT32 ImgW, ImgH;
UINT32 DrawW, DrawH;
UINT32 DestX, DestY;
UINT32 ScrnW, ScrnH;
EFI_GRAPHICS_OUTPUT_BLT_PIXEL Black = {0, 0, 0, 0};
BltOutput = NULL;
ImgW = 0; ImgH = 0;
Status = gBS->LocateProtocol(&gEfiGraphicsOutputProtocolGuid, NULL, (VOID **)&Gop);
if (EFI_ERROR(Status)) return Status;
Status = TransferBmpToBlt(LogoFile, &BltOutput, &ImgW, &ImgH);
if (EFI_ERROR(Status)) return Status;
ScrnW = Gop->Mode->Info->HorizontalResolution;
ScrnH = Gop->Mode->Info->VerticalResolution;
// Clipping logic: Don't exceed screen size
DrawW = (ImgW > ScrnW) ? ScrnW : ImgW;
DrawH = (ImgH > ScrnH) ? ScrnH : ImgH;
// Center logic: Calculate offset for clipped image
DestX = (ScrnW > DrawW) ? (ScrnW - DrawW) / 2 : 0;
DestY = (ScrnH > DrawH) ? (ScrnH - DrawH) / 2 : 0;
Print(L"Screen: %ux%u, Drawing: %ux%u at (%u,%u)\n", ScrnW, ScrnH, DrawW, DrawH, DestX, DestY);
// Clear screen to Black
Gop->Blt(Gop, &Black, EfiBltVideoFill, 0, 0, 0, 0, ScrnW, ScrnH, 0);
// Draw the image
Status = Gop->Blt(
Gop,
(EFI_GRAPHICS_OUTPUT_BLT_PIXEL *)BltOutput,
EfiBltBufferToVideo,
0, 0, // Source X, Y
DestX, DestY, // Destination X, Y
DrawW, DrawH, // Actual pixels to draw
ImgW * sizeof(EFI_GRAPHICS_OUTPUT_BLT_PIXEL) // Delta must use original buffer width
);
if (EFI_ERROR(Status)) {
Print(L"Gop->Blt Error: %r\n", Status);
} else {
gBS->Stall(5000000);
}
if (BltOutput) FreePool(BltOutput);
return Status;
}
EFI_STATUS
EFIAPI
UefiMain (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
VOID *LogoFile = NULL;
UINTN FileSize = 0;
Status = ReadLogo(&LogoFile, &FileSize);
if (EFI_ERROR(Status)) return Status;
Status = DrawLogo(LogoFile);
if (LogoFile) FreePool(LogoFile);
return Status;
}
留言