This post details recent updates to a simple UEFI shell utility for displaying BMP images that I first released in 2015 and subsequently updated in 2017, and again this year. Source code for the previous versions is available on Github at UEFI-Utilities-2016 and UEFI-Utilities-2018 respectfully.
My goals for this update were:
- Fix a problem with displaying 24-bit images
- Add discrete support for 1, 4, 8, 12 and 32-bit images
- Enhance image header checks before attempting to display an image
- Scroll screen up when necessary to fully display an image
- Eliminate the need to clear the screen and press any key to continue
These enhancements nearly doubled the size of the source code:
$ wc DisplayBMP.c DisplayBMP.c.old 636 1955 21141 DisplayBMP.c 356 1011 10917 DisplayBMP.c.old $
Not all features documented by the Windows Metafile (WMF) specification, which includes the BMP file format, are supported by this utility. There is no OS/2 functionality or data compression support, no ICC color profile support, no embedded color palette support, only support for the BITMAPINFOHEADER format, and more. Despite the missing support, the utility should be capable of displaying most BMP images created by modern software.
Here is the full source code for the current version of the utility:
// // Copyright (c) 2015-2019 Finnbarr P. Murphy. All rights reserved. // // Display an uncompressed BMP image // // License: BSD 2 clause License // // Portions Copyright (c) 2016-2017, Microsoft Corporation // Copyright (c) 2018, Intel Corporation. All rights reserved. // See relevant code in EDK11 for exact details // #include <Uefi.h> #include <Library/UefiLib.h> #include <Library/ShellCEntryLib.h> #include <Library/ShellLib.h> #include <Library/BaseLib.h> #include <Library/BaseMemoryLib.h> #include <Library/MemoryAllocationLib.h> #include <Library/UefiBootServicesTableLib.h> #include <Library/PrintLib.h> #include <Library/SafeIntLib.h> #include <Protocol/LoadedImage.h> #include <Protocol/SimpleFileSystem.h> #include <Protocol/GraphicsOutput.h> #include <IndustryStandard/Bmp.h> #define UTILITY_VERSION L"20190201" #undef DEBUG EFI_GRAPHICS_OUTPUT_BLT_PIXEL EfiGraphicsColors[16] = { // B G R reserved {0x00, 0x00, 0x00, 0x00}, // BLACK {0x98, 0x00, 0x00, 0x00}, // LIGHTBLUE {0x00, 0x98, 0x00, 0x00}, // LIGHGREEN {0x98, 0x98, 0x00, 0x00}, // LIGHCYAN {0x00, 0x00, 0x98, 0x00}, // LIGHRED {0x98, 0x00, 0x98, 0x00}, // MAGENTA {0x00, 0x98, 0x98, 0x00}, // BROWN {0x98, 0x98, 0x98, 0x00}, // LIGHTGRAY {0x30, 0x30, 0x30, 0x00}, // DARKGRAY {0xff, 0x00, 0x00, 0x00}, // BLUE {0x00, 0xff, 0x00, 0x00}, // LIME {0xff, 0xff, 0x00, 0x00}, // CYAN {0x00, 0x00, 0xff, 0x00}, // RED {0xff, 0x00, 0xff, 0x00}, // FUCHSIA {0x00, 0xff, 0xff, 0x00}, // YELLOW {0xff, 0xff, 0xff, 0x00} // WHITE }; VOID GetBackgroundColor( EFI_GRAPHICS_OUTPUT_BLT_PIXEL *Background ) { INTN Attribute; Attribute = gST->ConOut->Mode->Attribute & 0x7F; *Background = EfiGraphicsColors[Attribute >> 4]; // DEBUG *Background = EfiGraphicsColors[10]; } VOID GetCursorPosition( UINTN *x, UINTN *y ) { *x = gST->ConOut->Mode->CursorColumn; *y = gST->ConOut->Mode->CursorRow; } VOID SetCursorPosition( UINTN x, UINTN y ) { gST->ConOut->SetCursorPosition( gST->ConOut, x, y); } VOID AsciiToUnicodeSize( CHAR8 *String, UINT8 length, CHAR16 *UniString ) { int len = length; while (*String != '\0' && len > 0) { *(UniString++) = (CHAR16) *(String++); len--; } *UniString = '\0'; } // // Display the BMP image, convert to 24-bit if necessary, scroll screen if necessary // EFI_STATUS DisplayImage( EFI_GRAPHICS_OUTPUT_PROTOCOL *Gop, EFI_HANDLE *BmpBuffer ) { EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *Info; EFI_GRAPHICS_OUTPUT_BLT_PIXEL Background; EFI_GRAPHICS_OUTPUT_BLT_PIXEL *BltBuffer; EFI_GRAPHICS_OUTPUT_BLT_PIXEL *Blt; BMP_IMAGE_HEADER *BmpHeader; BMP_COLOR_MAP *BmpColorMap; EFI_STATUS Status = EFI_SUCCESS; UINT32 *Palette; UINT8 *BitmapData; UINT8 *Image; UINT8 *ImageHeader; UINTN SizeOfInfo; UINTN Pixels; UINTN Width, Height; UINTN ImageIndex; UINTN Index; UINTN ImageHeight; UINTN ImageRows; UINTN CurRow, CurCol; UINTN MaxRows, MaxCols; UINTN VertPixelDelta = 0; UINTN ImagePixelDelta = 0; if (BmpBuffer == NULL) { return RETURN_INVALID_PARAMETER; } BmpHeader = (BMP_IMAGE_HEADER *) BmpBuffer; BitmapData = (UINT8*)BmpBuffer + BmpHeader->ImageOffset; Palette = (UINT32*) ((UINT8*)BmpBuffer + 0x36); Pixels = BmpHeader->PixelWidth * BmpHeader->PixelHeight; BltBuffer = AllocateZeroPool( sizeof(EFI_GRAPHICS_OUTPUT_BLT_PIXEL) * Pixels); if (BltBuffer == NULL) { Print(L"ERROR: BltBuffer. No memory resources\n"); return EFI_OUT_OF_RESOURCES; } Image = (UINT8 *)BmpBuffer; BmpColorMap = (BMP_COLOR_MAP *) (Image + sizeof (BMP_IMAGE_HEADER)); Image = ((UINT8 *)BmpBuffer) + BmpHeader->ImageOffset; ImageHeader = Image; // fill blt buffer for (Height = 0; Height < BmpHeader->PixelHeight; Height++) { Blt = &BltBuffer[(BmpHeader->PixelHeight - Height - 1) * BmpHeader->PixelWidth]; for (Width = 0; Width < BmpHeader->PixelWidth; Width++, Image++, Blt++) { switch (BmpHeader->BitPerPixel) { case 1: // Convert 1-bit BMP to 24-bit color for (Index = 0; Index < 8 && Width < BmpHeader->PixelWidth; Index++) { Blt->Blue = BmpColorMap[((*Image) >> (7 - Index)) & 0x1].Blue; Blt->Green = BmpColorMap[((*Image) >> (7 - Index)) & 0x1].Green; Blt->Red = BmpColorMap[((*Image) >> (7 - Index)) & 0x1].Red; Blt++; Width++; } Blt--; Width--; break; case 4: // Convert 4-bit BMP Palette to 24-bit color Index = (*Image) >> 4; Blt->Blue = BmpColorMap[Index].Blue; Blt->Green = BmpColorMap[Index].Green; Blt->Red = BmpColorMap[Index].Red; if (Width < (BmpHeader->PixelWidth - 1)) { Blt++; Width++; Index = (*Image) & 0x0f; Blt->Blue = BmpColorMap[Index].Blue; Blt->Green = BmpColorMap[Index].Green; Blt->Red = BmpColorMap[Index].Red; } break; case 8: // Convert 8-bit BMP palette to 24-bit color Blt->Blue = BmpColorMap[*Image].Blue; Blt->Green = BmpColorMap[*Image].Green; Blt->Red = BmpColorMap[*Image].Red; break; case 24: // No conversion needed Blt->Blue = *Image++; Blt->Green = *Image++; Blt->Red = *Image; break; case 32: // Convert to 24-bit by ignoring final byte of each pixel. Blt->Blue = *Image++; Blt->Green = *Image++; Blt->Red = *Image++; break; default: FreePool(BltBuffer); return EFI_UNSUPPORTED; break; }; } // start each row on a 32-bit boundary! ImageIndex = (UINTN)Image - (UINTN)ImageHeader; if ((ImageIndex % 4) != 0) { Image = Image + (4 - (ImageIndex % 4)); } } // get max rows and columns for current mode gST->ConOut->QueryMode( gST->ConOut, gST->ConOut->Mode->Mode, &MaxCols, &MaxRows ); GetCursorPosition( &CurCol, &CurRow ); #ifdef DEBUG Print(L"CURSOR %02d %02d\n", CurCol, CurRow); #endif // get screen details for current mode Gop->QueryMode( Gop, Gop->Mode->Mode, &SizeOfInfo, &Info ); // calculate required image and screen properties Width = Info->HorizontalResolution; ImageHeight = BmpHeader->PixelHeight; ImageRows = ImageHeight/EFI_GLYPH_HEIGHT; if ((ImageRows * EFI_GLYPH_HEIGHT) < ImageHeight) { ImagePixelDelta = (ImageHeight - (ImageRows * EFI_GLYPH_HEIGHT))/2; ImageRows++; } if ((MaxRows * EFI_GLYPH_HEIGHT) < Info->VerticalResolution) { VertPixelDelta = (Info->VerticalResolution - (MaxRows * EFI_GLYPH_HEIGHT))/2; } // scroll required? if ((CurRow + ImageRows + 1) >= MaxRows) { GetBackgroundColor( &Background ); // calculate number of rows to scroll UINTN ScrollRows = (ImageRows - (MaxRows - CurRow) +1); #ifdef DEBUG Print(L"CurRow: %d ImageRows: %d ImagePixelDelta: %d ScrollRows: %d VertPixelDelta: %d\n", CurRow, ImageRows, ImagePixelDelta, ScrollRows, VertPixelDelta ); #endif // scroll up to make room to display image Status = Gop->Blt( Gop, NULL, EfiBltVideoToVideo, 0, VertPixelDelta + (ScrollRows * EFI_GLYPH_HEIGHT), // Source X,Y 0, VertPixelDelta, // Destination X,Y Width, (MaxRows - ScrollRows) * EFI_GLYPH_HEIGHT, 0 ); if (EFI_ERROR (Status)) { Print(L"ERROR: Scroll Up, Gop->Blt [%d]\n", Status); goto cleanup; } // color background of the scrolled area Status = Gop->Blt( Gop, &Background, EfiBltVideoFill, 0, 0, // Not Used 0, (MaxRows - ScrollRows) * EFI_GLYPH_HEIGHT, Width - 1, ScrollRows * EFI_GLYPH_HEIGHT, 0 ); if (EFI_ERROR (Status)) { Print(L"ERROR: Color Fill, Gop->Blt [%d]\n", Status); goto cleanup; } // display the image Status = Gop->Blt( Gop, BltBuffer, EfiBltBufferToVideo, 0, 0, // Source X,Y 0, ImagePixelDelta + ((MaxRows - ImageRows) * EFI_GLYPH_HEIGHT), // Destination X,Y BmpHeader->PixelWidth, BmpHeader->PixelHeight, 0 ); if (EFI_ERROR (Status)) { Print(L"ERROR: Image Display Gop->Blt [%d]\n", Status); goto cleanup; } SetCursorPosition( 0, MaxRows - 1 ); } else { // just display the image Status = Gop->Blt( Gop, BltBuffer, EfiBltBufferToVideo, 0, 0, // Source X,Y 0, ImagePixelDelta + ((CurRow + 1) * EFI_GLYPH_HEIGHT), // Destination X,Y BmpHeader->PixelWidth, BmpHeader->PixelHeight, 0 ); if (EFI_ERROR (Status)) { Print(L"ERROR: Image Display Gop->Blt [%d]\n", Status); goto cleanup; } SetCursorPosition( 0, CurRow + ImageRows ); } cleanup: FreePool(BltBuffer); return Status; } // // Print the BMP header details // VOID PrintBMPHeader( EFI_HANDLE *BmpBuffer ) { BMP_IMAGE_HEADER *BmpHeader; CHAR16 Buffer[100]; BmpHeader = (BMP_IMAGE_HEADER *) BmpBuffer; AsciiToUnicodeSize( (CHAR8 *)BmpHeader, 2, Buffer ); Print(L"\n"); Print(L" BMP Signature : %s\n", Buffer); Print(L" Size : %d\n", BmpHeader->Size); Print(L" Image Offset : %d\n", BmpHeader->ImageOffset); Print(L" Header Size : %d\n", BmpHeader->HeaderSize); Print(L" Image Width : %d\n", BmpHeader->PixelWidth); Print(L" Image Height : %d\n", BmpHeader->PixelHeight); Print(L" Planes : %d\n", BmpHeader->Planes); Print(L" Bits Per Pixel : %d\n", BmpHeader->BitPerPixel); Print(L" Compression Type : %d\n", BmpHeader->CompressionType); Print(L" Image Size : %d\n", BmpHeader->ImageSize); Print(L" X Pixels Per Meter : %d\n", BmpHeader->XPixelsPerMeter); Print(L" Y Pixels Per Meter : %d\n", BmpHeader->YPixelsPerMeter); Print(L" Number of Colors : %d\n", BmpHeader->NumberOfColors); Print(L" Important Colors : %d\n", BmpHeader->ImportantColors); Print(L"\n"); } // // Check that the image is a valid supported BMP // EFI_STATUS CheckBMPHeader( EFI_GRAPHICS_OUTPUT_PROTOCOL *Gop, EFI_HANDLE *BmpBuffer, INTN BmpImageSize ) { EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *Info; BMP_IMAGE_HEADER *BmpHeader; BMP_COLOR_MAP *BmpColorMap; EFI_STATUS Status = EFI_SUCCESS; UINT32 BltBufferSize; UINT32 ColorMapNum; UINT32 DataSize; UINT32 DataSizePerLine; UINTN SizeOfInfo; UINT8 *Image; // check parameters if (BmpBuffer == NULL) { return EFI_INVALID_PARAMETER; } if (BmpImageSize < sizeof (BMP_IMAGE_HEADER)) { Print(L"ERROR: BmpImageSize too small\n"); return EFI_INVALID_PARAMETER; } BmpHeader = (BMP_IMAGE_HEADER *) BmpBuffer; // not BMP format if (BmpHeader->CharB != 'B' || BmpHeader->CharM != 'M') { Print(L"ERROR: Unsupported image format. Not a BMP\n"); return EFI_UNSUPPORTED; } // BITMAPINFOHEADER format unsupported if (BmpHeader->HeaderSize != sizeof (BMP_IMAGE_HEADER) \ - ((UINTN) &(((BMP_IMAGE_HEADER *)0)->HeaderSize))) { Print(L"ERROR: Unsupported BITMAPFILEHEADER\n"); return EFI_UNSUPPORTED; } // compression type not 0 if (BmpHeader->CompressionType != 0) { Print(L"ERROR: Compression type not 0\n"); return EFI_UNSUPPORTED; } if ((BmpHeader->PixelHeight == 0) || (BmpHeader->PixelWidth == 0)) { Print(L"ERROR: BMP Header PixelHeight or PixelWidth is 0\n"); return EFI_UNSUPPORTED; } // data size in each line must be 4 byte aligned Status = SafeUint32Mult( BmpHeader->PixelWidth, BmpHeader->BitPerPixel, &DataSizePerLine ); if (EFI_ERROR (Status)) { Print(L"ERROR: Invalid BMP. PixelWidth or BitPerPixel\n"); return EFI_UNSUPPORTED; } Status = SafeUint32Add( DataSizePerLine, 31, &DataSizePerLine ); if (EFI_ERROR (Status)) { Print(L"ERROR: Invalid BMP. DataSizePerLine\n"); return EFI_UNSUPPORTED; } DataSizePerLine = (DataSizePerLine >> 3) &(~0x3); Status = SafeUint32Mult( DataSizePerLine, BmpHeader->PixelHeight, &BltBufferSize ); if (EFI_ERROR (Status)) { Print(L"ERROR: Invalid BMP. DataSizePerLine or PixelHeight\n"); return EFI_UNSUPPORTED; } Status = SafeUint32Mult( BmpHeader->PixelHeight, DataSizePerLine, &DataSize ); if (EFI_ERROR (Status)) { Print(L"ERROR: Invalid BMP. DataSizePerLine or PixelHeight\n"); return EFI_UNSUPPORTED; } if ((BmpHeader->Size != BmpImageSize) || (BmpHeader->Size < BmpHeader->ImageOffset) || (BmpHeader->Size - BmpHeader->ImageOffset != DataSize)) { Print(L"ERROR: Invalid image size\n"); return EFI_UNSUPPORTED; } // calculate colormap offset in the image. Image = (UINT8 *)BmpBuffer; BmpColorMap = (BMP_COLOR_MAP *) (Image + sizeof (BMP_IMAGE_HEADER)); if (BmpHeader->ImageOffset < sizeof (BMP_IMAGE_HEADER)) { Print(L"ERROR: Invalid colormap offset\n"); return EFI_UNSUPPORTED; } if (BmpHeader->ImageOffset > sizeof (BMP_IMAGE_HEADER)) { switch (BmpHeader->BitPerPixel) { case 1: ColorMapNum = 2; break; case 4: ColorMapNum = 16; break; case 8: ColorMapNum = 256; break; default: ColorMapNum = 0; break; } if (BmpHeader->ImageOffset - sizeof (BMP_IMAGE_HEADER) != sizeof (BMP_COLOR_MAP) * ColorMapNum) { Print(L"ERROR: Invalid colormap offset\n"); return EFI_UNSUPPORTED; } } // image size less than screen size Gop->QueryMode( Gop, Gop->Mode->Mode, &SizeOfInfo, &Info ); if ((BmpHeader->PixelWidth > (Info->HorizontalResolution - EFI_GLYPH_WIDTH*5)) || (BmpHeader->PixelHeight > (Info->VerticalResolution - EFI_GLYPH_HEIGHT*5))) { Print(L"ERROR: Image too big for screen at current resolution\n"); return EFI_UNSUPPORTED; } // supported bits per pixel if (BmpHeader->BitPerPixel != 1 && BmpHeader->BitPerPixel != 8 && BmpHeader->BitPerPixel != 8 && BmpHeader->BitPerPixel != 12 && BmpHeader->BitPerPixel != 24 && BmpHeader->BitPerPixel != 32) { Print(L"ERROR: BitPerPixel is not one of 1, 4, 8, 12, 24 or 32\n"); return EFI_UNSUPPORTED; } return Status; } VOID Usage( BOOLEAN ErrorMsg ) { if ( ErrorMsg ) { Print(L"ERROR: Unknown option(s).\n"); } Print(L"Usage: DisplayBMP [-v | --verbose] BMPfile\n"); Print(L" DisplayBMP [-V | --version]\n"); } INTN EFIAPI ShellAppMain( UINTN Argc, CHAR16 **Argv ) { EFI_GRAPHICS_OUTPUT_PROTOCOL *Gop; EFI_DEVICE_PATH_PROTOCOL *Dpp; SHELL_FILE_HANDLE FileHandle; EFI_FILE_INFO *FileInfo = NULL; EFI_STATUS Status = EFI_SUCCESS; EFI_HANDLE *Handles = NULL; EFI_HANDLE *FileBuffer = NULL; BOOLEAN Verbose = FALSE; UINTN HandleCount = 0; UINTN FileSize; if (Argc == 2) { if (!StrCmp(Argv[1], L"--version") || !StrCmp(Argv[1], L"-V")) { Print(L"Version: %s\n", UTILITY_VERSION); return Status; } else if (!StrCmp(Argv[1], L"--help") || !StrCmp(Argv[1], L"-h")) { Usage(FALSE); return Status; } else if (Argv[1][0] == L'-') { Usage(TRUE); return Status; } } else if (Argc == 3) { if (!StrCmp(Argv[1], L"--verbose") || !StrCmp(Argv[1], L"-v")) { Verbose = TRUE; } else if (Argv[1][0] == L'-') { Usage(FALSE); return Status; } } else { Usage(FALSE); return Status; } // Check last argument is not an option! if (Argv[Argc-1][0] == L'-') { Usage(TRUE); return Status; } // Open the file (has to be LAST arguement on command line) Status = ShellOpenFileByName( Argv[Argc - 1], &FileHandle, EFI_FILE_MODE_READ , 0); if (EFI_ERROR (Status)) { Print(L"ERROR: Could not open specified file [%d]\n", Status); return Status; } // Allocate buffer for file contents FileInfo = ShellGetFileInfo( FileHandle ); FileBuffer = AllocateZeroPool( (UINTN)FileInfo->FileSize ); if (FileBuffer == NULL) { Print(L"ERROR: File buffer. No memory resources\n"); return (SHELL_OUT_OF_RESOURCES); } // Read file contents into allocated buffer FileSize = (UINTN) FileInfo->FileSize; Status = ShellReadFile( FileHandle, &FileSize, FileBuffer ); if (EFI_ERROR (Status)) { Print(L"ERROR: ShellReadFile failed [%d]\n", Status); goto cleanup; } ShellCloseFile( &FileHandle ); // Try locating GOP by handle Status = gBS->LocateHandleBuffer( ByProtocol, &gEfiGraphicsOutputProtocolGuid, NULL, &HandleCount, &Handles ); if (EFI_ERROR (Status)) { Print(L"ERROR: No GOP handles found via LocateHandleBuffer\n"); goto cleanup; } #ifdef DEBUG Print(L"Found %d GOP handles via LocateHandleBuffer\n", HandleCount); #endif // Make sure we use the correct GOP handle Gop = NULL; for (UINTN Handle = 0; Handle < HandleCount; Handle++) { Status = gBS->HandleProtocol( Handles[Handle], &gEfiDevicePathProtocolGuid, (VOID **)&Dpp ); if (!EFI_ERROR(Status)) { Status = gBS->HandleProtocol( Handles[Handle], &gEfiGraphicsOutputProtocolGuid, (VOID **)&Gop ); if (!EFI_ERROR(Status)) { break; } } } FreePool(Handles); if (Gop == NULL) { Print(L"Exiting. Graphics console not found.\n"); goto cleanup; } Status = CheckBMPHeader( Gop, FileBuffer, FileSize ); if (EFI_ERROR (Status)) { goto cleanup; } if (Verbose) { PrintBMPHeader( FileBuffer ); } DisplayImage( Gop, FileBuffer ); cleanup: FreePool( FileBuffer ); return Status; }
There is nothing particularly unusual about the above code so I will not go into detail about each function. It was designed to be built using the UDK2018 snapshot of TianoCore EDK II (AKA EDK2). Some minor modifications, especially with Uint32Mult, SafeUint32Add and related functions, will be necessary to build this code on old snapshots of EDK2.
Some notes about the DisplayImage function may be helpful. Essentially, it is important not just to scroll whatever is currently displayed on the screen but to accurately scroll the correct region of the screen. What do I mean by this? When you are in a modern UEFI shell, the shell uses GOP (Graphic Output Protocol), rather than UGA, to render text on the screen. Suppose my current screen mode is set to 56 rows, the firmware vertically centers the 56 rows on the screen such that there may be zero or more blank rows of pixels above the first line of text (row 0) and a corresponding number of blank rows of pixels below row 55. Aesthetically, this is the visually most pleasing arrangement.
When the screen is scrolled up, this vertical centering of the rows needs to be taken into account. This is done by the VertPixelDelta variable calculation.
VertPixelDelta = 0; if ((MaxRows * EFI_GLYPH_HEIGHT) < Info->VerticalResolution) { VertPixelDelta = (Info->VerticalResolution - (MaxRows * EFI_GLYPH_HEIGHT))/2; }
In EDK2, EFI_GLYPH_HEIGHT is defined to be 19, and EFI_GLYPH_WIDTH is defined to be 8. Glyph height is the height, in pixels, of the character glyph. This value includes descenders and top and bottom borders. Similar for glyph width. Thus it appears that 12×8 is the default glyph size for EDK2.
VertPixelDelta is used in the GOP blt function to accurately scroll up the required number of text rows so that the image can be displayed at the bottom of the screen followed by the UEFI shell prompt.
// scroll region up Status = Gop->Blt( Gop, NULL, EfiBltVideoToVideo, 0, VertPixelDelta + (ScrollRows * EFI_GLYPH_HEIGHT), // Source X,Y 0, VertPixelDelta, // Destination X,Y Width, (MaxRows - ScrollRows) * EFI_GLYPH_HEIGHT, 0 );
Turning now to the question of how to aesthetically fit an image within rows of text. The approach I took was to first calculate the number of rows of screen text necessary to fully fit the image within. Then determine if the image needed to be vertically centered within these rows.
ImagePixelDelta = 0; ImageHeight = BmpHeader->PixelHeight; ImageRows = ImageHeight/EFI_GLYPH_HEIGHT; if ((ImageRows * EFI_GLYPH_HEIGHT) < ImageHeight) { ImagePixelDelta = (ImageHeight - (ImageRows * EFI_GLYPH_HEIGHT))/2; ImageRows++; }
ImagePixelDelta is used in the GOP blt function to accurately display the image at the correct location on the screen, i.e. at the bottom of the screen less one text row.
// display the image Status = Gop->Blt( Gop, BltBuffer, EfiBltBufferToVideo, 0, 0, // Source X,Y 0, ImagePixelDelta + ((MaxRows - ImageRows) * EFI_GLYPH_HEIGHT), // Dest X,Y BmpHeader->PixelWidth, BmpHeader->PixelHeight, 0 );
Here is the INF build configuration file used to build this utility:
[Defines] INF_VERSION = 1.25 BASE_NAME = DisplayBMP FILE_GUID = 4ea87c54-7895-4dcd-0455-747010f3ce51 MODULE_TYPE = UEFI_APPLICATION VERSION_STRING = 1.1 ENTRY_POINT = ShellCEntryLib VALID_ARCHITECTURES = X64 [Sources] DisplayBMP.c [Packages] MdePkg/MdePkg.dec ShellPkg/ShellPkg.dec [LibraryClasses] ShellCEntryLib ShellLib BaseLib BaseMemoryLib UefiLib SafeIntLib [Protocols] [BuildOptions] [Pcd]
Here are two (poor) screenshots of the new version of DisplayBPM in action:
Note that I have switched the compiler I use to build all the UEFI utilities that I maintain from GCC to LLVM Clang. Note also that I no longer use utility version numbers such as “1.0” or “1.1” within the source code. Now I use a date string of the form “YYYYMMDD”.
The source code and a pre-built 64-bit binary for this version of the utility, together with 2 sample BMPs, can be found on Github at UEFI-Utilities-2019.
Enjoy!