GDI+のBitmapをWICから使う

WIC(Windows Imaging Component)は非常に良くできたライブラリなのですが、残念ながら、ドロー系の機能がさっぱりありません。線を引くぐらいならまだしも、文字を描画するとなると絶望的な状態です。なので、結局、一部の処理は、GDI+などを利用することになるのですが、GDI+ <-> WICのInteroperability Layer系の物はオフィシャルには公開されていないようです。大したことではないので、実装するのは難しくはないのですが、かといって、楽な作業かといえば、そんなに楽な物でもありません。それなりに時間のかかる代物です。

ということで、GDI+のBitmapをラップして、IWICBitmap相当の物を作ってみました。とはいっても、手抜きなので、BGR(24bit)以外は対応できていません。おパレット系の関数も動きません。

// このクラスは直接は利用しない(CWICGdiplusBgrBitmap::Lockが内部的に利用)
class CWICGdiplusBitmapLock : public IWICBitmapLock
{
private:
  CWICGdiplusBitmapLock(
    IUnknown* parent,
    Gdiplus::Bitmap* bmp,
    const WICRect *prcLock,
    DWORD flags)
  {
    m_bmp = NULL;
    Gdiplus::Rect rc(prcLock->X, prcLock->Y, prcLock->Width, prcLock->Height);
    DWORD gdpFlags = 0;
    if(flags & WICBitmapLockRead) gdpFlags |= ImageLockModeRead;
    if(flags & WICBitmapLockWrite) gdpFlags |= ImageLockModeWrite;
    Gdiplus::Status status = bmp->LockBits(&rc, gdpFlags, PixelFormat24bppRGB, &m_bd);
    if(status != Ok)
      throw status;

    m_bmp = bmp;
    m_parent = parent;
    if(m_parent)
      m_parent->AddRef();
    m_co = 1; // Lock behaves like QueryInterface
  }

  ~CWICGdiplusBitmapLock()
  {
    if(m_bmp)
      m_bmp->UnlockBits(&m_bd);
    if(m_parent)
      m_parent->Release();
  }

public:
  static CComPtr<IWICBitmapLock> Create(
    IUnknown* parent,
    Gdiplus::Bitmap* bmp,
    const WICRect *prcLock,
    DWORD flags)
  {
    return new CWICGdiplusBitmapLock(parent, bmp, prcLock, flags);
  }
  
  HRESULT __stdcall QueryInterface(REFIID iid, void **ppvObject)
  {
    *ppvObject = NULL;
    if(iid == __uuidof(IUnknown))
      *ppvObject = dynamic_cast<IUnknown*>(this);
    else if(iid == __uuidof(IWICBitmapLock))
      *ppvObject = dynamic_cast<IWICBitmapLock*>(this);
    if(*ppvObject)
    {
      AddRef();
      return S_OK;
    }
    return E_NOINTERFACE;
  }
  
  ULONG __stdcall AddRef()
  {
    return ++m_co;
  }
  
  ULONG __stdcall Release()
  {
    ULONG n = --m_co;
    if(n == 0)
      delete this;
    return n;
  }
  
  HRESULT __stdcall GetDataPointer(UINT *pcbBufferSize, BYTE **ppbData)
  {
    *pcbBufferSize = m_bd.Stride * m_bd.Height;
    *ppbData = (BYTE*)m_bd.Scan0;
    return S_OK;
  }
  
  HRESULT __stdcall GetPixelFormat(WICPixelFormatGUID *pPixelFormat)
  {
    *pPixelFormat = GUID_WICPixelFormat24bppBGR;
    return S_OK;
  }

  HRESULT __stdcall GetSize(UINT *pWidth, UINT *pHeight)
  {
    *pWidth = (UINT)m_bd.Width;
    *pHeight = (UINT)m_bd.Height;
    return S_OK;
  }
  
  HRESULT __stdcall GetStride(UINT *pcbStride)
  {
    *pcbStride = m_bd.Stride;
    return S_OK;
  }

private:
  IUnknown* m_parent;
  Gdiplus::Bitmap* m_bmp;
  Gdiplus::BitmapData m_bd;
  ULONG m_co;
};

// GDI+のBitmapを使ったBGR(24bit)のIWICBitmapの実装
class CWICGdiplusBgrBitmap : public IWICBitmap
{
private:
  CWICGdiplusBgrBitmap(int width, int height)
  {
    m_bmp = new Gdiplus::Bitmap(width, height, PixelFormat24bppRGB);
    m_co = 0;
  }

  ~CWICGdiplusBgrBitmap()
  {
    delete m_bmp;
  }
  
public:
  static CComPtr<CWICGdiplusBgrBitmap> Create(int width, int height)
  {
    return new CWICGdiplusBgrBitmap(width, height);
  }
  
  HRESULT __stdcall QueryInterface(REFIID iid, void **ppvObject)
  {
    *ppvObject = NULL;
    if(iid == __uuidof(IUnknown))
      *ppvObject = dynamic_cast<IUnknown*>(this);
    else if(iid == __uuidof(IWICBitmapSource))
      *ppvObject = dynamic_cast<IWICBitmapSource*>(this);
    else if(iid == __uuidof(IWICBitmap))
      *ppvObject = dynamic_cast<IWICBitmap*>(this);
    if(*ppvObject)
    {
      AddRef();
      return S_OK;
    }
    return E_NOINTERFACE;
  }
  
  ULONG __stdcall AddRef()
  {
    return ++m_co;
  }
  
  ULONG __stdcall Release()
  {
    ULONG n = --m_co;
    if(n == 0)
      delete this;
    return n;
  }
  
  HRESULT __stdcall CopyPalette(IWICPalette *pIPalette)
  {
    return E_FAIL;
  }
  
  HRESULT __stdcall CopyPixels(
    const WICRect *prc,
    UINT cbStride,
    UINT cbBufferSize,
    BYTE *pbBuffer)
  {
    Gdiplus::Rect rc(prc->X, prc->Y, prc->Width, prc->Height);
    Gdiplus::BitmapData bd;
    if(m_bmp->LockBits(&rc, ImageLockModeRead, PixelFormat24bppRGB, &bd) != Ok)
      return E_FAIL;
    
    int h = bd.Height;
    const BYTE* p = (const BYTE*)bd.Scan0;
    size_t bpl = bd.Width * 3;
    for(int y = 0; y < h; y++)
    {
      memcpy(pbBuffer, p, bpl);
      pbBuffer += cbStride;
      p += bd.Stride;
    }
    
    m_bmp->UnlockBits(&bd);
    return S_OK;
  }
  
  HRESULT __stdcall GetPixelFormat(WICPixelFormatGUID *pPixelFormat)
  {
    *pPixelFormat = GUID_WICPixelFormat24bppBGR;
    return S_OK;
  }
  
  HRESULT __stdcall GetResolution(double *pDpiX, double *pDpiY)
  {
    *pDpiX = m_bmp->GetHorizontalResolution();
    *pDpiY = m_bmp->GetVerticalResolution();
    return S_OK;
  }
  
  HRESULT __stdcall GetSize(UINT *puiWidth, UINT *puiHeight)
  {
    *puiWidth = (UINT)m_bmp->GetWidth();
    *puiHeight = (UINT)m_bmp->GetHeight();
    return S_OK;
  }
  
  HRESULT __stdcall Lock(
    const WICRect *prcLock,
    DWORD flags,
    IWICBitmapLock **ppILock)
  {
    try
    {
      *ppILock = CWICGdiplusBitmapLock::Create(this, m_bmp, prcLock, flags);
      return S_OK;
    }
    catch(...)
    {
      return E_FAIL;
    }
  }
  
  HRESULT __stdcall SetPalette(IWICPalette *pIPalette)
  {
    return E_FAIL;
  }

  HRESULT __stdcall SetResolution(double dpiX, double dpiY)
  {
    m_bmp->SetResolution((float)dpiX, (float)dpiY);
    return S_OK;
  }
  
  Gdiplus::Bitmap* GetBitmap()
  {
    return m_bmp;
  }

private:
  Gdiplus::Bitmap* m_bmp;
  ULONG m_co;
};

使い方は簡単で、要は、CWICGdiplusBgrBitmap::GetBitmapで内包するBitmapにアクセスできることです。WICを使ってのファイルの保存などについては、画像を保存などを参考にしてください。

// 1024x768のビットマップを作成
CComPtr<CWICGdiplusBgrBitmap> bmp = CWICGdiplusBgrBitmap::Create(1024, 768);

// Graphicsオブジェクトを取得
Gdiplus::Graphics* g = Gdiplus::Graphics::FromBitmap(bmp->GetBitmap());

// Consolas 10ptで、50,50の位置にHello, worldと表示する
g->DrawString(
  L"Hello, world", -1,
  &Gdiplus::Font(L"Consolas", 10),
  Gdiplus::PointF(50, 50),
  &SolidBrush(Color(0xff, 0, 0, 0)));

// Graphicsオブジェクトを破棄
delete g;

...

既存のIWICBitmapSourceからの構築

すでにWICでメモリ上に持っているIWICBitmapSourceをコピーしてビットマップを初期化したい場合には、次のようなコードを書く必要があるかもしれません。エラーの処理は適当に追加してください。

// 適当に例外を投げるためのマクロ; 手抜きなので注意!!!
#define throwOnFail(expr) do { HRESULT hr = expr; if(FAILED(hr)) throw hr; } while(0)

// 入力ソースをBGR(プロファイルがある場合には、sRGB)に変換
IWICBitmapSource* ConvertToSRGB(IWICBitmapSource* bmpSrc, IWICImagingFactory* factory)
{
  WICPixelFormatGUID fmt;
  throwOnFail(bmpSrc->GetPixelFormat(&fmt));
  
  // RGBの画像じゃない場合には、カラープロファイルを用いたIWICColorTransformによる変換を試みる
  if(fmt != GUID_WICPixelFormat24bppBGR && fmt !=GUID_WICPixelFormat24bppRGB)
  {
    UINT co;
    CComPtr<IWICColorContext> cc_src;
    HRESULT hr = bmpSrc->GetColorContexts(1, &cc_src, &co);
    if(SUCCEEDED(hr) && co == 1 && cc_src)
    {
      // sRGB profile
      CComPtr<IWICColorContext> cc_sRGB;
      throwOnFail(factory->CreateColorContext(&cc_sRGB));
      throwOnFail(cc_sRGB->InitializeFromExifColorSpace(1));
      
      CComPtr<IWICColorTransform> txf;
      throwOnFail(factory->CreateColorTransformer(&txf));
      
      throwOnFail(txf->Initialize(
        frameDecode,
        cc_src,
        cc_sRGB,
        GUID_WICPixelFormat24bppBGR));
      
      IWICBitmapSource* output = NULL;
      throwOnFail(txf->QueryInterface(
        __uuidof(IWICBitmapSource), (void**)&output));
      return output;
    }
  }

  // どうにもうまくいかない場合には、適当に変換する
  CComPtr<IWICFormatConverter> cvtr;
  throwOnFail(factory->CreateFormatConverter(&cvtr));
  
  throwOnFail(cvtr->Initialize(
    bmpSrc,
    GUID_WICPixelFormat24bppBGR,
    WICBitmapDitherTypeNone,
    NULL,
    0.0,
    WICBitmapPaletteTypeCustom));

  IWICBitmapSource* output = NULL;
  throwOnFail(cvtr->QueryInterface(
    __uuidof(IWICBitmapSource), (void**)&output));
  return output;
}

// 
CComPtr<CWICGdiplusBgrBitmap> CopyToGdiplusBitmap(IWICBitmapSource* src, IWICImagingFactory* factory)
{
  CComPtr<IWICBitmapSource> bmpSrc = ConvertToSRGB(src, factory);

  UINT w, h;
  throwOnFail(bmpSrc->GetSize(&w, &h));

  CComPtr<CWICGdiplusBgrBitmap> outBmp;
  outBmp = CWICGdiplusBgrBitmap::Create(w, h);

  double resX, resY;
  if(SUCCEEDED(bmpSrc->GetResolution(&resX, &resY)))
    outBmp->SetResolution(resX, resY);
  
  CComPtr<IWICBitmapLock> lock;

  WICRect rcCopy = {0, 0, w, h};
  throwOnFail(outBmp->Lock(&rcCopy, WICBitmapLockWrite, &lock));
  UINT stride = 0, bufSize = 0;
  BYTE* raw = NULL;
  throwOnFail(lock->GetDataPointer(&bufSize, &raw));
  throwOnFail(lock->GetStride(&stride));
  throwOnFail(bmpSrc->CopyPixels(&rcCopy, stride, bufSize, raw));
  return outBmp;
}