SyntaxHighlighter

2013年5月13日月曜日

[EDK]BMPデコーダを作成

前回投稿記事のBMPデコード部分があまりにもアレだったので、ここを参考にBMPデコーダを作成しました。

ソース

OS/2形式、Windows形式の1, 4, 8, 24, 32bit BMPに対応するように作ってみました。
あまりお目にかかる機会のないであろう、ランレベル圧縮やビットフィールドについては対応せず。


大したことやってないですが、以下概要。

Bitmapファイルの各種情報を解析、保持するために、構造体を定義。
#define  MAX_PALETTES  256

#pragma pack(1)
typedef struct {
  CHAR8   Type[2];
  UINT32  FileSize;
  UINT32  Reserved;
  UINT32  Offset;
} BITMAP_FILE_HEADER;

typedef struct {
  UINT32  HeaderSize;
  INT16   Width;
  INT16   Height;
  UINT16  Planes;
  UINT16  BitCount;
} BITMAP_CORE_HEADER;

typedef struct {
  UINT32  HeaderSize;
  INT32   Width;
  INT32   Height;
  UINT16  Planes;
  UINT16  BitCount;
  UINT32  Compression;
  UINT32  ImageSize;
  INT32   XPixPerMeter;
  INT32   YPixPerMeter;
  UINT32  ClrUsed;
  UINT32  ClrImportant;
} BITMAP_INFO_HEADER;

typedef struct {
  UINT8  Red;
  UINT8  Green;
  UINT8  Blue;
} BITMAP_COLOR_PALETTE;

typedef struct {
  VOID                  *FileData;
  VOID                  *InfoData;
  VOID                  *PaletteData;
  VOID                  *ImageData;
  UINTN                 FileSize;
  BOOLEAN               IsOs2;
  UINTN                 Width;
  UINTN                 Height;
  BOOLEAN               Invert;
  UINTN                 BitCount;
  UINTN                 Compression;
  UINTN                 UsedPalette;
  BITMAP_COLOR_PALETTE  Palette[MAX_PALETTES];
} BITMAP_INFORMATION;
#pragma pack()

Bitmapファイルのヘッダを読んで、BITMAP_INFORMATION構造体へ情報を入れておきます。
OS/2形式とWindows形式ではヘッダの大きさが異なるので、Information HeaderのSize情報を使用して場合分け。
STATIC
EFI_STATUS
ParseFileHeader (
  IN OUT BITMAP_INFORMATION  *BitmapInfo
)
{
  BITMAP_FILE_HEADER  *FileHeader;

  if (BitmapInfo->FileSize < sizeof (BITMAP_FILE_HEADER)) {
    return EFI_BAD_BUFFER_SIZE;
  }
  FileHeader = (BITMAP_FILE_HEADER*) BitmapInfo->FileData;
  if (!(FileHeader->Type[0] == 'B' && FileHeader->Type[1] == 'M')) {
    return EFI_UNSUPPORTED;
  }

  BitmapInfo->FileSize = MIN (BitmapInfo->FileSize, FileHeader->FileSize);
  BitmapInfo->InfoData  = FileHeader + 1;
  BitmapInfo->ImageData = (UINT8*) BitmapInfo->FileData + FileHeader->Offset;

  return EFI_SUCCESS;
}


STATIC
EFI_STATUS
ParseInformationHeader (
  IN OUT BITMAP_INFORMATION  *BitmapInfo
)
{
  BITMAP_CORE_HEADER  *CoreHeader;
  BITMAP_INFO_HEADER  *InfoHeader;

  CoreHeader = (BITMAP_CORE_HEADER*) BitmapInfo->InfoData;
  if (BitmapInfo->FileSize < ((UINTN) BitmapInfo->ImageData - (UINTN) BitmapInfo->FileData)) {
    return EFI_BAD_BUFFER_SIZE;
  }

  if (CoreHeader->HeaderSize == sizeof (BITMAP_CORE_HEADER)) {
    BitmapInfo->IsOs2 = TRUE;
    BitmapInfo->Width = CoreHeader->Width;
    if (CoreHeader->Height >= 0) {
      BitmapInfo->Height = CoreHeader->Height;
      BitmapInfo->Invert = FALSE;
    } else {
      BitmapInfo->Height = CoreHeader->Height * (-1);
      BitmapInfo->Invert = TRUE;
    }
    BitmapInfo->BitCount = CoreHeader->BitCount;
    BitmapInfo->Compression = 0;
    if (BitmapInfo->BitCount > 8) {
      BitmapInfo->UsedPalette = 0;
    } else {
      BitmapInfo->UsedPalette = 1 << BitmapInfo->BitCount;
    }

    BitmapInfo->PaletteData = CoreHeader + 1;
  } else if (CoreHeader->HeaderSize == sizeof (BITMAP_INFO_HEADER)) {
    BitmapInfo->IsOs2 = FALSE;
    InfoHeader = (BITMAP_INFO_HEADER*) BitmapInfo->InfoData;
    BitmapInfo->Width    = InfoHeader->Width;
    if (InfoHeader->Height >= 0) {
      BitmapInfo->Height   = InfoHeader->Height;
      BitmapInfo->Invert   = FALSE;
    } else {
      BitmapInfo->Height   = InfoHeader->Height * (-1);
      BitmapInfo->Invert   = TRUE;
    }
    BitmapInfo->BitCount = InfoHeader->BitCount;
    BitmapInfo->Compression = InfoHeader->Compression;
    if (BitmapInfo->BitCount > 8) {
      BitmapInfo->UsedPalette = 0;
    } else {
      if (InfoHeader->ClrUsed == 0) {
        BitmapInfo->UsedPalette = 1 << BitmapInfo->BitCount;
      } else {
        BitmapInfo->UsedPalette = InfoHeader->ClrUsed;
      }
    }

    BitmapInfo->PaletteData = InfoHeader + 1;
  } else {
    return EFI_UNSUPPORTED;
  }

  return EFI_SUCCESS;
}

1, 4, 8bitカラーBitmapの場合、カラーパレットを利用するので、パレット情報を保持。
ここでも、OS/2形式とWindows形式でパレットの使い方が違う(パレットサイズがOS/2は24bit、Windowsは32bit)ので場合分けを挟んでいます。
STATIC
EFI_STATUS
GetPalette (
  IN OUT BITMAP_INFORMATION  *BitmapInfo
)
{
  UINTN  Index;
  struct {
    UINT8  Blue;
    UINT8  Green;
    UINT8  Red;
  } *PaletteDataOs2 = BitmapInfo->PaletteData;
  struct {
    UINT8  Blue;
    UINT8  Green;
    UINT8  Red;
    UINT8  Reserved;
  } *PaletteDataWin = BitmapInfo->PaletteData;

  if (BitmapInfo->IsOs2) {
    for (Index = 0; Index < BitmapInfo->UsedPalette; Index++, PaletteDataOs2++) {
      BitmapInfo->Palette[Index].Red   = PaletteDataOs2->Red;
      BitmapInfo->Palette[Index].Green = PaletteDataOs2->Green;
      BitmapInfo->Palette[Index].Blue  = PaletteDataOs2->Blue;
    }
  } else {
    for (Index = 0; Index < BitmapInfo->UsedPalette; Index++, PaletteDataWin++) {
      BitmapInfo->Palette[Index].Red   = PaletteDataWin->Red;
      BitmapInfo->Palette[Index].Green = PaletteDataWin->Green;
      BitmapInfo->Palette[Index].Blue  = PaletteDataWin->Blue;
    }
  }

  return EFI_SUCCESS;
}

最後に、ピクセルの情報をEFI_GRAPHICS_OUTPUT_BLT_PIXELに変換しました。
Bitmapファイルは通常、左下から右上に向かってデータが格納されているため、Y軸をデクリメントさせて処理しています。
また、各行のサイズはDWORDアラインされている必要が有るため、途中アライメント処理を挟みました。
STATIC
EFI_STATUS
FillBltBufferWithPalette (
  IN     BITMAP_INFORMATION            *BitmapInfo,
  IN OUT EFI_GRAPHICS_OUTPUT_BLT_PIXEL *BltBuffer
)
{
  UINT8  *BitmapData;
  UINTN  XIndex;
  UINTN  YIndex;
  UINTN  PixPerByte;
  UINTN  Pos;
  UINTN  BitPos;
  UINTN  BltPos;
  UINT8  PaletteIndex;

  PixPerByte = 8 / BitmapInfo->BitCount;

  BitmapData = BitmapInfo->ImageData;

  for (YIndex = BitmapInfo->Height; YIndex > 0; YIndex--) {
    for (XIndex = 0; XIndex < BitmapInfo->Width;) {
      Pos    = (YIndex - 1) * ALIGN_VALUE (BitmapInfo->Width / PixPerByte, 4) + (XIndex / PixPerByte);
      for (BitPos = 0; BitPos < 8; BitPos += BitmapInfo->BitCount, XIndex++) {
        if (BitmapInfo->Invert) {
          BltPos = (YIndex - 1) * BitmapInfo->Width + XIndex;
        } else {
          BltPos = (BitmapInfo->Height - YIndex) * BitmapInfo->Width + XIndex;
        }
        PaletteIndex = BitFieldRead8(BitmapData[Pos], (7 - BitPos) - (BitmapInfo->BitCount - 1), (7 - BitPos));

        BltBuffer[BltPos].Red      = BitmapInfo->Palette[PaletteIndex].Red;
        BltBuffer[BltPos].Green    = BitmapInfo->Palette[PaletteIndex].Green;
        BltBuffer[BltPos].Blue     = BitmapInfo->Palette[PaletteIndex].Blue;
        BltBuffer[BltPos].Reserved = 0;
      }
    }
  }

  return EFI_SUCCESS;
}


STATIC
EFI_STATUS
FillBltBufferWithoutPalette (
  IN     BITMAP_INFORMATION            *BitmapInfo,
  IN OUT EFI_GRAPHICS_OUTPUT_BLT_PIXEL *BltBuffer
)
{
  UINT8  *BitmapData;
  UINTN  XIndex;
  UINTN  YIndex;
  UINTN  BytePerPix;
  UINTN  Pos;
  UINTN  BltPos;

  BitmapData = BitmapInfo->ImageData;
  BytePerPix = BitmapInfo->BitCount / 8;

  for (YIndex = BitmapInfo->Height; YIndex > 0; YIndex--) {
    for (XIndex = 0; XIndex < BitmapInfo->Width; XIndex++) {
      Pos    = (YIndex - 1) * ALIGN_VALUE (BitmapInfo->Width * BytePerPix, 4) + XIndex * BytePerPix;
      if (BitmapInfo->Invert) {
        BltPos = (YIndex - 1) * BitmapInfo->Width + XIndex;
      } else {
        BltPos = (BitmapInfo->Height - YIndex) * BitmapInfo->Width + XIndex;
      }

      BltBuffer[BltPos].Red      = BitmapData[Pos + 2];
      BltBuffer[BltPos].Green    = BitmapData[Pos + 1];
      BltBuffer[BltPos].Blue     = BitmapData[Pos + 0];
      BltBuffer[BltPos].Reserved = 0;
    }
  }

  return EFI_SUCCESS;
}


試しにNt32Pkg上でEclipseのキャプチャ画像を表示させてみるとこんな感じ。



以上、かなりざっくりと作ったので読みにくい箇所はあると思います。
ざっくり作っても動くものが作れる程度に単純な仕様で素晴らしい、と捉えるべきでしょうか。

2013年5月3日金曜日

[EDK]GraphicsOutputProtocolでBMPを表示させて遊んでみた

UEFIでなにかやってみたかったので、Graphics Output Protocolで画面にBMP画像を表示させてみました。
悲しいことにVMWarePlayerではGraphics Output Protocolに実装不足があったのでNt32Pkgを使って試してます。

使った画像は/MdeModulePkg/Logo/Logo.bmp。
何か表示出来ればいいという程度で作ったのでBMPの処理関係はいろいろ決め打ちです。

infファイル


## @file
#  This is the shell application
#
#  Copyright (c) 2009 - 2010, Intel Corporation. All rights reserved.

#
#  This program and the accompanying materials
#  are licensed and made available under the terms and conditions of the BSD License
#  which accompanies this distribution. The full text of the license may be found at
#  http://opensource.org/licenses/bsd-license.php
#  THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
#  WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
#
#
##

[Defines]
  INF_VERSION                    = 0x00010006
  BASE_NAME                      = TestApp
  FILE_GUID                      = 305671d4-c671-4792-8ada-e424a87f4632
  MODULE_TYPE                    = UEFI_APPLICATION
  VERSION_STRING                 = 1.0
  ENTRY_POINT                    = UefiMain

#
# The following information is for reference only and not required by the build tools.
#
#  VALID_ARCHITECTURES           = IA32 X64 IPF EBC
#

[Sources]
  Main.c

[Packages]
  MdePkg/MdePkg.dec
  ShellPkg/ShellPkg.dec

[LibraryClasses]
  UefiApplicationEntryPoint
  UefiLib
  
[Guids]
  gEfiFileInfoGuid
  
[Protocols]
  gEfiGraphicsOutputProtocolGuid
  gEfiSimpleFileSystemProtocolGuid

と、cソースファイル

#include <Uefi.h>
#include <Library/BaseLib.h>
#include <Library/UefiApplicationEntryPoint.h>
#include <Library/UefiLib.h>
#include <Library/DebugLib.h>
#include <Library/MemoryAllocationLib.h>

#include <Guid/FileInfo.h>

#include <Protocol/DevicePath.h>
#include <Protocol/GraphicsOutput.h>
#include <Protocol/SimpleFileSystem.h>

#define  MAX_BUFFER_SIZE  SIZE_1MB

EFI_SYSTEM_TABLE   *gST;
EFI_BOOT_SERVICES  *gBS;


#pragma pack(1)
typedef struct {
  CHAR8   Type[2];
  UINT32  Size;
  UINT32  Reserved;
  UINT32  Offset;
  UINT32  CoreHeaderSize;
  UINT32  Width;
  UINT32  Height;
  UINT16  Planes;
  UINT16  BitCount;
} BITMAP_FILE_HEADER;
#pragma pack()

STATIC
EFI_STATUS
LoadBitmapFile (
  IN     CHAR16  *Path,
     OUT VOID    **BmpBuffer,
     OUT UINTN   *BmpSize
  );

STATIC
EFI_STATUS
DrawBmp (
  IN     EFI_GRAPHICS_OUTPUT_PROTOCOL  *GraphicsOutput,
  IN     VOID                          *BmpBuffer,
  IN     UINTN                         BmpSize
  );


EFI_STATUS
EFIAPI
UefiMain (
  IN     EFI_HANDLE        ImageHandle,
  IN     EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_STATUS                    Status = EFI_SUCCESS;
  EFI_GRAPHICS_OUTPUT_PROTOCOL  *GraphicsOutput;
  VOID                          *BmpBuffer = NULL;
  UINTN                         BmpSize;

  gST = SystemTable;
  gBS = gST->BootServices;

  gBS->LocateProtocol (
         &gEfiGraphicsOutputProtocolGuid,
         NULL,
         &GraphicsOutput
       );

  Status = LoadBitmapFile (L"Logo.bmp", &BmpBuffer, &BmpSize);
  if (EFI_ERROR (Status)) {
    if (BmpBuffer != NULL) {
      FreePool (BmpBuffer);
      return Status;
    }
  }

  Status = DrawBmp (GraphicsOutput, BmpBuffer, BmpSize);

  if (BmpBuffer != NULL) {
    FreePool (BmpBuffer);
  }

  return EFI_SUCCESS;
}

STATIC
EFI_STATUS
LoadBitmapFile (
  IN     CHAR16  *Path,
     OUT VOID    **BmpBuffer,
     OUT UINTN   *BmpSize
)
{
  EFI_STATUS                       Status = EFI_SUCCESS;
  EFI_SIMPLE_FILE_SYSTEM_PROTOCOL  *SimpleFile;
  EFI_FILE_PROTOCOL                *Root;
  EFI_FILE_PROTOCOL                *File;
  UINTN                            BufferSize;
  VOID                             *Buffer = NULL;

  Status = gBS->LocateProtocol (
                  &gEfiSimpleFileSystemProtocolGuid,
                  NULL,
                  &SimpleFile
                );
  if (EFI_ERROR (Status)) {
    DEBUG ((EFI_D_ERROR, "%r on Locate EFI Simple File System Protocol.\n", Status));
    return Status;
  }

  Status = SimpleFile->OpenVolume (SimpleFile, &Root);
  if (EFI_ERROR (Status)) {
    DEBUG ((EFI_D_ERROR, "%r on Open volume.\n", Status));
    return Status;
  }

  Status = Root->Open (Root, &File, Path, EFI_FILE_MODE_READ, EFI_FILE_READ_ONLY);
  if (EFI_ERROR (Status)) {
    DEBUG ((EFI_D_ERROR, "%r on Open file.\n", Status));
    return Status;
  }

  BufferSize = MAX_BUFFER_SIZE;
  Buffer = AllocatePool (BufferSize);
  if (Buffer == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }
  Status = File->Read (
                   File,
                   &BufferSize,
                   Buffer
                 );
  if (BufferSize == MAX_BUFFER_SIZE) {
    DEBUG ((EFI_D_ERROR, "Buffer Size Too Small.\n"));
    if (Buffer != NULL) {
      FreePool (Buffer);
    }
    return EFI_OUT_OF_RESOURCES;
  }

  Buffer = ReallocatePool (BufferSize, MAX_BUFFER_SIZE, Buffer);

  *BmpBuffer = Buffer;
  *BmpSize   = BufferSize;

  return EFI_SUCCESS;
}


STATIC
EFI_STATUS
DrawBmp (
  IN     EFI_GRAPHICS_OUTPUT_PROTOCOL  *GraphicsOutput,
  IN     VOID                          *BmpBuffer,
  IN     UINTN                         BmpSize
)
{
  EFI_STATUS                     Status = EFI_SUCCESS;
  BITMAP_FILE_HEADER             *BitmapHeader;
  UINT8                          *BitmapData;
  UINT32                         *Palette;
  UINTN                          Pixels;
  UINTN                          XIndex;
  UINTN                          YIndex;
  UINTN                          Pos;
  UINTN                          BltPos;
  EFI_GRAPHICS_OUTPUT_BLT_PIXEL  *BltBuffer;

  BitmapHeader = (BITMAP_FILE_HEADER *) BmpBuffer;

  DEBUG ((EFI_D_ERROR, "%d x %d\n", BitmapHeader->Width, BitmapHeader->Height));

  if (BitmapHeader->CoreHeaderSize != 40) {
    return EFI_UNSUPPORTED;
  }
  if (BitmapHeader->BitCount != 8) {
    return EFI_UNSUPPORTED;
  }

  BitmapData = (UINT8*)BmpBuffer + BitmapHeader->Offset;
  Palette    = (UINT32*) ((UINT8*)BmpBuffer + 0x36);/*決め打ち*/

  Pixels = BitmapHeader->Width * BitmapHeader->Height;
  BltBuffer = AllocateZeroPool (
                sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL) * Pixels
              );
  if (BltBuffer == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  for (YIndex = BitmapHeader->Height; YIndex > 0; YIndex--) {
    for (XIndex = 0; XIndex < BitmapHeader->Width; XIndex++) {
      Pos    = (YIndex - 1) * ((BitmapHeader->Width + 3) / 4) * 4 + XIndex;
      BltPos = (BitmapHeader->Height - YIndex) * BitmapHeader->Width + 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 = GraphicsOutput->Blt (
                    GraphicsOutput,
                    BltBuffer,
                    EfiBltBufferToVideo,
                    0                  , 0                   , /*Source X, Y*/
                    200                , 200                 , /*Dest X, Y*/
                    BitmapHeader->Width, BitmapHeader->Height, /*Width, Height*/
                    0
                  );

  FreePool (BltBuffer);

  return EFI_SUCCESS;
}


実行結果はこんな感じです。


地味ではありますが、画像ファイルが表示されました。

今回はBitmapファイルのフォーマットに触れることが出来、色々と悩まされつつも楽しかったです。
標準でBMPファイルを扱えるプロトコルがあればよかったのですが、軽く調べた中では見つからず、結果として普段触らない領域に触れることが出来ました。


参考:
UEFI仕様
Bitmapフォーマット情報