No.006 - 04 zlib圧縮の基本 |
zlib 圧縮の基本をやります。 |
zlibファイル 訳(当サイト内) | zlib.h | readme | faq | index |
・zlib |
|||||||||||||||||||||||||||||||||||||||||||||||
zlib は zip や PNG画像 の圧縮などに使われている圧縮アルゴリズムのライブラリです。 フリーソフトです。 執筆時 zlib の最新のバージョンは 1.2.2 です。 zlib の入手先は zlib Home Page 。 PNG は PNG Home Page。 |
|||||||||||||||||||||||||||||||||||||||||||||||
・なぜzlibなのか |
|||||||||||||||||||||||||||||||||||||||||||||||
当初、筆者も自前の圧縮アルゴリズムを考えたこともありますが、正直危険です。 自前の圧縮アルゴリズムなんて研究してたら年単位のヒマがつぶれます。 最終目的も今んとこそこじゃないし。 というわけでフリーのアルゴリズムを使います。 zlib は国際的な圧縮方法なので高い信頼性があります。PNGにも使われてます。 このサイトでは圧縮アルゴリズムはzlibしか扱いませんが UNIX系で探したりすればもっといろいろしてみると選択肢は広がるはずですので必要に応じて探してみるといいかもしれません。 |
|||||||||||||||||||||||||||||||||||||||||||||||
・まずzlib入手 |
|||||||||||||||||||||||||||||||||||||||||||||||
zlib Home Page へ行ってソースコードを手にいれましょう。 ファイル形式はzlib122.zipでもzlib-1.2.2.tar.gz.tarでも構いません。 ミラーサイトがいっぱいありますが、US (www.zlib.net) とか France (www.gzip.org) からじかにダウンロードすりゃ十分だと思います。 さて早速ダウンロードしたファイルを解凍します。 |
|||||||||||||||||||||||||||||||||||||||||||||||
・拡張子 C → CPP に変える |
|||||||||||||||||||||||||||||||||||||||||||||||
解凍したファイルをいざコンパイル!!と若干前のめりになりがちですが 慌ててはいけません。 拡張子CでコンパイルしたものとCPPでコンパイルしたものに互換性がないからです。 同時コンパイル可能なのにCとCPPでは互いの関数は使えません。せつなさいっぱいです。 というわけで各Cファイル拡張子をCPPに変えます。 一個一個手動でかえても構いませんが面倒なので下記に簡単な「拡張子一括変更プログラム」を用意しました。 150行そこらのソースでコンパイルも面倒だろうってコトで今回のみバイナリ・ファイルもあります。 ※ 「拡張子一括変更プログラム」 ext_changer.zip(28KB) 説明:指定したフォルダの拡張を一括変更します。サブディレクトリは無視します。 探せばvectorとか窓の杜にもっといいソフトウェアがあるかと思います。 |
|||||||||||||||||||||||||||||||||||||||||||||||
・コンパイル |
|||||||||||||||||||||||||||||||||||||||||||||||
zlib ライブラリは
さらに今回gzファイルを扱わないのでgzio.cもいらないです。 inflate.cpp のinflate()関数等であろうことか this なんて変数を宣言しています。 当然 C++ではコンパイル・エラー。ぐふぅ。 しかたがないのでthisをなんか適当な名前(例えばz_thisとか)に変えてやる必要があります。 ほかにもc →c++の移行にともなうキャスト演算エラーが発生するので順次修正します。 自分のファイルに#include するのはzlib.hだけでいいようです。 |
|||||||||||||||||||||||||||||||||||||||||||||||
・ライブラリ作成する? |
|||||||||||||||||||||||||||||||||||||||||||||||
こういう一般ライブラリを毎回コンパイルすんのは大変なので 必要に応じて .lib とか DLL にしたほうが楽かもしれません。 ライブラリの作り方自体はもうわんさといろんなサイトで紹介されてるので、そちらを参考にしてください。 |
|||||||||||||||||||||||||||||||||||||||||||||||
圧縮サンプルソース解説 圧縮
以上が圧縮の手順ですが展開の場合も同様です。 deflate ⇒ inflate になるだけです。 |
|||||||||||||||||||||||||||||||||||||||||||||||
・参考 |
|||||||||||||||||||||||||||||||||||||||||||||||
奥村晴彦 教授 のzlib入門 http://oku.edu.mie-u.ac.jp/~okumura/compression/zlib.html |
|||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||
拡張子一括変更プログラム ext_changer.cpp |
#include <windows.h> #include <stdio.h> #include <shlobj.h> // 拡張子変更実行 int chang_ext(HWND hwnd, HWND hedit_path, HWND hedit1, HWND hedit2){ int file_cnt=0, slen; char tmp[MAX_PATH], tmp2[MAX_PATH], dir_path[MAX_PATH], find_file[MAX_PATH], ext1[MAX_PATH], ext2[MAX_PATH]; GetWindowText(hedit_path, dir_path, MAX_PATH); GetWindowText(hedit1, ext1, MAX_PATH); sprintf(find_file, "%s\\*%s", dir_path, ext1); GetWindowText(hedit2, ext2, MAX_PATH); SHFILEOPSTRUCT sfo; // SHFILEOPSTRUCT構造体 memset(&sfo, 0, sizeof(SHFILEOPSTRUCT)); sfo.hwnd = hwnd; // ウィンドウハンドル sfo.wFunc = FO_RENAME; // 実行する操作 WIN32_FIND_DATA win32_find_data; HANDLE hfind = FindFirstFile(find_file, &win32_find_data); if(hfind != INVALID_HANDLE_VALUE){ do{ sprintf(tmp2, "%s\\%s", dir_path, win32_find_data.cFileName); GetFullPathName(tmp2, MAX_PATH, tmp, NULL); slen = strlen(tmp)-strlen(ext1); strncpy(tmp2, tmp, slen); strcpy(tmp2+slen, ext2); sfo.pFrom = tmp; // 対象ファイル名 sfo.pTo = tmp2; // 目的ファイル名 tmp[strlen(tmp)+1] = NULL; tmp2[strlen(tmp2)+1] = NULL; SHFileOperation(&sfo); file_cnt++; } while(FindNextFile(hfind, &win32_find_data)); FindClose(hfind); } if (file_cnt) sprintf(tmp, "%d 個の[%s]ファイルを[%s]に変更しました", file_cnt, ext1, ext2); else sprintf(tmp, "変更ファイルはありませんでした", file_cnt, ext1, ext2); MessageBox(hwnd, tmp, "ファイル数", MB_OK); return 0; } // フォルダの選択ダイアログ(コールバック関数) int CALLBACK BrowseCallbackProc(HWND hwnd, UINT msg, LPARAM lparam, LPARAM lpdata){ if(msg==BFFM_INITIALIZED) SendMessage(hwnd, BFFM_SETSELECTION, (WPARAM)TRUE, lpdata); return DefWindowProc(hwnd, msg, lparam, lpdata); } // フォルダの選択ダイアログ call_folderdialog(HWND hwnd, HWND hedit){ BROWSEINFO binfo; ITEMIDLIST *p_id_list; TCHAR dir_path[MAX_PATH]; GetWindowText(hedit, dir_path, MAX_PATH); // BROWSEINFO構造体に値を設定 binfo.hwndOwner = hwnd; // ダイアログの親ウインドウのハンドル binfo.pidlRoot = NULL; // ルートフォルダを示すITEMIDLISTのポインタ (NULLの場合デスクトップフォルダが使われます) binfo.pszDisplayName = dir_path; // 選択されたフォルダ名を受け取るバッファのポインタ binfo.lpszTitle = TEXT("フォルダの選択"); // ツリービューの上部に表示される文字列 binfo.ulFlags = BIF_NEWDIALOGSTYLE; // 表示されるフォルダの種類を示すフラグ binfo.lpfn = BrowseCallbackProc; // BrowseCallbackProc関数のポインタ binfo.lParam = (LPARAM)dir_path; // コールバック関数に渡す値 // フォルダ選択ダイアログを表示 p_id_list = ::SHBrowseForFolder(&binfo); if(p_id_list != NULL){ // フォルダ選択実行 if(::SHGetPathFromIDList(p_id_list, dir_path)) SetWindowText(hedit, dir_path); ::CoTaskMemFree( p_id_list ); // p_id_listのメモリを開放 } } // テキスト作成 HWND make_stext(HWND parent, int x, int y, int cx, int cy, char *caption){ return CreateWindow( TEXT("static"), caption, WS_CHILD | WS_VISIBLE, x, y, cx, cy, parent, (HMENU)0, GetModuleHandle(NULL), NULL ); }; // ボタン作成 HWND make_button(HWND parent, int x, int y, int cx, int cy, char *caption, int id){ return CreateWindow( TEXT("BUTTON"), caption, WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, x, y, cx, cy, parent, (HMENU)id, GetModuleHandle(NULL), NULL ); }; // エディットボックス作成 HWND make_edit(HWND parent, int x, int y, int cx, int cy, char *caption, int id){ return CreateWindow( TEXT("EDIT"), caption, WS_CHILD | WS_VISIBLE | WS_BORDER | ES_LEFT | ES_AUTOHSCROLL, x, y, cx, cy, parent, (HMENU)id, GetModuleHandle(NULL), NULL ); }; // ウィンドウ作成メッセージ on_create(HWND hwnd){ char s[MAX_PATH]; make_stext(hwnd, 10, 10, 100, 25, "ディレクトリ"); make_stext(hwnd, 120, 80, 100, 25, "→"); make_button(hwnd, 380, 30, 50, 25, "参照", 101); make_edit(hwnd, 10, 30, 360, 25, "", 102); // ファイルパス make_edit(hwnd, 40, 80, 60, 25, ".c", 103); make_edit(hwnd, 160, 80, 60, 25, ".cpp", 104); make_button(hwnd, 240, 80, 60, 25, "変更", 105); SetFocus(GetDlgItem(hwnd, 102)); GetCurrentDirectory(MAX_PATH, s); SetWindowText(GetDlgItem(hwnd, 102), s); }; // ウィンドウ・プロシージャ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { switch(msg) { case WM_CREATE: on_create(hwnd); break; case WM_DESTROY: PostQuitMessage(0); break; case WM_COMMAND: switch(LOWORD(wp)){ case 101: call_folderdialog(hwnd, GetDlgItem(hwnd, 102)); break; case 105: chang_ext(hwnd, GetDlgItem(hwnd, 102), GetDlgItem(hwnd, 103), GetDlgItem(hwnd, 104)); break; } break; } return DefWindowProc(hwnd, msg, wp, lp); } int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR lpCmdLine, int nCmdShow ) { HWND hwnd; WNDCLASS winc; MSG msg; winc.style = CS_HREDRAW | CS_VREDRAW; winc.lpfnWndProc = WndProc; winc.cbClsExtra = winc.cbWndExtra = 0; winc.hInstance = hInstance; winc.hIcon = LoadIcon(NULL, IDI_APPLICATION); winc.hCursor = LoadCursor(NULL, IDC_ARROW); winc.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1); winc.lpszMenuName = NULL; winc.lpszClassName = TEXT("EXT_CHANGER"); if (!RegisterClass(&winc)) return 0; hwnd = CreateWindow( TEXT("EXT_CHANGER"), TEXT("拡張子を一括で変更するプログラム"), WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, 450, 150, NULL, NULL, hInstance, NULL ); if (hwnd == NULL) return 0; while(GetMessage(&msg , NULL , 0 , 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } |
zlib 圧縮のテストプログラムです。
圧縮ボタンを押すと「元のファイル」を圧縮して「圧縮ファイル名」に保存します。
展開ボタンを押すと「圧縮ファイル名」を展開して「元のファイル」に保存します。
ファイルが上書き保存されないように注意してください。
zlib_test.cpp |
#include <windows.h> #include <stdio.h> #include <stdlib.h> #include "zlib.h" #define INBUFSIZ 4096 // 入力バッファサイズ(任意) #define OUTBUFSIZ 4096 // 出力バッファサイズ(任意) // 圧縮 void do_compress(char *in_file, char *out_file){ char inbuf[INBUFSIZ]; // 入力バッファ char outbuf[OUTBUFSIZ]; // 出力バッファ char err_msg[250]; FILE *fin, *fout; // 入力・出力ファイル z_stream z; // ライブラリとやりとりするための構造体 int count, flush, status; if (!(fin = fopen(in_file, "rb"))) return; if (!(fout = fopen(out_file, "wb"))) return; // すべてのメモリ管理をライブラリに任せる z.zalloc = Z_NULL; z.zfree = Z_NULL; z.opaque = Z_NULL; // 初期化 第2引数は圧縮の度合。0〜9 の範囲の整数で,0 は無圧縮, Z_DEFAULT_COMPRESSION (= 6) が標準 if (deflateInit(&z, Z_DEFAULT_COMPRESSION) != Z_OK) { sprintf(err_msg, "圧縮初期化エラー(deflateInit関数): %s\n", (z.msg) ? z.msg : "???"); MessageBox(NULL, err_msg, "エラー", MB_OK); fclose(fin); fclose(fout); return; } z.avail_in = 0; // 入力バッファ中のデータのバイト数 z.next_out = outbuf; // 出力ポインタ z.avail_out = OUTBUFSIZ; // 出力バッファのサイズ // 通常は deflate() の第2引数は Z_NO_FLUSH にして呼び出す flush = Z_NO_FLUSH; while (1) { if (z.avail_in == 0) { // 入力が尽きれば z.next_in = inbuf; // 入力ポインタを入力バッファの先頭に z.avail_in = fread(inbuf, 1, INBUFSIZ, fin); // データを読み込む // 入力が最後になったら deflate() の第2引数は Z_FINISH にする if (z.avail_in < INBUFSIZ) flush = Z_FINISH; } status = deflate(&z, flush); // 圧縮する if (status == Z_STREAM_END) break; // 完了 if (status != Z_OK) { // エラー sprintf(err_msg, "圧縮エラー(deflate関数): %s\n", (z.msg) ? z.msg : "???"); MessageBox(NULL, err_msg, "エラー", MB_OK); fclose(fin); fclose(fout); return; } if (z.avail_out == 0) { // 出力バッファが尽きれば // まとめて書き出す if (fwrite(outbuf, 1, OUTBUFSIZ, fout) != OUTBUFSIZ) { MessageBox(NULL, "書込エラー(fwrite関数)\n", "エラー", MB_OK); fclose(fin); fclose(fout); return; } z.next_out = outbuf; // 出力バッファ残量を元に戻す z.avail_out = OUTBUFSIZ; // 出力ポインタを元に戻す } } // 残りを吐き出す if ((count = OUTBUFSIZ - z.avail_out) != 0) { if (fwrite(outbuf, 1, count, fout) != count) { MessageBox(NULL, "書込エラー(fwrite関数)\n", "エラー", MB_OK); fclose(fin); fclose(fout); return; } } // 後始末 if (deflateEnd(&z) != Z_OK) { sprintf(err_msg, "圧縮完了エラー(deflateEnd関数): %s\n", (z.msg) ? z.msg : "???"); MessageBox(NULL, err_msg, "エラー", MB_OK); fclose(fin); fclose(fout); return; } fclose(fin); fclose(fout); return; } // 展開(復元) void do_decompress(char *in_file, char *out_file){ char inbuf[INBUFSIZ]; // 入力バッファ char outbuf[OUTBUFSIZ]; // 出力バッファ char err_msg[250]; FILE *fin, *fout; // 入力・出力ファイル int count, status; z_stream z; // ライブラリとやりとりするための構造体 if (!(fin = fopen(in_file, "rb"))) return; if (!(fout = fopen(out_file, "wb"))) return; // すべてのメモリ管理をライブラリに任せる z.zalloc = Z_NULL; z.zfree = Z_NULL; z.opaque = Z_NULL; // 初期化 z.next_in = Z_NULL; z.avail_in = 0; if (inflateInit(&z) != Z_OK) { sprintf(err_msg, "展開初期化エラー(inflateInit関数): %s\n", (z.msg) ? z.msg : "???"); MessageBox(NULL, err_msg, "エラー", MB_OK); fclose(fin); fclose(fout); return; } z.next_out = outbuf; // 出力ポインタ z.avail_out = OUTBUFSIZ; // 出力バッファ残量 status = Z_OK; while (status != Z_STREAM_END) { if (z.avail_in == 0) { // 入力残量がゼロになれば z.next_in = inbuf; // 入力ポインタを元に戻す z.avail_in = fread(inbuf, 1, INBUFSIZ, fin); // データを読む } status = inflate(&z, Z_NO_FLUSH); // 展開 if (status == Z_STREAM_END) break; // 完了 if (status != Z_OK) { // エラー sprintf(err_msg, "展開エラー(inflate関数): %s\n", (z.msg) ? z.msg : "???"); MessageBox(NULL, err_msg, "エラー", MB_OK); fclose(fin); fclose(fout); return; } if (z.avail_out == 0) { // 出力バッファが尽きれば // まとめて書き出す if (fwrite(outbuf, 1, OUTBUFSIZ, fout) != OUTBUFSIZ) { MessageBox(NULL, "書込エラー(fwrite関数)\n", "エラー", MB_OK); fclose(fin); fclose(fout); return; } z.next_out = outbuf; // 出力ポインタを元に戻す z.avail_out = OUTBUFSIZ; // 出力バッファ残量を元に戻す } } // 残りを吐き出す if ((count = OUTBUFSIZ - z.avail_out) != 0) { if (fwrite(outbuf, 1, count, fout) != count) { MessageBox(NULL, "書込エラー(fwrite関数)\n", "エラー", MB_OK); fclose(fin); fclose(fout); return; } } // 後始末 if (inflateEnd(&z) != Z_OK) { sprintf(err_msg, "展開終了エラー(inflateEnd関数): %s\n", (z.msg) ? z.msg : "???"); MessageBox(NULL, err_msg, "エラー", MB_OK); fclose(fin); fclose(fout); return; } fclose(fin); fclose(fout); return; } // ファイルオープン・ダイアログ int call_open_dialog(HWND hwnd, HWND hedit){ char s[MAX_PATH]; GetWindowText(hedit, s, MAX_PATH); static OPENFILENAME ofn; int res; ofn.lStructSize = sizeof (OPENFILENAME); ofn.hwndOwner = hwnd; ofn.lpstrFilter = TEXT("All files {*.*}\0*.*\0\0"); ofn.lpstrCustomFilter = NULL; ofn.nMaxCustFilter = 0; ofn.nFilterIndex = 0; ofn.lpstrFile = s; ofn.nMaxFile = MAX_PATH; ofn.Flags = 0; res = GetOpenFileName(&ofn); if (res) SetWindowText(hedit, s); return res; } // テキスト作成 HWND make_stext(HWND parent, int x, int y, int cx, int cy, char *caption){ return CreateWindow( TEXT("static"), caption, WS_CHILD | WS_VISIBLE, x, y, cx, cy, parent, (HMENU)0, GetModuleHandle(NULL), NULL ); }; // ボタン作成 HWND make_button(HWND parent, int x, int y, int cx, int cy, char *caption, int id){ return CreateWindow( TEXT("BUTTON"), caption, WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, x, y, cx, cy, parent, (HMENU)id, GetModuleHandle(NULL), NULL ); }; // エディットボックス作成 HWND make_edit(HWND parent, int x, int y, int cx, int cy, char *caption, int id){ return CreateWindow( TEXT("EDIT"), caption, WS_CHILD | WS_VISIBLE | WS_BORDER | ES_LEFT | ES_AUTOHSCROLL, x, y, cx, cy, parent, (HMENU)id, GetModuleHandle(NULL), NULL ); }; // ウィンドウ作成メッセージ on_create(HWND hwnd){ make_stext(hwnd, 10, 10, 200, 25, "元のファイル名"); make_edit(hwnd, 10, 30, 360, 25, "", 101); // ファイルパス make_button(hwnd, 380, 30, 50, 25, "参照", 102); make_stext(hwnd, 10, 120, 200, 25, "圧縮ファイル名"); make_edit(hwnd, 10, 140, 360, 25, "", 103); // ファイルパス make_button(hwnd, 380, 140, 50, 25, "参照", 104); make_button(hwnd, 50, 70, 80, 35, "圧縮 ↓", 105); make_button(hwnd, 200, 70, 80, 35, "↑ 展開", 106); SetFocus(GetDlgItem(hwnd, 101)); }; // ウィンドウ・プロシージャ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { char s1[MAX_PATH], s2[MAX_PATH]; switch(msg) { case WM_CREATE: on_create(hwnd); break; case WM_DESTROY: PostQuitMessage(0); break; case WM_COMMAND: switch(LOWORD(wp)){ case 102: call_open_dialog(hwnd, GetDlgItem(hwnd, 101)); break; case 104: call_open_dialog(hwnd, GetDlgItem(hwnd, 103)); break; case 105: GetWindowText(GetDlgItem(hwnd, 101), s1, MAX_PATH); GetWindowText(GetDlgItem(hwnd, 103), s2, MAX_PATH); do_compress(s1, s2); break; case 106: GetWindowText(GetDlgItem(hwnd, 101), s1, MAX_PATH); GetWindowText(GetDlgItem(hwnd, 103), s2, MAX_PATH); do_decompress(s2, s1); break; } break; } return DefWindowProc(hwnd, msg, wp, lp); } int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR lpCmdLine, int nCmdShow ) { HWND hwnd; WNDCLASS winc; MSG msg; winc.style = CS_HREDRAW | CS_VREDRAW; winc.lpfnWndProc = WndProc; winc.cbClsExtra = winc.cbWndExtra = 0; winc.hInstance = hInstance; winc.hIcon = LoadIcon(NULL, IDI_APPLICATION); winc.hCursor = LoadCursor(NULL, IDC_ARROW); winc.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1); winc.lpszMenuName = NULL; winc.lpszClassName = TEXT("ZLIB_WIN"); if (!RegisterClass(&winc)) return 0; hwnd = CreateWindow( TEXT("ZLIB_WIN"), TEXT("zlib圧縮するプログラム"), WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, 450, 220, NULL, NULL, hInstance, NULL ); if (hwnd == NULL) return 0; while(GetMessage(&msg , NULL , 0 , 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } |