初版 2014/06/07

概要

jpgファイルを拡大縮小して表示するプログラムです。
マウスホイールによる拡大縮小及び左クリックしながら中心位置の移動ができます。
イメージがウィンドウに収まらない場合は、スクロールバーが表示されます。
jpgファイルの読み込みはGDI+を用いています。
表示するファイルは、ファイルメニューの読み込みにより選択します。
表示位置・拡大縮小・スクロール処理を行うためにImageWindowクラスを作成しています。
jpgファイルの読み込み等は、GDI+によりjpgファイルをメモリ上に描画しウィンドウに転送するプログラム(32/64bit)がベースになります。

テスト環境

コンパイラ

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

実行環境

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

動作例

マウス操作

マウスホイール操作

奥へ回すとカーソル位置を中心に拡大表示される。
逆へ回すとカーソル位置を中心に縮小表示される。

マウス左ボタン

左ボタンを押すとカーソルが手の形状になります。押したままカーソルを移動させると画像が移動します。左ボタンを離した位置で移動が終了します。
ウィンドウ内に画像が全部表示されている場合は移動しません。

スクロールバー

スクロールバーを操作すると画像が移動します。

メニュー

ファイルメニュー

読み込み 表示するjpgファイルを選択します。
終了 プログラムを終了します。

表示メニュー

等倍 jpgファイルを等倍で表示します。
ジャスト jpgファイルをウィンドウに収まるように表示します。

プログラムソースの概要

jpgviewm4.cpp

_tWinMain

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

WndProc

WM_CREATEメッセージ
ウィンドウの初期化時にWM_CREATEメッセージが発生するので、手形状のカーソルを読み込み、ImageWindowクラスにウィンドウのデバイスコンテキストを登録します。
WM_SIZEメッセージ
ウィンドウサイズが変化した時に呼び出されます。ウィンドウサイズをImageWindow::wm_sizeにより登録します。
WM_LBUTTONDOWNメッセージ
マウスの左ボタンが押されたときに呼び出されます。移動の中心座標を保存しカーソルを手形状に変更します。
WM_MOUSEMOVEメッセージ
マウスが移動中に呼び出されます。
マウスの左ボタンが押された後の場合、マウス座標を取得しImageWindow::MoveImageにより画像を移動させます。
ImageWindow::MoveImage内ではScrollWindow APIにより画像を移動させています。
WM_LBUTTONUPメッセージ
マウスの左ボタンが離されたときに呼び出されます。カーソル形状を元に戻します。
WM_MOUSEWHEELメッセージ
マウスのホイールが回転した時に呼び出されます。
ImageWindow::wm_mousewheelで画像サイズ・位置等の計算を行います。
InvalidateRect APIによりWM_PAINTメッセージを発生させ計算結果に基づき画像の拡大縮小を行ってウィンドウに表示します。
WM_HSCROLL・WM_VSCROLLメッセージ
スクロールバーにクリック等の操作されたときに呼び出されます。
メッセージが発生するだけでスクロールバーの表示は変更されません。
ImageWindow::wm_hscroll,ImageWindow::wm_bscrollで画像位置を計算しImageWindow::ScrollBarDrawでスクロールバーの変更を行い、ScrollWindow APIで画像をスクロールさせます。
WM_COMMANDメッセージ
メニューが選択されたときに呼び出されます。
LOWORD(wp)により選択されたメニューのIDが取得できます。
IDM_READ
GetFileNameによりファイル名の取得を行います。
あらかじめ読み込まれている画像があればメモリ等を開放します。(ImageWindow::DelImage)
ImageWindow::LoadImageによりjpgファイルを読み込みメモリ上に描画します。
ImageWindow::JustSizeによりウィンドウに収まるように画像の大きさを調整します。
InvalidateRect APIによりWM_PAINTメッセージを発生させウィンドウに表示します。
IDM_END
WM_DESTORYメッセージを呼び出しプログラムを終了します。
IDM_DEF
画像を等倍でウィンドウに表示します。
ウィンドウに収まらない場合は、左上を基準に表示します。
IDM_JUST
ImageWindow::JustSizeによりウィンドウに収まるサイズを計算します。
InvalidateRect APIによりWM_PAINTメッセージを発生させウィンドウに表示します。

imagewindow.h

画像をウィンドウに表示する基本的な機能を提供するImageWindowクラスを提供します。
以下の機能をサポートしています。
画像をメモリ上に読み込む。
拡大縮小及び移動に対応しウィンドウに収まらない場合は、スクロールバーを提供します。

ImageWindowクラス主要メンバー

int dx,dy;	// ウィンドウ上の表示位置(左上)
int ix,iy;	// 読み込んだ画像サイズ
int wx,wy;	// ウィンドウサイズ
double scale;	// 表示倍率
SCROLLINFO hsi,vsi; // スクロール情報構造体
int GetCurrentPositionX(void);	//	表示位置のX座標を返す
int GetCurrentPositionY(void);	//	表示位置のY座標を返す
void ClearPos(void);	//	表示座標を0,0 倍率を1にする
void wm_size(int x,int y);	//	ウィンドウサイズを設定する
void SetHWND(HWND h);	//	デバイスコンテキストを設定する
bool LoadImage(TCHAR* fname,HDC hdc);	// ファイルより画像を読み込む
void DelImage(void);	//	ImageWindowクラスに読み込まれている画像を解放する
bool IsImage(void);	//	有効な画像がImageWindowに読み込まれている場合はtrueを返す
void ScrollBarDraw(void);	//	ImageWindow内の情報をもとにスクロールバーを設定・表示	
void JustSize(void);	//	ウィンドウに収まるサイズの位置・倍率をImageWindowクラスに設定
void wm_hscroll(WPARAM wp,LPARAM lp);	//	WM_HSCROLLメッセージより画像位置・スクロールバーを設定する
void wm_vscroll(WPARAM wp,LPARAM lp);	//	WM_VSCROLLメッセージより画像位置・スクロールバーを設定する
bool wm_mousewheel(int m_x,int m_y,int m_delta);	//	中心位置及びスクロールの回転方向よりImageWindowクラス内の位置・倍率を設定
void MoveImage(int m_x,int m_y,int cx,int cy);	//	開始位置及び現在座標より画像をスクロール・スクロールバーを設定する
void wm_paint(HDC hdc);	//	ImageWindowクラスの情報をもとにウィンドウに画像を表示

ソースコード

jpgviewm4.cpp

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

#include <windows.h>
#include <gdiplus.h>
#include <tchar.h>
#include "imagewindow.h"
#include "resource.h"

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

using namespace Gdiplus;

GdiplusStartupInput gdiSI;
ULONG_PTR gdiToken;

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
BOOL InitApp(HINSTANCE, WNDPROC, TCHAR*);
BOOL InitInstance(HINSTANCE, TCHAR*, int);
BOOL GetFileName(TCHAR* fname,int sz); // 読込ファイル名の取得

HINSTANCE hInst;

// 表示するjpegファイル名
TCHAR* szFile=0;

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

int WINAPI _tWinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst,TCHAR* lpsCmdLine, int nCmdShow){
	MSG msg;
	BOOL b;

	TCHAR szClassName[] = TEXT("JPGVIEWM4");
	GdiplusStartup(&gdiToken, &gdiSI, NULL);

	hInst=hCurInst;
	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;

	HPEN hPen, hOldPen;
	HBRUSH hBrush;
	static int cx=0;	//	移動元の中心
	static int cy=0;	//	移動元の中心

	static int mode=0;	//	マウス移動中のモード 1:移動中
	static HCURSOR hHandCur;	//	ハンドカーソル
	static HCURSOR hOldCur;	//	カーソル
	static int m_x,m_y;
	int m_delta;
	static ImageWindow iw;
	static TCHAR buf[MAX_PATH];

	switch (msg) {
	case WM_CREATE:
		hHandCur=LoadCursor(NULL, IDC_HAND);
		iw.SetHWND(hWnd);
		break;
	case WM_SIZE:
		iw.wm_size(LOWORD(lp),HIWORD(lp));
		iw.ScrollBarDraw();
		break;
	case WM_LBUTTONDOWN:	//	マウス左ボタンが押された
		mode=1;
		cx=GET_X_LPARAM(lp)-iw.GetCurrentPositionX();
		cy=GET_Y_LPARAM(lp)-iw.GetCurrentPositionY();
		hOldCur=GetCursor();
		SetCursor(hHandCur);
		break;
	case WM_LBUTTONUP:	//	マウス左ボタンが離された
		if(mode){
			SetCursor(hOldCur);
			mode=0;
		}
		break;
	case WM_HSCROLL:
		iw.wm_hscroll(wp,lp);
		break;
	case WM_VSCROLL:
		iw.wm_vscroll(wp,lp);
		break;
	case WM_MOUSEMOVE:
		m_x=GET_X_LPARAM(lp);
		m_y=GET_Y_LPARAM(lp);
		if(mode==1)	//	ドラッグ中
			iw.MoveImage(m_x,m_y,cx,cy);
		break;
	case WM_MOUSEWHEEL:	//	拡大縮小
		m_delta=GET_WHEEL_DELTA_WPARAM(wp);
		if(iw.wm_mousewheel(m_x,m_y,m_delta)==true)
			InvalidateRect(hWnd,0,TRUE);
		break;
	case WM_PAINT:	// ウィンドウの描画が必要な場合に呼び出される。
		if(iw.IsImage()==true){
			hdc = BeginPaint(hWnd, &ps);
			hPen = CreatePen(PS_SOLID, 1, RGB(255, 255, 255));
			hOldPen =(HPEN) SelectObject(hdc, hPen);
			hBrush = (HBRUSH)GetStockObject(NULL_BRUSH);

			SetStretchBltMode(hdc, HALFTONE);
			iw.wm_paint(hdc);

			SelectObject(hdc, hOldPen);
			DeleteObject(hPen);
			EndPaint(hWnd, &ps);
		}
		break;
	case WM_COMMAND:
		switch(LOWORD(wp)) {
			case IDM_READ:
				if(GetFileName(buf,sizeof(buf)/sizeof(TCHAR))){
					hdc=GetDC(hWnd);
					iw.DelImage();
					szFile=buf;
					if(iw.LoadImage(szFile,hdc)==false){
						MessageBox(0,szFile,_TEXT("Error"),MB_OK);
						PostQuitMessage(0);
					}
					ReleaseDC(hWnd,hdc);
					SetWindowText(hWnd,szFile);

					iw.JustSize();
					InvalidateRect(hWnd,0,TRUE);
				}
				break;
			case IDM_END:
				SendMessage(hWnd,WM_DESTROY,0,0);
				break;
			case IDM_DEF:
				iw.ClearPos();
				iw.ScrollBarDraw();
				InvalidateRect(hWnd,0,TRUE);
				break;
			case IDM_JUST:
				iw.JustSize();
				InvalidateRect(hWnd,0,TRUE);
				break;
		}
		break;
	case WM_DESTROY:	// ウィンドウを閉じる場合に呼び出される。
		iw.DelImage();
		PostQuitMessage(0);
		break;
	default:
		return (DefWindowProc(hWnd, msg, wp, lp));
	}
	return 0L;
}

//	ウィンドウクラスの登録 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(LTGRAY_BRUSH);
	wc.lpszMenuName = _TEXT("IDM_MENU");
	wc.lpszClassName = (TCHAR*)szClassName;
	return (RegisterClass(&wc));
}

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

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

// 読込ファイル名の取得

BOOL GetFileName(TCHAR* fname,int sz){
	OPENFILENAME o;
	fname[0]=_T('\0');
	ZeroMemory(&o,sizeof(o));
	o.lStructSize=sizeof(o);
	o.Flags=0;
	o.lpstrFile=fname;
	o.nMaxFile=sz;
	o.lpstrFilter=_TEXT("JPEG(*.JPG)\0");
	o.nFilterIndex=1;
	return GetOpenFileName(&o);
}

imagewindow.h

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

using namespace Gdiplus;

class ImageWindow{
	BITMAPINFOHEADER bmih;
	BYTE *pBits;
	HBITMAP hBitmap,HOldBitmap;
	HDC hdcBMP;
	HWND hWnd;
	int dx,dy;
	int ix,iy;
	int wx,wy;
	double scale;
	SCROLLINFO hsi,vsi;
public:
	ImageWindow(){
		dx=dy=ix=iy=wx=wy=0;
		scale=1.0;
	}
	int GetCurrentPositionX(void){
		return dx;
	}
	int GetCurrentPositionY(void){
		return dy;
	}
	void ClearPos(void){
		dx=dy=0;
		scale=1.0;

	}
	void wm_size(int x,int y){
		wx=x;
		wy=y;
		if(ix*scale < wx){	//	横幅がウィンドウに収まっている場合はウィンドウの真ん中に表示する
			dx=int((double(wx) - double(ix)*scale ) /2);
		}
		if(iy*scale < wy){	//	横幅がウィンドウに収まっている場合はウィンドウの真ん中に表示する
			dy=int((double(wy) - double(iy)*scale ) /2);
		}
	}
	void SetHWND(HWND h){
		hWnd=h;
	}
	bool LoadImage(TCHAR* fname,HDC hdc){
		Image* imageP;
#ifndef UNICODE
		WCHAR wTitle[MAX_PATH];
		MultiByteToWideChar(932,0,szFile,-1,wTitle,sizeof(wTitle)/sizeof(TCHAR));
		imageP=Bitmap::FromFile(wTitle);
#else
		imageP=new Image(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(0,_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=new Gdiplus::Graphics(hdcBMP);
		MyGraphics->DrawImage(imageP,0,0,ix,iy);
		delete imageP;
		delete MyGraphics;
		return true;
	}

	void DelImage(void){
		if(hdcBMP){
			SelectObject(hdcBMP,HOldBitmap);
			DeleteObject(hBitmap);
			DeleteDC(hdcBMP);
		}
	}
	bool IsImage(void){
		if(hdcBMP)
			return true;
		else
			return false;
	}
	void ScrollBarDraw(void){
		hsi.cbSize=sizeof(SCROLLINFO);
		hsi.fMask= SIF_POS | SIF_RANGE | SIF_PAGE;
		hsi.nMin=0;
		hsi.nMax=int(double(ix)*scale-1);
		hsi.nPage=wx;
		hsi.nPos=-dx;
		SetScrollInfo(hWnd,SB_HORZ,&hsi,TRUE);

		vsi.cbSize=sizeof(SCROLLINFO);
		vsi.fMask= SIF_POS | SIF_RANGE | SIF_PAGE;
		vsi.nMin=0;
		vsi.nMax=int(double(iy)*scale-1);
		vsi.nPage=wy;
		vsi.nPos=-dy;
		SetScrollInfo(hWnd,SB_VERT,&vsi,TRUE);
	}
	void JustSize(void){
		int sx,sy;
		double pxy=double(ix)/double(iy);	// オリジナル横/縦
		double dxy=double(wx)/double(wy);	// 変換後横/縦
		int dx2,dy2;
		if(pxy>dxy){	//	オリジナルが横が長いので横基準に縦を縮小する
			dx2=wx;
			dy2=(int)double(wx/pxy);
		}else{
			dy2=wy;
			dx2=(int)double(wy*pxy);
		}
		sx=dx2;
		sy=dy2;
		scale=double(sx)/double(ix);
		dx=(wx-sx)/2;
		dy=(wy-sy)/2;
		ScrollBarDraw();
	}
	void wm_hscroll(WPARAM wp,LPARAM lp){
		int rdx;
		int ddx=GetCurrentPositionX();
		rdx=ddx;
		switch(LOWORD(wp)){
		case SB_LINEUP:
			++ddx;
			break;
		case SB_LINEDOWN:
			--ddx;
			break;
		case SB_PAGEUP:
			ddx+=hsi.nPage;
			break;
		case SB_PAGEDOWN:
			ddx-=hsi.nPage;
			break;
		case SB_THUMBTRACK:
			ddx=-HIWORD(wp);
			break;
		}
		if(rdx != ddx){
			if(0<ddx)
				ddx=0;
			else{
				if(ddx< ((int)hsi.nPage-hsi.nMax) )
					ddx=(int)hsi.nPage-hsi.nMax;
			}
			dx=ddx;
			hsi.nPos=-ddx;
			ScrollBarDraw();
			ScrollWindow(hWnd, ddx-rdx, 0,NULL, NULL);
		}
	}
	void wm_vscroll(WPARAM wp,LPARAM lp){
		int rdy;
		int ddy=GetCurrentPositionY();
		rdy=ddy;
		switch(LOWORD(wp)){
		case SB_LINEUP:
			++ddy;
			break;
		case SB_LINEDOWN:
			--ddy;
			break;
		case SB_PAGEUP:
			ddy+=vsi.nPage;
			break;
		case SB_PAGEDOWN:
			ddy-=vsi.nPage;
			break;
		case SB_THUMBTRACK:
			ddy=-HIWORD(wp);
			break;
		}
		if(0<ddy)
			ddy=0;
		else{
			if(ddy< ((int)vsi.nPage-vsi.nMax) )
				ddy=(int)vsi.nPage-vsi.nMax;
		}
		if(rdy != ddy){
			dy=ddy;
			vsi.nPos=-ddy;
			ScrollBarDraw();
			ScrollWindow(hWnd, 0, ddy-rdy, NULL, NULL);
		}
	}

	bool wm_mousewheel(int m_x,int m_y,int m_delta){	//	拡大縮小
		double rscale=scale;
		if(m_delta<0)
			scale/=2;
		else
			scale*=2;

		int isx;
		int isy;
		isx=int(double(ix)*scale);
		isy=int(double(iy)*scale);
		if(isx>64 && isy>64){
			int rdx=dx;
			int rdy=dy;
			dx=int(m_x-double(m_x-dx)/rscale*scale);
			dy=int(m_y-double(m_y-dy)/rscale*scale);
			ScrollBarDraw();
			return true;
		}else
			scale=rscale;
		return false;
	}
	void MoveImage(int m_x,int m_y,int cx,int cy){
		int rdx=dx;
		int rdy=dy;
		dx=m_x-cx;	//	移動量
		dy=m_y-cy;	//	移動量
		if(wx < ix*scale){	//	ウィンドウサイズより表示サイズが大きい場合
			if( ix*scale+dx < wx)	//	右端に達している場合
				dx=int(wx-double(ix)*scale);
			if(0<dx)	//	左端に達している場合
				dx=0;

		}else{	//	ウィンドウサイズに表示サイズが収まっている場合
			dx=rdx;
		}

		if(wy < iy*scale){	//	ウィンドウサイズより表示サイズが大きい場合
			if( iy*scale+dy < wy)	//	下端に達している場合
				dy=int(wy-double(iy)*scale);
			if(0<dy)	//	上端に達している場合
				dy=0;
		}else{	//	ウィンドウサイズに表示サイズが収まっている場合
			dy=rdy;
		}

		if(rdx!=dx || rdy!=dy){
			ScrollWindow(hWnd, dx-rdx,dy-rdy ,NULL, NULL);
			ScrollBarDraw();
		}
	}
	void wm_paint(HDC hdc){
		StretchBlt(hdc,dx,dy,int((double)ix*scale),int((double)iy*scale),hdcBMP,0,0,ix,iy,SRCCOPY);
	}
};

resource.h

#define IDM_READ	2000
#define IDM_END		2010

#define IDM_JUST	2100
#define IDM_DEF		2110

resource.rc

#include <windows.h>
#include "resource.h"


//-------------------------
// メニュー(IDM_MENU)
//-------------------------
IDM_MENU MENU DISCARDABLE
{
	POPUP "ファイル(&F)"
	{
		MENUITEM "読み込み(&R)",IDM_READ
		MENUITEM "終了(&X)", IDM_END
	}
	POPUP "表示(&V)"
	{
		MENUITEM "等倍(&D)", IDM_DEF
		MENUITEM "ジャスト(&J)", IDM_JUST
	}
}

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