スポンサーリンク

コンソールアプリケーションでデバッグ用のウィンドウを表示-3-RGB,RGBA対応

RGB画像だけではなく、RGBAにも対応する。

まずはウィンドウの本体のクラスを書く。これはデータのポインタを受け取ったら背景画像とブレンドして表示用画像を作成する。

CImageViewWindow.hpp

#pragma once


#include "gwindbg.hpp"
#include <mutex>
 
//! @brief DIBを扱う
struct DIBData {
  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;           //!< 画像の高さ(ピクセル,自然数)

  int m_bitcount;
  DIBData() {
    DeleteDC(m_hMemDC);
    DeleteObject(m_hBitmap);
  }
  ~DIBData() {
    m_hBitmap = nullptr;
    m_hMemDC = nullptr;
    m_lpPixel = nullptr;
    m_width = 0;
    m_height = 0;
    m_bitcount = 0;
  }
  void create(int width, int height, int bitcount) {
    if (width != m_width || height != m_height) {
      if (m_hBitmap) {
        DeleteObject(m_hBitmap);
        m_hBitmap = nullptr;
      }
      if (m_hMemDC) {
        DeleteDC(m_hMemDC);
        m_hMemDC = nullptr;
      }
      //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 = bitcount;
      m_bmpInfo.bmiHeader.biCompression = BI_RGB;

      HDC hdc = GetDC(nullptr);
      m_hBitmap = CreateDIBSection(hdc, &m_bmpInfo, DIB_RGB_COLORS, (void**)&m_lpPixel, nullptr, 0);
      m_hMemDC = CreateCompatibleDC(hdc);
      SelectObject(m_hMemDC, m_hBitmap);
      ReleaseDC(nullptr, hdc);

      m_width = width;
      m_height = height;
      m_bitcount = bitcount;
    }
  }
};
//! @brief 画素の値を透明度でブレンドする
//! @param [in] back 背景画像の色のr,g,bのいずれかの値
//! @param [in] data 塗りたい色のr,g,bのいずれかの値
//! @param [in] dataalpha 塗りたい色の透明度
//データの色の割合

unsigned
char pixel_alpha_blend( unsigned char back, unsigned char data, unsigned char dataalpha ) { float weight_data = dataalpha / 255.0f;//データの色の割合 float weight_bkc = 1.0 - weight_data;//背景色の割合 int ret = data * weight_data + back * weight_bkc; if (data > 255)data = 255; return ret; }
//! @brief RGB,RGBAの画像を表示するウィンドウのクラス。
class CImageViewWindow : public GraphicDebugWindow {

  DIBData dibdata_background; //!< 背景画像
  DIBData dibdata_result;     //!< 表示画像

  int m_magnification;    //!< 表示倍率
  unsigned char* m_img_p; //!< 画像データへのポインタ
  int m_img_width;        //!< 画像データの幅
  int m_img_height;       //!< 画像データの高さ
  int m_pixelbyte;        //!< 画像データのピクセルのバイト数。RGBなら3,RGBAなら4

  std::mutex m_mainmuitex;

public:
  CImageViewWindow() {}

  //! @brief 背景(透明を表現するチェスボードのような画像)を作成する
  void create_background_image() {

    const int blocksize = 10;

    if (get_hwnd() == nullptr)
      return;
    RECT rect;
    GetClientRect(get_hwnd(), &rect);

    //現在の背景画像が、画面サイズより小さかったら作り直す。
    //画面サイズより大きな背景画像が既に作成済みならそのまま使用する
    if (dibdata_background.m_width < rect.right || dibdata_background.m_height < rect.bottom) {

      dibdata_background.create(rect.right, rect.bottom, 32);
      int bkwidth = dibdata_background.m_width;
      int bkheight = dibdata_background.m_height;

      //背景画像の作成(チェスボードのような画像)
      for (int x = 0; x < bkwidth; x++) {
        for (int y = 0; y < bkheight; y++) {

          int pos = y * bkwidth + x;

          bool xb = (bool)((x / blocksize) % 2);
          bool yb = (bool)((y / blocksize) % 2);

          if (xb == yb) {
            dibdata_background.m_lpPixel[pos] = RGB(100, 100, 100);
          }
          else {
            dibdata_background.m_lpPixel[pos] = RGB(200, 200, 200);
          }

        }
      }
    }

  }
  //! @brief ウィンドウのサイズが変更されたときの関数
  void change_client_size() {
    create_background_image();

    //結果画像のサイズ変更(クライアント領域に合わせる)
    RECT rect;
    HWND hwnd = get_hwnd();
    if (hwnd == nullptr)
      return;
    GetClientRect(hwnd, &rect);
    dibdata_result.create(rect.right, rect.bottom, 32);

    //画像書き込み
    m_mainmuitex.lock();

    //背景を結果にコピー
    BitBlt(
      dibdata_result.m_hMemDC, 0, 0, dibdata_background.m_width, dibdata_background.m_height,
      dibdata_background.m_hMemDC, 0, 0, SRCCOPY);

    if (m_img_p) {
      for (int x = 0; x < m_img_width; x++) {
        for (int y = 0; y < m_img_height; y++) {
          int pos_src_pix = (y * m_img_width + x);// 元画像の画素の座標(ピクセル単位)

          {
            // 元画像の画素の色を取得
            unsigned int sr = m_img_p[pos_src_pix*m_pixelbyte + 0];
            unsigned int sg = m_img_p[pos_src_pix*m_pixelbyte + 1];
            unsigned int sb = m_img_p[pos_src_pix*m_pixelbyte + 2];
            unsigned int sa = m_img_p[pos_src_pix*m_pixelbyte + 3];

            // 書き込み先のピクセル座標の開始地点x,y
            int pos_dst_x_offset = x * m_magnification;
            int pos_dst_y_offset = y * m_magnification;
            if (pos_dst_x_offset >= dibdata_result.m_width)continue;
            if (pos_dst_y_offset >= dibdata_result.m_height)continue;

            int resultwidth = dibdata_result.m_width;
            unsigned char dr, dg, db;//表示ピクセル色
            for (int xblock = 0; xblock < m_magnification; xblock++) {
              for (int yblock = 0; yblock < m_magnification; yblock++) {
                int dx = pos_dst_x_offset + xblock;
                int dy = pos_dst_y_offset + yblock;
                if (dx >= dibdata_result.m_width)continue;
                if (dy >= dibdata_result.m_height)continue;

                int pos_dst_pix = (dy * resultwidth + dx);
                int pos_bk_pix = (dy * dibdata_background.m_width + dx);

                //透明度付きの場合だけ、背景色との混合を行う
                if (m_pixelbyte == 4) {
                  COLORREF bkcolor = dibdata_background.m_lpPixel[pos_bk_pix];//下地の色を取得
                  dr = pixel_alpha_blend(GetRValue(bkcolor), sr, sa);
                  dg = pixel_alpha_blend(GetGValue(bkcolor), sg, sa);
                  db = pixel_alpha_blend(GetBValue(bkcolor), sb, sa);
                }
                else {
                  dr = sr;
                  dg = sg;
                  db = sb;
                }
                dibdata_result.m_lpPixel[pos_dst_pix] = RGB(dr, dg, db);
              }
            }
          }
        }
      }
    }

    m_mainmuitex.unlock();

  }
  //! @brief 画面に画像を設定
  //! @param [in] width 画像幅
  //! @param [in] height 画像高さ
  //! @param [in] magnification 表示倍率。小さすぎる画像を大きく表示できる
  //! @param [in] p 画像データ。RGBRGBRGB...
  //! @param [in] pixelbyte 1ピクセルのバイト数 3か4
  //! @return なし
  void display(int width, int height, int magnification, unsigned char* p, int pixelbyte) {

    m_img_width = width;
    m_img_height = height;
    m_img_p = p;
    m_magnification = magnification;
    m_pixelbyte = pixelbyte;

    change_client_size();

    HWND hwnd = get_hwnd();
    if (hwnd == nullptr)
      return;
    InvalidateRect(hwnd, nullptr, TRUE);


  }
  virtual LRESULT WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp)override
  {
    PAINTSTRUCT ps;
    HDC hdc;
    switch (msg)
    {
    case WM_SIZE:
      change_client_size();
      InvalidateRect(hWnd, nullptr, TRUE);
      break;
    case WM_SIZING:
      change_client_size();
      InvalidateRect(hWnd, nullptr, TRUE);
      break;
    case WM_PAINT:
      hdc = BeginPaint(hWnd, &ps);

      m_mainmuitex.lock();
      //DIBの内容をウィンドウに表示
      if (dibdata_background.m_hMemDC) {
        RECT rect;
        GetClientRect(hWnd, &rect);

        BitBlt(
          hdc, 0, 0,
          rect.right, rect.bottom,
          dibdata_result.m_hMemDC, 0, 0, SRCCOPY);
      }
      m_mainmuitex.unlock();
      EndPaint(hWnd, &ps);
      break;
    case WM_DESTROY:
      PostQuitMessage(0);
      return 0;
    }
    return DefWindowProc(hWnd, msg, wp, lp);
  }

};

gwindbg.hpp

GraphicDebugWindowはほとんど変わらないが以下のように変更する。

open関数はウィンドウ生成をスレッドに投げてしまうため、open関数終了時点でウィンドウ生成が完了していない可能性がある。このためopen関数呼び出し元で、「ウィンドウがまだないのに画像を設定する」という構図になる可能性がある。それを防ぐために、スレッド側でCreateWindowが完了するまではopen関数内で待機したい。このために、m_hwndがnullptrの間ループするのだが、空ループと同じようなもののため、最適化で消えてしまう可能性がある。そこでm_hwndにvolatileを付けて最適化を抑止する。さらにUpdateWindowはちゃんと描画が行われるまで待機する。

#pragma once

#include <Windows.h>
#include <thread>

class GraphicDebugWindow {

  //! @brief WNDCLASSに設定するウィンドウプロシージャ
  static LRESULT CALLBACK StaticWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
  {
    //CreateWindowの最後の引数で渡したthisポインタを取得
    GraphicDebugWindow* This = (GraphicDebugWindow*)GetWindowLongPtr(hWnd, GWLP_USERDATA);
    if (!This) {//取得できなかった(ウィンドウ生成中)場合
      if (message == WM_CREATE) {
        This = (GraphicDebugWindow*)((LPCREATESTRUCT)lParam)->lpCreateParams;
        if (This) {
          SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)This);
          return This->WndProc(hWnd, message, wParam, lParam);
        }
      }
    }
    else {//取得できた場合(ウィンドウ生成後)
      return This->WndProc(hWnd, message, wParam, lParam);
    }
    return DefWindowProc(hWnd, message, wParam, lParam);
  }

  //メインループのスレッド
  std::unique_ptr<std::thread> m_mainloop;

  HINSTANCE hInstance;
  
  //ウィンドウができるまでループしたいが、m_hwndがnullptrである間ループするだけだと
//
最適化で消えてしまう可能性があるので、volatileを付けておく
volatile HWND m_hwnd;
protected:

public:

  GraphicDebugWindow() {}

  bool registerWindowClass() {
    //インスタンスハンドルの取得
    hInstance = GetModuleHandle(0);

    WNDCLASS winc;

    winc.style = CS_HREDRAW | CS_VREDRAW;
    winc.lpfnWndProc = StaticWndProc;
    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("SZL_TEST_GUI_WINDOW");

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

    return true;
  }

  //! @brief スレッドの内容
  int MainLoop(const TCHAR* title, int posx, int posy, int width, int height) {

    //メッセージループはウィンドウを生成したスレッドに属していなければいけないので
    //CreateWindowの時点でスレッドに入っていなければならないのでここに書く
    m_hwnd = CreateWindow(
      TEXT("SZL_TEST_GUI_WINDOW"), title,
      WS_OVERLAPPEDWINDOW | WS_VISIBLE,
      posx, posy, width, height, NULL, NULL,
      hInstance, this
    );

    if (m_hwnd == NULL) {
      return 0;
    }

    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0)) {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }

    //スレッドから抜けるときはウィンドウハンドルをnullptrにする
    m_hwnd = nullptr;

    return msg.wParam;

  }

  //! @brief ウィンドウ作成
  bool open(const TCHAR* title, int posx, int posy, int width, int height) {

    if (registerWindowClass() == false)
      return false;

    m_mainloop.reset(//! @brief スレッド作成
      new std::thread(
        &GraphicDebugWindow::MainLoop,
        this,
        title,
        posx,
        posy,
        width,
        height
      ));

    // 窓が生成されるまで待つ
    while (m_hwnd == nullptr);
    UpdateWindow(m_hwnd);

return true; } //! @brief ウィンドウを閉じたいときに呼び出す void close() { if (m_hwnd) { SendMessage(m_hwnd, WM_DESTROY, 0, 0); } if (m_mainloop) { if (m_mainloop->joinable()) { m_mainloop->join(); } m_mainloop.reset(); } } void join() { if (m_mainloop) { if (m_mainloop->joinable()) { m_mainloop->join(); } m_mainloop.reset(); } } //! @brief 外にウィンドウハンドルを公開 HWND get_hwnd() { return m_hwnd; } virtual ~GraphicDebugWindow() { close(); } //! @brief メンバ関数版ウィンドウプロシージャ virtual LRESULT WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { PAINTSTRUCT ps; HDC hdc; switch (msg) { case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProc(hWnd, msg, wp, lp); } };

使用例

#include <iostream>
#include<vector>

#include "CImageViewWindow.hpp"

#pragma warning(disable:4996)


int main()
{
  CImageViewWindow gw;

#if 0
  gw.open(TEXT("2×3×RGB画像を表示"), 100, 100, 300, 300);

  typedef unsigned char rgb_t[3];

  rgb_t texdata[2*3];
  texdata[0][0] = 0;
  texdata[0][1] = 255;
  texdata[0][2] = 0;

  texdata[1][0] = 255;
  texdata[1][1] = 0;
  texdata[1][2] = 0;

  texdata[2][0] = 255;
  texdata[2][1] = 0;
  texdata[2][2] = 0;

  texdata[3][0] = 0;
  texdata[3][1] = 255;
  texdata[3][2] = 0;

  texdata[4][0] = 0;
  texdata[4][1] = 255;
  texdata[4][2] = 0;

  texdata[5][0] = 255;
  texdata[5][1] = 0;
  texdata[5][2] = 0;

  gw.display(2, 3, 57, &texdata[0][0], 3);

#else
  gw.open(TEXT("2×3×RGBA画像を表示"), 100, 100, 300, 300);

  typedef unsigned char rgba_t[4];

  rgba_t texdata[2 * 3];
  texdata[0][0] = 0;
  texdata[0][1] = 255;
  texdata[0][2] = 0;
  texdata[0][3] = 0;

  texdata[1][0] = 255;
  texdata[1][1] = 0;
  texdata[1][2] = 0;
  texdata[1][3] = 50;

  texdata[2][0] = 255;
  texdata[2][1] = 0;
  texdata[2][2] = 0;
  texdata[2][3] = 100;

  texdata[3][0] = 0;
  texdata[3][1] = 255;
  texdata[3][2] = 0;
  texdata[3][3] = 150;

  texdata[4][0] = 0;
  texdata[4][1] = 255;
  texdata[4][2] = 0;
  texdata[4][3] = 200;

  texdata[5][0] = 255;
  texdata[5][1] = 0;
  texdata[5][2] = 0;
  texdata[5][3] = 255;

  gw.display(2, 3, 57, &texdata[0][0], 4);

#endif


  gw.join();
}

実行結果

コメントを残す

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

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


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