スポンサーリンク

| キーワード:

DIBの4バイト境界について。あとDIBの保存。

BMPファイルは1スキャンラインを4バイト境界に合わせなければならないという所を前回確認した。

実はDIB Sectionでアクセスできる画素データは、この4バイト境界が考慮されている。つまり24ビットDIBで幅が4バイトの倍数にない場合、画像内に4バイト境界に合わせるためのダミーデータが点在している。

これはこれでいいのだが、画素データを直接編集したいだけの場合は注意が必要で、普通に画像幅×y座標+x座標の式で目的の画素のアドレスを求められないことになる。といっても画素幅が画素バイトになるだけなのだが。

4の倍数ということは素因数分解したときに2が二つ以上出てくるということで、この条件を満たす数はかなり多い。例えば100は2^2×5^2なので、100の倍数は全て4バイト境界の条件を満たす。だから実験として300×300とかの画像で確認などすると正常に動作してしまい、考慮漏れとなる可能性がある。言うまでもないことだが256や512など2のn乗系の数字は0,1,2以外全部当てはまる。

実験コード

このプログラムでは24bit DIBを作り画面に表示して保存している。画素のアドレスはCalcPosで行っているが、移動方法は

1スキャンラインバイト数×y座標 +1画素バイト数×x座標

としている。

なお1スキャンラインバイト数の計算とBMPファイルへの書き出しは前回作成したBMPwriter.hppの関数を使っている。

#include<windows.h>

#include "BMPwriter.hpp"

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

//24bit DIB 作成
void createMemDC_24(HDC hdc,int width,int height) {
  //DIBの情報を設定する
  bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
  bmpInfo.bmiHeader.biWidth = width;
  bmpInfo.bmiHeader.biHeight = -height; //-を指定しないと上下逆になる
  bmpInfo.bmiHeader.biPlanes = 1;
  bmpInfo.bmiHeader.biBitCount = 24;
  bmpInfo.bmiHeader.biCompression = BI_RGB;

  hBitmap = CreateDIBSection(hdc, &bmpInfo, DIB_RGB_COLORS, (void**)& lpPixel, nullptr, 0);
  hMemDC = CreateCompatibleDC(hdc);

  SelectObject(hMemDC, hBitmap);

}
//1スキャンラインのバイト数(何バイト移動すれば次の行へいけるのか)を計算
int get_scanline_bytes(const int width,const int bitcount) {
  return width * (bitcount / 8) + getbmp4line_err(width, bitcount); //getbmp4line_errはBMPwriter.hpp内

}

//! @brief 与えられた座標のアドレスを返す
//! @param [in] head 画像の先頭アドレス
//! @param [in] width 画像幅(画素数)
//! @param [in] height 画像高さ(画素数)
//! @param [in] x x座標
//! @param [in] y y座標
//! @param [in] bitcount 1画素のビット数
//! @return x,yの要素のアドレス
unsigned char* calcPos(
  unsigned char* head, 
  int width, int height, 
  int x, int y, int bitcount) {

  int pixelbyte = bitcount / 8;

  int scanlinebyte = get_scanline_bytes(width, bitcount);


  unsigned char* yhead = head + scanlinebyte * y;

  return yhead + x * pixelbyte;

}
//! @brief 画像を灰色で塗りつぶす
//! @param [in] 対象となる画像の先頭アドレス
//! @param [in] width 画像幅(画素数)
//! @param [in] height 画像高さ(画素数)
//! @param [in] bitcount DIBの1画素のビット数
//! @return なし
void clear_data(unsigned char* img, int width, int height, int bitcount) {
  int scanlinebyte = get_scanline_bytes(width, bitcount);
  int size = scanlinebyte * height;
  for (size_t i = 0; i < size; i++) {
    img[i] = 200;
  }
}

//! @brief 画像の真ん中に線を引く
//! @param [in] 対象となる画像の先頭アドレス
//! @param [in] width 画像幅(画素数)
//! @param [in] height 画像高さ(画素数)
//! @param [in] bitcount DIBの1画素のビット数
//! @return なし
void drawline_data(unsigned char* img, int width, int height,int bitcount) {
  for (int y = 0; y < height; y++) {
    for (int x = 0; x < width; x++) {
      unsigned char* pos = calcPos(img, width, height, x, y, bitcount);
      if (x == width / 2) {
        pos[0] = 255;
        pos[1] = 0;
        pos[2] = 0;
      }
    }
  }
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
{
  PAINTSTRUCT ps;
  static HPEN hpen1;
  static HPEN hpen2;
  static HPEN open;
  int iwidth = 4*30+1; //4の倍数にならない値を作成
  int iheight = 80;

  switch (msg) {
  case WM_CREATE:
    {
      HDC hdc = GetDC(hwnd);
      createMemDC_24(hdc, iwidth, iheight);//24bit DIB 作成
      ReleaseDC(hwnd, hdc);

      //DIBを灰色でクリア
      clear_data((unsigned char*)lpPixel, iwidth, iheight, 24);

      hpen1 = CreatePen(PS_SOLID, 5, RGB(255, 0, 0));// 何かしら描画する
      hpen2 = CreatePen(PS_SOLID, 3, RGB(0, 0, 255));
      open = (HPEN)SelectObject(hMemDC, hpen1);
      MoveToEx(hMemDC, 0, 0, nullptr);
      LineTo(hMemDC, 20, 50);

      SelectObject(hMemDC, hpen2);
      MoveToEx(hMemDC, 0, 0, nullptr);
      LineTo(hMemDC, 50, 20);

      SelectObject(hMemDC, open);

      drawline_data((unsigned char*)lpPixel, iwidth, iheight,24);//縦に一本線を入れる。座標計算が間違っていたら斜めの線になる

//bmpファイルに保存。lpPixelは4byte境界が考慮されているのでそのまま出力できる。bmp_writeはBMPwriter.hpp内 bmp_write(L"c:\\data\\a.bmp", lpPixel, iwidth, iheight, 24);
} return 0; case WM_PAINT: { HDC hdc = BeginPaint(hwnd, &ps); BitBlt(hdc, 0, 0, iwidth, iheight, hMemDC, 0, 0, SRCCOPY);// 画像を画面に表示 EndPaint(hwnd, &ps); } return 0; case WM_DESTROY: DeleteObject(hpen1); DeleteObject(hpen2); PostQuitMessage(0); return 0; } return DefWindowProc(hwnd, msg, wp, lp); }
//! @brief エントリポイント
//! @param [in] hInstance 現在のインスタンスハンドル
//! @param [in] hPrevInstance 以前のインスタンスハンドル
//! @param [in] lpCmdLine コマンドライン引数の文字列
//! @param [in] ウィンドウの表示状態
int WINAPI WinMain(
  HINSTANCE hInstance, 
  HINSTANCE hPrevInstance,
  PSTR lpCmdLine, 
  int nCmdShow) {

  WNDCLASSEX wc;
  wc.cbSize = sizeof(WNDCLASSEX);
  wc.style = CS_HREDRAW | CS_VREDRAW;
  wc.lpfnWndProc = WndProc;
  wc.cbClsExtra = 0;
  wc.cbWndExtra = 0;
  wc.hInstance = hInstance;
  wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
  wc.hCursor = LoadCursor(NULL, IDC_ARROW);
  wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
  wc.lpszMenuName = NULL;
  wc.lpszClassName = TEXT("SZLCODE");
  wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);

  if (!RegisterClassEx(&wc)) 
    return 0;

  const int top = CW_USEDEFAULT;
  const int left = CW_USEDEFAULT;
  const int width = 300;
  const int height = 200;

  HWND hwnd;
  hwnd = CreateWindowEx(
    WS_EX_COMPOSITED,
    TEXT("SZLCODE"), TEXT("win32api example"),
    WS_OVERLAPPEDWINDOW | WS_VISIBLE,
    top, left,
    width, height,
    NULL, NULL,
    hInstance, NULL
  );

  if (hwnd == NULL) return 0;

  //


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

  return (int)msg.wParam;
}

コメントを残す

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

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


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