Microsoft Visual Studio .NET 2003 |
■リソースの圧縮 | Prev Top Next |
関連ページ:なし |
画像圧縮アルゴリズムのひとつランレングス法を解説します。ランレングス法は最も単純なアルゴリズムです。
[0x42][0x4D][0x38][0x00][0x03][0x00][0x00][0x00][0x00][0x00][0x36][0x00][0x00][0x00][0x28][0x00][0x00][0x00][0x00][0x01][0x00][0x00][0x00][0x01][0x00][0x00][0x01][0x00][0x18][0x00][0x00][0x00][0x00][0x00][0x02]
これはある画像ファイルのバイナリデータの一部です。 ランレングス法ではデータの連続する繰り返しパターンに着目します。上記のサンプルでは赤文字の部分が連続しています。 この繰り返し数とバイトデータをワンセットにして圧縮データを作成します。 上記のデータの場合ランレングス法により圧縮すると次のようになります。
[0x00][0x42][0x00][0x4D][0x00][0x38][0x00][0x00][0x00][0x03][0x04][0x00][0x00][0x36][0x02][0x00][0x00][0x28][0x03][0x00][0x00][0x01][0x02][0x00][0x00][0x01][0x01][0x00][0x00][0x01][0x00][0x00][0x00][0x18][0x04][0x00][0x00][0x02]
赤文字が繰り返し数 - 1で青文字がバイトデータとなります。 このように表現するために必要となるバイト数が少なくなっていることがわかります。 しかしこの圧縮法の場合、連続する繰り返しパターンがほとんど存在しない場合は逆に容量が大きくなります。最悪の場合、元のサイズの2倍になります。 FAXイメージのように連続するパターンが多い場合は効率よく圧縮できます。
図1
図1が色の繰り返しパターンが少ない画像です。
図2
図2が色の繰り返しパターンが多い画像です。
画像の圧縮前の容量は196,664 バイトです。
それぞれの画像を圧縮した場合の結果表です。
図名 | 圧縮後の容量 | 圧縮率 |
図1 | 377,998 バイト | 192 % |
図2 | 8,722 バイト | 4 % |
ゲームで使用されるテクスチャーは、一般的に図1のような感じになります。 このような画像の場合圧縮後の方が容量が大きくなります。したがってランレングス法は、ゲームではあまり効果的に使用できません。 それでもアルゴリズムは単純なので利用できる場合は積極的に利用すべきでしょう(ゲーム以外で?)。
//ランレングス法で圧縮変換 private: HRESULT ConvRunlength( LPBYTE lpBuffer, //圧縮前のデータ DWORD NumberOfBytesRead, //圧縮前のデータのバイト数 LPBYTE lpRLEBuffer, //圧縮後のデータ DWORD* pTotal //圧縮後のデータのバイト数 ) { HRESULT hr = -1; BYTE ByteData = lpBuffer[0]; //バイトデータ BYTE Cnt = 0; //同じデータの数 - 1 try { (*pTotal) = 0; for( DWORD i=1; i<NumberOfBytesRead; i++ ) { //バイトデータが同じとき if( ByteData == lpBuffer[i] ) { if( Cnt < 255 ) Cnt++; //255になったとき出力 else { if( lpRLEBuffer ) { lpRLEBuffer[(*pTotal)] = Cnt; lpRLEBuffer[(*pTotal)+1] = ByteData; } Cnt = 0; (*pTotal)+=2; ByteData = lpBuffer[i]; } } //バイトデータが違うとき出力 else { if( lpRLEBuffer ) { lpRLEBuffer[(*pTotal)] = Cnt; lpRLEBuffer[(*pTotal)+1] = ByteData; } Cnt = 0; (*pTotal)+=2; ByteData = lpBuffer[i]; } } //残りのデータ if( lpRLEBuffer ) { lpRLEBuffer[(*pTotal)] = Cnt; lpRLEBuffer[(*pTotal)+1] = ByteData; } (*pTotal)+=2; } catch( System::Exception* ) { hr = -1; goto ERROR_EXIT; } hr = 0; ERROR_EXIT: return hr; }ランレングス法でバイナリデータを圧縮する関数です。一応マネージドコードですが、Win API関数を使用するため、windows.hをインクルードしてください。
//ランレングス法で圧縮されたデータを解凍する private: HRESULT ConvFromRunlength( LPBYTE lpRLEBuffer, //圧縮後のデータ DWORD NumberOfBytesRead, //圧縮後のデータのバイト数 LPBYTE lpBuffer, //圧縮前のデータ DWORD* pTotal //圧縮前のデータのバイト数 ) { HRESULT hr = -1; //バイト数整合性チェック if( NumberOfBytesRead % 2 != 0 ) { hr = -2; goto ERROR_EXIT; } try { (*pTotal) = 0; for( DWORD i=0; i<NumberOfBytesRead; i+=2 ) { for( int j=0; j<lpRLEBuffer[i]+1; j++ ) { if( lpBuffer ) lpBuffer[(*pTotal)] = lpRLEBuffer[i+1]; (*pTotal)++; } } } catch( System::Exception* ) { hr = -1; goto ERROR_EXIT; } hr = 0; ERROR_EXIT: return hr; }
ランレングス法で圧縮されたバイナリデータを解凍する関数です。
HANDLE hFile; //ファイルのハンドル LPBYTE lpBuffer = NULL, lpRLEBuffer = NULL; //バイトデータ DWORD NumberOfBytesRead = 0; //読み込んだファイルのサイズ DWORD Total = 0; //圧縮後のファイルサイズ DWORD FileSize; //ファイルサイズ //画像ファイル名 char pSrcFile[256] = "17.bmp"; //画像ファイル(バイナリファイル)を開く hFile = CreateFile( pSrcFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL , NULL ); if( hFile == INVALID_HANDLE_VALUE ) goto EXIT_ERROR; //エラー処理へ //開いた画像ファイルの容量を調べる。容量が4GBより小さいことを前提にしていることに注意。詳しくはAPI関数のリファレンスを FileSize = GetFileSize( hFile, NULL ); if( FileSize == 0xFFFFFFFF ) { CloseHandle( hFile ); goto EXIT_ERROR; //エラー処理へ } //開いた画像ファイルのバイナリデータをセットするための配列の領域を確保 lpBuffer = new BYTE[FileSize]; //ファイルを読み込む if( ReadFile( hFile, lpBuffer, FileSize, &NumberOfBytesRead, NULL ) == false ) { CloseHandle( hFile ); goto EXIT_ERROR; //エラー処理へ } //圧縮変換した後のバイト数を計算 if( FAILED( ConvRunlength( lpBuffer, NumberOfBytesRead, NULL, &Total ) ) ) { CloseHandle( hFile ); goto EXIT_ERROR; //エラー処理へ } //計算した分の容量確保 lpRLEBuffer = new BYTE[Total]; //圧縮後のバイトデータを取得 if( FAILED( ConvRunlength( lpBuffer, NumberOfBytesRead, lpRLEBuffer, &Total ) ) ) { CloseHandle( hFile ); goto EXIT_ERROR; //エラー処理へ } //ファイルを閉じる CloseHandle( hFile ); //ファイル名の拡張子を.datに変更する //まず.の場所をファイル名の右側から探す int i; int l = strlen(pSrcFile); for( i=l-1; i>=0; i-- ) { if( pSrcFile[i] == '.' ) break; } //.がみつかった場合、拡張子をそのままdatに変換 if( i >= 0 ) { pSrcFile[i+1] = 'd'; pSrcFile[i+2] = 'a'; pSrcFile[i+3] = 't'; pSrcFile[i+4] = '\0'; } //.がみつからないのでファイル名の後ろにつける //なお配列の自動拡張は行ってないので十分な大きさをあらかじめ確保しておくように(256バイトもとっとけば十分だと思うが) else { pSrcFile[l+0] = '.'; pSrcFile[l+1] = 'd'; pSrcFile[l+2] = 'a'; pSrcFile[l+3] = 't'; pSrcFile[l+4] = '\0'; } //変名したファイル名でファイルを検索 //ファイルが存在するときはメッセージボックスを表示し上書きするか確認してもらうようにした hFile = CreateFile( pSrcFile, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL , NULL ); if( hFile == INVALID_HANDLE_VALUE ) { if( MessageBox( NULL, "圧縮ファイルが存在します。上書きしますか?", "ファイル確認", MB_YESNO ) == IDYES ) hFile = CreateFile( pSrcFile, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL , NULL ); else { CloseHandle( hFile ); goto EXIT_ERROR; //エラー処理へ } } //圧縮ファイルを出力 if( WriteFile( hFile, lpRLEBuffer, Total, &NumberOfBytesRead, NULL ) == false ) { CloseHandle( hFile ); goto EXIT_ERROR; //エラー処理へ } CloseHandle( hFile ); EXIT_ERROR: //後始末 if( lpBuffer ) { delete[] lpBuffer; lpBuffer = NULL; } if( lpRLEBuffer ) { delete[] lpRLEBuffer; lpRLEBuffer = NULL; }
でもって関数の使い方サンプル。解凍処理での関数の使い方はほとんどこれと同じなので省略の方向で。