前面介绍了 EFI_GRAPHICS_OUTPUT_PROTOCOL 的各种用法,这里介绍一下如何使用这个 Protocol 进行截屏,将屏幕信息保存为 BMP 文件。
原理是:用 GraphicsOutput->Blt 将屏幕信息保存在指定的内存位置,然后加一个 BMP 的文件头,再重新排列一下颜色(BMP是直接颜色BGR排列下来),最后将这块内存保存下来就得到需要的BMP文件了。
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/ShellCEntryLib.h>
#include <stdio.h>
#include <stdlib.h>
#include <wchar.h>
#include <time.h>
#include <Protocol/EfiShell.h>
#include <Library/ShellLib.h>
#include <Protocol/SimpleFileSystem.h>
#include <Protocol/BlockIo.h>
#include <Library/DevicePathLib.h>
#include <Library/HandleParsingLib.h>
#include <Library/SortLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/BaseMemoryLib.h>
#include <Protocol/LoadedImage.h>
extern EFI_BOOT_SERVICES *gBS;
extern EFI_SYSTEM_TABLE *gST;
extern EFI_RUNTIME_SERVICES *gRT;
extern EFI_SHELL_ENVIRONMENT2 *mEfiShellEnvironment2;
extern EFI_HANDLE gImageHandle;
static EFI_GUID GraphicsOutputProtocolGuid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID;
static EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutput = NULL;
#pragma pack(1)
//Copied from C\MdePkg\Include\Protocol\UgaDraw.h
typedef struct {
UINT8 Blue;
UINT8 Green;
UINT8 Red;
UINT8 Reserved;
} EFI_UGA_PIXEL;
/* This should be compatible with EFI_UGA_PIXEL */
typedef struct {
UINT8 b, g, r, a;
} EG_PIXEL;
typedef struct {
CHAR8 CharB;
CHAR8 CharM;
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; // Compressed image size in bytes
UINT32 XPixelsPerMeter;
UINT32 YPixelsPerMeter;
UINT32 NumberOfColors;
UINT32 ImportantColors;
} BMP_IMAGE_HEADER;
typedef struct {
UINTN Width;
UINTN Height;
BOOLEAN HasAlpha;
EG_PIXEL *PixelData;
} EG_IMAGE;
#pragma pack()
//
// Basic image handling
//
EG_IMAGE * egCreateImage(IN UINTN Width, IN UINTN Height, IN BOOLEAN HasAlpha)
{
EG_IMAGE *NewImage;
NewImage = (EG_IMAGE *) AllocatePool(sizeof(EG_IMAGE));
if (NewImage == NULL)
return NULL;
NewImage->PixelData = (EG_PIXEL *) AllocatePool(
Width * Height * sizeof(EG_PIXEL));
if (NewImage->PixelData == NULL) {
FreePool(NewImage);
return NULL;
}
NewImage->Width = Width;
NewImage->Height = Height;
NewImage->HasAlpha = HasAlpha;
return NewImage;
}
//
// Save BMP image
//
VOID egEncodeBMP(IN EG_IMAGE *Image, OUT UINT8 **FileDataReturn,
OUT UINTN *FileDataLengthReturn)
{
BMP_IMAGE_HEADER *BmpHeader;
UINT8 *FileData;
UINTN FileDataLength;
UINT8 *ImagePtr;
UINT8 *ImagePtrBase;
UINTN ImageLineOffset;
EG_PIXEL *PixelPtr;
UINTN x, y;
ImageLineOffset = Image->Width * 3;
if ((ImageLineOffset % 4) != 0)
ImageLineOffset = ImageLineOffset + (4 - (ImageLineOffset % 4));
// allocate buffer for file data
FileDataLength = sizeof(BMP_IMAGE_HEADER) +
Image->Height * ImageLineOffset;
FileData = AllocateZeroPool(FileDataLength);
if (FileData == NULL) {
Print(L"Error allocate %d bytes\n", FileDataLength);
*FileDataReturn = NULL;
*FileDataLengthReturn = 0;
return;
}
// fill header
BmpHeader = (BMP_IMAGE_HEADER *)FileData;
BmpHeader->CharB = 'B';
BmpHeader->CharM = 'M';
BmpHeader->Size = FileDataLength;
BmpHeader->ImageOffset = sizeof(BMP_IMAGE_HEADER);
BmpHeader->HeaderSize = 40;
BmpHeader->PixelWidth = Image->Width;
BmpHeader->PixelHeight = Image->Height;
BmpHeader->Planes = 1;
BmpHeader->BitPerPixel = 24;
BmpHeader->CompressionType = 0;
BmpHeader->XPixelsPerMeter = 0xb13;
BmpHeader->YPixelsPerMeter = 0xb13;
// fill pixel buffer
ImagePtrBase = FileData + BmpHeader->ImageOffset;
for (y = 0; y < Image->Height; y++) {
ImagePtr = ImagePtrBase;
ImagePtrBase += ImageLineOffset;
PixelPtr = Image->PixelData +
(Image->Height - 1 - y) * Image->Width;
for (x = 0; x < Image->Width; x++) {
*ImagePtr++ = PixelPtr->b;
*ImagePtr++ = PixelPtr->g;
*ImagePtr++ = PixelPtr->r;
PixelPtr++;
}
}
*FileDataReturn = FileData;
*FileDataLengthReturn = FileDataLength;
}
VOID egFreeImage(IN EG_IMAGE *Image)
{
if (Image != NULL) {
if (Image->PixelData != NULL)
FreePool(Image->PixelData);
FreePool(Image);
}
}
/**
Function opens and returns a file handle to the root directory of a volume.
@param DeviceHandle A handle for a device
@return A valid file handle or NULL is returned
**/
EFI_FILE_HANDLE
EfiLibOpenRoot (
IN EFI_HANDLE DeviceHandle
)
{
EFI_STATUS Status;
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *Volume;
EFI_FILE_HANDLE File;
File = NULL;
//
// File the file system interface to the device
//
Status = gBS->HandleProtocol (
DeviceHandle,
&gEfiSimpleFileSystemProtocolGuid,
(VOID *) &Volume
);
//
// Open the root directory of the volume
//
if (!EFI_ERROR (Status)) {
Status = Volume->OpenVolume (
Volume,
&File
);
}
//
// Done
//
return EFI_ERROR (Status) ? NULL : File;
}
static EFI_GUID ESPGuid = { 0xc12a7328, 0xf81f, 0x11d2,
{ 0xba, 0x4b, 0x00, 0xa0, 0xc9, 0x3e, 0xc9, 0x3b } };
static EFI_STATUS egFindESP(OUT EFI_FILE_PROTOCOL *RootDir)
{
EFI_STATUS Status;
return Status;
}
EFI_STATUS egSaveFile(IN CHAR16 *FileName,
IN UINT8 *FileData, IN UINTN FileDataLength)
{
EFI_STATUS Status;
EFI_FILE_HANDLE FileHandle;
UINTN BufferSize;
EFI_FILE_PROTOCOL *Root;
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *SimpleFileSystem;
Status = gBS->LocateProtocol(
&gEfiSimpleFileSystemProtocolGuid,
NULL,
(VOID **)&SimpleFileSystem);
if (EFI_ERROR(Status)) {
Print(L"Cannot find EFI_SIMPLE_FILE_SYSTEM_PROTOCOL \r\n");
return Status;
}
Status = SimpleFileSystem->OpenVolume(SimpleFileSystem, &Root);
if (EFI_ERROR(Status)) {
Print(L"OpenVolume error \r\n");
return Status;
}
Status = Root->Open(
Root,
&FileHandle,
FileName,
EFI_FILE_MODE_READ |
EFI_FILE_MODE_WRITE |
EFI_FILE_MODE_CREATE,
0);
if (EFI_ERROR(Status))
{
Print(L"Error Open NULL\n");
return Status;
}
BufferSize = FileDataLength;
Status = FileHandle->Write(FileHandle, &BufferSize, FileData);
FileHandle->Close(FileHandle);
return Status;
}
int
EFIAPI
main (
IN int Argc,
IN char **Argv
)
{
EFI_STATUS Status;
EG_IMAGE *Image;
UINT8 *FileData;
UINTN FileDataLength;
Status = gBS->LocateProtocol(&GraphicsOutputProtocolGuid,
NULL, (VOID **) &GraphicsOutput);
if (EFI_ERROR(Status)) {
GraphicsOutput = NULL;
Print(L"Loading Graphics_Output_Protocol error!\n");
return EFI_SUCCESS;
}
// allocate a buffer for the whole screen
Image = egCreateImage(GraphicsOutput->Mode->Info->HorizontalResolution,
GraphicsOutput->Mode->Info->VerticalResolution,
FALSE);
if (Image == NULL) {
Print(L"Error egCreateImage returned NULL\n");
return EFI_SUCCESS;
}
// get full screen image
if (GraphicsOutput != NULL) {
GraphicsOutput->Blt(GraphicsOutput,
(EFI_GRAPHICS_OUTPUT_BLT_PIXEL *) Image->PixelData,
EfiBltVideoToBltBuffer,
0, 0, 0, 0, Image->Width, Image->Height, 0); }
// encode as BMP
egEncodeBMP(Image, &FileData, &FileDataLength);
egFreeImage(Image);
if (FileData == NULL) {
Print(L"Error egEncodeBMP returned NULL\n");
return EFI_SUCCESS;
}
// save to file on the ESP
Status = egSaveFile(L"screenshot.bmp", FileData, FileDataLength);
FreePool(FileData);
if (EFI_ERROR(Status)) {
Print(L"Error egSaveFile: %x\n", Status);
return EFI_SUCCESS;
}
return EFI_SUCCESS;
}
运行结果:

完整代码下载:
SCRCap
特别注意,本文使用的代码来自 https://github.com/chengs/UEFI 再次表示感谢。
参考:
1.http://biosengineer.blogspot.com/2011/09/uefi-screenshot-capture-screen.html UEFI Screenshot (Capture screen)
2.http://kurtqiao.blogspot.com/2013/03/how-to-take-screen-shot-under-uefi-shell.htmlHow to take screen shot under UEFI shell