FC2ブログ

ホーム > DIBファイルフォーマット > ピクセルデータのランレングス圧縮

ピクセルデータのランレングス圧縮



ピクセルデータランレングス圧縮は、以下のような流れになります。

  1. まずは絶対値モードとしてピクセルデータを読み込み、前のデータと比較して同値なら、取り込んだデータを絶対値モードとしてファイルに書き出してから、エンコードモードに切り替える。
  2. エンコードモードピクセルデータを読み込み、前のデータと比較して違いがあれば、取り込んだデータをエンコードモードとしてファイルにに書き出してから、絶対値モードに切り替える。
  3. どちらのモードも蓄えたピクセルデータの個数が255以上になったら、ファイルに書き出し絶対値モードに切り替える。
  4. 行末に達したら、蓄えたデータをファイルに書き出し、行末コード「0x00,0x00」を書き込む。
  5. 全部読み終わったら、終端コード「0x00,0x01」をファイルに書き込み終了する。

ランレングスデータの詳細については「ランレングス圧縮されたピクセルデータの解凍」をご参照ください。




ビットマップバイト幅の算出マクロです。

//ビットマップバイト幅の算出マクロ
#ifndef WIDTHBYTES
#define WIDTHBYTES(bits) (((bits)+31)/32*4)
#endif//WIDTHBYTES


ランレングス圧縮する際に必要になってくるグローバル変数の使用を避けるために、便宜上CRleクラスを独自に定義しました。

//CRleクラス
class CRle : public CObject
{
public:
//コンストラクタ
CRle(){};
//デストラクタ
virtual ~CRle(){};

//4ビットランレングス圧縮
INT_PTR EncodeRle4(LPVOID pBits,DWORD dwSizeImage,UINT width,UINT height,CFile* outfile);
//8ビットランレングス圧縮
INT_PTR EncodeRle8(LPVOID pBits,DWORD dwSizeImage,UINT width,UINT height,CFile* outfile);
private:
//4ビットランレングス圧縮データをファイルに書き込みます。
INT_PTR _WriteEncodeRLE4(CFile* outfile);
//8ビットランレングス圧縮データをファイルに書き込みます。
INT_PTR _WriteEncodeRLE8(CFile* outfile);
//エンコード時に使用するモード定義
enum{ABS_MODE,ENCODE_MODE};
//キューバッファ(データ比較)
CByteArray m_arrayQueue;
//コードバッファ(コード保持)
CByteArray m_arrayCode;
//現在のモードを保持する変数
UINT m_uMode;
};


4ビットランレングス圧縮関数です。CRleクラスを作成して、その中のEncodeRle4関数を呼び出します。

//4ビットランレングス圧縮(スタンダードコール)
int __stdcall _EncodeRle4(LPVOID pBits,DWORD dwSizeImage,
UINT width,UINT height,CFile* outfile)
{
//CRleクラスのEncodeRle4関数を呼び出します。
CRle Rle;
return Rle.EncodeRle4(pBits,dwSizeImage,width,height,outfile);
}


4ビットランレングス圧縮関数の本体です。

//4ビットランレングス圧縮
INT_PTR CRle::EncodeRle4(LPVOID pBits,DWORD dwSizeImage,UINT width,UINT height,CFile* outfile)
{
//与えられたピクセルビットへのポインタが有効ではない場合、
//FALSEを返して終了する。

if (!AfxIsValidAddress(pBits,dwSizeImage)) return FALSE;

//ビットマップバイト幅を算出しておきます。
DWORD widthBytes=WIDTHBYTES(4*width);

INT_PTR nWriteSize=0; //書き込み済みバイト数を0にする。
BYTE writeCode[2]; //書き込みコード格納用バッファ

//圧縮ループ
for(UINT i=0;i<height;i++){ //行数分のループ
m_uMode=ABS_MODE; //初期値は絶対値モード
m_arrayQueue.RemoveAll(); //キューバッファクリア
m_arrayCode.RemoveAll(); //コードバッファクリア
PBYTE src=(PBYTE)pBits+widthBytes*i;
for(UINT j=0;j<width;j++){ //横方向のループ
//ピクセルデータの取得
BYTE b=(((j%2)==0)?((*src)>>4):(*src++))&0x0F;
m_arrayQueue.Add(b); //キュー末尾に加える
m_arrayCode.Add(b); //スタック末尾に加える

//コードバッファが255以上の時
if (m_arrayCode.GetSize()>=255){
nWriteSize+=_WriteEncodeRLE4(outfile); //ファイルに書き込む
m_arrayQueue.RemoveAll(); //キューバッファクリア
m_arrayCode.RemoveAll(); //コードバッファクリア
m_uMode=ABS_MODE; //絶対値モード
continue;
}
if (m_arrayQueue.GetSize()<3) continue; //キューバッファが3個未満なら処理しない

//ピクセルデータを比較
switch(m_uMode){
case ABS_MODE: //絶対値モード
//新しいピクセルと2つ前のピクセルが同じデータであれば、
//エンコードモードに切り替える

if (m_arrayQueue.GetAt(0)==m_arrayQueue.GetAt(2))
{
//コードバッファの末尾に加えた三つのコードを一時退避して、
//それ以前のコードをファイルに書き込む

INT_PTR nTail=m_arrayCode.GetSize()-1;
BYTE Code3=m_arrayCode.GetAt(nTail--);
BYTE Code2=m_arrayCode.GetAt(nTail--);
BYTE Code1=m_arrayCode.GetAt(nTail) ;
m_arrayCode.SetSize(nTail);
nWriteSize+=_WriteEncodeRLE4(outfile);
//コードバッファをクリアしてから、
//退避していたコードを復帰する

m_arrayCode.RemoveAll();
m_arrayCode.Add(Code1);
m_arrayCode.Add(Code2);
m_arrayCode.Add(Code3);
m_uMode=ENCODE_MODE; //エンコードモードに切り替える
}
m_arrayQueue.RemoveAt(0); //比較し終わったのでキューを一つ進める
break;
case ENCODE_MODE: // エンコードモード
//新しいピクセルと2つ前のピクセルが違えば、
//絶対値モードに切り替える

if (m_arrayQueue.GetAt(0)!=m_arrayQueue.GetAt(2))
{
//コードバッファに最後に加えたコードを一時退避して、
//それ以前のコードをファイルに書き込む

INT_PTR nTail=m_arrayCode.GetSize()-1;
BYTE Code1=m_arrayCode.GetAt(nTail);
m_arrayCode.SetSize(nTail);
nWriteSize+=_WriteEncodeRLE4(outfile);
//スタックをクリアしてから、退避していたコードを復帰する
m_arrayCode.RemoveAll();
m_arrayCode.Add(Code1);
m_arrayQueue.RemoveAt(0); //キューを一つ進める
m_uMode=ABS_MODE; //絶対値モードに切り替える
}
m_arrayQueue.RemoveAt(0); //比較し終わったのでキューを一つ進める
break;
}
}
//行末コードの書き込み
nWriteSize+=_WriteEncodeRLE4(outfile); //残りのデータの吐き出し
writeCode[0]=writeCode[1]=0;
outfile->Write(&writeCode,2);
nWriteSize+=2;
}
//画像の終端コードの書き込み
writeCode[0]=0;
writeCode[1]=1;
outfile->Write(&writeCode,2);
nWriteSize+=2;
return nWriteSize;
}


4ビットランレングス圧縮データデータをファイルに書き込むための内部関数です。

//4ビットランレングス圧縮データをファイルに書き込みます。
INT_PTR CRle::_WriteEncodeRLE4(CFile* outfile)
{
INT_PTR nWriteSize=0;
BYTE writeCode[4];
INT_PTR nCodeSize=m_arrayCode.GetSize(); //書き込むコード数
PBYTE src=m_arrayCode.GetData(); //コードバッファの先頭アドレスを得る

if (m_uMode==ABS_MODE){//絶対値モードの時の処理
if (nCodeSize<=2){ //2コード以下の書込み
writeCode[0]=2; //エンコードモードとしてコード数を書き込む
for(int i=0;i<(nCodeSize/2);i++){
writeCode[1]=(*src++)<<4; //上位4ビットに偶数番目のピクセルを入れる
writeCode[1]|=*src++; //下位4ビットに奇数番目のピクセルを入れる
outfile->Write(&writeCode,2);
nWriteSize+=2;
}
writeCode[0]=1; //エンコードモードとしてコード数を書き込む
if (nCodeSize%2){
writeCode[1]=(*src++)<<4; //上位4ビットに偶数番のピクセル、下位4ビットは0
outfile->Write(&writeCode,2);
nWriteSize+=2;
}
}
else { //3コード以上の書き込み
writeCode[0]=0; //エスケープコード
writeCode[1]=(BYTE)nCodeSize; //繰り返し回数
outfile->Write(&writeCode,2);
nWriteSize+=2;
//絶対値データの書き込み
for(int i=0;i<(nCodeSize/2);i++){
writeCode[0]=(*src++)<<4; //上位4ビットに偶数目のピクセルを入れる
writeCode[0]|=*src++; //下位4ビットに奇数目のピクセルを入れる
outfile->Write(&writeCode,1);
nWriteSize++;
}
if (nCodeSize%2){ //コード数が奇数個の時
writeCode[0]=(*src++)<<4;
outfile->Write(&writeCode,1);
nWriteSize++;
}
INT_PTR nPad=((nCodeSize/2)%2)+(nCodeSize%2);
if (nPad%2) {//コード数が5,6,9のときは、パディングの書き込む
writeCode[0]=0; //パディングとして0を一つ書き込む
outfile->Write(&writeCode,1);
nWriteSize++;
}
}
}
else { //エンコードモード書込み
writeCode[0]=(BYTE)nCodeSize; //コード数を書き込む
writeCode[1]=(*src++)<<4; //上位4ビットに偶数番目のピクセルを入れる
writeCode[1]|=*src++; //下位4ビットに奇数番目のピクセルを入れる
outfile->Write(&writeCode,2);
nWriteSize+=2;
}
return nWriteSize;
}


8ビットランレングス圧縮関数です。CRleクラスを作成して、その中のEncodeRle8関数を呼び出します。

//8ビットランレングス圧縮(スタンダードコール)
int __stdcall _EncodeRle8(LPVOID pBits,DWORD dwSizeImage,
UINT width,UINT height,CFile* outfile)
{
//CRleクラスのEncodeRle8関数を呼び出します。
CRle Rle;
return Rle.EncodeRle8(pBits,dwSizeImage,width,height,outfile);
}



8ビットランレングス圧縮関数の本体です。

//8ビットランレングス圧縮
INT_PTR CRle::EncodeRle8(LPVOID pBits,DWORD dwSizeImage,UINT width,UINT height,CFile* outfile)
{
//与えられたピクセルビットへのポインタが有効ではない場合、
//FALSEを返して終了する。

if (!AfxIsValidAddress(pBits,dwSizeImage)) return FALSE;

//ビットマップバイト幅を算出しておきます。
DWORD widthBytes=WIDTHBYTES(8*width);

INT_PTR nWriteSize=0; //書き込み済みバイト数を0にする。
BYTE writeCode[2]; //書き込みコード格納用バッファ

//圧縮ループ
for(UINT i=0;i<height;i++){ //行数分のループ
m_uMode=ABS_MODE; //初期値は絶対値モード
m_arrayQueue.RemoveAll(); //キューバッファクリア
m_arrayCode.RemoveAll(); //コードバッファクリア
PBYTE src=(PBYTE)pBits+widthBytes*i;
for(UINT j=0;j<width;j++){ //横方向のループ
//ピクセルデータの取得
BYTE b=*src++;
m_arrayQueue.Add(b); //キュー末尾に加える
m_arrayCode.Add(b); //スタック末尾に加える
//コードバッファが255以上の時
if (m_arrayCode.GetSize()>=255){
nWriteSize+=_WriteEncodeRLE8(outfile); //ファイルに書き込む
m_arrayQueue.RemoveAll(); //キューバッファクリア
m_arrayCode.RemoveAll(); //コードバッファクリア
m_uMode=ABS_MODE; //絶対値モード
continue;
}
if (m_arrayQueue.GetSize()<2) continue; //キューバッファが2個未満なら処理しない

//ピクセルデータを比較
switch(m_uMode){
case ABS_MODE: //絶対値モード
//新しいピクセルと前のピクセルが同じデータであれば、
//エンコードモードに切り替える

if (m_arrayQueue.GetAt(0)==m_arrayQueue.GetAt(1)){
//コードバッファの末尾に加えた二つのコードを一時退避して、
//それ以前のコードをファイルに書き込む

INT_PTR nTail=m_arrayCode.GetSize()-1;
BYTE Code2=m_arrayCode.GetAt(nTail--);
BYTE Code1=m_arrayCode.GetAt(nTail) ;
m_arrayCode.SetSize(nTail);
nWriteSize+=_WriteEncodeRLE8(outfile);
//コードバッファをクリアしてから、
//退避していたコードを復帰する

m_arrayCode.RemoveAll();
m_arrayCode.Add(Code1);
m_arrayCode.Add(Code2);
m_uMode=ENCODE_MODE; //エンコードモードに切り替える
}
m_arrayQueue.RemoveAt(0); //比較し終わったのでキューを一つ進める
break;
case ENCODE_MODE: // エンコードモード
//新しいピクセルと前のピクセルが違えば、
//絶対値モードに切り替える

if (m_arrayQueue.GetAt(0)!=m_arrayQueue.GetAt(1))
{
//コードバッファに最後に加えたコードを一時退避して、
//それ以前のコードをファイルに書き込む

INT_PTR nTail=m_arrayCode.GetSize()-1;
BYTE Code1=m_arrayCode.GetAt(nTail);
m_arrayCode.SetSize(nTail);
nWriteSize+=_WriteEncodeRLE8(outfile);
//スタックをクリアしてから、退避していたコードを復帰する
m_arrayCode.RemoveAll();
m_arrayCode.Add(Code1);
m_uMode=ABS_MODE; //絶対値モードに切り替える
}
m_arrayQueue.RemoveAt(0); //比較し終わったのでキューを一つ進める
break;
}
}
//行末コードの書き込み
nWriteSize+=_WriteEncodeRLE8(outfile); //残りのデータの吐き出し
writeCode[0]=writeCode[1]=0;
outfile->Write(&writeCode,2);
nWriteSize+=2;
}
// 画像の終端コードの書き込み
writeCode[0]=0;
writeCode[1]=1;
outfile->Write(&writeCode,2);
nWriteSize+=2;
return nWriteSize;
}


8ビットランレングス圧縮データをファイルに書き込むための内部関数です。

//8ビットランレングス圧縮データをファイルに書き込みます。
INT_PTR CRle::_WriteEncodeRLE8(CFile* outfile)
{
INT_PTR nWriteSize=0;
BYTE writeCode[4];
INT_PTR nCodeSize=m_arrayCode.GetSize(); //書き込むコード数
PBYTE src=m_arrayCode.GetData(); //コードバッファの先頭アドレスを得る

if (m_uMode==ABS_MODE){//絶対値モードの時の処理
if (nCodeSize<=2){ //2コード以下の書込み
for(int i=0;i<nCodeSize;i++){
writeCode[0]=1; //エンコードモードとしてコード数1を書き込む
writeCode[1]=*src++; //ピクセルデータ
outfile->Write(&writeCode,2);
nWriteSize+=2;
}
}
else { //3コード以上の書き込み
writeCode[0]=0; //エスケープコード
writeCode[1]=(BYTE)nCodeSize; //繰り返し回数
outfile->Write(&writeCode,2);
outfile->Write(src,(UINT)nCodeSize);//ピクセルデータの書き込み
if (nCodeSize%2==1){
outfile->Write(&writeCode,1); //コード数が奇数個ならパディング0を書き込む
}
nWriteSize+=2+nCodeSize+nCodeSize%2;
}
}
else { //エンコードモード書き込み
writeCode[0]=(BYTE)nCodeSize; //コード数を書き込む
writeCode[1]=*src++; //繰り返すコードを書き込む
outfile->Write(&writeCode,2);
nWriteSize+=2;
}
return nWriteSize;
}


戻り値がTRUEなら、ファイルへの書き込みは成功です。

サンプルプログラム(VisualC++net2003ソリューション)DIBFileTest08.zip

上記のzipファイルをダウンロードしてからWinRAR等で解凍し、DIBFileTest08フォルダー内にある「DIBFileTest.sln」を開きます。「F5」キーを押すと、ビルド確認のダイヤログが表示されるので「Yes」を選択してソリューションをビルドします。



ビルドが終わると直ちにプログラムが自動起動してウィンドウが表示されます。ウィンドウのメニューで「ファイル」/「開く」を選択して、「ファイルを開く」ダイヤログを表示させ、resフォルダー内の「browseui261-4bit.bmp」を読み込みます。



16色ビットマップ画像(browseui261-4bit.bmp)



画像が表示されたところで「ファイル」/「名前を付けて保存」で「ファイル名を付けて保存」ダイヤログを表示させ、「browseui261-4bitRLE.bmp」と名づけて「保存」ボタンを押します。



「保存条件」ダイヤログが表示されるので、ランレングス圧縮にチェックをいれて「OK」ボタンを押します。



ツールバーの「新規」で画像を一旦を消しておいてから、再び「ファイル」/「開く」で「ファイルを開く」ダイヤログを表示してから、元の「browseui261-4bit.bmp」と新たに作成した「browseui261-4bitRLE.bmp」のファイル名にそれぞれにマウスカーソルを重ねてツールチップを出します。出てきたそれぞれのツールチップに表示されたのファイルサイズを比較します。元画像の「browseui261-4bit.bmp」が3.24 KBで圧縮後の画像「browseui261-4bitRLE.bmp」が2.94 KBであることを確認してください。



また新たに作成した「browseui261-4bitRLE.bmp」を読み込んで、元の画像と同じ画像が表示されるかも確認します。

8ビット画像についても同様にして動作確認してください。
「browseui262-8bit.bmp」 7.3 KB(圧縮前)
「browseui262-8bitRLE.bmp」5.06 KB(圧縮後)



ランレングス圧縮から解凍した256色ビットマップ画像(browseui262-8bitRLE.bmp)



スポンサーサイト



コメント: 0

この記事へのコメント
ブログ著者にのみ知らせます。

Trackback+Pingback: 0

TrackBack URL for this entry
http://hiroshi0945.blog75.fc2.com/tb.php/19-00dd3cdf
Listed below are links to weblogs that reference
ピクセルデータのランレングス圧縮 from マルチメディアファイルフォーマット

Home > DIBファイルフォーマット > ピクセルデータのランレングス圧縮

タグクラウド
ブロとも申請フォーム

この人とブロともになる

ブロとも一覧

このページの先頭へ戻る