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;

...