初版 2014/06/06

概要

GDI+はjpgファイルが簡単に表示でき便利ですが、再描画のたびにGDI+を用いては描画に時間がかかり画面がちらつきます。
jpgファイルをディスプレイと互換性のあるメモリデバイスコンテキストに描画し、ウィンドウの再描画が必要な場合は、メモリ上から転送するだけにすれば高速に描画できます。
このプログラムはtest.jpgファイルをウィンドウに表示します。

テスト環境

コンパイラ

Visual C++ 2008/2013 Express 32/64bit マルチバイト/UNICODE

実行環境

Windows XP Professional Service Pack 3 32bit(Virtual Box上の仮想マシーン)
Windows 7 Enterprise Service Pack 1 64bit

動作例

プログラムソースの概要

jpgviewm3.cpp

_tWinMain

GdiplusStartupによりGDI+の初期化をします。
その後Windowを作成します。
Windowが閉じられたら、GdiplusShutdownにより終了処理を実行します。

WndProc

ウィンドウの初期化時にWndProc関数にWM_CREATEメッセージが発生するので、load_img関数でjpgファイルをメモリ上に描画します。
WM_PAINTメッセージが発生したら、ウィンドウサイズを取得しimage_just_size関数でウィンドウに収まる縦横比率を変えない描画サイズを決定し、StretchBltでウィンドウに収まるように拡大縮小してメモリ上からウィンドウへ転送します。
SetStretchBltMode関数でHALFTONEを指定して、拡大縮小がきれいに実施てきるようにします。

load_img

jpgファイルよりGdiplus::Imageオブジェクトを作成します。
Imageオブジェクトより画像ファイルの縦横サイズを取得し、ビットマップを作成します。(CreateDIBSection)
ウィンドウのデバイスコンテキストより互換性のあるメモリデバイスコンテキストを作成し(CreateCompatibleDC)、ビットマップを関連付けます。(SelectObject)
メモリデバイスコンテキストよりGdiplus::Graphicsオブジェクトを作成し、Gdiplus::ImageをGdiplus::Graphicsに描画(DrawImage)します。
Gdiplus::ImageとGdiplus::Graphics(デストラクタにより自動解放される)オブジェクトを解放します。

ソースコード

jpgviewm3.cpp

// jpgファイルをウィンドウに表示する標準的なソース
// メモリ上に描画して画面に転送
// ウィンドウサイズに応じて縦横比を変えずに最大の大きさで表示。
// Visual C++ 2008/2013 Express (GDIPLUSが必要)

#include <windows.h>
#include <gdiplus.h>
#include <tchar.h>

#pragma comment(lib,"gdiplus.lib")

using namespace Gdiplus;

GdiplusStartupInput gdiSI;
ULONG_PTR           gdiToken;


LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

//	jpegファイルをメモリデバイスコンテキストにロードする
bool load_img(TCHAR* fname,HDC* hdcBMP,HDC hdc,int* ix,int* iy);


// imageをdx,dyにジャストフィットする画像サイズ(縦横比維持)を*sx,*syに作成する
void image_just_size(int* sx,int* sy,int px,int py,int dx,int dy);

BOOL InitApp(HINSTANCE, WNDPROC, TCHAR*);
BOOL InitInstance(HINSTANCE, TCHAR*, int);

HWND hWnd;
BITMAPINFOHEADER bmih;
BYTE *pBits;
HBITMAP hBitmap,HOldBitmap;

// 表示するjpegファイル名
TCHAR* szFile=_TEXT("test.jpg");

// 最初に呼び出される関数

int WINAPI _tWinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst,TCHAR* lpsCmdLine, int nCmdShow){
	MSG msg;
	BOOL b;
	
	TCHAR szClassName[] = TEXT("jpgviewm3");
	GdiplusStartup(&gdiToken, &gdiSI, NULL);
	if (!hPrevInst) {
		if (!InitApp(hCurInst,WndProc, szClassName))    //    ウィンドウクラスの登録
			return FALSE;
	}
	if (!InitInstance(hCurInst, szClassName, nCmdShow)) {    // ウィンドウの作成
		return FALSE;
	}

// 見慣れないメッセージループだがエラーの場合-1が返る場合があるのでこのように記述
// MSDNを見てください。

	while ((b=GetMessage(&msg, NULL, NULL, NULL))!=0 && b!=-1) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	GdiplusShutdown(gdiToken);

	return (int)msg.wParam;
}


// ウィンドウを作成/閉じる/移動等のメッセージにより起動される関数

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp){
	HDC hdc;
	PAINTSTRUCT ps;
	static Image* imageP=0;
	static int ix,iy;
	static HDC hdcBMP;
	HPEN hPen, hOldPen;
	HBRUSH hBrush;
	int sx,sy;

	switch (msg) {
	case WM_CREATE:
		::hWnd=hWnd;
		SetWindowText(hWnd,szFile);
		hdc=GetDC(hWnd);
		if(load_img(szFile,&hdcBMP,hdc,&ix,&iy)==false){
			MessageBox(0,szFile,_TEXT("Error"),MB_OK);
			PostQuitMessage(0);
		}
		ReleaseDC(hWnd,hdc);
		InvalidateRect(hWnd,0,TRUE);
		break;
	case WM_PAINT:	// ウィンドウの描画が必要な場合に呼び出される。
		if(hdcBMP){
			hdc = BeginPaint(hWnd, &ps);
			hPen = CreatePen(PS_SOLID, 1, RGB(255, 255, 255));
			hOldPen =(HPEN) SelectObject(hdc, hPen);
			hBrush = (HBRUSH)GetStockObject(NULL_BRUSH);
			RECT rect;
			GetClientRect(hWnd,&rect);
			image_just_size(&sx,&sy,ix,iy,(rect.right-rect.left),(rect.bottom-rect.top));
			SetStretchBltMode(hdc, HALFTONE);
			StretchBlt(hdc,0,0,sx,sy,hdcBMP,0,0,ix,iy,SRCCOPY);
			SelectObject(hdc, hOldPen);
			DeleteObject(hPen);
			EndPaint(hWnd, &ps);
		}
		break;
	case WM_DESTROY:	// ウィンドウを閉じる場合に呼び出される。
		if(hdcBMP){
			SelectObject(hdcBMP,HOldBitmap);
			DeleteObject(hBitmap);
			DeleteDC(hdcBMP);
		}
		PostQuitMessage(0);
		break;
	default:
		return (DefWindowProc(hWnd, msg, wp, lp));
	}
	return 0L;
}

//	jpegファイルをメモリデバイスコンテキストにロードする

bool load_img(TCHAR* fname,HDC* hdcBMP,HDC hdc,int* ix,int* iy){
		Image* imageP;
#ifndef UNICODE
		WCHAR wTitle[MAX_PATH];
		MultiByteToWideChar(932,0,szFile,-1,wTitle,sizeof(wTitle)/sizeof(TCHAR));
		imageP=Bitmap::FromFile(wTitle);
#else
		imageP=Bitmap::FromFile(fname);
#endif
		if(imageP==0){
			*ix=0;
			*iy=0;
			*hdcBMP=0;
			return false;
		}
		bmih.biSize=sizeof(bmih);
		bmih.biWidth=*ix=imageP->GetWidth();
		bmih.biHeight=*iy=imageP->GetHeight();
		bmih.biPlanes=1;
		bmih.biBitCount=32;
		bmih.biCompression=BI_RGB;
		bmih.biSizeImage=0;
		bmih.biXPelsPerMeter=0;
		bmih.biYPelsPerMeter=0;
		bmih.biClrUsed=0;
		bmih.biClrImportant=0;
		hBitmap=CreateDIBSection(NULL,(BITMAPINFO*)&bmih,0,(void**)&pBits,NULL,0);
		if(pBits==NULL){
			MessageBox(hWnd,_TEXT("メモリが不足しています。"),_TEXT("Message"),MB_YESNO | MB_ICONQUESTION);
			DeleteObject(hBitmap);
			delete imageP;
			*ix=0;
			*iy=0;
			return false;
		}
		*hdcBMP=CreateCompatibleDC(hdc);
		HOldBitmap=(HBITMAP)SelectObject(*hdcBMP,hBitmap);
		Graphics MyGraphics(*hdcBMP);
		MyGraphics.DrawImage(imageP,0,0,imageP->GetWidth(),imageP->GetHeight());
		delete imageP;
		return true;
}

// imageをdx,dyにジャストフィットする画像サイズ(縦横比維持)を*sx,*syに作成する

void image_just_size(int* sx,int* sy,int px,int py,int dx,int dy){
	double pxy=double(px)/double(py);	// オリジナル横/縦
	double dxy=double(dx)/double(dy);	// 変換後横/縦
	int dx2,dy2;
	if(pxy>dxy){	//	オリジナルが横が長いので横基準に縦を縮小する
		dx2=dx;
		dy2=(int)double(dx/pxy);
	}else{
		dy2=dy;
		dx2=(int)double(dy*pxy);
	}
	*sx=dx2;
	*sy=dy2;
}

//     ウィンドウクラスの登録 1回しか呼ばれないのに関数化しているのは、ウィンドウを2回呼び出す場合に
//  この関数を再利用できるからです。

BOOL InitApp(HINSTANCE hInst,WNDPROC WndProc,TCHAR* szClassName){
	WNDCLASS wc;
	wc.style = CS_HREDRAW | CS_VREDRAW;
	wc.lpfnWndProc = WndProc;
	wc.cbClsExtra = 0;
	wc.cbWndExtra = 0;
	wc.hInstance = hInst;
	wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	wc.hCursor = LoadCursor(NULL, IDC_ARROW);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName = NULL;
	wc.lpszClassName = (TCHAR*)szClassName;
	return (RegisterClass(&wc));
}

//     ウィンドウの作成 1回しか呼ばれないのに関数化しているのは、ウィンドウを2回呼び出す場合に
//  この関数を再利用できるからです。

BOOL InitInstance(HINSTANCE hInst, TCHAR* szClassName, int nCmdShow){
	hWnd = CreateWindow(szClassName,
			TEXT("jpgviewm3"),
			WS_OVERLAPPEDWINDOW,
			CW_USEDEFAULT,
			CW_USEDEFAULT,
			CW_USEDEFAULT,
			CW_USEDEFAULT,
			NULL,
			NULL,
			hInst,
			NULL);
	if (!hWnd)
		return FALSE;
	ShowWindow(hWnd, nCmdShow);
	UpdateWindow(hWnd);
	return TRUE;
}

実行ファイルとソースファイルのダウンロード