スポンサーリンク

メモリデバイスコンテキストのカプセル化

以前にも、以下の記事あたりで使ったのだが、メモリデバイスコンテキストをクラス化する話。

MdcCanvas.hpp

//! @file MdcCanvas.hpp
//! @brief メモリデバイスコンテキストのカプセル化
//! @date 2020/04/22 別コードから移植
//! @date 2020/04/25 コメント整理

#pragma once
#include <cassert>
#include <Windows.h>

namespace szl {
  struct cbit24_t;
  struct cbit32_t;

  //! @brief メモリデバイスコンテキストのカプセル化
  //! @tparam PIXEL_BITS 1画素のデータ型。
  template<typename PIXEL_BITS>
  class CMdcCanvas
  {
    //CreateDIBSection , DDB , DIB 関連

    BITMAPINFO m_bmpInfo;   //!< CreateDIBSectionに渡す構造体
    LPDWORD m_lpPixel;      //!< 画素へのポインタ
    HBITMAP m_hBitmap;      //!< 作成したMDCのビットマップハンドル
    HDC m_hMemDC;           //!< 作成したMDCのハンドル

    int m_imagesizemax;     //!< 合計画素数(width*height)
    int m_width;            //!< 画像の幅(ピクセル,自然数)
    int m_height;           //!< 画像の高さ(ピクセル,自然数)
  public:

    //! @brief 新しい画像を作成。すでにあるなら現在のものを破棄して再作成
    //! @param [in] width 画像の幅。( > 0)
    //! @param [in] height 画像の高さ。負数の時は左下原点になる
    //! @param [in] hdc CreateCompatibleDCに渡すDCのハンドル。指定しない場合はCreateDCで自動作成
    //! @retval true 作成した
    //! @retval false 作成しなかった
    bool create_screen(int width, int height, HDC hdc=nullptr);

    //! @brief 現在使用している画像を削除。削除済みか作成されていないなら何もしない
    //! @return なし
    void delete_screen();

    //! @brief 仮想画面の幅を取得
    //! @return 画像幅(自然数)
    int get_width()const { return m_width; }

    //! @brief 仮想画面の高さを取得
    //! @return 画像高さ(自然数)
    int get_height()const { return m_height; }


    //! @brief メモリデバイスコンテキストを取得 (const)
    //! @return メモリデバイスコンテキスト
    const HDC get_mem_dc()const { return m_hMemDC; }

    //! @brief メモリデバイスコンテキストを取得
    //! @return メモリデバイスコンテキスト
    HDC get_mem_dc() { return m_hMemDC; }

    //! @brief BITMAPINFO構造体を取得
    //! @return BITMAPINFO構造体のコピー
    BITMAPINFO get_BITMAPINFO()const { return m_bmpInfo; }

    //! @brief HBITMAPを取得
    //! @return HBITMAPのコピー
    HBITMAP get_HBITMAP()const { return m_hBitmap; }

    //! @brief 書き込み用のメモリ領域へのアドレスを取得
    PIXEL_BITS* get_mem_pixel() { return reinterpret_cast<PIXEL_BITS * >(m_lpPixel); }

    //! @brief 書き込み用のメモリ領域へのアドレスを取得
    const PIXEL_BITS* get_mem_pixel()const { return reinterpret_cast<PIXEL_BITS*>(m_lpPixel); }

    //! @brief 画面に転送を行う
    //! @param [in] hdc 転送先のデバイスコンテキスト
    //! @param [in] rop 転送モード
    //! @return なし
    void bitblt_to(const HDC hdc,const DWORD rop = SRCCOPY)const;

    //! @brief 仮想画面が確保されているか
    //! @retval true 使用可能
    //! @retval false 使用不可
    bool is_valid()const { return (m_hBitmap == nullptr) ? false : true; }

    //! @brief 一次元配列として画素へアクセスする
    //! @param [in] index 要素番号
    //! @return 画素への参照
    //! @note indexが不正な場合assertする
    PIXEL_BITS& operator[](const int index) {
      assert(index >= 0 && index < m_imagesizemax);
      return get_mem_pixel()[index];
    }

    //! @brief 一次元配列として画素へアクセスする(const)
    //! @param [in] index 要素番号
    //! @return 画素への参照
    //! @note indexが不正な場合assertする
    const PIXEL_BITS& operator[](const int index)const {
      assert(index >= 0 && index < m_imagesizemax);
      return get_mem_pixel()[index];
    }

    //! @brief 二次元配列として画素へアクセスする
    //! @param [in] x X座標
    //! @param [in] y Y座標
    //! @return 画素への参照
    //! @note 座標が不正な場合assertする
    PIXEL_BITS& operator()(const int x, const int y) {

      assert(x >= 0 && y >= 0);
      assert(x < get_width() && y < get_height());
      assert((x + y * get_width()) < m_imagesizemax);
      return get_mem_pixel()[x + y * get_width()];
    }

    //! @brief 二次元配列として画素へアクセスする
    //! @param [in] x X座標
    //! @param [in] y Y座標
    //! @return 画素への参照
    //! @note 座標が不正な場合assertする
    const PIXEL_BITS& operator()(const int x, const int y)const {
      assert(x >= 0 && y >= 0);
      assert(x < get_width() && y < get_height());
      assert((x + y * get_width()) < m_imagesizemax);
      return get_mem_pixel()[x + y * get_width()];

    }

    //! @brief 初期化を行う
    CMdcCanvas(void);
    //! @brief 画像を破棄する
    ~CMdcCanvas(void);
  };
  //! @struct cbit24_t
  //! @brief 1画素 3Byte の場合のデータ型
  struct cbit24_t {
    unsigned char v[3];          //!< データ3Byte

    cbit24_t() {}
    cbit24_t(unsigned char r, unsigned char g, unsigned char b) :v{ b,g,r } {}

    unsigned char& r() { return v[2]; } //!< 赤を取得
    unsigned char& g() { return v[1]; } //!< 緑を取得
    unsigned char& b() { return v[0]; } //!< 青を取得
  };

  //! @struct cbit32_t
  //! @brief 1画素 4Byte の場合のデータ型
  struct cbit32_t {
    unsigned char v[4];          //!< データ3Byte
    cbit32_t() {}
    cbit32_t(unsigned char r, unsigned char g, unsigned char b, unsigned char u = 0) :v{ b,g,r,u } {}

    unsigned char& u() { return v[3]; } //!< 未使用領域を取得
    unsigned char& r() { return v[2]; } //!< 赤を取得
    unsigned char& g() { return v[1]; } //!< 緑を取得
    unsigned char& b() { return v[0]; } //!< 青を取得
  };

  //! @brief データ型のビット数を返す
  //! @tparam T データ型
  //! @return sizeofの8倍
  template<typename T>
  inline constexpr int bit_count() { return sizeof(T)*8; }

  //! @brief データ型のビット数を返す(自分定義3byte用)
  //! @return 24
  template<>inline constexpr int bit_count<cbit24_t>() { return 24; }

  //! @brief データ型のビット数を返す(自分定義4byte用)
  //! @return 32
  template<>inline constexpr int bit_count<cbit32_t>() { return 32; }

  template<typename PIXEL_BITS>
  CMdcCanvas<PIXEL_BITS>::CMdcCanvas(void)
  {
    m_hBitmap = nullptr;
    m_hMemDC = nullptr;
    m_lpPixel = nullptr;
    m_width = 0;
    m_height = 0;
    m_imagesizemax = 0;
  }


  template<typename PIXEL_BITS>
  CMdcCanvas<PIXEL_BITS>::~CMdcCanvas(void)
  {
    delete_screen();
  }

  template<typename PIXEL_BITS>
  bool CMdcCanvas<PIXEL_BITS>::create_screen(int width, int height, const HDC hdc) {

    //指定がおかしければ削除される
    if (width <= 0 || abs(height) <= 0) {
      delete_screen();
      return false;
    }

    //既にあるなら削除する
    if (is_valid()) {
      delete_screen();
    }

    m_width = width;
    m_height = abs(height);



    //デバイスコンテキストのハンドルの設定
    HDC hdcd;
    if (hdc == nullptr) {
      hdcd = CreateDCA("DISPLAY", 0, 0, 0);
    }
    else {
      hdcd = hdc;
    }

    //DIBの情報を設定する
    m_bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    m_bmpInfo.bmiHeader.biWidth = width;
    m_bmpInfo.bmiHeader.biHeight = -height; //-を指定しないと上下逆になる
    m_bmpInfo.bmiHeader.biPlanes = 1;
    m_bmpInfo.bmiHeader.biBitCount = bit_count<PIXEL_BITS>();
    m_bmpInfo.bmiHeader.biCompression = BI_RGB;

    m_hBitmap = CreateDIBSection(hdcd, &m_bmpInfo, DIB_RGB_COLORS, (void**)&m_lpPixel, nullptr, 0);
    m_hMemDC = CreateCompatibleDC(hdcd);

    if (hdc == nullptr) {
      DeleteDC(hdcd);
    }

    if (m_hBitmap == nullptr || m_hMemDC == nullptr) {
      delete_screen();
      return false;
    }

    SelectObject(m_hMemDC, m_hBitmap);


    m_imagesizemax = m_width * m_height;

    return true;

  }

  template<typename PIXEL_BITS>
  void CMdcCanvas<PIXEL_BITS>::delete_screen() {
    if (m_hBitmap == nullptr)
      return;

    DeleteDC(m_hMemDC);
    DeleteObject(m_hBitmap);
    m_lpPixel = nullptr;
    m_hBitmap = nullptr;
    m_hMemDC = nullptr;

    m_width = 0;
    m_height = 0;
    m_imagesizemax = 0;
  }


  template<typename PIXEL_BITS>
  void CMdcCanvas<PIXEL_BITS>::bitblt_to(const HDC hdc,const DWORD rop)const {
    BitBlt(hdc, 0, 0, get_width(), get_height(), m_hMemDC, 0, 0, rop);
  }

  using CMdcCanvas24 = CMdcCanvas<cbit24_t>;
  using CMdcCanvas32 = CMdcCanvas<cbit32_t>;

}

使い方

#include<windows.h>

#include "MdcCanvas.hpp"

//メモリデバイスコンテキスト作成
szl::CMdcCanvas24 vscr;

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
  HDC hdc;
  switch (msg) {
  case WM_SIZE:
    InvalidateRect(hwnd, nullptr, TRUE);
    break;
  case WM_PAINT:
    PAINTSTRUCT ps;
    hdc = BeginPaint(hwnd, &ps);
    vscr.bitblt_to(hdc);//画面に表示
    EndPaint(hwnd, &ps);
    break;
  case WM_DESTROY:
    PostQuitMessage(0);
    return 0;
  }
  return DefWindowProc(hwnd, msg, wp, lp);
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
  PSTR lpCmdLine, int nCmdShow) {
  HWND hwnd;
  WNDCLASS winc;
  MSG msg;

  winc.style = CS_HREDRAW | CS_VREDRAW;
  winc.lpfnWndProc = WndProc;
  winc.cbClsExtra = winc.cbWndExtra = 0;
  winc.hInstance = hInstance;
  winc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
  winc.hCursor = LoadCursor(NULL, IDC_ARROW);
  winc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
  winc.lpszMenuName = NULL;
  winc.lpszClassName = TEXT("DC_TEST");

  if (!RegisterClass(&winc)) return 0;

  hwnd = CreateWindow(
    TEXT("DC_TEST"), TEXT("mem dc test"),
    WS_OVERLAPPEDWINDOW | WS_VISIBLE,
    CW_USEDEFAULT, CW_USEDEFAULT,
    300, 300,
    NULL, NULL,
    hInstance, NULL
  );


  //画像作成
  HDC dc = GetDC(hwnd);
  vscr.create_screen(200, 100,dc);
  ReleaseDC(hwnd, dc);


  int i = 0;

  //メモリに直接アクセス
  for (int i = 0; i < 200 * 100; i++) {
    vscr.get_mem_pixel()[i] = szl::cbit24_t(100, 255, 100);
  }

  //GDIオブジェクトとして扱う
  HPEN hp = CreatePen(PS_SOLID, 5, RGB(255, 0, 0));
  HPEN oldp = (HPEN)SelectObject(vscr.get_mem_dc(), hp);
  MoveToEx(vscr.get_mem_dc(), -10, -10,nullptr);
  LineTo(vscr.get_mem_dc(), 50, 80);
  SelectObject(vscr.get_mem_dc(), oldp);
  DeleteObject(hp);

  //二次元配列の形でアクセス
  vscr(0, 0) = szl::cbit24_t(255, 0, 0);
  vscr(0, 1) = szl::cbit24_t(255, 0, 0);
  vscr(0, 2) = szl::cbit24_t(0, 255, 0);
  vscr(0, 3) = szl::cbit24_t(0, 0, 255);

  if (hwnd == NULL) return 0;

  while (GetMessage(&msg, NULL, 0, 0)) DispatchMessage(&msg);
  return msg.wParam;
}

雑談

世の中にはたくさんの「簡単に使えるようにクラスにしてまとめました」があるが、そのほとんどは使い物にならないと私は考えている。もちろん自分で作ったものも含めて。

というのも、クラス化というのはある程度の目的や明確な用途があって初めて効果を発揮するものなので、普段自分が使う部分だけが使いやすくなるように作って使いやすくなったように見えてもそれは自分にとってだけなのだ。むしろ提示されたほうは学習コストが無駄にかかる。

コメントを残す

メールアドレスが公開されることはありません。

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)


この記事のトラックバックURL: