- 締切済み
WinAPIでの画像高速切り替え表示プログラム1
WinAPIを使用して、ビットマップ画像を8枚読み込み、それを連続高速表示するプログラムを作成しています。 今はSetTimerを使ってWM_TIMERを受け取ったときに画像をInvalidateRect(再描画)しています。 以下のソースで動作はするのですが、WM_TIMERは整数ミリ秒でしか設定できず、精度も悪く優先順位も遅いようなので他の方法を考えています。 画像8枚を6.25msecで切り替えて表示するというのをESCAPEするまで繰り返したいのですが...。 リフレッシュレートは160Hzにあげています。 QueryPerformanceFrequencyというものを使えばいいのかなと思っていますが、どこでどう使えばいいのか、それをどう受け取って再描画すればいいのかわかりません。 どなたかご教授お願いします。ソースファイルを書いていただけたら嬉しいです。 #include<windows.h> #define BMP_SUM8//画像の総数 #define TIMER_ID (100) // 作成するタイマの識別ID #define TIMER_ELAPSE (6) // WM_TIMERの発生間隔 LRESULT CALLBACK WindowProc(HWND hWnd,UINT uMsg,WPARAM wParam,LPARAM lParam){ HDC hdc; PAINTSTRUCT ps; HBITMAP hBitmap; int i; const char *filename[BMP_SUM]={"gazou0.bmp", "gazou1.bmp", "gazou2.bmp", "gazou3.bmp", "gazou4.bmp", "gazou5.bmp", "gazou6.bmp", "gazou7.bmp"}; static HDC hMemDC[BMP_SUM]; static BITMAP bmp; static int bmp_index;//現在の画像番号 LONG lResult; switch(uMsg) { case WM_CREATE: hdc=GetDC(hWnd); for(i=0;i<BMP_SUM;i++){ hBitmap=(HBITMAP)LoadImage(0,filename[i],IMAGE_BITMAP,0,0,LR_LOADFROMFILE); hMemDC[i]=CreateCompatibleDC(hdc); SelectObject(hMemDC[i],hBitmap); } GetObject(hBitmap,sizeof(BITMAP),&bmp); DeleteObject(hBitmap); ReleaseDC(hWnd,hdc); return 0; case WM_TIMER: if( wParam != TIMER_ID ) { break; // 識別IDが一致しないタイマメッセージはDefWindowProc()に任せる } if(++bmp_index >= BMP_SUM) bmp_index=0; for(i = 0; i < BMP_SUM; i++){ InvalidateRect( hWnd, NULL, FALSE ); } return 0; case WM_DESTROY: for(i=0;i<BMP_SUM;i++) DeleteDC(hMemDC[i]); PostQuitMessage(0); return 0; case WM_PAINT: hdc=BeginPaint(hWnd,&ps); BitBlt(hdc,0,0,bmp.bmWidth,bmp.bmHeight,hMemDC[bmp_index],0,0,SRCCOPY); EndPaint(hWnd,&ps); return 0; case WM_KEYDOWN: switch((CHAR)wParam) { case VK_ESCAPE: for(i=0;i<BMP_SUM;i++) DeleteDC(hMemDC[i]); PostQuitMessage(0); //WM_QUITメッセージを出す return 0; } } return DefWindowProc(hWnd,uMsg,wParam,lParam); } 入りきらないので2つに分けます。 続きは「WinAPIでの画像高速切り替え表示プログラム2」を見てください。
- みんなの回答 (7)
- 専門家の回答
みんなの回答
- sygh
- ベストアンサー率76% (42/55)
// No.4: int main(int argc, char** argv) { ULONG_PTR token = 0; Gdiplus::GdiplusStartupInput input; Gdiplus::GdiplusStartup(&token, &input, 0); glutInit(&argc, argv); glutInitWindowSize(g_winSize.cx, g_winSize.cy); glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH); glutCreateWindow("Precise Flip Test"); glutDisplayFunc(OnDisplay); glutIdleFunc(OnIdle); glutReshapeFunc(OnReshape); glutKeyboardFunc(OnKeyboard); glEnable(GL_TEXTURE_2D); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glClearColor(0.0, 0.5, 0.5, 1.0); wchar_t fileName[1024] = {}; for (int i = 0; i < IMG_NUM; i++) { swprintf_s(fileName, L"image%02d.png", i); // 24/32bit PNG/BMP, 24bit TIFF/JPEG に対応。サイズは縦横 2^n のみ。 if (!g_texHolders[i].LoadFromFile(fileName)) { return -1; } } const BOOL isAvailableQPC = ::QueryPerformanceCounter(&g_start); assert(isAvailableQPC); glutMainLoop(); // 以下の解放処理は一応記述していますが、終了時に呼ばれません。 // GLUT では解放処理を OS に委ねています。 // WGL などを使ってすべて自前で制御するときは、明示的に解放/再作成できます。 for (int i = 0; i < IMG_NUM; i++) { g_texHolders[i].Release(); } Gdiplus::GdiplusShutdown(token); return 0; } 以上です。 Direct3Dのサンプルは書きませんのであしからず。
- sygh
- ベストアンサー率76% (42/55)
// No.3: void OnDisplay() { glClear(GL_COLOR_BUFFER_BIT); glPushMatrix(); { glLoadIdentity(); glColor3d(1, 1, 0); glDisable(GL_BLEND); glRasterPos2d(-0.9, 0.9); static char message[256]; sprintf_s(message, "%3u FPS", g_fpsValue); DrawString(GLUT_BITMAP_HELVETICA_18, message); } glPopMatrix(); glRotated(2, 0, 0, 0.25); glBindTexture(GL_TEXTURE_2D, g_texHolders[g_currentBmpIndex].GetTextureID()); glColor3d(1, 1, 1); glEnable(GL_BLEND); glBegin(GL_POLYGON); { // UV と頂点位置の設定。 const double wInv = static_cast<double>(g_texHolders[g_currentBmpIndex].GetWidth()) / g_winSize.cx; const double hInv = static_cast<double>(g_texHolders[g_currentBmpIndex].GetHeight()) / g_winSize.cy; glTexCoord2d(0, 0); glVertex2d(-wInv, +hInv); glTexCoord2d(0, 1); glVertex2d(-wInv, -hInv); glTexCoord2d(1, 1); glVertex2d(+wInv, -hInv); glTexCoord2d(1, 0); glVertex2d(+wInv, +hInv); } glEnd(); glutSwapBuffers(); } void OnIdle() { LARGE_INTEGER stop = {}, freq = {}; ::QueryPerformanceCounter(&stop); ::QueryPerformanceFrequency(&freq); const LONGLONG diffStartStop = stop.QuadPart - g_start.QuadPart; if (diffStartStop * FPS_LIMIT < freq.QuadPart) { return; } static UINT fpsUpdateCounter; const UINT FPS_UPDATE_SKIP_FRAME_COUNT = 160; if (++fpsUpdateCounter >= FPS_UPDATE_SKIP_FRAME_COUNT) { g_fpsValue = (diffStartStop > 0) ? static_cast<UINT>(freq.QuadPart / diffStartStop) : 9999; fpsUpdateCounter = 0; } ::QueryPerformanceCounter(&g_start); g_currentBmpIndex = (g_currentBmpIndex + 1) % IMG_NUM; glutPostRedisplay(); // GLUT では既定で垂直同期されるので、FPS は基本的にモニタのリフレッシュレート以上にはならない。 } void OnReshape(int width, int height) { g_winSize.cx = width; g_winSize.cy = height; } void OnKeyboard(unsigned char key, int x, int y) { if (key == 27) // Esc { exit(0); } }
- sygh
- ベストアンサー率76% (42/55)
// No.2: class TexHolder { GLuint m_textureId; UINT m_width; UINT m_height; public: TexHolder() : m_textureId(GL_INVALID_VALUE) , m_width() , m_height() { } GLuint GetTextureID() const { return m_textureId; } UINT GetWidth() const { return m_width; } UINT GetHeight() const { return m_height; } bool IsValid() const { return (m_textureId != GL_INVALID_VALUE && m_textureId != GL_INVALID_OPERATION); } bool Generate() { this->Release(); glGenTextures(1, &m_textureId); return this->IsValid(); } void Release() { if (this->IsValid()) { glDeleteTextures(1, &m_textureId); m_textureId = GL_INVALID_VALUE; m_width = 0; m_height = 0; } } bool LoadFromFile(LPCWSTR pFilePath) { if (!this->Generate()) { this->Release(); return false; } Bitmap bmp(pFilePath); if (bmp.GetLastStatus() != Ok) { this->Release(); return false; } m_width = bmp.GetWidth(); m_height = bmp.GetHeight(); const PixelFormat pf = bmp.GetPixelFormat(); if (m_width != m_height || !IsModulo2(m_width)) { this->Release(); return false; } if (pf != PixelFormat32bppARGB && pf != PixelFormat24bppRGB) { this->Release(); return false; } BitmapData bmpData; bmp.LockBits(&Rect(0, 0, m_width, m_height), ImageLockModeRead, pf, &bmpData); glBindTexture(GL_TEXTURE_2D, m_textureId); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); if (pf == PixelFormat32bppARGB) { glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bmpData.Width, bmpData.Height, 0, GL_BGRA_EXT, GL_UNSIGNED_BYTE, bmpData.Scan0); } else { glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, bmpData.Width, bmpData.Height, 0, GL_BGR_EXT, GL_UNSIGNED_BYTE, bmpData.Scan0); } glBindTexture(GL_TEXTURE_2D, 0); bmp.UnlockBits(&bmpData); return true; } virtual ~TexHolder() { this->Release(); } }; namespace { const int IMG_NUM = 8; const UINT FPS_LIMIT = 160; TexHolder g_texHolders[IMG_NUM]; LARGE_INTEGER g_start; UINT g_fpsValue; UINT g_currentBmpIndex; SIZE g_winSize = {800, 800}; }
- sygh
- ベストアンサー率76% (42/55)
OpenGLの話は長くなるので、GLUTとGDI+による簡単なソースのみにとどめて、詳しい解説は割愛します。 入門の鉄板サイトを紹介しておきますので、あとはご自分で調べてください。 OpenGLの勉強をこれから始める人、あるいはOpenGL用ウィンドウの生成などの面倒な部分を省きたい場合はGLUTを使うと楽ですが、Windows専用でもいいから細かい制御までしたい場合や、MFCなどのGUIフレームワークと連携したい場合はWGLを使ってレンダリング コンテキストを直に作成したりします。 例えば、GLUTのglutMainLoop()は一度呼び出すと制御を返さないので、終了するにはウィンドウのクローズ ボタンを押すか、exit()で強制終了させる以外になくなります。解放処理も記述できません。 他にも、GLEWを使うと、垂直同期やマルチサンプル アンチエイリアス、GLSLといった上位機能へのアクセスが楽になります。 OpenGLおよび関連ライブラリはほとんど全てがオープンソースでクロス プラットフォームなので、組込機器や技術論文などによく使われていますが、Windows限定であればDirect3Dのほうが圧倒的に情報量が多く、大抵の場合OpenGLより高速で、さらにC++との相性が良いですが、COMの知識が必須となります。 グラフィックAPIはすさまじく変化が激しいので、人に聞くよりも自ら情報収集に努めないと、はっきり言ってついていけないです。映画やゲーム、アニメなどを見れば一目瞭然ですが、この10年で飛躍的に進化を遂げた分野のうちのひとつです。 http://www.wakayama-u.ac.jp/~tokoi/opengl/libglut.html http://wisdom.sakura.ne.jp/system/opengl/index.html http://glew.sourceforge.net/ http://msdn.microsoft.com/en-us/library/ms533798.aspx // No.1: #include <cstdlib> #include <GL/glut.h> #include <Windows.h> #include <GdiPlus.h> #include <vector> #include <cassert> #pragma comment(lib, "gdiplus.lib") using namespace Gdiplus; template<typename T> inline bool IsModulo2(const T& x) { return !(x & (x - 1)); } void DrawString(void* font, const char* str) { while (*str) { glutBitmapCharacter(font, *str); ++str; } }
- sygh
- ベストアンサー率76% (42/55)
// No.3 開始。 LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { static const LPCTSTR fileNames[BMP_NUM] = { _T("image00.bmp"), _T("image01.bmp"), _T("image02.bmp"), _T("image03.bmp"), _T("image04.bmp"), _T("image05.bmp"), _T("image06.bmp"), _T("image07.bmp"), }; switch (uMsg) { case WM_CREATE: { HDC hdc = ::GetDC(hWnd); for (UINT i = 0; i < BMP_NUM; i++) { g_bmpHolders[i].LoadBitmapFromFile(fileNames[i], hdc); } RECT clientRect = {}; ::GetClientRect(hWnd, &clientRect); const int clientWidth = clientRect.right - clientRect.left; const int clientHeight = clientRect.bottom - clientRect.top; const bool isBackBufCreated = g_backBuffer.UpdateBackBufferBitmap(hdc, clientWidth, clientHeight); assert(isBackBufCreated); ::ReleaseDC(hWnd, hdc); } return 0; case WM_DESTROY: ::PostQuitMessage(0); return 0; case WM_KEYDOWN: switch (wParam) { case VK_ESCAPE: ::PostQuitMessage(0); return 0; default: break; } } return ::DefWindowProc(hWnd, uMsg, wParam, lParam); } // No.3 終了。 ちなみに Direct3D だとダブルバッファリングやモニタのリフレッシュレートとの垂直同期(VSync)は、ほぼ自動でやってくれます。
- sygh
- ベストアンサー率76% (42/55)
// No.2 開始。 namespace { const UINT BMP_NUM = 8; // 画像の総数。 //const UINT FPS_LIMIT = 60; const UINT FPS_LIMIT = 160; // 1000[ms] / 6.25[ms] BmpHolder g_backBuffer; BmpHolder g_bmpHolders[BMP_NUM]; UINT g_fpsValue; // ブラシを使わずに単色で塗りつぶす。 void FillSolidRect(HDC hdc, COLORREF clr, const RECT* pRect) { ::SetBkColor(hdc, clr); ::ExtTextOut(hdc, 0, 0, ETO_OPAQUE, pRect, NULL, 0, NULL); } void RenderMyObjects(HDC hdc, const RECT* pCanvasRect) { static UINT currentBmpIndex; // 現在の画像番号。 { FillSolidRect(hdc, RGB(0, 128, 128), pCanvasRect); g_bmpHolders[currentBmpIndex].Render(hdc, 30, 30); static TCHAR message[256]; _stprintf_s(message, _T("%3u FPS"), g_fpsValue); RECT rect = { 4, 4, 4, 4 }; ::DrawText(hdc, message, _tcslen(message), &rect, DT_NOCLIP); } currentBmpIndex = (currentBmpIndex + 1) % BMP_NUM; } // アイドル時に描画する。 void OnIdle(HWND hWnd) { HDC hdc = ::GetDC(hWnd); { // 直接ウィンドウの DC に描画すると、描画の過程が見えてしまうのでフリッカー(ちらつき)が発生する。 // フリッカー防止のため、一旦バックバッファ(裏画面)用のビットマップに描画し、 // その後バックバッファをフロントバッファ(表画面)であるウィンドウに転送する。 RECT clientRect = {}; ::GetClientRect(hWnd, &clientRect); RenderMyObjects(g_backBuffer.GetMemDC(), &clientRect); ::BitBlt(hdc, 0, 0, g_backBuffer.GetWidth(), g_backBuffer.GetHeight(), g_backBuffer.GetMemDC(), 0, 0, SRCCOPY); } ::ReleaseDC(hWnd, hdc); } } // No.2 終了。
- sygh
- ベストアンサー率76% (42/55)
プログラム2のお礼で「ダブル バッファリング」に関して質問を受けたので、こちらに記載します。 インクルードとメイン関数は前回と同じです。 ちなみに画面のちらつきは、フレームレートというよりは描画途中の過程が表画面に見えてしまうことが原因です。実際、前回提示したコードでは、FPS表示部分は直接描画しているため、微妙にちらついているのが分かります。一旦裏画面に描画して、一気に表画面に転送するようにすると、ちらつきが見えなくなります。 // No.1 開始。 class BmpHolder { BITMAP m_bmpInfo; HBITMAP m_hBitmap; HDC m_hMemDC; bool m_isBackBuffer; public: BmpHolder() : m_bmpInfo() , m_hBitmap() , m_hMemDC() , m_isBackBuffer() {} virtual ~BmpHolder() { this->Release(); } LONG GetWidth() const { return m_bmpInfo.bmWidth; } LONG GetHeight() const { return m_bmpInfo.bmHeight; } HDC GetMemDC() const { return m_hMemDC; } // 描画されるビットマップをファイルからロードする。 bool LoadBitmapFromFile(LPCTSTR pFileName, HDC hdc) { this->Release(); m_hBitmap = static_cast<HBITMAP>(::LoadImage(NULL, pFileName, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE)); assert(m_hBitmap != NULL); if (m_hBitmap) { m_isBackBuffer = false; ::GetObject(m_hBitmap, sizeof(BITMAP), &m_bmpInfo); m_hMemDC = ::CreateCompatibleDC(hdc); assert(m_hMemDC != NULL); ::SelectObject(m_hMemDC, m_hBitmap); return true; } else { return false; } } // バックバッファ用のビットマップを更新する。 // ウィンドウの作成時・リサイズ時に明示的に呼び出すか、フレームの描画前に毎回呼び出す。 bool UpdateBackBufferBitmap(HDC hdc, int newWidth, int newHeight) { // 現在の幅・高さと同じバックバッファであれば更新しない。 if (this->GetWidth() == newWidth && this->GetHeight() == newHeight && m_isBackBuffer) { return true; } this->Release(); m_hBitmap = ::CreateCompatibleBitmap(hdc, newWidth, newHeight); assert(m_hBitmap != NULL); if (m_hBitmap) { m_isBackBuffer = true; ::GetObject(m_hBitmap, sizeof(BITMAP), &m_bmpInfo); m_hMemDC = ::CreateCompatibleDC(hdc); assert(m_hMemDC != NULL); ::SelectObject(m_hMemDC, m_hBitmap); return true; } else { return false; } } void Render(HDC hdc, int posX, int posY) { if (m_hMemDC) { ::BitBlt(hdc, posX, posY, this->GetWidth(), this->GetHeight(), m_hMemDC, 0, 0, SRCCOPY); } } void Release() { if (m_hMemDC) { ::DeleteDC(m_hMemDC); } if (m_hBitmap) { ::DeleteObject(m_hBitmap); } m_bmpInfo = BITMAP(); m_isBackBuffer = false; } }; // No.1 終了。
お礼
またまたソースを書いてくださってありがとうございます。 Direct3DというのはDirectXの一部(?)と聞きました。 OpenGLは少しやったことがあるのですが、Direct3Dについては何もわかりません。 OpenGLでも可能でしょうか。とは言ってもOpenGLもさわりだけしかやっておらずさわりで挫折しました。 OpenGLでも可能ならば、どのようにすればいいかヒントを頂けますでしょうか。 本当に何度もすみません。 よろしくお願いします。