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;

}

留言

這個網誌中的熱門文章

如何在VS2022 Community 簡要的設定UDK2018的環境

自製讀取 H 檔案並找出 define 的程式

如何Build UDK2018