Jpeg 画像を表示する

SUSIE のプラグイン(DLL) を使って JPEG 画像を表示します。
この使い方が解かると JPEG だけで無く SUSIE のように、ほとんどの全部の画像が表示できるようになります。

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

プロジェクトの設定

  1. DirectX では直接 JPEG 画像を扱えるのですが、通常の Windows の API にはその機能がありません。
    そこで SUSIE のプラグインを使って Jpeg 画像を表示するプログラムを作成します。
  2. Jpeg 画像を表示する SUSIE のプラグイン(Ifjpeg.spi) をダウンロードして下さい。
    検索サイトで調べればすぐ見つかるはずです。
    ifjpeg.spi の拡張子は spi になっていますが、実態は DLL です。
  3. Dyamic にリンクするので、プログラムの作成に必要な DLL 関係のファイルは不要です。
    詳細は DLL を Dyamic にリンクする を参照して下さい。
    プログラムを実行するには、次のファイルが必要です。
    カレントディレクトリまたは、C:\Windows\system32 のフォルダーに格納して下さい。
    Ifjpeg.spi Jpeg 画像をメモリに展開する DLL です
  4. 画像をメモリに展開する関数(GetPicture) の仕様は次のようになっています。
    DLL(spi) の仕様の詳細は、ダウンロードされたファイル(Spi_api.txt) を参照して下さい。
    BitMap(DIB) 形式の説明は BitMap(BMP)画像ファイル形式の説明書 を参照して下さい。
     ・GetPicture - 画像を展開する
        Prototype:
          extern "C" int _export PASCAL GetPicture(
                  LPSTR buf,long len,unsigned int flag,
                  HANDLE *pHBInfo,HANDLE *pHBm,
                  FARPROC lpPrgressCallback,long lData);
    
        Parameter:
          LPSTR buf : 入力がファイルの場合 ファイル名
                            メモリーの場合 ファイルイメージへのポインタ
          long len  : 入力がファイルの場合 読込み開始オフセット(MacBin対応のため)
                            メモリーの場合 データサイズ
          unsigned int flag : 追加情報 xxxx xxxx xxxx xSSS
                      SSS : 入力形式
                          000 : ディスクファイル
                          001 : メモリ上のイメージ
          HLOCAL *pHBInfo : BITMAPINFO 構造体が納められたメモリハンドルが
                                 返される。
          HLOCAL *pHBm    : ビットマップデータ本体のメモリハンドルが返される
          FARPROC lpPrgressCallback :
                    途中経過を表示するコールバック関数へのポインタ。
                    NULLの場合、plug-inは処理が終了するまでプロセスを占有し、
                    中断も出来ません。
                    コールバック関数のprototype:
                      int PASCAL ProgressCallback(
                                            int nNum,int nDenom,long lData);
                      まず nNum==0 でコールされ、nNum==nDenom になるまで
                      定期的に呼ばれる。
                      戻値が 非0 の時、Plug-inは処理を中断する。
          long lData : コールバック関数に渡すlongデータ。
                      ポインタ等を必要に応じて受け渡せる。
    
        Return:
          エラーコード。0なら正常終了。
    
        解説:
          プラグインはLocalAllocによって必要なメモリーを確保し、そのハンドルを
          返す。
          アプリケーションはLocalFreeによってメモリーを開放する必要がある。
    

プログラムの説明

  1. ofn は Jpeg ファイルを選択する OpenFileName 構造体です。
    szFileName[] に Jpeg ファイルの名前が格納されます。
    hBInfo からは展開した Jpeg 画像を描画するための領域が続きます。
        /*********************************************************/
        /*★ DLL(ifjpeg.spi) で JPEG 画像を表示する    前田 稔 ★*/
        /*********************************************************/
        #define NAME        "JPEG Image"
        #define TITLE       "ifjpeg.spi"
        #define EMSG(x)     MessageBox(hWnd,x,"JPEG Image", MB_OK)
    
        #ifndef STRICT
        #define STRICT
        #endif
    
        #include <windows.h>
        #include "resource.h"
    
        //グローバルデータ
        HWND            hWnd;
        OPENFILENAME    ofn;                //ファイルネーム構造体
        char            szFileName[128];    //ファイルネームが格納される
    
        HLOCAL      hBInfo= NULL;   //BITMAPINFO 構造体の LOCATION POINTER
        HLOCAL      hBm= NULL;      //ビットマップデータ本体の LOCATION POINTER
        HPALETTE    hPalette= NULL; //パレット構造体の POINTER
        BITMAPINFO  *pBInfo;        //hBInfo の LocalLock POINTER
        BYTE        *pBm;           //hBm の LocalLock POINTER
        HDC         hBufferDC;      //メモリデバイスコンテキスト
        HBITMAP     hBitmap;        //hBufferDC の BitMap 領域
        BOOL        bLoad = FALSE;  //Jpeg Image Load フラグ
        
  2. ifjpeg.spi(DLL) のエントリーポイントを定義します。
        // SPI ENTRY
        typedef int (PASCAL *GetPluginInfo)(int,LPSTR,int);
        typedef int (PASCAL *IsSupported)(LPSTR,DWORD);
        typedef int (PASCAL *GetPictureInfo)(LPSTR,long,UINT,struct PictureInfo*);
        typedef int (PASCAL *GetPicture)(LPSTR,long,UINT,HANDLE*,HANDLE*,FARPROC,long);
        typedef int (PASCAL *GetPreview)(LPSTR,long,UINT,HANDLE*,HANDLE*,FARPROC,long);
    
        GetPluginInfo   F_GetPluginInfo;
        IsSupported     F_IsSupported;
        GetPictureInfo  F_GetPictureInfo;
        GetPicture      F_GetPicture;
        GetPreview      F_GetPreview;
        
  3. Library のインスタンスと Prototype の宣言です。
        HINSTANCE       hMyLib= NULL;
    
        //プロトタイプ宣言
        LRESULT     CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
        ATOM        InitApp(HINSTANCE);
        BOOL        InitInstance(HINSTANCE, int);
        HRESULT     LoadImage(LPSTR);
        void        GetSpi(HINSTANCE);
        void        MBoxBI(HWND wnd, BITMAPINFO *PBi);
        void        WinSize(HWND hWnd, LONG Width, LONG Height);
        HPALETTE    DIBtoPAL(HANDLE hBInfo);
        
  4. WinMain() と InitApp() と InitInstance() は、関数が分かれていますが処理内容は何時もと同じです。
        //★ Windows Main
        int WINAPI  WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst,
                            LPSTR lpsCmdLine, int nCmdShow)
        {   MSG     msg;
        
            if (!InitApp(hCurInst))                 return FALSE;
            if (!InitInstance(hCurInst, nCmdShow))  return FALSE;
    
            while (GetMessage(&msg, NULL, 0, 0))
            {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
            return msg.wParam;
        }
    
        //★ Windows Initialize
        ATOM InitApp(HINSTANCE hInst)
        {
            WNDCLASSEX wc;
            wc.cbSize = sizeof(WNDCLASSEX);
            wc.style = CS_HREDRAW | CS_VREDRAW;
            wc.lpfnWndProc = WndProc;    //プロシージャ名
            wc.cbClsExtra = 0;
            wc.cbWndExtra = 0;
            wc.hInstance = hInst;        //インスタンス
            wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
            wc.hCursor = LoadCursor(NULL, IDC_ARROW);
            wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
            wc.lpszMenuName = MAKEINTRESOURCE(IDR_MENU);
            wc.lpszClassName = NAME;
            wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
            return (RegisterClassEx(&wc));
        }
    
        //★ Windows の生成
        BOOL InitInstance(HINSTANCE hInst, int nCmdShow)
        {
            hWnd = CreateWindow(NAME,TITLE,
                   WS_OVERLAPPEDWINDOW, //ウィンドウの種類
                   CW_USEDEFAULT,       //X座標
                   CW_USEDEFAULT,       //Y座標
                   CW_USEDEFAULT,       //幅
                   CW_USEDEFAULT,       //高さ
                   NULL,        //親ウィンドウのハンドル、親を作るときはNULL
                   NULL,        //メニューハンドル、クラスメニューを使うときはNULL
                   hInst,       //インスタンスハンドル
                   NULL);
            if (!hWnd)      return FALSE;
            ShowWindow(hWnd, nCmdShow);
            UpdateWindow(hWnd);
            return TRUE;
        }
        
  5. WM_CREATE: で描画するための領域を設定して DLL(ifjpeg.spi) をロードします。
    GetSpi() でロードした DLL に登録されている関数のエントリーポイントを設定します。
        //★ Windows CALLBACK Process
        LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp)
        {
            int             id;
            HDC             hdc;
            PAINTSTRUCT     ps;
    
            switch (msg)
            {   case WM_CREATE:
                    hdc = GetDC(hWnd);
                    hBufferDC = CreateCompatibleDC(hdc);
                    hBitmap = CreateCompatibleBitmap(hdc,GetSystemMetrics(SM_CXFULLSCREEN),
                                                     GetSystemMetrics(SM_CYFULLSCREEN));
                    SelectObject(hBufferDC,hBitmap);
                    ReleaseDC(hWnd,hdc);
                    hMyLib= LoadLibrary("ifjpeg.spi");
                    GetSpi(hMyLib);                 //★DLL(ifjpeg.spi)のEntryを設定
        
  6. ifjpeg.spi のエントリーポイントを設定する GetSpi() です。
        //★ ifjpeg.spi(DLL) とリンクして、ENTRY ADDRESS を設定
        void    GetSpi(HINSTANCE  MyLib)
        {
            if (MyLib)
            {
        OutputDebugString("Load ifjpeg.spi\n");
                F_GetPluginInfo= (GetPluginInfo)GetProcAddress(MyLib,"GetPluginInfo");
                F_IsSupported= (IsSupported)GetProcAddress(MyLib,"IsSupported");
                F_GetPictureInfo=
                    (GetPictureInfo)GetProcAddress(MyLib,"GetPictureInfo");
                F_GetPicture= (GetPicture)GetProcAddress(MyLib,"GetPicture");
                F_GetPreview= (GetPreview)GetProcAddress(MyLib,"GetPreview");
            }
        }
        
  7. IDM_OPEN: は Jpeg 画像を選択するメニューから呼ばれます。
    GetOpenFileName() で JPEG ファイルを選択して LoadImage() で入力します。
                case WM_COMMAND:
                    switch (LOWORD(wp))
                    {   case IDM_END:
                            SendMessage(hWnd, WM_CLOSE, 0, 0);
                            break;
                        case IDM_OPEN:
                            memset(&ofn,0,sizeof(OPENFILENAME));
                            ofn.lStructSize= sizeof(OPENFILENAME);
                            ofn.hwndOwner= hWnd;
                            ofn.lpstrFilter= "Jpeg (*.JPG)\0*.JPG\0\0";
                            ofn.nFilterIndex= 1;
                            ofn.lpstrFile= szFileName;
                            ofn.nMaxFile= 128;
                            ofn.Flags= OFN_HIDEREADONLY;
                            ofn.lpstrInitialDir= "C:\\Html\\img";
                            if (GetOpenFileName((LPOPENFILENAME)&ofn))
                            {   LoadImage(szFileName);  }
                            break;
                        case IDM_INFO:
                            pBInfo = (BITMAPINFO*)LocalLock(hBInfo);
                            MBoxBI(hWnd, pBInfo);
                            LocalUnlock(hBInfo);
                            break;
                    }
                    break;
        
  8. JPEG ファイルを入力する LoadImage() 関数です。
    BitmapInfo と BitmapImage と Palette がメモリに展開されて渡されます。
        //★ DLL を使って JPEG 画像をロード
        HRESULT     LoadImage(LPSTR imgname)
        {   int         ret;
        
            if (hBInfo)     LocalFree(hBInfo);
            if (hBm)        LocalFree(hBm);
            if (hPalette)   LocalFree(hPalette);
        
            // ★JPEG 画像をロードする
            if (F_GetPicture!=NULL)
            {
                ret= (*F_GetPicture)(imgname,0,0,&hBInfo,&hBm,NULL,1);
                if (ret!=0)
                {   EMSG("DLL GetPicture Error\n");     return FALSE;  }
                if (hBInfo != NULL)
                {   hPalette = DIBtoPAL(hBInfo);    }
        
                pBInfo = (BITMAPINFO*)LocalLock(hBInfo);
                WinSize(hWnd, pBInfo->bmiHeader.biWidth, pBInfo->bmiHeader.biHeight);
                LocalUnlock(hBInfo);
                bLoad= TRUE;
            }
            return TRUE;
        }
        
  9. パレットを設定する関数です。
        //BITMAPINFO からパレットを作ります
        HPALETTE DIBtoPAL(HANDLE hBInfo)
        {   BITMAPINFO  *pBInfo;
            LOGPALETTE  *pPAL;
            HPALETTE    hPAL;
            int         i;
        
            pBInfo = (BITMAPINFO*)LocalLock(hBInfo);
            if(pBInfo->bmiHeader.biBitCount >= 16)
            {   LocalUnlock(hBInfo);
                return NULL;
            }
            pPAL = (LOGPALETTE*)LocalAlloc(LPTR, sizeof(LOGPALETTE) + sizeof(PALETTEENTRY) * 256);
            pPAL->palVersion = 0x3000;
            pPAL->palNumEntries = (WORD)(1L << pBInfo->bmiHeader.biBitCount);
            for(i=0;i<pPAL->palNumEntries;i++)
            {   pPAL->palPalEntry[i].peRed  = pBInfo->bmiColors[i].rgbRed;
                pPAL->palPalEntry[i].peGreen= pBInfo->bmiColors[i].rgbGreen;
                pPAL->palPalEntry[i].peBlue = pBInfo->bmiColors[i].rgbBlue;
                pPAL->palPalEntry[i].peFlags= pBInfo->bmiColors[i].rgbReserved;
            }
            hPAL = CreatePalette(pPAL);
            LocalFree(pPAL);
            LocalUnlock(hBInfo);
            return(hPAL);
        }
        
  10. 画像に合わせてウインドウのサイズを設定する関数です。
        //★ Window Size の設定
        void    WinSize(HWND hWnd, LONG Width, LONG Height)
        {   RECT    rc;
            int     x, y;
        
            GetWindowRect(hWnd, &rc);
            x = rc.left;
            y = rc.top;
            rc.right = x + Width;
            rc.bottom = y + Height;
            AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, TRUE);
            MoveWindow(hWnd, x, y, rc.right - rc.left, rc.bottom - rc.top, TRUE);
            InvalidateRect(hWnd, NULL, TRUE);
        }
        
  11. IDM_INFO: で Jpeg 画像の情報を表示する関数 MBoxBI() を呼び出します。
                        case IDM_INFO:
                            pBInfo = (BITMAPINFO*)LocalLock(hBInfo);
                            MBoxBI(hWnd, pBInfo);
                            LocalUnlock(hBInfo);
                            break;
                    }
                    break;
        
  12. JPEG ファイルの情報を取得して MessageBox() で表示します。
        //★ BITMAPINFO を表示する
        void    MBoxBI(HWND wnd, BITMAPINFO *PBi)
        {   char        str[256];
        
            wsprintf(str,
                    "FileName = %s\nBISize = %d\n"
                    "BMP幅 = %d,          BMP高さ = %d\n"
                    "Planes = %d,         BitCount = %d\n"
                    "Compression = %d,    SizeImage = %d\n"
                    "XPelsPerMeter = %d,  YPelsPerMeter = %d\n"
                    "ClrUsed = %d,        ClrImportant = %d\n",
                    szFileName,                     PBi->bmiHeader.biSize,
                    PBi->bmiHeader.biWidth,         PBi->bmiHeader.biHeight,
                    PBi->bmiHeader.biPlanes,        PBi->bmiHeader.biBitCount,
                    PBi->bmiHeader.biCompression,   PBi->bmiHeader.biSizeImage,
                    PBi->bmiHeader.biXPelsPerMeter, PBi->bmiHeader.biYPelsPerMeter,
                    PBi->bmiHeader.biClrUsed,       PBi->bmiHeader.biClrImportant);
            MessageBox(wnd, str, "BITMAP情報", MB_OK);
        }
        
  13. WM_PAINT: で画像を描画します。
    メモリに展開されたイメージを元に DIB を作成して BitBlt() で描画します。
                case WM_PAINT:
                    if (!bLoad)  return (DefWindowProc(hWnd, msg, wp, lp));
                    hdc = BeginPaint(hWnd, &ps);
                    //メモリデバイスコンテキストをクリアしてイメージを生成
                    PatBlt(hBufferDC, 0, 0, GetSystemMetrics(SM_CXSCREEN),
                           GetSystemMetrics(SM_CYSCREEN), BLACKNESS);
                    pBInfo = (BITMAPINFO*)LocalLock(hBInfo);
                    pBm = (BYTE*)LocalLock(hBm);
                    StretchDIBits(
                        hBufferDC,
                        ps.rcPaint.left,
                        ps.rcPaint.top,
                        pBInfo->bmiHeader.biWidth - ps.rcPaint.left,
                        pBInfo->bmiHeader.biHeight - ps.rcPaint.top,
                        ps.rcPaint.left,
                        ps.rcPaint.top,
                        pBInfo->bmiHeader.biWidth - ps.rcPaint.left,
                        pBInfo->bmiHeader.biHeight - ps.rcPaint.top,
                        pBm,
                        pBInfo,
                        DIB_RGB_COLORS,
                        SRCCOPY);
                    LocalUnlock(hBInfo);
                    LocalUnlock(hBm);
                    //イメージを描画
                    BitBlt(hdc, 0, 0, GetSystemMetrics(SM_CXSCREEN),
                           GetSystemMetrics(SM_CYSCREEN),
                           hBufferDC, 0, 0, SRCCOPY);
                    EndPaint(hWnd, &ps);
                    break;
        
  14. WM_CLOSE: と WM_DESTROY: です。
    終了前に領域を開放して下さい。
                case WM_CLOSE:
                    id = MessageBox(hWnd,
                        "終了してもよいですか",
                        "終了確認",
                        MB_YESNO | MB_ICONQUESTION);
                    if (id == IDYES)
                    {   DestroyWindow(hWnd);   }
                    break;
                case WM_DESTROY:
                    if (hBInfo)     LocalFree(hBInfo);
                    if (hBm)        LocalFree(hBm);
                    if (hPalette)   LocalFree(hPalette);
                    DeleteObject(hBitmap);
                    DeleteDC(hBufferDC);
                    FreeLibrary(hMyLib);
                    PostQuitMessage(0);
                    break;
                default:
                    return (DefWindowProc(hWnd, msg, wp, lp));
            }
            return 0;
        }
        

【演習】

  1. プログラムを完成させて下さい。
  2. GIF も JPEG と同じように表示することが出来ます。
    GIF の DLL をダウンロードして試してみて下さい。
  3. PNG の DLL をダウンロードして試してみて下さい。

【メモ】

JPEG の圧縮方式は不可逆圧縮なので、圧縮前の画像に戻るとは限りません。
このことは「透明色を設定する」ときに重要で、黒(0.0.0)を透明色に設定することにして BMP から JPEG に変換して もう一度 BMP に戻すと、見た目には解かりませんが「0,0,0」の黒が少し薄くなることがあります。
透明色の黒は完全に 0,0,0 でなければならず、薄くなった部分は透明にはなりません。
それに比べて GIF は256色ですが、完全に復元されるので透明色を設定することもできます。

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