第174章 読みこんだビットマップをそのまま保存する


さて、ビットマップをファイルとして保存するにはどうしたらよいのでしょうか。 これは、ビットマップファイルを読み出した時と同じ行程を 書き込みにすればOKです。具体的にはBITMAPFILEHEADER構造体、 BITMAPINFOHEADER構造体、存在すればRGBQUAD構造体、そして ビットマップデータを書きこめばよいことになります。

今回は、手始めにファイルから読みこんだビットマップをそのまま、 名前を付けて保存するプログラムを作ります。 手順は最初に書いた通りです。特に難しくはありません。

// bmpfile05.rcの一部 ///////////////////////////////////////////////////////////////////////////// // // Menu // MYMENU MENU DISCARDABLE BEGIN POPUP "ファイル(&F)" BEGIN MENUITEM "開く(&O)", IDM_OPEN MENUITEM SEPARATOR MENUITEM "終了(&X)", IDM_END MENUITEM "名前を付けて保存(&A)", IDM_SAVEAS END POPUP "表示(&V)" BEGIN MENUITEM "情報表示(&I)", IDM_INFO POPUP "効果(&E)" BEGIN MENUITEM "左右逆転(&M)", IDM_MIRROR MENUITEM "上下逆転(&U)", IDM_UPDOWN END END END

メニューの「ファイル」の中に「名前を付けて保存」が加わりました。

// bmpfile05.cpp #ifndef STRICT #define STRICT #endif #include <windows.h> #include "resource.h" LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); ATOM InitApp(HINSTANCE); BOOL InitInstance(HINSTANCE, int); int ReadDIB(HWND, LONG *, LONG *); HPALETTE SetPalette(HWND, LPBITMAPINFOHEADER); int MakeBMPInfo(LPBITMAPINFOHEADER); int DrawBMP(HWND, HDC, int, int, char *); int SaveDIB(HWND); int GetSaveName(HWND, char *); char szClassName[] = "bmpfile05"; //ウィンドウクラス char szFileName[128]; //オープンするビットマップファイル HANDLE hMem, hMemInfo; char *szBuffer; DWORD dwFileSize, dwOffBits, dwSizeImage, dwClrUsed; WORD wBitCount; LPBITMAPINFO lpbmp_info; BITMAPFILEHEADER gBfh; BOOL bLoad = FALSE; HPALETTE hPalette; BOOL bMirror = FALSE, bUpdown = FALSE;

ビットマップファイルを読み出した時のBITMAPFILEHEADER構造体の 内容を保存しておくグローバル変数(gBfh)を用意してみました。 前回までのプログラムでは読み出したビットマップのBITMAPINFOHEADER以下 の内容はszBufferに格納されていました。今回作るファイルは まず、gBfhを書きこんで、次にszBufferを書きこめばできあがりです。 実は筆者はこれを勘違いしてszBufferにはビットマップデータしか 入っていないと思い込み大変めんどうなバグに陥ってしまいました。

int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst, LPSTR lpsCmdLine, int nCmdShow) { MSG msg; if (!InitApp(hCurInst)) return FALSE; if (!InitInstance(hCurInst, nCmdShow)) return FALSE; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } //ウィンドウ・クラスの登録 ATOM InitApp(HINSTANCE hInst) { WNDCLASSEX wc; wc.cbSize = sizeof(WNDCLASSEX); 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 = "MYMENU"; //メニュー名 wc.lpszClassName = (LPCSTR)szClassName; wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION); return (RegisterClassEx(&wc)); } //ウィンドウの生成 BOOL InitInstance(HINSTANCE hInst, int nCmdShow) { HWND hWnd; hWnd = CreateWindow(szClassName, "猫でもわかるBMP",//タイトルバーにこの名前が表示されます WS_OVERLAPPEDWINDOW,//ウィンドウの種類 CW_USEDEFAULT, //X座標 CW_USEDEFAULT, //Y座標 CW_USEDEFAULT, //幅 CW_USEDEFAULT, //高さ NULL, //親ウィンドウのハンドル、親を作るときはNULL NULL, //メニューハンドル、クラスメニューを使うときはNULL hInst, //インスタンスハンドル NULL); if (!hWnd) return FALSE; ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); return TRUE; }

このへんはいつもと同じです。

//ウィンドウプロシージャ LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { int id; OPENFILENAME ofn; HDC hdc; PAINTSTRUCT ps; char str[256]; static LONG wx, wy; static HMENU hMenu; switch (msg) { case WM_CREATE: hMenu = GetMenu(hWnd); break; case WM_INITMENU: if (bUpdown == TRUE) CheckMenuItem(hMenu, IDM_UPDOWN, MF_BYCOMMAND | MF_CHECKED); else CheckMenuItem(hMenu, IDM_UPDOWN, MF_BYCOMMAND | MF_UNCHECKED); if (bMirror == TRUE) CheckMenuItem(hMenu, IDM_MIRROR, MF_BYCOMMAND | MF_CHECKED); else CheckMenuItem(hMenu, IDM_MIRROR, MF_BYCOMMAND | MF_UNCHECKED); break; case WM_PAINT: if (bLoad) { hdc = BeginPaint(hWnd, &ps); if (dwClrUsed) { SelectPalette(hdc, hPalette, FALSE); RealizePalette(hdc); } DrawBMP(hWnd, hdc, wx, wy, szBuffer + dwOffBits - sizeof(BITMAPFILEHEADER)); EndPaint(hWnd, &ps); } else { return (DefWindowProc(hWnd, msg, wp, lp)); } break; case WM_COMMAND: switch (LOWORD(wp)) { case IDM_END: SendMessage(hWnd, WM_CLOSE, 0, 0); break; case IDM_OPEN: memset(&ofn, 0, sizeof(OPENFILENAME)); ofn.lStructSize = sizeof(OPENFILENAME); ofn.hwndOwner = hWnd; ofn.lpstrFilter = "Bitmap (*.BMP)\0*.BMP\0\0"; ofn.nFilterIndex = 1; ofn.lpstrFile = szFileName; ofn.nMaxFile = 128; ofn.Flags = OFN_HIDEREADONLY; if (GetOpenFileName((LPOPENFILENAME)&ofn)) ReadDIB(hWnd, &wx, &wy); break; case IDM_INFO: wsprintf(str, "ファイル名 = %s\n" "ファイルサイズ = %d\n" "BMP幅 = %d, BMP高さ = %d\n" "biBitCount = %d, biClrUsed = %d\n" "biSizeImage = %d, bOffBits = %d", szFileName, dwFileSize, wx, wy, wBitCount, dwClrUsed, dwSizeImage, dwOffBits); MessageBox(hWnd, str, "BITMAP情報", MB_OK); break; case IDM_MIRROR: bMirror = !bMirror; InvalidateRect(hWnd, NULL, TRUE); break; case IDM_UPDOWN: bUpdown = !bUpdown; InvalidateRect(hWnd, NULL, TRUE); break; case IDM_SAVEAS: SaveDIB(hWnd); break; } break; case WM_CLOSE: id = MessageBox(hWnd, "終了してもよいですか", "終了確認", MB_YESNO | MB_ICONQUESTION); if (id == IDYES) { if (hMem) GlobalFree(hMem); if (hMemInfo) GlobalFree(hMemInfo); if (hPalette) DeleteObject(hPalette); DestroyWindow(hWnd); } break; case WM_DESTROY: PostQuitMessage(0); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } return 0; }

WM_COMMANDの中で、IDM_SAVEASが選択されたら自作関数SaveDIBを 呼ぶようにしました。

int ReadDIB(HWND hWnd, LONG *pwx, LONG *pwy) { DWORD dwResult; HANDLE hF, hMem1, hMem2; LPBITMAPFILEHEADER lpBf; LPBITMAPINFOHEADER lpBi; char szFType[3]; RECT rc; int x, y; bLoad = TRUE; hF = CreateFile(szFileName, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hF == INVALID_HANDLE_VALUE) { MessageBox(hWnd, "ファイルのオープンに失敗しました", "Error", MB_OK); return -1; } hMem1 = GlobalAlloc(GHND, sizeof(BITMAPFILEHEADER)); lpBf = (LPBITMAPFILEHEADER)GlobalLock(hMem1); ReadFile(hF, (LPBITMAPFILEHEADER)lpBf, sizeof(BITMAPFILEHEADER), &dwResult, NULL); dwFileSize = lpBf->bfSize; szFType[0] = LOBYTE(lpBf->bfType); szFType[1] = HIBYTE(lpBf->bfType); szFType[2] = '\0'; dwOffBits = lpBf->bfOffBits; gBfh = *lpBf; //BITMAPFILEHEADERの内容をグローバル変数にコピー GlobalUnlock(hMem1); if (strcmp(szFType, "BM") != 0) { MessageBox(hWnd, "ビットマップではありません", "Error", MB_OK); GlobalFree(hMem1); CloseHandle(hF); return -1; } hMem2 = GlobalAlloc(GHND, sizeof(BITMAPINFOHEADER)); lpBi = (LPBITMAPINFOHEADER)GlobalLock(hMem2); ReadFile(hF, (LPBITMAPINFOHEADER)lpBi, sizeof(BITMAPINFOHEADER), &dwResult, NULL); *pwx = lpBi->biWidth; *pwy = lpBi->biHeight; wBitCount = lpBi->biBitCount; dwClrUsed = lpBi->biClrUsed; dwSizeImage = lpBf->bfSize - lpBf->bfOffBits; hMemInfo = GlobalAlloc(GHND, sizeof(BITMAPINFO) + dwClrUsed * sizeof(RGBQUAD)); lpbmp_info = (LPBITMAPINFO)GlobalLock(hMemInfo); if (hMem) { if(GlobalFree(hMem)) MessageBox(hWnd, "GlobalFree失敗", "Error", MB_OK); } hMem = GlobalAlloc(GHND, dwFileSize - sizeof(BITMAPFILEHEADER)); szBuffer = (char *)GlobalLock(hMem); if (dwClrUsed != 0 || wBitCount != 24) { hPalette = SetPalette(hWnd, lpBi); } //読みこむのはBITMAPINFOHEADER, RGBQUAD, ビットデータ SetFilePointer(hF, sizeof(BITMAPFILEHEADER), 0, FILE_BEGIN); ReadFile(hF, szBuffer, dwSizeImage + dwClrUsed * sizeof(RGBQUAD) + sizeof(BITMAPINFOHEADER), &dwResult, NULL); //BITMAPINFO構造体をセットする lpbmp_info->bmiHeader = *lpBi; MakeBMPInfo(lpBi); GetWindowRect(hWnd, &rc); x = rc.left; y = rc.top; rc.right = x + *pwx; rc.bottom = y + *pwy; AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, TRUE); MoveWindow(hWnd, x, y, rc.right - rc.left, rc.bottom - rc.top, TRUE); InvalidateRect(hWnd, NULL, TRUE); GlobalUnlock(hMemInfo); GlobalUnlock(hMem); if (GlobalFree(hMem1)) MessageBox(hWnd, "GlobalFree(hMem1)失敗", "Error", MB_OK); GlobalUnlock(hMem2); if (GlobalFree(hMem2)) MessageBox(hWnd, "GlobalFree(hMem2)失敗", "Error", MB_OK); CloseHandle(hF); return 0; }

この関数は前回とほぼ同じですが、gBfh = *lpBf; というように BITMAPFILEHEADER構造体の中身をグローバル変数にコピーしています。 当然コピーはGlobalUnlockする前です。

HPALETTE SetPalette(HWND hWnd, LPBITMAPINFOHEADER lpBi) { LPLOGPALETTE lpPal; LPRGBQUAD lpRGB; HANDLE hPal; WORD i; DWORD dwClrUsed; dwClrUsed = lpBi->biClrUsed; hPal = GlobalAlloc(GHND, sizeof(LOGPALETTE) + dwClrUsed * sizeof(PALETTEENTRY)); lpPal = (LPLOGPALETTE)GlobalLock(hPal); lpPal->palVersion = 0x300; lpPal->palNumEntries = (WORD)dwClrUsed; lpRGB = (LPRGBQUAD)((LPSTR)lpBi + lpBi->biSize); for (i = 0; i < dwClrUsed; i++, lpRGB++) { lpPal->palPalEntry[i].peRed = lpRGB->rgbRed; lpPal->palPalEntry[i].peGreen = lpRGB->rgbGreen; lpPal->palPalEntry[i].peBlue = lpRGB->rgbBlue; lpPal->palPalEntry[i].peFlags = 0; } GlobalUnlock(hPal); hPalette = CreatePalette(lpPal); if (hPalette == NULL) { MessageBox(hWnd, "パレット作成失敗", "Error", MB_OK); } GlobalFree(hPal); return hPalette; }

これは、前回と同じです。

int MakeBMPInfo(LPBITMAPINFOHEADER lpBi) { LPRGBQUAD lpRGB; int i; lpRGB = (LPRGBQUAD)(szBuffer + sizeof(BITMAPINFOHEADER)); for (i = 0; i < (int)lpBi->biClrUsed; i++) { lpbmp_info->bmiColors[i].rgbBlue = lpRGB->rgbBlue; lpbmp_info->bmiColors[i].rgbGreen = lpRGB->rgbGreen; lpbmp_info->bmiColors[i].rgbRed = lpRGB->rgbRed; lpbmp_info->bmiColors[i].rgbReserved = 0; lpRGB++; } return 0; }

これも同じです。

int DrawBMP(HWND hWnd, HDC hdc, int wx, int wy, char *szBuf) { RECT rc; GetClientRect(hWnd, &rc); int A, B, C, D; if (bUpdown == FALSE && bMirror == FALSE) { A = 0; B = 0; C = rc.right; D = rc.bottom; } if (bUpdown == FALSE && bMirror ==TRUE) { A = rc.right; B = 0; C = -rc.right; D = rc.bottom; } if (bUpdown == TRUE && bMirror == FALSE) { A = rc.right; B = rc.bottom; C = -rc.right; D = -rc.bottom; } if (bUpdown == TRUE && bMirror == TRUE) { A = 0; B = rc.bottom; C = rc.right; D = -rc.bottom; } StretchDIBits(hdc, A, B, //転送先座標 C, D, //幅、高さ 0, 0, //転送元座標 wx, wy, //転送元幅、高さ (char *)szBuf,//ビットマップデータ開始のアドレス lpbmp_info, //BITMAPINFO構造体へのポインタ DIB_RGB_COLORS, SRCCOPY); return 0; }

これも同じです。

int SaveDIB(HWND hWnd) { HANDLE hF; DWORD dwResult; char szFName[MAX_PATH]; if (bLoad == FALSE) { MessageBox(hWnd, "ビットマップがロードされていません", "Error", MB_OK); return -1; } GetSaveName(hWnd, szFName); hF = CreateFile(szFName, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (hF == INVALID_HANDLE_VALUE) { MessageBox(hWnd, "Error CreaeFile", "Error", MB_OK); return -3; } //BITMAPFILEHEADERを書きこむ WriteFile(hF, &gBfh, sizeof(gBfh), &dwResult, NULL); //szBufferにはBITMAPINFOHEADER以下が格納されているのでそのまま書きこむ szBuffer = (char *)GlobalLock(hMem); WriteFile(hF, szBuffer, GlobalSize(hMem), &dwResult, NULL); GlobalUnlock(hMem); CloseHandle(hF); return 0; }

今回新たに作った関数です。 ビットマップがロードされていない時は、注意を促して直ちに戻ります。

自作GetSaveName関数を呼んで新たに付けるファイル名を取得します。

CreateFile関数でGENERIC_WRITEでファイルをオープンします。 まず、グローバル変数にコピーしておいたgBfhを書きこみます。

あとは、szBufferを書きこめばできあがりです。うっかり、GlobalLockしないで szBufferを書きこんでしまうと非常にわかりにくいバグとなるので注意して下さい。 ここでは、書きこむサイズをGlobalSizeで取得していますが、時としてszBuffer に確保しているサイズより大きな値を返すことがあります。

DWORD GlobalSize( HGLOBAL hMem // グローバルメモリオブジェクトへのハンドル );

hMemにはGlobalAllocまたは、GlobalReAlloc関数の戻り値を指定します。

int GetSaveName(HWND hWnd, char *szF) { OPENFILENAME ofn; strcpy(szF, szFileName); memset(&ofn, 0, sizeof(OPENFILENAME)); ofn.lStructSize = sizeof(OPENFILENAME); ofn.hwndOwner = hWnd; ofn.lpstrFilter = "Bitmap (*.BMP)\0*.BMP\0\0"; ofn.nFilterIndex = 1; ofn.lpstrFile = szF; ofn.nMaxFile = 128; ofn.Flags = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT; if (GetSaveFileName((LPOPENFILENAME)&ofn)) return 0; return -1; }

セーブするファイル名を取得する関数です。 szFにはオープンしたファイル名をコピーしておきました。

今回の内容は簡単でした。しかし、ちょっとしたミスで非常に わかりにくいバグが発生する個所があるので注意して下さい。


[SDK第2部 Index] [総合Index] [Previous Chapter] [Next Chapter]

Update 03/Feb/1999 By Y.Kumei
当ホーム・ページの一部または全部を無断で複写、複製、 転載あるいはコンピュータ等のファイルに保存することを禁じます。