2022/09/05(月) [n年前の日記]
#1 [prog][windows] GDI+でpng画像を読み込んで描画
Windows10 x64 21H2上で、C/C++ と Windows APIを使って画像を描画する実験をしているところ。Windows API (GDI?)ではbitmap画像しか扱えないけれど、Windows XP から使えるようになった GDI+ なるものを使えばpng画像も扱えるという話を見かけたので試してみた。
環境は、Windows10 x64 21H2 + MinGW (g++ 9.2.0)。MSYS2 + MinGW-w64 ではなく、MinGW (+ MSYS) を使ってコンパイルしている。
ちなみに、GDI+ を使う場合は C++ を使うことになるらしい。
環境は、Windows10 x64 21H2 + MinGW (g++ 9.2.0)。MSYS2 + MinGW-w64 ではなく、MinGW (+ MSYS) を使ってコンパイルしている。
ちなみに、GDI+ を使う場合は C++ を使うことになるらしい。
◎ pngファイルを読み込む版。 :
まずは、実行ファイルと同じディレクトリに置いてある png ファイルを読み込んで、描画する処理を試してみた。以下のサンプルや解説を参考にさせてもらった。
_GDI+入門
_GDI+でBMP/PNG/JPEGの読み込みを簡単に | 株式会社ヘキサドライブ
_01_gdiplus01.cpp
せっかくだから、SetTimer() を使って、一定時間毎にタイマーで処理が走るようにして、簡単なアニメーションをするようにしてみた。SetTimer() を使ってタイマーをセットすれば、そこで指定したミリ秒(1/1000秒)が経過すると、WM_TIMER というメッセージがWindowsから送られてくるようになるので、WM_TIMER が送られてきた時の処理を書けばアニメーションができる。また、InvalidateRect() を呼べば、再描画を要求できる(WM_PAINT メッセージが送られてくる)。
ただ、SetTimer() の精度(?)は、Windows NT系OSの場合、10ms らしい。60 FPS を期待して16ms前後を指定しても、おそらく 20ms ぐらいの間隔で処理することになるのだろう…。
使用画像は以下。
_test.png
_bg.png
コンパイルは以下。01_gdiplus01.exe が生成される。
実行結果は以下。
たしかに、GDI+ を使うことで、png画像を読み込んで描画することができた。赤いボールのpng画像はアルファチャンネルを持っているので、周辺がちゃんと抜けた状態で描画されている。
ただ、少々問題が…。ウインドウ内がとにかくちらつく。描画処理として、ウインドウ内消去、背景画像相当の描画、ボールの描画を行っているので、その描画過程が見えちゃってちらつくのだな…。まあ、解決策については後で考えることにする。
_GDI+入門
_GDI+でBMP/PNG/JPEGの読み込みを簡単に | 株式会社ヘキサドライブ
_01_gdiplus01.cpp
#define WINVER 0x0501 // 0x0501 = WindowsXP #define _WIN32_WINNT 0x0501 #include <windows.h> #include <gdiplus.h> using namespace Gdiplus; #pragma comment(lib, "user32.lib") #pragma comment(lib, "gdiplus.lib") #define SCRW 512 #define SCRH 512 #define TM_COUNT1 1 #define FPS 60 LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; static Image *img; static Image *bg; static Graphics *g; RECT rect; static float x, y, dx, dy; static int wdw_w, wdw_h; switch (uMsg) { case WM_CREATE: // get window size GetClientRect(hWnd, &rect); wdw_w = rect.right - rect.left; wdw_h = rect.bottom - rect.top; img = new Image(L"test.png"); bg = new Image(L"bg.png"); // int work x = (float)(wdw_w / 2); y = (float)(wdw_h / 2); dx = (float) wdw_w / (float)FPS; dy = dx * 0.7; SetTimer(hWnd, TM_COUNT1, (int)(1000 / FPS), NULL); break; case WM_TIMER: // main loop x += dx; y += dy; if (x <= 0 || x + img->GetWidth() >= wdw_w) dx *= -1; if (y <= 0 || y + img->GetHeight() >= wdw_h) dy *= -1; InvalidateRect(hWnd, NULL, TRUE); // InvalidateRect(hWnd, NULL, FALSE); break; case WM_PAINT: hdc = BeginPaint(hWnd, &ps); g = new Graphics(hdc); g->DrawImage(bg, 0, 0, bg->GetWidth(), bg->GetHeight()); g->DrawImage(img, (int)x, (int)y, img->GetWidth(), img->GetHeight()); delete (g); EndPaint(hWnd, &ps); break; case WM_DESTROY: delete (img); delete (bg); PostQuitMessage(0); break; default: return DefWindowProc(hWnd, uMsg, wParam, lParam); } return 0; } int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nCmdShow) { MSG msg; GdiplusStartupInput gpSI; ULONG_PTR lpToken; /* GDI+初期化 */ GdiplusStartup(&lpToken, &gpSI, NULL); HCURSOR hCursor = LoadCursor(NULL, IDC_ARROW); HBRUSH hBrush = (HBRUSH)(COLOR_WINDOW + 1); WNDCLASS wcl = { 0, WndProc, 0, 0, hInst, NULL, hCursor, hBrush, NULL, "mh" }; DWORD style = WS_OVERLAPPEDWINDOW | WS_VISIBLE; if (!RegisterClass(&wcl) || !CreateWindowEx(0, "mh", "gdiplus test", style, CW_USEDEFAULT, CW_USEDEFAULT, SCRW, SCRH, NULL, NULL, hInst, NULL)) return FALSE; while (GetMessage(&msg, NULL, 0, 0) > 0) { TranslateMessage(&msg); DispatchMessage(&msg); } /* GDI+終了 */ GdiplusShutdown(lpToken); return msg.wParam; }
せっかくだから、SetTimer() を使って、一定時間毎にタイマーで処理が走るようにして、簡単なアニメーションをするようにしてみた。SetTimer() を使ってタイマーをセットすれば、そこで指定したミリ秒(1/1000秒)が経過すると、WM_TIMER というメッセージがWindowsから送られてくるようになるので、WM_TIMER が送られてきた時の処理を書けばアニメーションができる。また、InvalidateRect() を呼べば、再描画を要求できる(WM_PAINT メッセージが送られてくる)。
ただ、SetTimer() の精度(?)は、Windows NT系OSの場合、10ms らしい。60 FPS を期待して16ms前後を指定しても、おそらく 20ms ぐらいの間隔で処理することになるのだろう…。
使用画像は以下。
_test.png
_bg.png
コンパイルは以下。01_gdiplus01.exe が生成される。
g++ 01_gdiplus01.cpp -o 01_gdiplus01.exe -mwindows -static -lstdc++ -lgcc -lgdiplus -lgdi32
- GDI+ を使うので、-lgdiplus -lgdi32 をつけないといかんらしい。
実行結果は以下。
たしかに、GDI+ を使うことで、png画像を読み込んで描画することができた。赤いボールのpng画像はアルファチャンネルを持っているので、周辺がちゃんと抜けた状態で描画されている。
ただ、少々問題が…。ウインドウ内がとにかくちらつく。描画処理として、ウインドウ内消去、背景画像相当の描画、ボールの描画を行っているので、その描画過程が見えちゃってちらつくのだな…。まあ、解決策については後で考えることにする。
◎ リソースファイル内のpng画像を利用する。 :
上記のサンプルはpngファイルを読み込んで利用しているけれど、せっかくだからリソースファイル(.exeファイルに含めるデータ群)にpng画像を含めておいて、それを利用するようにしたい。
以下を参考にして作業。
_プログラミングTips : Vistaの透け透けウィンドウにアルファ付き画像を描画する
_winapi - C++ GDI+ how to get and load image from resource? - Stack Overflow
_gdi+ - "undefined reference to `CreateStreamOnHGlobal@12'" error faced on executing gdiplus c++ code - Stack Overflow
リソース内からpng画像を一発で読み込む関数は用意されてないらしくて、いくつかの手順を踏んで処理しないといけないらしい…。
さておき。リソースファイルは以下。
_res.rc
「RCDATA」を記述することで、何らかのバイナリファイルであることを指定している。「PNG」や「IMAGE」を記述している事例も見かけたのだけど、自分の環境では「RCDATA」にしないと上手く行かなかった。MinGW を使って作業している関係だろうか…?
リソースファイル関連ヘッダファイルは以下。ボール画像とBG画像のID番号を設定している。
_resource.h
C++のソースは以下。
_02_gdiplus_res.cpp
使用画像は以下。
_test.png
_bg.png
コンパイル/ビルドは以下。
実行結果は以下。
png画像を描画することができている。リソースからpng画像を読み込むこともできると分かった。
まあ、これもウインドウ内のちらつきが酷いけど…。
以下を参考にして作業。
_プログラミングTips : Vistaの透け透けウィンドウにアルファ付き画像を描画する
_winapi - C++ GDI+ how to get and load image from resource? - Stack Overflow
_gdi+ - "undefined reference to `CreateStreamOnHGlobal@12'" error faced on executing gdiplus c++ code - Stack Overflow
リソース内からpng画像を一発で読み込む関数は用意されてないらしくて、いくつかの手順を踏んで処理しないといけないらしい…。
さておき。リソースファイルは以下。
_res.rc
#include "resource.h" IDI_BALL RCDATA "test.png" IDI_BG RCDATA "bg.png"
「RCDATA」を記述することで、何らかのバイナリファイルであることを指定している。「PNG」や「IMAGE」を記述している事例も見かけたのだけど、自分の環境では「RCDATA」にしないと上手く行かなかった。MinGW を使って作業している関係だろうか…?
リソースファイル関連ヘッダファイルは以下。ボール画像とBG画像のID番号を設定している。
_resource.h
#ifndef RESOURCE_H_ #define RESOURCE_H_ #define IDI_BALL 101 #define IDI_BG 102 #endif
C++のソースは以下。
_02_gdiplus_res.cpp
#define WINVER 0x0501 // 0x0501 = WindowsXP #define _WIN32_WINNT 0x0501 #include <windows.h> #include <gdiplus.h> #include "resource.h" using namespace Gdiplus; #pragma comment(lib, "user32.lib") #pragma comment(lib, "gdiplus.lib") #define SCRW 512 #define SCRH 512 #define TM_COUNT1 1 #define FPS 60 HINSTANCE hInst; // Load image from resource Bitmap *LoadImageFromResource( HINSTANCE hinst, // handle instance LPCTSTR pName, // resource ID LPCTSTR pType // resource type ) { // Search resource HRSRC hRes = FindResource(hinst, pName, pType); if (hRes == NULL) { MessageBox(NULL, "Not found resource", "Error", MB_OK); return NULL; } // get resource size DWORD Size = SizeofResource(hinst, hRes); if (Size == 0) { MessageBox(NULL, "Resource size = 0", "Error", MB_OK); return NULL; } HGLOBAL hData = LoadResource(hinst, hRes); if (hData == NULL) { MessageBox(NULL, "Failure load resource", "Error", MB_OK); return NULL; } const void *pData = LockResource(hData); if (pData == NULL) { MessageBox(NULL, "Failure lock resource", "Error", MB_OK); return NULL; } // Copy resource data HGLOBAL hBuffer = GlobalAlloc(GMEM_MOVEABLE, Size); if (hBuffer == NULL) { MessageBox(NULL, "Failure alloc", "Error", MB_OK); return NULL; } void *pBuffer = GlobalLock(hBuffer); if (pBuffer == NULL) { MessageBox(NULL, "Failure lock", "Error", MB_OK); GlobalFree(hBuffer); return NULL; } CopyMemory(pBuffer, pData, Size); GlobalUnlock(hBuffer); // read image IStream *pStream; if (CreateStreamOnHGlobal(hBuffer, TRUE, &pStream) != S_OK) { MessageBox(NULL, "Failure create stream", "Error", MB_OK); GlobalFree(hBuffer); return NULL; } Bitmap *pBitmap = Bitmap::FromStream(pStream); pStream->Release(); return pBitmap; } LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; static Gdiplus::Bitmap* img; static Gdiplus::Bitmap* bg; static Graphics *g; RECT rect; static float x, y, dx, dy; static int wdw_w, wdw_h; switch (uMsg) { case WM_CREATE: // get window size GetClientRect(hWnd, &rect); wdw_w = rect.right - rect.left; wdw_h = rect.bottom - rect.top; img = LoadImageFromResource(hInst, MAKEINTRESOURCE(IDI_BALL), RT_RCDATA); bg = LoadImageFromResource(hInst, MAKEINTRESOURCE(IDI_BG), RT_RCDATA); // not work // img = LoadImageFromResource(hInst, TEXT("IDI_BALL"), TEXT("RCDATA")); // bg = LoadImageFromResource(hInst, TEXT("IDI_BG"), TEXT("RCDATA")); if (img != NULL && bg != NULL) { // init work x = (float)(wdw_w / 2); y = (float)(wdw_h / 2); dx = (float) wdw_w / (float)FPS; dy = dx * 0.5; // Set timer SetTimer(hWnd, TM_COUNT1, (int)(1000 / FPS), NULL); } break; case WM_TIMER: // main loop x += dx; y += dy; if (x <= 0 || x + img->GetWidth() >= wdw_w) dx *= -1; if (y <= 0 || y + img->GetHeight() >= wdw_h) dy *= -1; InvalidateRect(hWnd, NULL, TRUE); // InvalidateRect(hWnd, NULL, FALSE); break; case WM_PAINT: hdc = BeginPaint(hWnd, &ps); g = new Graphics(hdc); if (bg != NULL) g->DrawImage(bg, 0, 0, bg->GetWidth(), bg->GetHeight()); if (img != NULL) g->DrawImage(img, (int)x, (int)y, img->GetWidth(), img->GetHeight()); delete (g); EndPaint(hWnd, &ps); break; case WM_DESTROY: delete (img); delete (bg); PostQuitMessage(0); break; default: return DefWindowProc(hWnd, uMsg, wParam, lParam); } return 0; } int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nCmdShow) { MSG msg; GdiplusStartupInput gpSI; ULONG_PTR lpToken; hInst = hInstance; /* GDI+初期化 */ GdiplusStartup(&lpToken, &gpSI, NULL); HCURSOR hCursor = LoadCursor(NULL, IDC_ARROW); HBRUSH hBrush = (HBRUSH)(COLOR_WINDOW + 1); WNDCLASS wcl = { 0, WndProc, 0, 0, hInst, NULL, hCursor, hBrush, NULL, "mh" }; DWORD style = WS_OVERLAPPEDWINDOW | WS_VISIBLE; if (!RegisterClass(&wcl) || !CreateWindowEx(0, "mh", "gdiplus test", style, CW_USEDEFAULT, CW_USEDEFAULT, SCRW, SCRH, NULL, NULL, hInst, NULL)) return FALSE; while (GetMessage(&msg, NULL, 0, 0) > 0) { TranslateMessage(&msg); DispatchMessage(&msg); } /* GDI+終了 */ GdiplusShutdown(lpToken); return msg.wParam; }
使用画像は以下。
_test.png
_bg.png
コンパイル/ビルドは以下。
windres res.rc res.o g++ -c 02_gdiplus_res.cpp g++ 02_gdiplus_res.o res.o -o 02_gdiplus_res.exe -mwindows -static -lstdc++ -lgcc -lgdiplus -lgdi32 -lole32
- 上から順に、「リソースをコンパイルして .o を生成」「C++ソースをコンパイルして .o を生成」「.o群をリンクして.exe を生成」を行っている。
- CreateStreamOnHGlobal() がリンク時に見つからなかったけど、ole32 が必要になるらしい。リンクの指定として、-lole32 を追加した。
実行結果は以下。
png画像を描画することができている。リソースからpng画像を読み込むこともできると分かった。
まあ、これもウインドウ内のちらつきが酷いけど…。
◎ ダブルバッファを利用。 :
前述のサンプルは、ウインドウ内がとにかくちらついてしまうので、そのあたりを改善したい。ダブルバッファを導入すれば改善されるらしいので試してみる。
以下のページが参考になった。
_VC++でGDI+ そにょ8 ?ダブルバッファリング? - yuyarinの日記
_ビットマップのマルチバッファリング【Windowsプログラミング研究所】
仕組みとしては…。
resource.h、res.rc、使用するpng画像は、前述のものをそのまま利用する。
_res.rc
_resource.h
_test.png
_bg.png
C++のソースは以下。
_03_gdiplus_dbuf.cpp
コンパイル/ビルドは以下。03_gdiplus_dbuf.exe が生成される。
一応、前述の2つのファイルについても記述した Makefile も置いておく。Makefile があれば、make と打つだけでコンパイル/ビルドができる。
_Makefile
実行結果は以下。
ちらつきがかなり改善された。
まあ、bitmapをウインドウ内に転送している過程が見えちゃってるというか、いわゆるティアリングのような見た目が発生している気もするけれど…。
_ティアリング、スタッタリングについて | ドスパラ サポートFAQ よくあるご質問|お客様の「困った」や「知りたい」にお応えします。
それでも、ダブルバッファを導入してないサンプルと比べたら雲泥の差かなと…。
以下のページが参考になった。
_VC++でGDI+ そにょ8 ?ダブルバッファリング? - yuyarinの日記
_ビットマップのマルチバッファリング【Windowsプログラミング研究所】
仕組みとしては…。
- ウインドウと同サイズの、空のbitmapを用意しておく。
- 一旦、そのbitmapにアレコレを描画。
- そのbitmapをウインドウ内に描画する。
resource.h、res.rc、使用するpng画像は、前述のものをそのまま利用する。
_res.rc
_resource.h
_test.png
_bg.png
C++のソースは以下。
_03_gdiplus_dbuf.cpp
#define WINVER 0x0501 // 0x0501 = WindowsXP #define _WIN32_WINNT 0x0501 #include <windows.h> #include <gdiplus.h> #include "resource.h" using namespace Gdiplus; #pragma comment(lib, "user32.lib") #pragma comment(lib, "gdiplus.lib") #define SCRW 512 #define SCRH 512 #define TM_COUNT1 1 #define FPS 60 HINSTANCE hInst; // Load image from resource Bitmap *LoadImageFromResource( HINSTANCE hinst, // handle instance LPCTSTR pName, // resource ID LPCTSTR pType // resource type ) { // Search resource HRSRC hRes = FindResource(hinst, pName, pType); if (hRes == NULL) { MessageBox(NULL, "Not found resource", "Error", MB_OK); return NULL; } // get resource size DWORD Size = SizeofResource(hinst, hRes); if (Size == 0) { MessageBox(NULL, "Resource size = 0", "Error", MB_OK); return NULL; } HGLOBAL hData = LoadResource(hinst, hRes); if (hData == NULL) { MessageBox(NULL, "Failure load resource", "Error", MB_OK); return NULL; } const void *pData = LockResource(hData); if (pData == NULL) { MessageBox(NULL, "Failure lock resource", "Error", MB_OK); return NULL; } // Copy resource data HGLOBAL hBuffer = GlobalAlloc(GMEM_MOVEABLE, Size); if (hBuffer == NULL) { MessageBox(NULL, "Failure alloc", "Error", MB_OK); return NULL; } void *pBuffer = GlobalLock(hBuffer); if (pBuffer == NULL) { MessageBox(NULL, "Failure lock", "Error", MB_OK); GlobalFree(hBuffer); return NULL; } CopyMemory(pBuffer, pData, Size); GlobalUnlock(hBuffer); // read image IStream *pStream; if (CreateStreamOnHGlobal(hBuffer, TRUE, &pStream) != S_OK) { MessageBox(NULL, "Failure create stream", "Error", MB_OK); GlobalFree(hBuffer); return NULL; } Bitmap *pBitmap = Bitmap::FromStream(pStream); pStream->Release(); return pBitmap; } LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; static Bitmap* img; static Bitmap* bg; static Bitmap* offScrnBmp; static Graphics* onScrn; static Graphics* offScrn; RECT rect; static float x, y, dx, dy; static int wdw_w, wdw_h; switch (uMsg) { case WM_CREATE: // get window size GetClientRect(hWnd, &rect); wdw_w = rect.right - rect.left; wdw_h = rect.bottom - rect.top; // create off screen bitmap offScrnBmp = new Bitmap(wdw_w, wdw_h); // Load image from resource img = LoadImageFromResource(hInst, MAKEINTRESOURCE(IDI_BALL), RT_RCDATA); bg = LoadImageFromResource(hInst, MAKEINTRESOURCE(IDI_BG), RT_RCDATA); // not work // img = LoadImageFromResource(hInst, TEXT("IDI_BALL"), TEXT("RCDATA")); // bg = LoadImageFromResource(hInst, TEXT("IDI_BG"), TEXT("RCDATA")); if (img != NULL && bg != NULL) { x = (float)(wdw_w / 2); y = (float)(wdw_h / 2); dx = (float) wdw_w / (float)FPS; dy = dx * 0.5; SetTimer(hWnd, TM_COUNT1, (int)(1000 / FPS), NULL); } break; case WM_TIMER: // main loop x += dx; y += dy; if (x <= 0 || x + img->GetWidth() >= wdw_w) dx *= -1; if (y <= 0 || y + img->GetHeight() >= wdw_h) dy *= -1; // InvalidateRect(hWnd, NULL, TRUE); InvalidateRect(hWnd, NULL, FALSE); // not clear background break; case WM_PAINT: // draw image to off screen offScrn = new Graphics(offScrnBmp); if (bg != NULL) offScrn->DrawImage(bg, 0, 0, bg->GetWidth(), bg->GetHeight()); if (img != NULL) offScrn->DrawImage(img, (int)x, (int)y, img->GetWidth(), img->GetHeight()); delete (offScrn); // draw off screen to window hdc = BeginPaint(hWnd, &ps); onScrn = new Graphics(hdc); onScrn->DrawImage(offScrnBmp, 0, 0, offScrnBmp->GetWidth(), offScrnBmp->GetHeight()); delete(onScrn); EndPaint(hWnd, &ps); break; case WM_DESTROY: delete (img); delete (bg); delete (offScrnBmp); PostQuitMessage(0); break; default: return DefWindowProc(hWnd, uMsg, wParam, lParam); } return 0; } int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nCmdShow) { MSG msg; GdiplusStartupInput gpSI; ULONG_PTR lpToken; hInst = hInstance; /* GDI+初期化 */ GdiplusStartup(&lpToken, &gpSI, NULL); HCURSOR hCursor = LoadCursor(NULL, IDC_ARROW); HBRUSH hBrush = (HBRUSH)(COLOR_WINDOW + 1); WNDCLASS wcl = { 0, WndProc, 0, 0, hInst, NULL, hCursor, hBrush, NULL, "mh" }; DWORD style = WS_OVERLAPPEDWINDOW | WS_VISIBLE; if (!RegisterClass(&wcl) || !CreateWindowEx(0, "mh", "gdiplus test", style, CW_USEDEFAULT, CW_USEDEFAULT, SCRW, SCRH, NULL, NULL, hInst, NULL)) return FALSE; while (GetMessage(&msg, NULL, 0, 0) > 0) { TranslateMessage(&msg); DispatchMessage(&msg); } /* GDI+終了 */ GdiplusShutdown(lpToken); return msg.wParam; }
コンパイル/ビルドは以下。03_gdiplus_dbuf.exe が生成される。
windres res.rc res.o g++ -c 03_gdiplus_dbuf.cpp g++ 03_gdiplus_dbuf.o res.o -o 03_gdiplus_dbuf.exe -mwindows -static -lstdc++ -lgcc -lgdiplus -lgdi32 -lole32
一応、前述の2つのファイルについても記述した Makefile も置いておく。Makefile があれば、make と打つだけでコンパイル/ビルドができる。
_Makefile
実行結果は以下。
ちらつきがかなり改善された。
まあ、bitmapをウインドウ内に転送している過程が見えちゃってるというか、いわゆるティアリングのような見た目が発生している気もするけれど…。
_ティアリング、スタッタリングについて | ドスパラ サポートFAQ よくあるご質問|お客様の「困った」や「知りたい」にお応えします。
それでも、ダブルバッファを導入してないサンプルと比べたら雲泥の差かなと…。
◎ GDI+は遅いらしい。 :
関連情報をググっていたら、GDI+ は描画が遅いという話を見かけた。GDI+ を使わずに、GDIで済ませられるなら、GDIだけを使ったほうが速いのだとか。
_Windows 10 で描画処理が遅くなる問題について(WebArchive)
_kenjinote/DrawSpeedTest: DWM が有効か無効かで GDI+ の描画速度が異なるのでその検証を行うためのプロジェクトです。
_Pasture | GDI+のビットマップ転送速度
png画像を使えるようになったのはありがたいけど、bitmapだけを使うようにして、GDIで処理したほうがいいのかもしれない…。
_Windows 10 で描画処理が遅くなる問題について(WebArchive)
_kenjinote/DrawSpeedTest: DWM が有効か無効かで GDI+ の描画速度が異なるのでその検証を行うためのプロジェクトです。
_Pasture | GDI+のビットマップ転送速度
png画像を使えるようになったのはありがたいけど、bitmapだけを使うようにして、GDIで処理したほうがいいのかもしれない…。
[ ツッコむ ]
以上です。