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のキャプチャ画像を表示させてみるとこんな感じ。



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

0 件のコメント:

コメントを投稿