JPEG,GIF,BMP画像Viewer (Win32 MFC)

since 2000/03/30
last modified 2000/04/10

はじめに

なぜにLinux UnknownなのにWin32のプログラムが置いてあるのか、疑問にもつ人 が多いと思います。しかし、作ったものはしょうがない。製造元責任として、 ここに置かせといて下さい(コンテンツが少ないのが、本音)。

このJPEG,GIF,BMP画像Viewerは、そのプログラム名にあるとおり、 MDIでBITMAP, GIF, JPEGフォーマットの画像ファイルを表示します。 また表示したBITMAP,GIF,JPEGフォーマットの画像に対してモノクロ、 反転(ネガポジ)処理を実行できます。

ここでは、JPEG,GIF,BMP画像Viewerの作成において、最も重要なビットマップ について説明します。

目次
ビットマップ(DDBとDIB)

デバイス依存ビットマップ(DDB)とデバイス独立ビットマップ(DIB)

MS-Windows 95/NT はデバイス依存ビットマップ(DDB:device-dependent bitmap) とデバイス独立ビットマップ(DIB:device-independent bitmap)DIBの二種類のビッ トマップをサポートしています。

DDBは古い形式のビットマップであり、同一のコンピュータ上のプログラム間で は転送可能だが、デバイス依存性によりDisk,Modemでの転送は不可能です。 このためDIBと比べ柔軟性に欠けます。DDBはペンやフォント等と同様、 GDIオブジェクトのひとつであり、Microsoft FoundationClass(MFC) ライブラリ のCBitmapクラスにより表現されます。

DIBは自身のカラー情報をカラーテーブルとして収めているため、カラーパレッ ト管理が容易です。しかし、DIBのMFCライブラリにおけるクラスは存在しないため、 Win32APIを利用する必要があります。

先頭へ戻る

デバイス依存ビットマップ(DDB)

BITMAP構造体

DDBは下記に示すBITMAP構造体としてメモリ上に収められています。 BITMAP構造体は、論理ビットマップの高さ、幅、カラーフォーマット、および ビット値を定義します。

   typedef struct tagBITMAP
   {
      int  bmType;
      int  bmWidth;
      int  bmHeight;
      int  bmWidthBytes;
      BYTE bmPlanes;
      BYTE bmBitsPixel;
      LPVOID bmBits;
   }BITMAP;
bmType
ビットマップのタイプを指定します。論理ビットマップでは、このメン バは0でなければなりません。
bmWidth
ビットマップの幅をピクセル単位で指定します。このメンバは1以上でな ければなりません。
bmHeight
ビットマップの高さをピクセル単位で指定します。このメンバは1以上でな ければなりません。
bmWidthBytes
ラスタ行ごとのバイト数を指定します。この値は、GDIがビットマップの ビット値が整数値(2バイト)の配列であると仮定しているため、偶数値で無 ければなりません。つまり、bmWidthBytes * 8 の値はbmWidthメンバと bmBitsPixelメンバの積の値以上でその積に最も近い16の倍数でなければな りません。
bmPlanes
ビットマップのカラープレーンの数を指定します。このメンバは通常1です。
bmBitsPixel
1ピクセルを定義するのに必要な各プレーン上の隣接したカラービットの 数を指定します。
bmBits
ビットマップのビット値の位置を指定します。bmBitsメンバはバイト値 の配列へのlongポインタでなければなりません。

DDBの表示方法

  1. CDC::CreateCompatibleDC関数を使いビットマップ用の特別なメモリデバイ スコンテキストを作成します。
  2. CDC::SelectObject関数を使い、ビットマップをメモリデバイスコンテキスト に選択します。
  3. CDCのメンバ関数StretchBltかBitBltを使い、メモリデバイスコンテキスト からディスプレイデバイスコンテキストにコピーします。

DDBの表示例

DDBの表示例として、ビットマップをリソースからロードし表示します。 リソースは、RSファイルに以下のように登録されています。

   IDB_REDBLOCKS   BITMAP DISCARDABLE   "res\\RedBlocks.bmp"

   CMyView::OnDraw(CDC* pDC)
   {
      CBitmap bitmap;
      CDC dcDisplayMemory;

      bitmap.LoadBitmap(IDB_REDBLOCKS);
      dcDisplayMemory.CreateCompatibleDC(pDC);
      dcDisplayMemory.SelectObject(&bitmap);
      pDC->BitBlt(100, 100, 54, 96, &dcDisplayMemory, 0, 0, SRCCOPY);
   }

先頭へ戻る

デバイス独立ビットマップ(DIB)

DIBはカラー情報の形式が決まっているため、プログラム内で直接配列を書き換 えることが出来るため、非常に便利です。

DIBの構造

DIBは下記に示す3つの部分から成ります。

BITMAPINFOHEADER構造体

BITMAPINFOHEADER構造体は、ビットマップの縦横のサイズ、ピクセルあたりのビッ ト数、4bpp,8bppビットマップの場合は圧縮情報、カラーテーブルエントリ数を 収めています。

   typedef struct tagBITMAPINFOHEADER
   {
      DWORD biSize;            // BITMAPINFOHEADERのサイズ
      LONG  biWidth;           // 幅(ピクセル単位)
      LONG  biHeight;          // 高さ(ピクセル単位)
      WORD  biPlanes=1;
      WORD  biBitCount;        // 1ピクセルあたりのカラービット数
      DWORD biCompression;     // BI_RGB, BI_RLE4, BI_RLE8のいづれか
      DWORD biSizeImage;       // DIBビットイメージのバイト数(BI_RLE4,8の場合)
      LONG  biXPelsPerMeter=0;
      LONG  biYPelsPerMeter=0;
      DWORD biClrUsed;         // カラーテーブルエントリの数
      DWORD biClrImportant;    // カラーテーブルエントリの数
   }BITMAPINFOHEADER, *LPBITMAPINFOHEADER;

RGBQUAD構造体

RGBQUAD構造体はBITMAPINFOHEADER構造体の直後にカラーテーブルエントリを 格納します。

   typedef struct tagRGBQUAD
   {
      BYTE rgbBlue;            // 青色用カラーテーブル
      BYTE rgbGreen;           // 緑色用カラーテーブル
      BYTE rgbRed;             // 赤色用カラーテーブル
      BYTE rgbReserved;        // フラグ用カラーテーブル
   }RGBQUAD;                   // 32ビットカラーテーブルエントリ

1ピクセルあたりのカラービット数に応じて、ひとつのピクセルは 連続した1,4,8,16,24,32ビットから構成されます。16bpp,24bpp,32bppのDIBでは、 各ピクセルをRGBカラーとして表現し、一般的にカラーテーブルエントリを 使用しません。16bppのDIBでは一般的に赤、緑、青の5ビットを使います。 24bppのDIBでは各カラー値に8ビットを使います。

1bppのDIBは、実際にはモノクロDIBですが、このようなDIBは、黒と白をカラー 値として持つわけではなく、選択した任意の2色を収めることが出来ます。モノ クロビットマップはふたつの32ビットカラーテーブルエントリを持ち、それぞれ は赤、緑、青色用の8ビットに加え、フラグ用の8ビットも収めています。

8bppのDIBは非常に一般的で、モノクロDIBと同様、32ビットカラーテーブル エントリを持っている。そして、カラーテーブルエントリの数は256になります。 各ピクセルはこのカラーテーブルへのインデックスとなります。

DIBビットイメージ

DIBビットイメージはRGBQUAD構造体の直後に格納されています。DIBビットイメー ジは下部の行から一行づつ格納され、各行は4バイト境界に一致するように パディグされています。

このため、1ピクセルあたりのカラービット数により、DIBビットイメージの 大きさは次のプログラムにより求まります。

DWORD m_dwBytes; // ラスタ行ごとのバイト数
DWORD m_dwSizeImage; // DIBビットイメージのバイト数

dwBytes = ((DWORD) biWidth * biBitCount) / 32;
if (((DWORD) biWidth * biBitCount) % 32) dwBytes++;
dwBytes *= 4;
m_dwSizeImage = dwBytes * biHeight;

例えば、64*64の24bppのDIBでは、各座標に対して3バイトのカラービット数を 持つため、64*64*3 = 12288(BYTES)のサイズが必要となります。これを上記 計算式で求めると、

dwBytes = 192(BYTES), m_dwSizeImage = 12288(BYTES)
となります。

DIBビットイメージの先頭には座標(63,0)から(63,63)までの一行をラスタ行 として格納しています。この後に62,61,・・・,1,0行というように 各ラスタ行を順番に格納しています。このため、24bppのDIBで各座標(x,y)の カラー成分は、次の計算式で求めることが出来ます。

青成分 x+64*y*3
緑成分 x+64*y*3+1
赤成分 x+64*y*3+2

DIBの表示方法

DIBを表示する場合は、メモリデバイスコンテキストを使用する必要は ありません。このためDIBの表示方法は次の様になります。

  1. BITMAPINFOHEADER,RGBQUAD構造体,DIBビットイメージを作成します。
  2. Win32API ::StrechDIBits関数を使用し、ディスプレイデバイス コンテキストにDIBをコピーします。

DIBの表示例

DIBの表示例として、ビットマップをリソースからロードし表示します。 リソースは、RSファイルに以下のように登録されています。

   IDB_REDBLOCKS   BITMAP DISCARDABLE   "res\\RedBlocks.bmp"

   ---CMyView.cpp---

   LPBITMAPINFO lpBitmapInfo;

   void CMyView::OnInitailUpdate()
   {
      CScrollView::OnInitailUpdate();

      CSize totalSize(30000, 40000);   //30 x 40 cm
      CSize lineSize = CSize(totalsize.cx / 100, totalSize.cy / 100);
      SetScrollSizes(MM_HIMETRIC, totalSize, totalSize, lineSize);

      // リソース読み込み
      LPVOID lpvResource = (LPVOID) ::LoadResource(NULL, 
                           ::FindResource(NULL,MAKEINTRESOURCE(IDB_REDBLOCKS),
                           RT_BITMAP));
   
      lpBitmapInfo = (LPBITMAPINFO)::LockResource(lpvResource);

      CClientDC dc(this);
   }  
   
   void CMyView::OnDraw(CDC* pDC)
   {
      // DIBビットイメージの先頭アドレスを求める
      int nNumColors;
      switch(lpBitmap->biBitCount)
      {
         case  1:   nNumColors =   2;   break;
         case  4:   nNumColors =  16;   break;
         case  8:   nNumColors = 256;   break;
         default:   nNumColors =   0;   break;
      }
      LPBYTE lpvBits = (LPBYTE)lpBitmapInfo + sizeof(LPBITMAPINFO)
                       + sizof(RGBQUAD) * nNumColors;

      // DIBリソース表示
      ::StretchDIBits(pDC->mhDC, 0, 0,
         (int) lpBitmapInfo->biWidth, (int) lpBitmapInfo->biHeight,
         lpvBits, lpBitmapInfo, DIB_RGB_COLORS, SRCCOPY);
   }

先頭へ戻る

ピクチャ(CPictureHolder)

ピクチャは、Visual Basic のPictureプロパティとして知られています。ビット マップ、アイコン、メタファイルの他にJpeg,Gif画像も扱うことが出来ます。 CPictureHolderクラスは、このPictureプロパティをインプリメントしたもの です。

ピクチャの表示方法

  1. Win32 API ::OleLoadPictureFile関数を使用し、ピクチャを読み込み ます。
  2. CPictureHolder::SetPictureDispatch関数を使用し、ピクチャを CPictureHolderオブジェクトとして取り込みます。
  3. CPictureHolder::Render関数を使用して、ピクチャを描画します。

ピクチャの表示例

   ---CMyView.cpp---

   #include < afxctl.h >
   CPictureHolder* m_pPicture;

   BOOL CMyView::OnOpenDocument(LPCTSTR lpszPathName)
   {
      LPSISPATCH pDisp;
      COleVariant varName(lpszPathName);

      if (SUCCEEDED(OleLoadPictureFile(varName, &pDisp)))
      {
         if (m_pPicture != NULL) DeleteContents();
         m_pPicture = new CPictureHolder;
         m_pPicture->SetPictureDispatch(LPPICTUREDISP)pDisp);
         return TRUE;
      }
      return FALSE;
   }

   void CMyView::DeleteContents()
   {
      delete m_pPicture;
      m_pPicture = NULL;
   }   

   void CMyView::OnDraw(CDC* pDC)
   {
      CSize size(0,0);
      OLE_XSIZE_HIMETRIC width;
      OLE_YSIZE_HIMETRIC height;

      // ピクチャをウィンドウの中央に描画
      if (m_pPicture != NULL)
      {
         CRect rect;
         GetClientRect(&rect);
         if (SUCCEEDED(m_pPicture.m_pPict->get_Width(&width)) &&
             SUCCEEDED(m_pPicture.m_pPict->get_Height(&height)))
            size = CSize(width, height);
         else return;
         
         // 左上の位置を計算
         CPoint ptOffset((rect.right - size.cx) / 2,
                         (rect.left  - size.cy) / 2);
         if (ptOffset.x < 0) ptOffset.x = 0;
         if (ptOffset.y < 0) ptOffset.y = 0;

         // ピクチャのレンダリング
         rect = CRect(ptOffset, size);
         m_pPicture->Render(pDC, &rect, &rect);
      }   
   }

先頭へ戻る

ピクチャ => デバイス依存ビットマップ(DDB)変換

ピクチャ => DDB 変換方法

  1. Win32 API ::OleLoadPictureFile関数を使用し、ピクチャを読み込み ます。
  2. CPictureHolder::SetPictureDispatch関数を使用し、ピクチャを CPictureHolderオブジェクトとして取り込みます。
  3. CPictureHolder::m_pPictデータメンバ(IPictureインターフェイスのポ インタ)を使用し、ビットマップハンドル(HBITMAP)を取得します。
  4. CBitmap::FromHandle関数を使用し、取得したビットマップハンドルか らDDBを作成します。

ピクチャ => DDB 変換例

   ---CMyDoc.cpp---

   #include < afxctl.h >
   CPictureHolder* m_pPicture;

   BOOL CMyDoc::OnOpenDocument(LPCTSTR lpszPathName)
   {
      LPSISPATCH pDisp;
      COleVariant varName(lpszPathName);

      if (SUCCEEDED(OleLoadPictureFile(varName, &pDisp)))
      {
         if (m_pPicture != NULL) DeleteContents();
         m_pPicture = new CPictureHolder;
         m_pPicture->SetPictureDispatch(LPPICTUREDISP)pDisp);
         if(!GetPictureBitmap()) return FALSE;
         return TRUE;
      }
      return FALSE;
   }

   void CMyDoc::DeleteContents()
   {
      delete m_pPicture;
      m_pPicture = NULL;
      CDocument::DeleteContents();
   }   

   HBitmap CMyDoc::GetBitmap()
   {
      short type;
      OLE_HANDLE hBitmap;
      
      if (!m_pPicture) return NULL;
      IPicture *pPict = m_pPicture->m_pPict;
      ASSERT(pPict);
     
      pPict->get_Type(&type);
      return type == PICTYPE_BITMAP &&
         SUCCEEDED(pPict->get_Handle(&hBitmap)) ? (HBITMAP)hBitmap : NULL;
   }

   BOOL CMyDoc::GetPictureBitmap()
   {
      HBITMAP hBitmap = GetBitmap();
      if (hBitmap == NULL) return FALSE;
      m_pBitmap = new CBitmap;
      m_pPitmap = CBitmap::FromHandle(hBitmap);
      if (m_pBitmap == NULL) return FALSE;
      return TRUE;
   }

先頭へ戻る

ピクチャ => デバイス独立ビットマップ(DIB)変換

ピクチャ => DIB 変換方法

  1. Win32 API ::OleLoadPictureFile関数を使用し、ピクチャを読み込み ます。
  2. CPictureHolder::SetPictureDispatch関数を使用し、ピクチャを CPictureHolderオブジェクトとして取り込みます。
  3. CPictureHolder::m_pPictデータメンバ(IPictureインターフェイスのポ インタ)を使用し、ビットマップハンドル(HBITMAP)を取得します。
  4. メモリデバイスコンテキストを作成し、Win32 API ::SelectObject, ::GetObject関数を使用し、取得したビットマップハンドルを選択後、 構造体BITMAPを格納します。
  5. 構造体BITMAPからBITMAPINFOHEADER,RGBQUAD構造体を作成します。
  6. Win32 API ::GetDIBits関数を使用し、DIBビットイメージを取得します。

ピクチャ => DIB 変換例

   ---CMyView.cpp---

   #include < afxctl.h >
   LPBITMAPINFOHEADER m_lpBMIH;
   CPalette* m_lpPalette;
   LPVOID    m_lpvColorTable;
   char*     m_lpImage;
   DWORD     m_dwSizeImage;

   CMyView::CMyView()
   {
      m_lpPalette = new CPalette;
   }

   CMyView::~CMyView()
   {
      delete m_lpPalette;
      delete m_lpImage;
      delete m_lpBMIH;
      m_lpImage = NULL;
      m_lpBMIH  = NULL;
   }

   CSize CMyView::GetPictureSize(CPictureHolder *pHolder)
   {
      ASSERT(pHolder);
      
      CSize size(0,0);
      OLE_XSIZE_HIMETRIC width;
      OLE_YSIZE_HIMETRIC height;

      if (pHolder && pHolder->m_pPict &&
          SUCCEEDED(m_pPicture.m_pPict->get_Width(&width)) &&
          SUCCEEDED(m_pPicture.m_pPict->get_Height(&height)))
      {
         CDC *pDC = getDC();
         ASSERT(pDC);
         size = CSize(width, height);
         pDC->HIMETRICtoDP(&size);
         ReleaseDC(pDC);
      }
      return size;
   }

   // 透明GIF使用時の背景の消去しきれないゴミを消去
   BOOL CMyView::OnEraseBkgnd(CDC* pDC)
   {
      CRect rectClip;
      if (pDC->GetClipBox(&rectClip) != NULLREGION)
          pDC->FillSolidRect(&rectClip, ::GetSysColor(COLOR_APPWORKSPACE));
      return TRUE;
   }
   
   void CMyView::OnDraw(CDC* pDC)
   {
      CMyDoc* pDoc->GetDocument();
      ASSERT_VALID(pDoc);
      
      CPoint orgin;
      CSize  size;
   
      origin.x =  20;
      origin.y = -20;
      size.cx = m_lpBMIH->biWidth;
      size.cy = m_lpBMIH->biHeight;

      //表示用パレットを前景パレットで設定
      CPalette* oldPalette = pDC->SelectPalette(m_lpPalette, FALSE);
      pDC->RealizePalette();

      ::StretchDIBits(pDC->GetSafeHdc(), origin.x, origin.y, size.cx,size.cy,
          0, 0, m_lpBMIH->biWidth, m_lpBMIH->biHeight,
          m_lpImage, (LPBITMAPINFO) m_lpBMIH, DIB_RGB_COLORS, SRCCOPY);
      
      //残りのパレットは背景パレットで設定
      pDC->SelectPalette(oldPalette, TRUE);
   }

   void CMyView::OnInitailUpdate()
   {
      CScrollView::OnInitailUpdate();
      CSize sizeTotal(0, 0);
      SetScrollSizes(MM_HIMETRIC, sizeTotal);

      CMyDoc* pDoc = GetDocument();
      ASSERT_VALID(pDoc);
      
      CDC* pDC = new CDC;
      BITMAP bm;
      HDC hdc = pDC->GetSafeHdc();
      if (hcd == NULL)
      {
         CClientDC dc(this);
         OnprepareDC(&dc);
         HBITMAP hBitmap = pDoc->GetBitmap();
         pDC->CreateCompatibleDC(&DC);
         ::SelectObject(pDC->GetSafeHdc(), hBitmap);
         ::GetObject(hBitmap, sizeof(bm), &bm);
         if (PictureCnvDIB(pDC->GetSafeHdc(), &bm) == NULL) return;
         ReleseDC(&dc);
      }
      delete pDC;
   }

   int CMyView::PictureCnvDIB(HDC hdc, BITMAP* pBitmap)
   {
      CMyDoc* pDoc = GetDocument();
      ASSERT_VALID(pDoc);
      
      // 1ビットあたりのカラービット数からパレットのエントリ数を確認
      int m_nColorTableEntries = 0;
      switch(pBitmap->bmBitsPixel)
      {
         case 1:   m_nColorTableEntries =   2; break;
         case 4:   m_nColorTableEntries =  16; break;
         case 8:   m_nColorTableEntries = 256; break;
         default:  m_nColorTableEntries =   0;
      }

      // BITMAPINFOHEADER 作成
      int nSize = sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD)
                  * m_nColorTableEntries;
      m_lpBMIH = (LPBITMAPINFOHEADER) new char[nSize];
      m_lpvColorTable = (LPBYTE) m_lpBMIH->sizeof(BITMAPINFOHEADER);
      memset(m_lpvColorTable, 0, sizeof(RGBQUAD) * m_nColorTableEntries);

      m_lpBMIH->biSize          = sizeof(BITMAPINFOHEADER);
      m_lpBMIH->biWidth         = pBitmap->bmWidth;
      m_lpBMIH->biHeight        = pBitmap->bmHeight;
      m_lpBMIH->biPlanes        = 1;
      m_lpBMIH->biBitCount      = pBitmap->bmBitsPixel;
      m_lpBMIH->biCommpression  = BI_RGB;
      m_lpBMIH->biSizeImage     = 0;
      m_lpBMIH->biXpelsPerMeter = 0;
      m_lpBMIH->biYpelsPerMeter = 0;
      m_lpBMIH->biClrUsed       = m_nColorTableEntries;
      m_lpBMIH->biClrImportant  = m_nColorTableEntries;

      dwBytes = ((DWORD) m_lpBMIH->biWidth * m_lpBMIH->biBitCount) / 32;
      if (((DWORD) m_lpBMIH->biWidth * m_lpBMIH->biBitCount) % 32) dwBytes++;
      dwBytes *= 4;
      m_dwSizeImage = dwBytes * m_lpBMIH->biHeight;
      m_lpImage = new char[m_dwSizeImage];
      
      int res = ::GetDIBits(hdc, pDoc->GetBitmap(),
         (UINT)0, (UINT)m_lpBMIH->biHeight,
         (LPVOID)m_lpImage, (LPBITMAPINFO)m_lpBMIH, DIB_RGB_COLORS);

      //論理パレット作成
      LPLOGPALETTE pLogPal = (LPLOGPALETTE) new char[2 * sizeof(WORD) +
         m_nColorTableEntries * sizeof(PALETTEENTRY)];
     
      //バージョンとパレットのエントリ数を設定
      pLogPal->palVersion = 0x300;
      pLogPal->palNumEntries = m_nColorTableEntries;
      // システムパレットを取得する
      ::GetSystemPaletteEntries(hdc, 0, m_nColorTableEntries, pLogPal->palPalEntry);

      m_lpPalette->CreatePalette(pLogPal);
      delete pLogPal;

      return res;
   }

先頭へ戻る

サンプルプログラム

Jpeg,Gif,Bitmap画像Viewer(DDB) (MS-Windows95/98/NT用, lzh圧縮)
Jpeg,Gif,Bitmap画像Viewer(DDB)のソースコード (Win32MFC, lzh圧縮)

MFC CPictureHolderクラスからCBitmapクラスに変換し、画像を表示しま す。ただ、モノクロ、反転(ネガポジ)処理は表示した画像を1ドット づつ変換しているので遅いです(画像の表示にはCDCクラスのStretchBlt関数 を使用)。

Jpeg,Gif,Bitmap画像Viewer(DIB24bpp) (MS-Windows95/98/NT用, lzh圧縮)
Jpeg,Gif,Bitmap画像Viewer(DIB24bpp)のソースコード (Win32MFC, lzh圧縮)

MFC CPictureHolderクラスから24bppのDIBを作成し、画像を表示します。 モノクロ、反転(ネガポジ)処理は直接24bppのDIBを変換しているので p_ddbサンプルプログラムと比較して速いです (画像の表示にはWin32 API ::StretchDIBits関数を使用)。
先頭へ戻る

問題点

今回のプログラムでは Win32 API ::OleLoadPictureFile 関数を使用して BITMAP, GIF, JPEG画像を取得しています。この関数はパレット調節に不具合が あるため、画面を256色表示に設定した場合に、原画像を正確に描画できません。

また、この関数で取得した画像を Win32 API ::StrectchDiBits関数で描画する と、画面を24bpp表示に設定した場合に格子状のノイズが残ってしまいます。

先頭へ戻る


[Top Pageに戻る]