LHA で圧縮された BMP 画像を表示



LHA で圧縮された BMP 画像ファイルを解凍して表示します。
吉崎栄泰氏が開発された unlha32 をダウンロードして使用します。

圧縮されたファイルを解凍して表示するには BMP イメージを自分で解析しなければなりません。
これを機会に BMP ファイルの構造を理解して下さい。
詳細は BitMap(BMP)画像ファイル形式の説明書 を参照して下さい。

前田稔(Maeda Minoru)の超初心者のプログラム入門

アプリケーションの作成

  1. [ファイル] [新規作成] [プロジェクト] から新規プロジェクト(LzhBmp)を作成します。
    unlha32 をダウンロードて次のファイルをプロジェクトに格納して下さい。
    unlhavc.lib コンパイルのとき使用する LIB ファイルです
    unlha32.h コンパイルのとき使用する Lib Header ファイルです
    unlha32.dll 実行時に呼び出される DLL ファイルです
  2. ayu.bmp を LHA で圧縮して ayu.lzh を作成します。
    ファイル名が合っていれば画像は何でもかまいませんが、1MB の領域しか確保していないので超えないようにして下さい。
  3. [プロジェクト] [プロパティ] [リンカ] [入力] から unlhavc.lib をリンクします。
    #pragma を使ってリンクすることもできます。
  4. StdAfx.h に次のヘッダーファイルを取り込んで下さい。
        // TODO: プログラムで必要なヘッダー参照を追加してください。
        #include <time.h>
        #include "unlha32.h"
        
  5. テンプレートで作成された LzhBmp.cpp のソースコードを修正します。
    解凍するバッファ領域とイメージ領域は 1MB を確保しています。
        struct      //BMP ファイル構造体
        {   BITMAPFILEHEADER    g_Bf;
            BITMAPINFOHEADER    g_Bi;
            char                g_Img[1000000]; //パレット&イメージ
        }   g_Bmp;
        LPBITMAPINFO            g_PBinfo = (LPBITMAPINFO)&g_Bmp.g_Bi;
        ULONG                   g_PltSize;      //パレットのサイズ
    
        struct      //パレット設定構造体
        {   WORD                palVersion;
            WORD                palNumEntries;
            PALETTEENTRY        palPalEntry[256];
        }   g_sPal;
        LPLOGPALETTE            g_Pal = (LPLOGPALETTE)&g_sPal;
    
        char                    BmpName[128]= "ayu.bmp";    //BMP File Name
        BOOL                    bLoad = FALSE;              //BMP Load フラグ
        HPALETTE                hPalette;                   //パレットハンドル
        BYTE                    buf[1000000];               //解凍領域
    
        int                 ReadDIB(HWND hWnd);
        int                 ReadLHA(HWND hWnd);
        HPALETTE            SetPalette(HWND hWnd);
        
  6. LzhBmp.cpp の修正の続きです。
    WM_CREATE: から LHA を解凍して BMP イメージを設定する ReadLHA() 関数を呼びます。
    ReadDIB() は BmpName[128]= "ayu.bmp" から直接イメージデータを転送するテスト用の関数です。
    WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
    {               :
                    :
        switch( message ) 
        {
            case WM_CREATE:
                //ReadDIB(hWnd);
                ReadLHA(hWnd);
                break;
    
  7. WM_PAINT: から BMP イメージを描画します。
    g_Bmp 構造体に設定したイメージを SetDIBitsToDevice() で描画します。
            case WM_PAINT:
                hdc = BeginPaint (hWnd, &ps);
                // TODO: この位置に描画用のコードを追加してください...
                if (bLoad)
                {   if (g_Bmp.g_Bi.biClrUsed)
                    {   SelectPalette(hdc, hPalette, FALSE);
                        RealizePalette(hdc);
                    }
                    SetDIBitsToDevice(hdc,
                        0, 0,                   //転送先座標
                        g_Bmp.g_Bi.biWidth, g_Bmp.g_Bi.biHeight, //幅、高さ
                        0, 0,                   //転送元座標
                        0, g_Bmp.g_Bi.biHeight, //走査開始番号、走査線の本数
                        //ビットマップデータ開始のアドレス
                        (char *)(g_Bmp.g_Img+g_PltSize),
                        g_PBinfo,               //BITMAPINFO構造体へのポインタ
                        DIB_RGB_COLORS);
                }
                EndPaint( hWnd, &ps );
                break;
    
  8. LzhBmp.cpp の最後(または先頭)に ReadLHA() と ReadDIB() と SetPalette() を追加します。
    ReadLHA() は WM_CREATE: から呼ばれる ayu.lzh を解凍する関数です。
    buf に解凍された BMP ファイルイメージの BITMAPFILEHEADER を g_Bmp.g_Bf に転送します。
    BMP ヘッダを確認して BITMAPINFOHEADER とピクセルイメージを g_Bmp.g_Bi(g_Img[]) に転送します。
    パレットを持っているときはパレットを設定して描画の準備が整います。
    //★ LHA ファイルを解凍してメモリに格納
    int ReadLHA(HWND hWnd)
    {   DWORD       dLen;
        char        szFType[3];
    
        bLoad = TRUE;
        UnlhaExtractMem(hWnd,"ayu.lzh c: ayu.bmp",buf,1000000,NULL,NULL,&dLen);
        MoveMemory(&g_Bmp.g_Bf,buf,sizeof(BITMAPFILEHEADER));
        szFType[0] = LOBYTE(g_Bmp.g_Bf.bfType);
        szFType[1] = HIBYTE(g_Bmp.g_Bf.bfType);
        szFType[2] = '\0';
        if (strcmp(szFType, "BM") != 0)
        {   MessageBox(hWnd, "ビットマップではありません", "Error", MB_OK);
            return -1;
        }
        MoveMemory(&g_Bmp.g_Bi,buf+sizeof(BITMAPFILEHEADER),1000000);
        g_PltSize = g_Bmp.g_Bf.bfOffBits - sizeof(BITMAPFILEHEADER)
                                         - sizeof(BITMAPINFOHEADER);
        if (g_Bmp.g_Bi.biClrUsed != 0 || g_Bmp.g_Bi.biBitCount != 24)
            hPalette = SetPalette(hWnd);
        return 0;
    }
    
    //★ BMP ファイルを直接入力してメモリに格納
    int ReadDIB(HWND hWnd)
    {   DWORD               dwResult;
        HANDLE              hF;
        char                szFType[3];
    
        bLoad = TRUE;
        hF = CreateFile(BmpName, GENERIC_READ, 0, NULL, OPEN_EXISTING,
                        FILE_ATTRIBUTE_NORMAL, NULL);
        if (hF == INVALID_HANDLE_VALUE)
        {   MessageBox(hWnd, "ファイルのオープンに失敗しました", "Error", MB_OK);
            return -1;
        }
        ReadFile(hF, &g_Bmp.g_Bf, sizeof(BITMAPFILEHEADER), &dwResult, NULL);
        szFType[0] = LOBYTE(g_Bmp.g_Bf.bfType);
        szFType[1] = HIBYTE(g_Bmp.g_Bf.bfType);
        szFType[2] = '\0';
        if (strcmp(szFType, "BM") != 0)
        {   MessageBox(hWnd, "ビットマップではありません", "Error", MB_OK);
            CloseHandle(hF);
            return -1;
        }
        ReadFile(hF, &g_Bmp.g_Bi, 1000000, &dwResult, NULL);
        //パレットのサイズ
        g_PltSize = g_Bmp.g_Bf.bfOffBits - sizeof(BITMAPFILEHEADER)
                                         - sizeof(BITMAPINFOHEADER);
        if (g_Bmp.g_Bi.biClrUsed != 0 || g_Bmp.g_Bi.biBitCount != 24)
            hPalette = SetPalette(hWnd);
        CloseHandle(hF);
        return 0;
    }
    
    //★ パレットの設定
    HPALETTE SetPalette(HWND hWnd)
    {   WORD            i;
        DWORD           dwClrUsed;
    
        //パレット構造体の初期化
        g_Pal->palVersion = 0x300;
        g_Pal->palNumEntries = 256;
        for (i = 0; i < 256; i++)           //256 パレットの初期化
        {
            g_Pal->palPalEntry[i].peRed = (BYTE) (((i >> 5) & 0x07) * 255 / 7);
            g_Pal->palPalEntry[i].peGreen = (BYTE) (((i >> 2) & 0x07) * 255 / 7);
            g_Pal->palPalEntry[i].peBlue = (BYTE) (((i >> 0) & 0x03) * 255 / 3);
            g_Pal->palPalEntry[i].peFlags = (BYTE) 0;
        }
        dwClrUsed = g_Bmp.g_Bi.biClrUsed;   //BMP の色数
        if (dwClrUsed>255)  dwClrUsed= 256;
        MoveMemory(g_sPal.palPalEntry,g_Bmp.g_Img,dwClrUsed*4);
        for (i=0; i<dwClrUsed; i++)
        {   BYTE                       r = g_Pal->palPalEntry[i].peRed;
            g_Pal->palPalEntry[i].peRed  = g_Pal->palPalEntry[i].peBlue;
            g_Pal->palPalEntry[i].peBlue = r;
        }
        hPalette = CreatePalette(g_Pal);
        if (hPalette == NULL)	MessageBox(hWnd, "パレット作成失敗", "Error", MB_OK);
        return hPalette;
    }
    
  9. [デバッグ] [デバッグなしで開始] を選択して、ビルド(コンパイル)に続いて実行を行います。
    コンパイルの進行状況とエラーがあれば、エラーメッセージが表示されます。
    BMP 画像が表示されたら完成です。

プログラムの説明

  1. LZH を解凍してメモリに格納するだけなら簡単です。
    UnlhaExtractMem() 関数を呼び出すだけです。
    dLen には LZH を解凍してメモリ展開したサイズが格納されます。
        UnlhaExtractMem(hWnd,"ayu.lzh c: ayu.bmp",buf,1000000,NULL,NULL,&dLen);
        
  2. 問題はメモリに格納した BMP 画像の取り扱い方法です。
    このプログラムでは BMP 画像への理解を深めていただくために、二種類の方法で入力します。
    1. ReadDIB() 関数を使う。
      ReadFile() で BMP ファイル(ayu.bmp) を直接入力して設定します。
    2. ReadLHA() 関数を使う。
      LHA で圧縮されたファイル(ayu.lzh) を解凍して設定します。
    3. WM_CREATE: でどちらかを選択します。
                case WM_CREATE:
                    //ReadDIB(hWnd);
                    ReadLHA(hWnd);
                    break;
              
  3. BMP ファイルの構造は次のようになっています。
    BITMAPFILEHEADER 構造体 BMP ファイルヘッダーです
    BITMAPINFOHEADER 構造体 BMP 画像情報のヘッダーです
    RGBQUARD 構造体 カラーテーブルです
    ビット配列 ビット形式の画像イメージです
  4. BITMAPFILEHEADER 構造体です。
        typedef struct tagBITMAPFILEHEADER
        {
            WORD    bfType;         //"BM"の2バイト
            DWORD   bfSize;         //ファイルのサイズ
            WORD    bfReserved1;
            WORD    bfReserved2;
            DWORD   bfOffBits;      //ファイルの先頭からビット配列までのオフセット値
        } BITMAPFILEHEADER; 
        
  5. BITMAPINFOHEADER 構造体です。
        typedef struct tagBITMAPINFOHEADER
        {
            DWORD  biSize;          //構造体の大きさが入ります
            LONG   biWidth;         //ビットマップの幅
            LONG   biHeight;        //ビットマップの高さ
            WORD   biPlanes;        //プレーンの数ですが1でなくてはいけません
            WORD   biBitCount;      //ピクセルあたりの色数。1,4,8,24のいずれか
            DWORD  biCompression;   //圧縮方式を示します。0で圧縮なし
            DWORD  biSizeImage;     //ビットマップビットのサイズ。圧縮の時のみ必要
            LONG   biXPelsPerMeter; //水平解像度を示します。ピクセル数/インチ
            LONG   biYPelsPerMeter; //垂直解像度を示します
            DWORD  biClrUsed;       //イメージで使われている色数
            DWORD  biClrImportant;  //イメージで使われている重要な色の数
        } BITMAPINFOHEADER;
        
  6. RGBQUARD 構造体です。
        typedef struct tagRGBQUAD   //カラーテーブル
        {
            BYTE    rgbBlue;
            BYTE    rgbGreen; 
            BYTE    rgbRed;
            BYTE    rgbReserved;
        } RGBQUAD; 
        
  7. ReadDIB() 関数と ReadLHA() 関数の処理は基本的に同じなので、ReadLHA() 関数を例にして説明します。
    解凍した BMP ファイル情報の先頭から sizeof(BITMAPFILEHEADER) バイトを g_Bmp.g_Bf(BITMAPFILEHEADER 構造体) に 格納します。
    bfType に "BM" が格納されていることを確認します。
        UnlhaExtractMem(hWnd,"ayu.lzh c: ayu.bmp",buf,1000000,NULL,NULL,&dLen);
        MoveMemory(&g_Bmp.g_Bf,buf,sizeof(BITMAPFILEHEADER));
        szFType[0] = LOBYTE(g_Bmp.g_Bf.bfType);
        szFType[1] = HIBYTE(g_Bmp.g_Bf.bfType);
        szFType[2] = '\0';
        if (strcmp(szFType, "BM") != 0)
        {   MessageBox(hWnd, "ビットマップではありません", "Error", MB_OK);
            return -1;
        }
        
  8. 次のアドレスから BITMAPINFOHEADER とパレットとイメージに格納します。
    BITMAPFILEHEADER と BITMAPINFOHEADER の間がパディングされているので、一度にまとめてコピーすることはできません。
    私は過去にこのことが原因で大変苦労した苦い経験があります。 (^_^;
        MoveMemory(&g_Bmp.g_Bi,buf+sizeof(BITMAPFILEHEADER),1000000);
        g_PltSize = g_Bmp.g_Bf.bfOffBits - sizeof(BITMAPFILEHEADER)
                                         - sizeof(BITMAPINFOHEADER);
        
  9. RGBQUARD 構造体(カラーテーブル) がいつも存在するとは限りません。
    カラーテーブルの有無を確認して設定します。
        if (g_Bmp.g_Bi.biClrUsed != 0 || g_Bmp.g_Bi.biBitCount != 24)
            hPalette = SetPalette(hWnd);
        
  10. 画像を表示するソースコードです。
        if (g_Bmp.g_Bi.biClrUsed)
        {   SelectPalette(hdc, hPalette, FALSE);
            RealizePalette(hdc);
        }
        SetDIBitsToDevice(hdc,
            0, 0,                   //転送先座標
            g_Bmp.g_Bi.biWidth, g_Bmp.g_Bi.biHeight, //幅、高さ
            0, 0,                   //転送元座標
            0, g_Bmp.g_Bi.biHeight, //走査開始番号、走査線の本数
            //ビットマップデータ開始のアドレス
            (char *)(g_Bmp.g_Img+g_PltSize),
            g_PBinfo,               //BITMAPINFO構造体へのポインタ
            DIB_RGB_COLORS);
        

超初心者のプログラム入門(Win32API C++)