前回に引き続き、CALBUMクラス説明をメンバー関数定義について行います。
【CALBUMのメンバー関数定義部】
//Album(Graphics)の初期化(1DCに1回だけ実施する)
bool CALBUM::Init(HWND hWnd) {
//(解説:この関数は「仮想ウィンドウ」と呼ばれるコンパチDCに読み込ませたフルスクリーンのコンパチビットマップ(m_hCanvas)を作成して、WM_PAINTに集中する描画処理をこのビットマップを使って平準化します。そのプロセスはコメントを追ってご理解ください。)
//ウィンドウ関係
m_hWnd = hWnd; //ウィンドウハンドルを記録
HDC hDC = GetDC(hWnd); //対象ウィンドウのDCを取得
m_hDC = CreateCompatibleDC(hDC); //コンパチDCを作成
m_Left = m_Top = 0; //フルスクリーンに拡大した
m_Right = GetSystemMetrics(SM_CXFULLSCREEN); //時のキャンヴァスサイズを
m_Bottom = GetSystemMetrics(SM_CYFULLSCREEN); //記録しておく
m_hCanvas = CreateCompatibleBitmap(hDC, //m_hDCではないので注意
m_Right, m_Bottom); //m_hDCにするとモノクロとなる
SelectObject(m_hDC, m_hCanvas); //キャンバスの実体
PatBlt(m_hDC, m_Left, m_Top, m_Right, m_Bottom, PATCOPY); //初期描画
ReleaseDC(hWnd, hDC); //hDCを開放
//(解説:仮想ウィンドウが出来たら、そのデバイスコンテキストでGraphicsクラスオブジェクトを作ります。即ち、今後のGDI+処理は仮想ウィンドウに対して行うことになります。)
if(m_pGraphics = new Graphics(m_hDC))
return TRUE;
else
return FALSE;
}
//出力先ウィンドウのWM_SIZEで実行
void CALBUM::GetSize(LPARAM lParam, HWND TBar, HWND SBar) {
//(解説:この関数はWM_SIZEの処理(BCCSkeltonの"OnSize"関数)において、メンバー変数のm_Y、m_Widthおよびm_Height(ツールバー、ステータスバーがあればその高さを反映させて)を求める処理を行います。m_Xは既に0で初期化されているので登場しません。)
m_Width = LOWORD(lParam);
m_Height = HIWORD(lParam);
RECT rec;
if(TBar) {
GetWindowRect(TBar, &rec);
m_Y = rec.bottom - rec.top;
m_Height -= m_Y;
}
if(SBar) {
GetWindowRect(SBar, &rec);
m_Height -= rec.bottom - rec.top;
}
}
//m_hWndウィンドウのWM_PAINTメッセージで実行
void CALBUM::OnPaint(HDC hDC, int x = 0, int y = 0) {
//ウィンドウを仮想ウィンドウ(Bitmap)で描画
BitBlt(hDC, m_Left, m_Top, m_Right, m_Bottom, m_hDC, x, y, SRCCOPY);
}
//(解説:この関数もWM_PAINTの処理(BCCSkeltonの"OnPaint"関数)において、描画された「仮想ウィンドウ」を実際のウィンドウに張り付けます。この為に描画プロセスが見えたり、ちらつきが出たりしないで画面が変わるように見えます。)
//描画色設定(Alpha, R, G, B)
void CALBUM::SetCol(int Alpha, int R, int G, int B) {
m_Col = Color(Alpha, R, G, B); //Alpha, R, G, B
}
//(解説:単にRGBの値で色を選択するだけですが、GDI+では更に透明感を出すαブレンドが加わります。)
//画面消去(直前のSetCol関数で消去色を設定する)
bool CALBUM::Cls() {
if(!m_pGraphics)
return FALSE;
m_pGraphics->Clear(m_Col);
//再描画を要求
InvalidateRect(m_hWnd, NULL, TRUE);
return TRUE;
}
//(解説:GDI+のClearメソッドを使っています。ただそれだけでは描画されないのでInvalidateRectを使って画面を書き換えます。主としてサムネイル表示と標準表示の切り替えに使います。)
//ペン設定(直前のSetCol関数で消去色を設定する)
void CALBUM::SetPen(int width) {
if(m_pPen)
delete m_pPen;
m_pPen = new Pen(m_Col, width);
}
//(解説:ペンオブジェクトの選択です。色と太さを指定します。)
//指定(x, y, w, h)の矩形を描画(要色、ペン設定)
bool CALBUM::Rect(int x, int y, int w, int h) {
if(!m_pGraphics)
return FALSE;
m_pGraphics->DrawRectangle(m_pPen, x, y, w, h);
//再描画を要求
InvalidateRect(m_hWnd, NULL, TRUE);
return TRUE;
}
//(解説:GDI+のDrawRectangleメソッドです。ペンオブジェクトでx、y、w、hで示される矩形を、同じくInvalidateRextを使い描画します。具体的にはサムネイル表示の際の選択写真を囲む処理で使います。)
//フォトを追加(ファイル名がNULLだと「ファイルを開く」ダイアログが開く)
bool CALBUM::Add(char* fn, char* ftr = IMGFILTER, char* cmt = "") {
//(解説:コメントの通りですが、この処理はデフォルトで定義炭のGDI+のフィルター(IMGFLT)を使い、コメントはNULLになっています。)
if(*fn) //ファイル名が与えられた場合
lstrcpy(m_FileName, fn);
else {
//イメージファイルを選択
OPENFILENAME ofn; //オープンファイルダイアログ構造体
ZeroMemory(&ofn, sizeof(ofn)); //ゼロ初期化
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = m_hWnd;
ofn.lpstrFilter = ftr; //引数でフィルタリングする
ofn.nFilterIndex = 1;
ofn.lpstrFile = m_FileName;
ofn.nMaxFile = MAX_PATH;
ofn.lpstrTitle = "イメージファイルの選択";
ofn.lpstrInitialDir = ".";
ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT;
ofn.lpstrDefExt = NULL;
if(!GetOpenFileName(&ofn)) {
return FALSE;
}
}
//(解説:ここまででマルチバイト文字のm_FileNameが確定します。)
//写真を追加
m_Photo[m_NoI] = new (CPHOTO); //写真(CPH0TOインスタンス)の作成
m_Photo[m_NoI]->SetPhoto(m_FileName, cmt);
//(解説:CPHOTOオブジェクトをnewで一つ増やし、それを配列の次<新規>を指しているm_NoIにSetPhoto関数でデータを与えます。)
//写真数を増やす
m_NoI++;
if(m_NoI > MAXIMG) {
MessageBox(m_hWnd, "これ以上のイメージ読み込みはできません", "警告", MB_OK | MB_ICONERROR);
m_NoI--;
return FALSE;
}
//(解説:写真最大数迄このように増やしてゆきます。)
return TRUE;
}
//フォトの削除
bool CALBUM::Delete(int n) {
if(n >= m_NoI || n < 0) //違法な引数
return FALSE;
m_Photo[n]->Delete();
//誤 for(int i = n; n < m_NoI - 1; i++) {
for(int i = n; i < m_NoI - 1; i++) {
m_Photo[i] = m_Photo[i + 1];
}
if(m_NoI > 0) {
m_NoI--;
m_Photo[m_NoI] = 0;
}
if(m_Selected && m_Selected >= n)
--m_Selected;
return TRUE;
}
//(解説:サムネイルを使っているとおかしな引数が与えられる可能性があるので最初にエラーチェックを入れています。指定された番号の写真オブジェクトを削除し、その一つ上から順繰りに後釜に入れてゆき、最後のポインターをゼロにして、配列数(m_NoI)を一つ減らします。)
//全フォトの削除
void CALBUM::DeleteAll() {
for(int i = m_NoI - 1; i >= 0; i--)
delete m_Photo[i];
m_NoI = 0; //読み込みImage数
m_Selected = 0; //選択されているイメージの配列番号
m_CurPage = 0; //サムネイル表示で現在選択されているページ
m_RFType = RotateNoneFlipNone; //回転、反転なし
}
//(解説:単純に最新の写真から最初の写真迄削除し、変数を初期化します。)
//選択イメージの設定(0~ベース)
void CALBUM::Select(int n) {
if(n < 0)
m_Selected = 0;
else {
if(n < m_NoI)
m_Selected = n;
else
m_Selected = m_NoI - 1;
}
}
//(解説:何番の写真が選択されているかを示す変数m_Selectedに値を入れます。0未満が指定されれば先頭の写真、写真数を超えれば最後の写真を選択します。)
//選択イメージ幅(LOWORD)、高さ(HIWORD)の取得
DWORD CALBUM::GetImgSize() {
//エラー対応
if(!m_Photo[m_Selected])
return 0;
//現在選択されている写真のファイル名をワイド文字にする
mbstowcs(m_WFName, m_Photo[m_Selected]->m_FName, MAX_PATH);
//現在選択されている写真のファイル名でイメージクラスインスタンスを生成
m_Image = new (Image)(m_WFName);
//m_Imageインスタンスの生成に失敗した場合
if(!m_Image)
return 0;
//イメージ幅(LOWORD)、高さ(HIWORD)を取得
DWORD size = MAKELONG((WORD)m_Image->GetWidth(), (WORD)m_Image->GetHeight());
//m_Imageインスタンスを解放し、ポインターをゼロ初期化
delete m_Image;
m_Image = 0;
return size;
}
//(解説:写真画像の描画関係処理では、未生成の写真が選ばれるとすぐにプログラムが落ちるので最初に「エラー対応」をします。また、画像の幅、高さを取得する為に、わざわざGDI+のImageクラスインスタンスを生成してGetWidth/Heightメソッドで取得するので、いちいち一方だけ処理するのは不経済なのでDWORDで一緒に渡すようにしました。)
//m_Selectedのファイルのフルパスファイル名を返す
char* CALBUM::GetFileName() {
return m_Photo[m_Selected]->m_FName;
}
//選択イメージのコメントの取得
char* CALBUM::GetComment() {
return m_Photo[m_Selected]->m_Comment;
}
//選択イメージのコメントの設定
void CALBUM::SetComment(char* cmt) {
m_Photo[m_Selected]->SetComment(cmt);
}
//(解説:元々はCPHOTOクラスに設定したメンバー関数ですが、画像のメンバー変数を外す際にCALBUMクラスへ引っ越しました。)
//イメージの表示(x, yからw幅、h高さの矩形に原寸表示比率で表示)
bool CALBUM::ShowImg(int x, int y, int w, int h) {
//(解説:ImageクラスのDrawImageメソッドは30もオーバーロード関数があり、サムネイルも別途関数があるのですが、オリジナルの画像の現寸縦横比のまま指定矩形の中央に最大サイズで描画する機能が見当たらなかったので自分で書きました。)
//エラー対応
if(!m_Photo[m_Selected])
return FALSE;
//現在選択されている写真のファイル名をワイド文字にする
mbstowcs(m_WFName, m_Photo[m_Selected]->m_FName, MAX_PATH);
//現在選択されている写真のファイル名でイメージクラスインスタンスを生成
m_Image = new (Image)(m_WFName);
//(解説:描画の際にのみCPHOTOのm_FNmaeファイルパスで指定される画像ファイルでImageクラスオブジェクトを生成します。また、他のCOMと同様、ファイル名はワイド文字(wcs)で与えなければなりません。)
//m_Imageインスタンスの生成に失敗した場合
if(!m_Image)
return FALSE;
//画像を回転させる-RotateFlipType:Rotate<A>Flip<B> <A:None, 90, 180, 270>, <B:X, Y, XY>
m_Image->RotateFlip(m_RFType);
//(解説:最初はアルバムの表示はオリジナルの回転なしでしたが、意外に回転しないと縦にならない写真が多いので、後から加えた機能です。回転はRotaryFlipTypeと呼ばれるenum変数で表され、"Rotary180FlipX"のように表示されます。これをアルバムの表示用変数にしたのがm_RFTypeです。)
//イメージの原寸を取得
int imgw = m_Image->GetWidth();
int imgh = m_Image->GetHeight();
//(解説:まず原寸の幅と高さを取ります。)
//w、h内で原寸の縦横対比のイメージを中央に配置
double asp1, asp2;
asp1 = (double)imgh / (double)imgw;
asp2 = (double)h / (double)w;
//(解説:整数をdoubleにキャストして原寸の縦横比と表示矩形の縦横比を求めます。)
if(asp1 > asp2) {
imgh = h;
imgw = h / asp1;
}
else {
imgw = w;
imgh = w * asp1;
}
//(解説:それ等を比較して長い方が表示矩形の一辺とし、原寸の比率で他の一辺の長さを決めます。)
x += (w - imgw) / 2;
y += (h - imgh) / 2;
//(解説:表示は中央表示にします。)
//m_Imageインスタンスを表示
m_pGraphics->DrawImage(m_Image, x, y, imgw, imgh);
//(解説:DrawImageは最もオーソドックスなx、y、w、hでの指定にしました。)
//m_Imageインスタンスを解放
delete m_Image;
m_Image = 0;
//(解説:以前はCPHOTOオブジェクトで保持していましたが、メモリーリソースの節約のために、Imageインスタンスは使ったら使い捨てて、表示したら毎回廃棄します。)
//再描画を要求
InvalidateRect(m_hWnd, NULL, TRUE);
//(解説:お約束の描画要求です。)
return TRUE;
}
//イメージの表示(第1引数TRUE左上、FALSE中央、第2引数TRUE原寸、FALSEサイズ調整)
bool CALBUM::ShowImg(bool lefttop, bool asis) {
//(解説:以下は↑の表示の相方にあたるもので、表示位置を左上、サイズを原寸のまま表示できるようにしたものです。)
//エラー対応
if(!m_Photo[m_Selected])
return FALSE;
//現在選択されている写真のファイル名をワイド文字にする
mbstowcs(m_WFName, m_Photo[m_Selected]->m_FName, MAX_PATH);
//現在選択されている写真のファイル名でイメージクラスインスタンスを生成
m_Image = new (Image)(m_WFName);
//m_Imageインスタンスの生成に失敗した場合
if(!m_Image)
return FALSE;
//画像を回転させる-RotateFlipType:Rotate<A>Flip<B> <A:None, 90, 180, 270>, <B:X, Y, XY>
m_Image->RotateFlip(m_RFType);
//イメージの原寸を取得
int imgw = m_Image->GetWidth();
int imgh = m_Image->GetHeight();
//サイズ調整の場合は比率に応じて調整
if(!asis) {
double asp1, asp2;
asp1 = (double)imgh / (double)imgw;
asp2 = (double)m_Height / (double)m_Width;
if(asp1 > asp2) {
imgh = m_Height;
imgw = m_Height / asp1;
}
else {
imgw = m_Width;
imgh = m_Width * asp1;
}
}
//左上に表示
int x = m_X;
int y = m_Y;
//中央に表示
if(!lefttop) {
x += (m_Width - imgw) / 2;
y += (m_Height - imgh) / 2;
}
//m_Imageインスタンスを表示
m_pGraphics->DrawImage(m_Image, x, y, imgw, imgh);
//m_Imageインスタンスを解放
delete m_Image;
m_Image = 0;
//再描画を要求
InvalidateRect(m_hWnd, NULL, TRUE);
return TRUE;
}
//サムネイル表示(引数はサムネイルの幅・高さ、ページ)
bool CALBUM::ShowThumbnail(int size, int page = -1) {
//(解説:ImageクラスにGetThumbnailImageメソッドがあるのですが、これも原寸の縦横比を変えられるのでDrawImageで縮小表示するのと何ら変わりなく、最初の自作ShowImage関数を使っています。この変数ではサムネイルの描画フレーム矩形サイズを指定し、ページ番号を指定することができます。ページ番号はデフォルト値が決められており、指定されないと「選択された写真が表示されるページ」が表示されます。)
//エラー対応
if(!m_NoI)
return FALSE;
//イメージ表示の為の画面数、横表示数、縦表示数設定
int sel = m_Selected; //m_Selectedを退避保存
m_Column = m_Width / size; //列数(1~ベース)
m_Row = m_Height / size; //行数(1~ベース)
if(!(m_Column * m_Row)) {
MessageBox(m_hWnd, "ウィンドウサイズが小さすぎます",
"警告", MB_OK | MB_ICONERROR);
return FALSE; //"Div. by zero" error
}
m_Page = m_NoI / (m_Column * m_Row); //ページ数(1~ベース)
if(page == -1) //現在選択されている写真のページ
m_CurPage = page = m_Selected / (m_Column * m_Row);
else
m_CurPage = page;
//(解説:ここまでがパラメーター確定の処理です。)
if(page > m_Page) //page引数が大きすぎて表示できない
return FALSE;
//(解説:ここからサムネイル表示と選択画像を囲む矩形を表示する処理です。Select関数でm_Selectedが変わるのでその値をsel変数に一旦退避し、処理が終了する際に戻しています。)
for(int i = 0; i < m_Row; i++) {
for(int j = 0; j < m_Column; j++) {
int n = page * (m_Column * m_Row) + i * m_Column + j;
if(n < m_NoI) {
Select(n);
ShowImg(size * j, size * i + m_Y, size, size);
if(n == sel) {
Rect(size * j, size * i + m_Y, size, size);
}
}
else {
m_Selected = sel;
return TRUE;
}
}
}
m_Selected = sel;
return TRUE;
}
//サムネイル表示のページ当り表示写真数
int CALBUM::HowManyThumb(int size) {
m_Column = m_Width / size; //列数(1~ベース)
m_Row = m_Height / size; //行数(1~ベース)
return (m_Column * m_Row);
}
//(解説:この関数は「前へ」「次へ」で選択写真を1ページ分移動する為に使います。この処理のために、m_Selectedがm_NoIを超えたり、0未満になったりすることがあり得るのです。)
//出力先ウィンドウのx, y座標からサムネイルの番号を得る
int CALBUM::GetWhichThumb(int size, int x, int y) {
int col = x / size;
int row = y / size;
return (m_CurPage * (m_Column * m_Row) + row * m_Column + col);
}
//(解説:これはマウス左クリックで写真を選択する際に画面の位置から選択写真の配列引数を求める為に使います。)
如何でしたでしょうか?CPHOTOクラス、CALBUMクラスで基本的な「写真の選択、読み込み、表示」と「複数写真の管理」ができたので、これをスケルトンに組み込んでゆきます。(注)
注:実は単なるアルバム写真の表示では飽き足らなくなり、画像ファイルの加工やその詳細データの表示機能もアルバムに付け加えてゆくことになりますが、それはスケルトンの解説で紹介します。