mieki256's diary



2022/09/06(火) [n年前の日記]

#1 [prog][windows] GDIでダブルバッファ描画

Windows10 x64 21H2上で、C/C++ (MinGW) と Windows API を使ってウインドウ描画ができるか実験中。

_昨日、GDI+を使ってpng画像を描画できるのか、 実験していたわけだけど。GDI+ は描画が遅いという話を見かけたので、GDI+ ではなく GDI だけを使って描画ができるならそのほうがいいのかなと…。そんなわけで、GDI でダブルバッファ描画をする方法を試してみた。まあ、png画像の使用を諦めて、bmp画像(bitmap画像)だけを利用することになってしまうけど…。

環境は、Windows10 x64 21H2 + MinGW (gcc 9.2.0)。MSYS2 + MinGW-w64 ではなくて、MinGW (+ MSYS) で実験してる。

以下を参考にさせてもらいました。

_ビットマップのマルチバッファリング【Windowsプログラミング研究所】

今回書いたサンプルの実行結果は以下。ちょっと解像度が荒いけど、雰囲気は伝わるかと…。ダブルバッファで描画しているので、ちらつきは感じられない。

リソースファイル。 :

リソースファイルは以下。このファイルは、windres を使って .o に変換する。

_res.rc
BALL_IMG        BITMAP  "ball.bmp"
BALL_MSK        BITMAP  "ball_mask.bmp"
BG_IMG          BITMAP  "bg2.bmp"
  • 3つのbmp画像ファイルを含めるように指定している。
  • 先頭に書かれている「BALL_IMG」「BALL_MSK」「BG_IMG」が、C言語のソース内で指定する識別名。

使用画像。 :

使用画像は以下。

_images.zip
  • ball.bmp : 赤いボール画像。透明にしたい部分は黒(#000000)にしておく。
  • ball_mask.bmp : ボール画像のマスク画像。不透明部分は黒(#000000)、透明部分は白(#ffffff)。アルファチャンネルを反転した感じの画像。
  • bg2.bmp : 背景用画像。タイル状に描画することを前提にしている。

一応、png画像も載せておきます。

ball.png

ball_mask.png

bg2.png

C言語のソースファイル。 :

C言語のソースファイルは以下。今回は GDI+ を使ってないので、C++ ではなく C言語で書くことができる。

_01_dblbuf.c
/* GDI Double buffer sample */

#include <windows.h>

#define SCRW        1280
#define SCRH        720

#define FPS         60

#define TM_COUNT1   101

#define LPCLASSNAME     TEXT("DoubleBufferSample")
#define WINDOW_TITLE    TEXT("Double Buffer Sample")


LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
{
  static HBITMAP hBitmap;
  static HBITMAP hBitmap_mask;
  static HBITMAP hBitmap_bg;
  static HBITMAP offScrnBitmap;
  static int wdw_w, wdw_h;
  static int imgw, imgh;
  static int bgw, bgh;
  static int bgx, bgy;
  static float x, y;
  static float dx, dy;

  switch (msg)
    {
    case WM_CREATE:
      // get window size
      {
        RECT rc;
        GetClientRect(hwnd, &rc);
        wdw_w = rc.right - rc.left;
        wdw_h = rc.bottom - rc.top;
      }

      // create off screen bitmap
      {
        HDC hdc;
        hdc = GetDC(hwnd);
        offScrnBitmap = CreateCompatibleBitmap(hdc, wdw_w, wdw_h);
        ReleaseDC(hwnd, hdc);    // GetDC() -> ReleaseDC()
      }

      // load bitmaps from resource
      hBitmap = LoadBitmap(((LPCREATESTRUCT)lp)->hInstance, TEXT("BALL_IMG"));
      hBitmap_mask = LoadBitmap(((LPCREATESTRUCT)lp)->hInstance, TEXT("BALL_MSK"));
      hBitmap_bg = LoadBitmap(((LPCREATESTRUCT)lp)->hInstance, TEXT("BG_IMG"));

      // get images size
      {
        BITMAP bp;

        // get ball image size
        GetObject(hBitmap, sizeof(BITMAP), &bp);
        imgw = bp.bmWidth;
        imgh = bp.bmHeight;

        // get bg image size
        GetObject(hBitmap_bg, sizeof(BITMAP), &bp);
        bgw = bp.bmWidth;
        bgh = bp.bmHeight;
      }

      // init work
      x = wdw_w / 2;
      y = wdw_h / 2;
      dx = (float)wdw_w / (float)FPS;
      dy = dx * 0.2;
      bgx = 0;
      bgy = 0;

      SetTimer(hwnd, TM_COUNT1, (int)(1000 / FPS), NULL);
      return 0;

    case WM_TIMER:
      // main loop
      x += dx;
      y += dy;

      if (x <= 0 || (x + imgw) >= wdw_w) dx *= -1;

      if (y <= 0 || (y + imgh) >= wdw_h) dy *= -1;

      bgx -= 1;
      bgy -= 1;

      if (bgx <= -(bgw)) bgx += bgw;

      if (bgy <= -(bgh)) bgy += bgh;

      if (0)
        {
          // clear background
          InvalidateRect(hwnd, NULL, TRUE);
        }
      else
        {
          // not clear background
          InvalidateRect(hwnd, NULL, FALSE);
        }

      break;

    case WM_PAINT:
      // draw window
      {
        HDC hdc, hBmpDC, hMemDC;
        PAINTSTRUCT ps;

        hdc = BeginPaint(hwnd, &ps);
        // hdc = GetDC(hwnd);

        // set off-screen bitmap
        hMemDC = CreateCompatibleDC(hdc);
        SelectObject(hMemDC, offScrnBitmap);

        // draw bg to off-screen
        hBmpDC = CreateCompatibleDC(hdc);
        SelectObject(hBmpDC, hBitmap_bg);
        {
          int by = bgy;

          while (by <= wdw_h)
            {
              int bx = bgx;

              while (bx <= wdw_w)
                {
                  BitBlt(hMemDC, bx, by, bgw, bgh, hBmpDC, 0, 0, SRCCOPY);
                  bx += bgw;
                }

              by += bgh;
            }
        }
        DeleteDC(hBmpDC);

        // draw mask to off-screen
        hBmpDC = CreateCompatibleDC(hdc);
        SelectObject(hBmpDC, hBitmap_mask);
        BitBlt(hMemDC, (int)x, (int)y, imgw, imgh, hBmpDC, 0, 0, SRCAND);
        DeleteDC(hBmpDC);

        // draw source image to off-screen
        hBmpDC = CreateCompatibleDC(hdc);
        SelectObject(hBmpDC, hBitmap);
        BitBlt(hMemDC, (int)x, (int)y, imgw, imgh, hBmpDC, 0, 0, SRCPAINT);
        DeleteDC(hBmpDC);

        // drawing to off-screen is finished

        // draw off-screen bitmap to window
        BitBlt(hdc, 0, 0, wdw_w, wdw_h, hMemDC, 0, 0, SRCCOPY);

        DeleteDC(hMemDC);     // Create*() -> DeleteDC()

        EndPaint(hwnd, &ps);
      }

      return 0;

    case WM_DESTROY:
      KillTimer(hwnd, TM_COUNT1);  // kill timer

      DeleteObject(offScrnBitmap);
      DeleteObject(hBitmap);
      DeleteObject(hBitmap_mask);
      DeleteObject(hBitmap_bg);
      PostQuitMessage(0);
      return 0;
    }

  return DefWindowProc(hwnd, msg, wp, lp);
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                   PSTR lpCmdLine, int nCmdShow)
{
  HWND hwnd;
  MSG msg;
  WNDCLASS winc;

  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    = LPCLASSNAME;

  if (!RegisterClass(&winc)) return -1;

  hwnd = CreateWindow(
    LPCLASSNAME, WINDOW_TITLE,
    WS_OVERLAPPEDWINDOW | WS_VISIBLE,
    CW_USEDEFAULT, CW_USEDEFAULT,
    SCRW, SCRH,
    NULL, NULL,
    hInstance, NULL
    );

  if (hwnd == NULL) return -1;

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

  return msg.wParam;
}

  • SetTimer() で指定したミリ秒が経過すると、その都度、WM_TIMER というメッセージが Windows から送られてくる。
  • WM_TIMER が来た時に、ボールやBG画像の表示位置を更新することで、アニメーションをさせている。
  • InvalidateRect() を呼べば、ウインドウの再描画を要求できる。(WM_PAINTメッセージが送られてくる。)
  • InvalidateRect(hwnd, NULL, TRUE); なら、背景を消去しつつ、ウインドウを再描画(するように要求)。
  • InvalidateRect(hwnd, NULL, FALSE); なら、背景を消去せずに、ウインドウを再描画(するように要求)。ダブルバッファ描画をする時はこちらを指定する。
  • WM_PAINT が来た時に、描画処理を行う。
  • WM_DESTROY が来たら、終了処理を行う。タイマーを殺して、bitmap を全部削除しておく。

Makefile。 :

Makefile は以下。.c や .rc から .o を生成後、.o群をリンクして .exe を生成する。

_Makefile
01_dblbuf.exe: 01_dblbuf.o res.o
        gcc 01_dblbuf.o res.o -o 01_dblbuf.exe -mwindows

01_dblbuf.o: 01_dblbuf.c
        gcc -c 01_dblbuf.c

res.o: res.rc ball.bmp ball_mask.bmp bg2.bmp
        windres res.rc res.o

.PHONY: clean
clean:
        rm -f *.exe
        rm -f *.o

コンパイル/ビルド。 :

全部で以下のファイルになる。
  • 01_dblbuf.c
  • ball.bmp
  • ball_mask.bmp
  • bg2.bmp
  • Makefile
  • res.rc
これを任意のディレクトリに置いて make と打てば、コンパイル/ビルドがされて、01_dblbuf.exe が生成される。

ちなみに、make clean と打てば、*.o や *.exe を削除してくれる。

余談。 :

DeleteDC() と ReleaseDC() の使い分けが分からなくて悩んだけれど、以下のページを眺めたら分かってきた。

_API 関数解説
  • GetDC() を使って取得したデバイスコンテキストは、ReleaseDC() を使って解放する。
  • Create*() を使って取得したデバイスコンテキストは、DeleteDC() を使って解放する。

余談その2。 :

今回のソースは、中括弧(?)を利用して、変数のスコープを極力狭めつつ書いてみた。

_変数のスコープ(C言語) - 超初心者向けプログラミング入門
_C/C++ 中括弧でローカルスコープを作る - 【はてな】ガットポンポコ

#2 [prog][windows] ビットマップ画像を描画するスクリーンセーバを書いてみた

せっかく C/C++ と GDI を使って、bitmap画像を描画、ダブルバッファ描画、アニメーションができたので…。以前、MinGW を使ってビルドが通ることを確認できた、Windows用スクリーンセーバのサンプルソースにも処理を反映させてみたい。

_スクリーンセーバのビルドの実験中
_Windows10にmingwをインストールし直した

環境は、Windows10 x64 21H2 + MinGW (gcc 9.2.0)。

ちなみに、MSYS2 + MinGW-w64 (gcc 12.2.0) では、Windows用スクリーンセーバはビルドできない。
_msys2 + mingw-w64でスクリーンセーバをビルドしようとしてハマった

さておき。今回書いたサンプルの実行結果は以下。解像度が荒くてアレだけど、雰囲気は伝わるかと…。

リソースファイル。 :

リソースファイルは以下。以前のサンプルの最初のほうに、bitmap画像群を追記した。このファイルは、windres を使って、.rc から .o に変換することになる。

_resource.rc
/* resource.rc */

#include <windows.h>
#include <scrnsave.h>

#define IDC_STATIC_ICON     2300
#define IDC_STATIC_TEXT     2301

/* bitmap */
IDI_BALL        BITMAP  "ball.bmp"
IDI_BALL_MSK    BITMAP  "ball_mask.bmp"
IDI_BG          BITMAP  "bg2.bmp"
IDI_PREVIEW     BITMAP  "scrsav_preview.bmp"

/* Config dialog */
DLG_SCRNSAVECONFIGURE DIALOG DISCARDABLE  0, 0, 220, 50
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "Setting Sample Screensaver"
 FONT 9, "Segoe UI"
BEGIN
  DEFPUSHBUTTON "OK", IDOK, 85, 30, 50, 14
  ICON          IDI_WARNING, IDC_STATIC_ICON, 6, 5, 20, 20
  LTEXT         "This is the template for the configuration dialog.", 
                IDC_STATIC_TEXT, 35, 12, 230, 12
END
※ このWeb日記システムの関係で、FONT指定行がおかしな状態で表示されてます。先頭の空白は削除してもらえればと…。


ヘッダーファイルは以下。でも、今回コレは使ってない気がする…。

_resource.h
#ifndef RESOURCE_H_
#define RESOURCE_H_

#define IDC_STATIC_ICON     2300
#define IDC_STATIC_TEXT     2301

#define IDI_BALL            2401
#define IDI_BALL_MSK        2402
#define IDI_BG              2403

#endif

使用画像。 :

使用画像は以下。4つのbmp画像を使っている。

_images.zip
  • ball.bmp : 赤いボール画像。透明部分は黒(#000000)にしておく。
  • ball_mask.bmp : ボールのマスク画像。不透明部分は黒(#000000)、透明部分は白(#ffffff)。
  • bg2.bmp : bg画像(背景画像)。タイル状に描画することを前提にしている。
  • scrsav_preview.bmp : プレビュー画像。スクリーンセーバの設定画面で表示されてる小さい窓の中に表示するための画像。サイズは 152 x 112 ドット。

一応、png画像も載せておきます。

ball.png

ball_mask.png

bg2.png

scrsav_preview.png

C言語のソースファイル。 :

_mgscrsv2.c
/* mingwscr.c */

#include <windows.h>
#include <tchar.h>
#include <scrnsave.h>
#include <time.h>
#include <stdlib.h>

#define FPS        60

#define ID_TIMER   101


/* screensaver main processing */
LRESULT WINAPI ScreenSaverProc(HWND hWnd, UINT msg,
                               WPARAM wParam, LPARAM lParam)
{
  static HBITMAP hBitmap;
  static HBITMAP hBitmap_mask;
  static HBITMAP hBitmap_bg;
  static HBITMAP hBitmap_preview;
  static HBITMAP offScrnBitmap;

  static int wdw_w, wdw_h;
  static int imgw, imgh;
  static int bgw, bgh;
  static int pvw, pvh;
  static float x, y, dx, dy, bgx, bgy;
  int bx, by;

  switch (msg)
    {
    case WM_CREATE:
      /* initialize */

      // get window size
      {
        RECT rc;

        GetClientRect(hWnd, &rc);
        wdw_w = rc.right - rc.left;
        wdw_h = rc.bottom - rc.top;
      }

      // create off-screen bitmap
      {
        HDC hdc;

        hdc = GetDC(hWnd);
        offScrnBitmap = CreateCompatibleBitmap(hdc, wdw_w, wdw_h);
        ReleaseDC(hWnd, hdc);
      }

      // load bitmaps
      hBitmap = LoadBitmap(((LPCREATESTRUCT)lParam)->hInstance, TEXT("IDI_BALL"));
      hBitmap_mask = LoadBitmap(((LPCREATESTRUCT)lParam)->hInstance, TEXT("IDI_BALL_MSK"));
      hBitmap_bg = LoadBitmap(((LPCREATESTRUCT)lParam)->hInstance, TEXT("IDI_BG"));
      hBitmap_preview = LoadBitmap(((LPCREATESTRUCT)lParam)->hInstance, TEXT("IDI_PREVIEW"));

      // get images size
      {
        BITMAP bp;

        // get ball image size
        GetObject(hBitmap, sizeof(BITMAP), &bp);
        imgw = bp.bmWidth;
        imgh = bp.bmHeight;

        // get bg image size
        GetObject(hBitmap_bg, sizeof(BITMAP), &bp);
        bgw = bp.bmWidth;
        bgh = bp.bmHeight;

        // get preview image size
        GetObject(hBitmap_preview, sizeof(BITMAP), &bp);
        pvw = bp.bmWidth;
        pvh = bp.bmHeight;
      }

      // init work
      x = wdw_w / 2;
      y = wdw_h / 2;
      dx = (float)wdw_w / (float)FPS;
      dy = dx * 0.5;
      bgx = 0;
      bgy = 0;

      // create timer
      SetTimer(hWnd, ID_TIMER, (int)(1000 / FPS), NULL);
      break;

    case WM_TIMER:
      /* main loop */

      // update ball position
      x += dx;
      y += dy;

      if (x <= 0 || x + imgw >= wdw_w) dx *= -1;

      if (y <= 0 || y + imgh >= wdw_h) dy *= -1;

      // update bg position
      bgx -= 2;
      bgy -= 2;

      if (bgx <= -bgw) bgx += bgw;

      if (bgy <= -bgh) bgy += bgh;

      // draw bitmap

      if (wdw_w >= 160)
        {
          // draw fullscreen

          HDC hdc, hBmpDC, hMemDC;

          hdc = GetDC(hWnd);

          // set off-screen bitmap
          hMemDC = CreateCompatibleDC(hdc);
          SelectObject(hMemDC, offScrnBitmap);

          // draw bg to off-screen
          hBmpDC = CreateCompatibleDC(hdc);
          SelectObject(hBmpDC, hBitmap_bg);
          {
            int by = bgy;

            while (by <= wdw_h)
              {
                int bx = bgx;

                while (bx <= wdw_w)
                  {
                    BitBlt(hMemDC, bx, by, bgw, bgh, hBmpDC, 0, 0, SRCCOPY);
                    bx += bgw;
                  }

                by += bgh;
              }
          }
          DeleteDC(hBmpDC);

          // draw ball mask to off-screen
          hBmpDC = CreateCompatibleDC(hdc);
          SelectObject(hBmpDC, hBitmap_mask);
          BitBlt(hMemDC, (int)x, (int)y, imgw, imgh, hBmpDC, 0, 0, SRCAND);
          DeleteDC(hBmpDC);

          // draw ball source to off-screen
          hBmpDC = CreateCompatibleDC(hdc);
          SelectObject(hBmpDC, hBitmap);
          BitBlt(hMemDC, (int)x, (int)y, imgw, imgh, hBmpDC, 0, 0, SRCPAINT);
          DeleteDC(hBmpDC);

          // drawing to off-screen is finished

          // draw off-screen bitmap to window
          BitBlt(hdc, 0, 0, wdw_w, wdw_h, hMemDC, 0, 0, SRCCOPY);

          DeleteDC(hMemDC);      // Create*() -> DeleteDC()
          ReleaseDC(hWnd, hdc);  // GetDC() -> ReleaseDC()
        }
      else
        {
          // draw preview
          HDC hdc, hBmpDC;
          hdc = GetDC(hWnd);
          hBmpDC = CreateCompatibleDC(hdc);
          SelectObject(hBmpDC, hBitmap_preview);
          BitBlt(hdc, 0, 0, pvw, pvh, hBmpDC, 0, 0, SRCCOPY);
          DeleteDC(hBmpDC);      // Create*() -> DeleteDC()
          ReleaseDC(hWnd, hdc);  // GetDC() -> ReleaseDC()
        }

      break;

    case WM_DESTROY:
      KillTimer(hWnd, ID_TIMER);  // kill timer

      // delete bitmap
      DeleteObject(offScrnBitmap);
      DeleteObject(hBitmap);
      DeleteObject(hBitmap_mask);
      DeleteObject(hBitmap_bg);
      DeleteObject(hBitmap_preview);

      PostQuitMessage(0);
      break;

    default:
      break;
    }

  return DefScreenSaverProc(hWnd, msg, wParam, lParam);
}

/* Processing of dialog boxes for configuration */
BOOL WINAPI ScreenSaverConfigureDialog(HWND hDlg, UINT msg,
                                       WPARAM wParam, LPARAM lParam)
{
  switch (msg)
    {
    case WM_INITDIALOG:
      MessageBox(
        hDlg,
        _T("This screen saver has no configuration options."),
        _T("Sample Screensaver by using MinGW"),
        MB_OK | MB_ICONWARNING);
      EndDialog(hDlg, IDOK);
      return TRUE;

    case WM_COMMAND:
      switch (LOWORD(wParam))
        {
        case IDOK:
          EndDialog(hDlg, IDOK);
          return TRUE;

        case IDCANCEL:
          EndDialog(hDlg, IDCANCEL);
          return TRUE;
        }

      return FALSE;
    }

  return FALSE;
}

/* Register non-standard window classes required by dialog boxes for configuration */
BOOL WINAPI RegisterDialogClasses(HANDLE hInst)
{
  return TRUE;
}

Makefile。 :

Makefile は以下。

_Makefile
mgscrsv2.scr: mgscrsv2.o resource.o
        gcc mgscrsv2.o resource.o -o mgscrsv2.scr -mwindows -lscrnsave

mgscrsv2.o: mgscrsv2.c
        gcc -c mgscrsv2.c

resource.o: resource.rc resource.h ball.bmp ball_mask.bmp bg2.bmp scrsav_preview.bmp
        windres resource.rc -o resource.o

.PHONY: clean
clean:
        rm -f *.scr
        rm -f *.o
  • Windows用プログラムなので、-mwindows を渡している。
  • スクリーンセーバを作るので、-lscrnsave を渡して「libscrnsave.a (or libscrnsavw.a) をリンクせよ」と指定している。

コンパイル/ビルド。 :

全部で以下のファイルになる。
  • ball.bmp
  • ball_mask.bmp
  • bg2.bmp
  • Makefile
  • mgscrsv2.c
  • resource.h
  • resource.rc
  • scrsav_preview.bmp
これらを、同じディレクトリに置いて make と打てば、コンパイル/ビルドされて mgscrsv2.scr が生成される。

ちなみに、make clean と打てば、*.o や *.scr を削除できる。

動作確認。 :

エクスプローラ等で mgscrsv2.scr を右クリック。
  • 「Test」を選べば、フルスクリーン表示時の動作確認ができる。
  • 「構成」を選べば、スクリーンセーバの設定画面で「設定」をクリックした時の動作を確認できる。

正しく動作しているようなら、スクリーンセーバを本来置くべき場所に .scr をコピーして、スクリーンセーバとして動作するのか確認する。

  • Windows が 64bit版、スクリーンセーバが32bit版なら、C:\Windows\SysWOW64\ に .scr をコピー。
  • Windows が 64bit版、スクリーンセーバが64bit版なら、C:\Windows\System32\ に .scr をコピー。
  • Windows が 32bit版なら、C:\Windows\System32\ 以下に .scr をコピー。

今回は MinGW を使ってビルドしたので、生成された .scr は32bit版プログラム。

Windows10 64bit + 32bit版プログラムなので ―― C:\Windows\SysWOW64\ に .scr をコピーして動作確認することになる。

試してみたところ、スクリーンセーバとして、それらしく動いてくれた。

ちなみに、作業に使ったメインPCとは別のPC(Windows10 x64 21H2)にコピーして動作確認したけれど、そちらでもちゃんと動いてくれた。

覚書。 :

Windows用のプログラムをC/C++で書く場合、まずはメイン関数が必要になる。

  • コンソールプログラム(DOS窓上で使う感じのプログラム)なら、main() という名前の関数が必要。
  • GUIアプリなら、WinMain() という名前の関数が必要。

GUIアプリの場合、えてして他にも、WndProc() とか WindowProc() という名前の関数も書くことになるらしい。これはウィンドウプロシージャと呼ばれる関数で、Windowsから送られてきたメッセージを処理する関数なのだとか。

そして、スクリーンセーバはGUIアプリに分類される。だから本来は、WinMain() という関数を書かなきゃいけない。

ところが、前述のスクリーンセーバのサンプルソースを眺めても、WinMain関数なんて書いてない。…どうなってるの?

なんでも、「スクリーンセーバの処理って大体似た感じになるよね」「WinMain() や WndProc() ってフツーは同じ処理を書くよね」ということで、そのあたりは scrnsave.lib (または scrnsavw.lib。MinGW の場合は libscrnsave.a or libscrnsavw.a) の中に既に入っているらしい。そして、「そこから呼び出される3つの関数だけ書けばスクリーンセーバを作れますよ」ということになっている模様。

そんなわけで、scrnsave.h と scrnsav*.lib (libscrnsav*.a) を使う場合は、以下の3つの関数の中身だけ書けばスクリーンセーバが作れてしまう。
  • ScreenSaverProc()
  • ScreenSaverConfigureDialog()
  • RegisterDialogClasses()

とは言え、GDI / GDI+ / DirectX / OpenGL等々を使わないと画面の描画はできないので、そのあたりの知識が必要になってしまうあたり、まだハードルが高い感じはする…。もっと簡単に作れるようにできないものか…。

余談。プレビューモードのウインドウサイズ。 :

今回書いたサンプルでは、プレビューモード(スクリーンセーバ設定画面の小窓に描画する処理)をどのように処理するかで悩んでしまった。

これがもし、座標を指定して線を描画する系のスクリーンセーバなら、フルスクリーン表示だろうと、プレビューモードだろうと、ウインドウサイズに合わせて座標値を計算して描画すればいいだけだから対応するのは難しくなさそうだけど…。

しかし、bitmap画像を描画する場合、bitmapは等倍で描画されて拡大縮小してくれないわけで…。フルスクリーン表示に合わせて座標値等を決めてしまうと、プレビューモード時は変な表示になってしまう。

一応、GDIにも、処理速度はさておいて拡大縮小描画する関数があるらしいので…。あらかじめ描画サイズを決めておいて、空のbitmapにアレコレ描いてから、それをディスプレイ一杯に拡大描画、もしくはプレビューモードの小さいウインドウに縮小描画すれば対応できるだろうけど…。

そのあたり面倒臭くなってしまったので、今回は、プレビューモード用のbitmap画像を一枚用意して、おそらくプレビューモードだろうと思われる時は、そのbitmap画像だけ描画してお茶濁しすることにした。

ちなみに、Windows10上で、スクリーンセーバ設定画面の小窓のサイズを測ってみたら、152 x 112 だった。巷のスクリーンショット等を調べた感じでは、Windows95 の頃からそのサイズだった模様。

そんなわけで、ウインドウサイズを取得して、もし横幅が160ドット以下なら「これはプレビューモードで呼び出されているのだろう」と判断して処理をするようにしてみた。こういうやり方が妥当なのかどうかは分からんけど…。

以上、1 日分です。

過去ログ表示

Prev - 2022/09 - Next
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30

カテゴリで表示

検索機能は Namazu for hns で提供されています。(詳細指定/ヘルプ


注意: 現在使用の日記自動生成システムは Version 2.19.6 です。
公開されている日記自動生成システムは Version 2.19.5 です。

Powered by hns-2.19.6, HyperNikkiSystem Project