2013年9月8日日曜日

Windows での画像ファイルの読み書き(1)

Windows Imaging Component を使った画像ファイルの読み書きをしてみたので、備忘として記します。

Windows Imaging Component は Windows SDK に含まれており、静止画や動画を読み書きするための API が提供されています。画像処理をするようなプログラムを書くときに有用です。

Windows での画像の読み書きでは win32api や GDI+ もありますが、GUI はないプログラムだと Windows Imaging Component がもっとも使い勝手がよいのでは、と思います。


まずは画像ファイルからの読み込みです。

画像ファイルから画素を取得するまでの簡単な流れは以下となります。

  1. IWICImaginFactory オブジェクトの生成
  2. IWICImaginFactory オブジェクトからのデコーダの生成
  3. デコーダからのフレームの生成
  4. フレームからの画素の取得

コードを以下に記載します。本コードで便利なところは、ファイル名から画像フォーマットを判別して適宜ファイルを読んでくれるところです。これで、大体どんな画像ファイルでも一律に読み出せます。

本コードでは、ファイルから読みだした画素は 24ビット BGR を前提としていますが、IWICFormatConverter を使用して適宜画素フォーマットを変換することができます。その例ものちほど載せたいと思います。

#include<wincodec.h> // Windows Imaging Component。要windowscodecs.lib。
#include<iostream>

// Windows Imaging Component を使用した画素の取得。
HRESULT GetPixelsFromImageFile(LPCWSTR imageFileName, UINT *puiWidth, UINT *puiHeight, BYTE **ppcbBuffer)
{
 // ファクトリ。デコーダの生成に使用。
 IWICImagingFactory *pFactory = NULL;
 // デコーダ。画像ファイルからのフレーム取得に使用。
 IWICBitmapDecoder *pDecoder = NULL;
 // フレーム。画像一枚を格納。画素の取得に使用。
 IWICBitmapFrameDecode *pFrame = NULL;
 
 HRESULT hr = S_OK;
 if(SUCCEEDED(hr))
 {
  // ファクトリの生成。
  hr = CoCreateInstance(
      CLSID_WICImagingFactory,
      NULL,
      CLSCTX_INPROC_SERVER,
      IID_IWICImagingFactory,
      (LPVOID*)&pFactory
    );
 }
 
 if(SUCCEEDED(hr))
 {
  // デコーダの生成。ファイル名からデコーダを判別して生成。
  hr = pFactory->CreateDecoderFromFilename(
      imageFileName,
      NULL,
      GENERIC_READ,
      WICDecodeMetadataCacheOnDemand,
      &pDecoder
    );
 }
 
 if(SUCCEEDED(hr))
 {
  // 一枚目のフレームを取得。
  hr = pDecoder->GetFrame(0, &pFrame);
 }
  
 WICPixelFormatGUID guidPixelFormat;
 if(SUCCEEDED(hr))
 {
  // フォーマット情報を取得。
  hr = pFrame->GetPixelFormat(&guidPixelFormat);
 }
 
 if(SUCCEEDED(hr))
 {
  // フォーマットが 24ビット BGR (1色8ビット) か検査。
  if(guidPixelFormat != GUID_WICPixelFormat24bppBGR)
  {
   hr = E_FAIL;
  }
 }
 
 if(SUCCEEDED(hr))
 {
  // 画像の幅、高さを取得。
  hr = pFrame->GetSize(puiWidth, puiHeight);
 }
 
 if(SUCCEEDED(hr))
 {
  // 画素の領域を確保。
  *ppcbBuffer = new BYTE[*puiWidth * *puiHeight * 3];
  if(*ppcbBuffer == NULL)
  {
   hr = E_FAIL;
  }
 }
 
 if(SUCCEEDED(hr))
 {
  // 画素を取得。
  hr = pFrame->CopyPixels(
     NULL,
     (*puiWidth * 3),
     (*puiWidth * *puiHeight * 3),
     *ppcbBuffer);
 }
 
 if(FAILED(hr))
 {
  // エラーが発生した場合は、画素の領域を解放。
  if(*ppcbBuffer)
  {
   delete [] *ppcbBuffer;
  }
  *ppcbBuffer = NULL;
 }

 // ファクトリ、デコーダ、フレームの解放。
 if(pFactory)
 {
  pFactory->Release();
 }
 
 if(pDecoder)
 {
  pDecoder->Release();
 }

 if(pFrame)
 {
  pFrame->Release();
 }
 
 return hr;
}

// main 関数
int main(int argc, char* argv[])
{
 CoInitialize(NULL); // COM コンポーネントの初期化

 LPCWSTR imageFile =
  L"C:\\Users\\Public\\Pictures\\Sample Pictures\\Chrysanthemum.jpg";
 UINT uiWidth = 0, uiHeight = 0;
 BYTE *pcbBuffer = NULL;
 HRESULT hr = GetPixelsFromImageFile(
        imageFile,
        &uiWidth,
        &uiHeight,
        &pcbBuffer);

 if(pcbBuffer == NULL)
 {
  std::wcerr << L"failed to read " << imageFile << std::endl;
 }
 else
 {
  // 画素を表示。
  for(UINT y = 0; y < uiHeight; y++)
  {
   for(UINT x = 0; x < uiWidth; x++)
   {
    UCHAR r = pcbBuffer[(y * uiWidth + x) * 3 + 2];
    UCHAR g = pcbBuffer[(y * uiWidth + x) * 3 + 1];
    UCHAR b = pcbBuffer[(y * uiWidth + x) * 3 + 0];
    std::cout << "(" << x << ", " << y << ")" << " : "
        << "r=" << (int)r << ", "
        << "g=" << (int)g << ", "
        << "b=" << (int)b << std::endl;
   }
  }

 }

 if(pcbBuffer)
 {
  delete [] pcbBuffer;
 }

 CoUninitialize(); // COM コンポーネントの終了

 return 0;
}


少し補足です。

Windows Imaging Component の使用には wincodec.h をインクルードし、windowscodecs.lib をリンクする必要があります。

Windows Imaging Component は COM コンポーネントなので、ファクトリ生成前に CoInitialize() を、プログラム終了前に CoUninitialize() を実行する必要があります。

試していないのですが、gif など複数枚の画像を格納できるフォーマットの場合、IWICBitmapDecoder::GetFrameCount() で枚数を取得でき、IWICBitmapDecoder::GetFrame() の1番目の引数で取得するフレーム番号を指定して任意の画像を取得できると思います。

0 件のコメント:

コメントを投稿