Page 1 of 1

EFI & BMP - Shell error when compiling with vs2019

Posted: Fri Feb 05, 2021 9:23 am
by gomidas
I am trying to render a bmp file for my EFI app. Normally, I do not use uefi-shell.

I found this link to begin: https://blog.fpmurphy.com/2015/08/displ ... shell.html

I modified given code like this:

gBMP.h

Code: Select all

#pragma once
#include "efi_reference.h"
/*
"efi_reference.h" contains:
#include <efi.h>
#include <efilib.h>
#include <efipoint.h>
*/
#include <efishellparm.h>

typedef struct {
    CHAR8  CharB;
    CHAR8  CharM;
    UINT32 Size;
    UINT16 Reserved[2];
    UINT32 ImageOffset;
    UINT32 HeaderSize;
    UINT32 PixelWidth;
    UINT32 PixelHeight;
    UINT16 Planes;
    UINT16 BitPerPixel;
    UINT32 CompressionType;
    UINT32 ImageSize;
    UINT32 XPixelsPerMeter;
    UINT32 YPixelsPerMeter;
    UINT32 NumberOfColors;
    UINT32 ImportantColors;
} __attribute__((__packed__)) BMP_IMAGE_HEADER;

static VOID
AsciiToUnicodeSize(CHAR8* String,
    UINT8 length,
    CHAR16* UniString)
{
    int len = length;

    while (*String != '\0' && len > 0) {
        *(UniString++) = (CHAR16) * (String++);
        len--;
    }
    *UniString = '\0';
}

static EFI_STATUS
DisplayImage(EFI_GRAPHICS_OUTPUT_PROTOCOL* Gop, EFI_HANDLE* BmpBuffer)
{
    BMP_IMAGE_HEADER* BmpHeader = (BMP_IMAGE_HEADER*)BmpBuffer;
    EFI_GRAPHICS_OUTPUT_BLT_PIXEL* BltBuffer;
    EFI_STATUS Status = EFI_SUCCESS;
    UINT8* BitmapData;
    UINT32* Palette;
    UINTN   Pixels;
    UINTN   XIndex;
    UINTN   YIndex;
    UINTN   Pos;
    UINTN   BltPos;

    BitmapData = (UINT8*)BmpBuffer + BmpHeader->ImageOffset;
    Palette = (UINT32*)((UINT8*)BmpBuffer + 0x36);

    Pixels = (UINTN)BmpHeader->PixelWidth * (UINTN)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;
    }

    for (YIndex = BmpHeader->PixelHeight; YIndex > 0; YIndex--) {
        for (XIndex = 0; XIndex < BmpHeader->PixelWidth; XIndex++) {
            Pos = (YIndex - 1) * ((UINTN)((UINTN)BmpHeader->PixelWidth + 3) / 4) * 4 + XIndex;
            BltPos = (BmpHeader->PixelHeight - YIndex) * BmpHeader->PixelWidth + XIndex;
            BltBuffer[BltPos].Blue = (UINT8)BitFieldRead32(Palette[BitmapData[Pos]], 0, 7);
            BltBuffer[BltPos].Green = (UINT8)BitFieldRead32(Palette[BitmapData[Pos]], 8, 15);
            BltBuffer[BltPos].Red = (UINT8)BitFieldRead32(Palette[BitmapData[Pos]], 16, 23);
            BltBuffer[BltPos].Reserved = (UINT8)BitFieldRead32(Palette[BitmapData[Pos]], 24, 31);
        }
    }


    Status = Gop->Blt(Gop,
        BltBuffer,
        EfiBltBufferToVideo,
        0, 0,            /* Source X, Y */
        50, 50,          /* Dest X, Y */
        BmpHeader->PixelWidth, BmpHeader->PixelHeight,
        0);

    FreePool(BltBuffer);

    return Status;
}

static RenderBMP(char file[]/*zani_logo.bmp*/, EFI_HANDLE *gImageHandle, EFI_GRAPHICS_OUTPUT_PROTOCOL *Gop, UINTN HandleCount)
{
    EFI_STATUS Status = EFI_SUCCESS;
    EFI_HANDLE* HandleBuffer = NULL;

    SHELL_FILE_HANDLE FileHandle;
    EFI_FILE_INFO* FileInfo = NULL;
    EFI_HANDLE* FileBuffer = NULL;
    UINTN FileSize;
    int OrgMode, NewMode = 0, Pixels = 0;
    BOOLEAN LowerHandle = FALSE;

    // Open the file ( has to be LAST arguement on command line )
    Status = ShellOpenFileByName(file, &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 0;
    }

    // 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,
        &HandleBuffer);
    if (EFI_ERROR(Status)) {
        Print(L"ERROR: No GOP handles found via LocateHandleBuffer\n");
        goto cleanup;
    }
    else {
        Print(L"Found %d GOP handles via LocateHandleBuffer\n", HandleCount);
        if (LowerHandle)
            HandleCount = 0;
        else
            HandleCount--;

        Status = gBS->OpenProtocol(HandleBuffer[HandleCount],
            &gEfiGraphicsOutputProtocolGuid,
            (VOID**)&Gop,
            gImageHandle,
            NULL,
            EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL);
        if (EFI_ERROR(Status)) {
            Print(L"ERROR: OpenProtocol [%d]\n", Status);
            goto cleanup;
        }

        FreePool(HandleBuffer);
    }

    // Figure out maximum resolution and use it
    OrgMode = Gop->Mode->Mode;
    for (int i = 0; i < Gop->Mode->MaxMode; i++) {
        EFI_GRAPHICS_OUTPUT_MODE_INFORMATION* Info;
        UINTN SizeOfInfo;

        Status = Gop->QueryMode(Gop, i, &SizeOfInfo, &Info);
        if (EFI_ERROR(Status) && Status == EFI_NOT_STARTED) {
            Gop->SetMode(Gop, Gop->Mode->Mode);
            Status = Gop->QueryMode(Gop, i, &SizeOfInfo, &Info);
        }
        if (EFI_ERROR(Status)) {
            continue;
        }
        if (Info->PixelsPerScanLine > Pixels) {
            Pixels = Info->PixelsPerScanLine;
            NewMode = i;
        }
    }

    // change screen mode
    Status = Gop->SetMode(Gop, NewMode);
    if (EFI_ERROR(Status)) {
        Print(L"ERROR: SetMode [%d]\n, Status");
        goto cleanup;
    }

    DisplayImage(Gop, FileBuffer);

    Status = Gop->SetMode(Gop, OrgMode);


cleanup:

    FreePool(FileBuffer);
    return Status;
}
When I put my gBMP header into main.c as header then put my RenderBMP as: RenderBMP(L"logo.bmp",ImageHandle,gop,HandleCount); I see following errors:

Code: Select all

Error LNK2019	unresolved external symbol BitFieldRead32 için DisplayImage ...
Error LNK2019	unresolved external symbol ShellOpenFileByName için RenderBMP ...	
Error LNK2019	unresolved external symbol ShellGetFileInfo için RenderBMP ...	
Error LNK2019	unresolved external symbol ShellReadFile için RenderBMP ...
Error LNK2019	çözümlenmemiş dış sembol ShellCloseFile için RenderBMP ...	
What am I missing here ?

Re: EFI & BMP - Shell error when compiling with vs2019

Posted: Fri Feb 05, 2021 2:43 pm
by Octocontrabass
Those functions are provided by MdePkg and ShellPkg, so you need to link against those libraries in order to use them.

You are building your EFI application with EDK II, right?

Re: EFI & BMP - Shell error when compiling with vs2019

Posted: Fri Feb 05, 2021 3:14 pm
by PeterX
This is not causing the error messages and it's only a minor bug: In the source code on line 321: The "-sign has to be before Status (before the comma/break), not behind it.
Since the code is from 2015 there is no way to tell the author afaik.

Greetings
Peter

Re: EFI & BMP - Shell error when compiling with vs2019

Posted: Fri Feb 05, 2021 4:44 pm
by gomidas
Octocontrabass wrote:Those functions are provided by MdePkg and ShellPkg, so you need to link against those libraries in order to use them.

You are building your EFI application with EDK II, right?
Upss, I forgot to mention I use GNU-EFI. VS2019 advised header as efishellparm.h

If I do not have those pkg can I use EFI_FILE_PROTOCOL ?

Re: EFI & BMP - Shell error when compiling with vs2019

Posted: Fri Feb 05, 2021 7:17 pm
by Octocontrabass
Yes, you can use anything that's listed in the UEFI specification (and supported by your firmware) without any libraries.

Re: EFI & BMP - Shell error when compiling with vs2019

Posted: Sun Feb 07, 2021 5:47 am
by gomidas
After searching for hours I found more information:

I use photoshops OS/2 latest bitmap version. Size information for this format is given in first 4 bytes after B and M characters. But storing it in struct with a INT32 variable not possible. I checked 8bit values on a online HEX viewer. First two bytes are B and M then checked next 4 values which were: 0,12,0,28 and as hex: 00,0c,00,1c then I put 000c001c to a online hex converter and result was :786460. This was exact size of the file. Maybe my knowledge is very limited. I passed next two of 2bytes CHAR8 reserved1[2], CHAR8 reserved2[2] and for imageOffset I did: INT8 ImageOffset[4] as hex 001a0000 and as demical 1703936 which I am not sure thats correct. I pass four of 8 bits and get image size by INT16 IMG_W, INT16 IMG_H which gives result 512x512.

Re: EFI & BMP - Shell error when compiling with vs2019

Posted: Mon Feb 08, 2021 3:13 am
by bzt
gomidas wrote:If I do not have those pkg can I use EFI_FILE_PROTOCOL ?
Absolutely yes, there's an example on our wiki, works for GNU-EFI too.
gomidas wrote:I use photoshops OS/2 latest bitmap version.
I would recommend to forget about BMP altogether. Instead, wrap up UEFI's AllocPool and FreePool calls in malloc() and free(), and use stb_image.h. It is a dependency-free image decoder (well, only depends on malloc/free) for PNG, JPEG etc. You can configure which formats you want with defines. It is a header-only library, no static library nor linking required!

Then decoding a png in memory for example goes like this:

Code: Select all

    stbi__context s;
    stbi__result_info ri;
    /* Set up context, tell the decoder we have the png file entirely in memory */
    /* You could define file reader callbacks, but that's a bit more complicated */
    s.read_from_callbacks = 0;
    s.img_buffer = s.img_buffer_original = FileBuffer;
    s.img_buffer_end = s.img_buffer_original_end = FileBuffer + FileSize;
    /* we want a byte for each color */
    ri.bits_per_channel = 8;
    data = (uint8_t*)stbi__png_load(&s, (int*)&w, (int*)&h, (int*)&f, 0, &ri);
The image's BmpHeader->PixelWidth will be in w, BmpHeader->PixelHeight in h. The BitmapData equivalent data will be an allocated byte array with the pixels, each pixel requiring f bytes (bytes per pixel, 3 for rgb images and 4 for rgba images). Most likely you'll only get f = 4, rgba images, which means data[0] is the red component of the first pixel, data[1] is the green, data[2] is the blue, and data[3] is the alpha (transparency). Then data[4] is the red component of the second pixel etc. You don't have to care about palettes and such, stb_image will convert everything for you into true-color pixels. You can use this data array easily then, no need to flip the image horizontally or mess with formats directly. Plus PNG requires far less storage space.

Wrapping up AllocPool goes something like this:

Code: Select all

void *malloc (size_t __size)
{
    void *ret;
    return !EFI_ERROR(BS->AllocatePool(EfiLoaderData, __size, &ret)) ? ret : NULL;
}

void free (void *__ptr)
{
    return BS->FreePool(__ptr);
}
And a little advertisement, if you don't want to mess with file handling and memory allocation, give a try to POSIX-UEFI. The stb_image.h works with it just as-is, and you can load files with the usual fopen() calls.

Cheers,
bzt

Re: EFI & BMP - Shell error when compiling with vs2019

Posted: Mon Feb 08, 2021 11:53 am
by gomidas
bzt wrote:I would recommend to forget about BMP altogether. Instead, wrap up UEFI's AllocPool and FreePool calls in malloc() and free(), and use stb_image.h.
Thank you for your help. I downloaded the stb-master file then added it to the project inlcude directory from C/C++ properties. Then I add header <stb_image.h>. I just got error when I define stbi__context and stbi__result_info.

Code: Select all

stbi__context s; //I see add <stb_image.h> at suggestions but I get existing error at visual studio.
stbi__result_info ri;
Evil says, did you think it will be really that easy hehe:D

Re: EFI & BMP - Shell error when compiling with vs2019

Posted: Mon Feb 08, 2021 7:57 pm
by gomidas
Whatever, I continued my reading attempts after re-checking the documentation I realized that DIB header is not optional and also that vary. However, that has a 32 bit size identifier just after imgOffset(address). I checked my file and yes that use 12 bytes as identifier. I got that I was going to need need a second header and at this time I had to start reading from a specific position of file. I just found SetPosition(...) for EFI which used as:

Code: Select all

BMP_file->SetPosition(BMP_file, 0); /*0 OUR STARTING POS TO READ*/
So, I changed my first read buffer size to sizeof(BMP_FILE_HEADER); This is the 14 byte standard header but I also store 32bit DIB_HEADER_SIZE_INFO here.

After reading it I check size of DIB if 12 I make a second read at this time I start from 18 and Buffer Size is sizeof(OS21XBITMAPHEADER);


HERE IS MY CODE:

Code: Select all

#pragma once
#include "efi_reference.h"
#include "gFile.h"

typedef struct {
    INT16 IMG_W;
    INT16 IMG_H;
    INT16 NUM_COLOR_PLANES;
    INT16 NUM_BITS_PER_PIXEL;
} __attribute__((__packed__)) OS21XBITMAPHEADER;

typedef VOID(*AccessFileInfo)(OS21XBITMAPHEADER* OS21XBITMAPHEADER_Info);

typedef struct {
    CHAR8 charB;
    CHAR8 charM;
    INT8 FileSize[4];
    CHAR8 reserved1[2];
    CHAR8 reserved2[2];
    INT8 ImageOffset[4];
    INT8 DIB_HEADER_SIZE[4];

} __attribute__((__packed__)) BMP_FILE_HEADER;

typedef VOID(*AccessFileInfo)(BMP_FILE_HEADER* BMP_FileHeaderInfo);


VOID ListOS21XBITMAPHEADERINFO(OS21XBITMAPHEADER* OS21XBITMAPHEADER_Info)
{

    Print(L"IMAGE WIDTH: %d, HEIGHT: %d", OS21XBITMAPHEADER_Info->IMG_W, OS21XBITMAPHEADER_Info->IMG_H);

    Print(L"\n");

    Print(L"NUM BITS PER PIXEL: %d", OS21XBITMAPHEADER_Info->NUM_BITS_PER_PIXEL);
}

VOID ListBMPFileInfo(BMP_FILE_HEADER* BMP_FileHeaderInfo)
{
    Print(L"BMP IDENTIFIER: %c%c\n", BMP_FileHeaderInfo->charB, BMP_FileHeaderInfo->charM);

    Print(L"FILE SIZE: %d,%d,%d,%d", (INT32)BMP_FileHeaderInfo->FileSize[3], (INT32)BMP_FileHeaderInfo->FileSize[2], (INT32)BMP_FileHeaderInfo->FileSize[1], (INT32)BMP_FileHeaderInfo->FileSize[0]);
    
    Print(L"\n");

    Print(L"ImageOffset: %d,%d,%d,%d", (INT32)BMP_FileHeaderInfo->ImageOffset[3], (INT32)BMP_FileHeaderInfo->ImageOffset[2], (INT32)BMP_FileHeaderInfo->ImageOffset[1], (INT32)BMP_FileHeaderInfo->ImageOffset[0]);

    Print(L"\n");

    Print(L"DIB HEADER SIZE: %d,%d,%d,%d", (INT32)BMP_FileHeaderInfo->DIB_HEADER_SIZE[3], (INT32)BMP_FileHeaderInfo->DIB_HEADER_SIZE[2], (INT32)BMP_FileHeaderInfo->DIB_HEADER_SIZE[1], (INT32)BMP_FileHeaderInfo->DIB_HEADER_SIZE[0]);
    
    Print(L"\n");


}

EFI_STATUS gOpenBMPFile(EFI_FILE_PROTOCOL *Directory, char FileName[], AccessFileInfo callbk)
{
    EFI_STATUS           Status = 0;

    INTN                 BufferSize         = 0;
    BMP_FILE_HEADER*     BMP_FileHeaderInfo = 0;

    //FOR IF OS21X
    OS21XBITMAPHEADER*   BMP_OS21XBITMAP_DIPHEADER_INFO = 0;
    AccessFileInfo       callbkOS21XBITMAP              = ListOS21XBITMAPHEADERINFO;
    /*OTHER HEADERS MAY COME HERE*/

    EFI_FILE_PROTOCOL*   BMP_file = 0;

    Status = Directory->Open(Directory, &BMP_file, (CHAR16*)FileName, EFI_FILE_MODE_READ, 0);

    if (EFI_ERROR(Status)) { Print(L"%s", L"ERROR1"); return Status; }

    UINTN FileSizeInBytes = gReadFileSize(EfiDirectory, L"zani_logo.bmp", ListFileInfo);
    BufferSize = sizeof(BMP_FILE_HEADER);

    Status = gBS->AllocatePool(EfiBootServicesCode, BufferSize, (VOID**)&BMP_FileHeaderInfo);

    Status = BMP_file->Read(BMP_file, &BufferSize, BMP_FileHeaderInfo);
    if (EFI_ERROR(Status))
    {
        Print(L"%s", L"ERROR2");
    }
    else
    {
        Print(L"\nOPENED FILE:%s\n", FileName);
        callbk(BMP_FileHeaderInfo);
    }

    if (EFI_ERROR(Status))
    {
        Print(L"%s", L"CANNOT CLOSE FILE!");
    }

    if (BMP_FileHeaderInfo->DIB_HEADER_SIZE[0] == (INT8)12 /*OS21XBITMAPHEADER SIZE 4BYTE HEX VALUE's 0th pos*/) {
        /*14 BYTES FIXED STANDARD HEADER + 4 FIXED BYTES FOR DIB SIZE*/
        BMP_file->SetPosition(BMP_file, 18);

        Status = gBS->AllocatePool(EfiBootServicesCode, BufferSize, (VOID**)&BMP_OS21XBITMAP_DIPHEADER_INFO);
        BufferSize = sizeof(OS21XBITMAPHEADER);
        Status = BMP_file->Read(BMP_file, &BufferSize, BMP_OS21XBITMAP_DIPHEADER_INFO);
        if (!EFI_ERROR(Status))
        {
            Print(L"\nDIP HEADER:%s\n", FileName);
            callbkOS21XBITMAP(BMP_OS21XBITMAP_DIPHEADER_INFO);
        }

    }

    Status = gBS->FreePool(BMP_FileHeaderInfo);             if (EFI_ERROR(Status)){Print(L"%s", L"CANNOT FREE POOL!");}
    Status = gBS->FreePool(BMP_OS21XBITMAP_DIPHEADER_INFO); if (EFI_ERROR(Status)){Print(L"%s", L"CANNOT FREE POOL!");}
    Status = BMP_file->Close(BMP_file);

    if (EFI_ERROR(Status))
    {
        Print(L"%s", L"CANNOT CLOSE FILE!");
    }

    return Status;
}



RESULT:
Image

Re: EFI & BMP - Shell error when compiling with vs2019

Posted: Tue Feb 09, 2021 5:05 am
by bzt
gomidas wrote:Then I add header <stb_image.h>. I just got error when I define stbi__context and stbi__result_info.
Then you surely haven't included the file. First result of searching for "stb_image Visual Studio".
gomidas wrote:Evil says, did you think it will be really that easy hehe:D
It is extremely easy to use. I'm not entirely sure what you're doing, but have you read the comment at the top of that file?

In exactly one file, where you deal with image decoding, you add some defines (the desired configuration) and an include. If you don't want Photoshop, Targa or X11 pixmap support and you don't have POSIX stdio (because under UEFI you don't have that) for example, then you do:

Code: Select all

#define STB_IMAGE_IMPLEMENTATION
#define STBI_NO_PSD
#define STBI_NO_TGA
#define STBI_NO_PNM
#define STBI_NO_STDIO
#include <stb_image.h>
That simple. And it supports OS/2 BMPs too out-of-the-box, just sayin'.

Cheers,
bzt

Re: EFI & BMP - Shell error when compiling with vs2019

Posted: Wed Feb 10, 2021 11:31 pm
by gomidas
Well well well... I put some time and finished bmp reader here is the result of it:
https://www.youtube.com/watch?v=cQd_JGi ... e=youtu.be (Rendering starts @0:28)

If you have any other comments let me know.