No.007 - 04 ビットマップの読込・保存 |
BMPファイルの読込・保存です。 最近めっきり見かけないRLE圧縮の読込・保存もやります。 |
・ビットマップのファイル構造 |
||||||||||||||||||||||||||||||||
ビットマップファイルのヘッダ構造部には実にいろいろな構造体が存在します。 しかもどれもこれも名前似すぎです。なるべくわかりやすく書こうと思いますが、至らぬ点はご了承ください。 |
||||||||||||||||||||||||||||||||
ビットマップファイルは大きくわけて4つのブロックに分かれます。
順を追ってみていきます |
||||||||||||||||||||||||||||||||
1.BMPファイルヘッダ |
||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
2.BMPインフォ |
||||||||||||||||||||||||||||||||
BITMAPINFO構造体です。 かなりわかりづらい構造体です(一見意味不明な仕様です)。 BMPインフォは { BITMAPINFOHEADER bmiHeader; // BMPインフォヘッダ RGBQUAD bmiColors[1]; // RGBQUAD } というたった2つのメンバ変数を持つだけです。 必要な情報はBITMAPINFOHEADERに はいっていて、 カラーテーブルはRGBQUADから取得します BITMAPINFOHEADER ビットマップ・インフォ・ヘッダ構造体について説明します。 構造体サイズは40です。・・・(OS/2互換DIBならBITMAPCOREHEADERな ので12です)。 画像横幅、画像高さ メンバは周知のとおりです。 biPlanes はカラープレーンです。==1です。定義されたときから今までずっと1のままです。(Windows初期の名残) biBitCount ビット深度です。現在は 1, 4, 8, 16, 24, 32 のどれかです。 biCompression は圧縮フラグです。ピクセルデータがどんな配列になってるかのフラグで、 BI_RGB、BI_RLE4、BI_RLE8、BI_BITFIELDSのいずれかの値です。 余程のコトが無い限りBI_RGBの値を使います。 BI_RGB なら圧縮なし、ピクセルデータはそのまま並んでます。 BI_RLE4 なら4bit RLE圧縮、BI_RLE8 なら8bit RLE圧縮、 BI_BITFIELDS なら16bit か 32bit で、この構造体の直後に保存されている「指定カラーマスク」を使用しますが、BI_BITFIELDSは事実上は存在しないと思われます。(指定カラーマスクの使えるアプリケーション見た事無いよ)
biSizeImage は画像のバイト数(==行のバイト数*高さ)が入ってるはずですが、0が入ってること もあるようです。 biXPelsPerMeter、biYPelsPerMeter は水平解像度、垂直解像度を示していて、 1メートルあたりのピクセル数らしいのですがwindows内部では使いません。通常は0が入ってるようです。(ちなみにpelという言葉は picture element の略でIBMが使う言葉だそうです) biClrUsedはカラーテーブルの配列数です。8bit以下のDIBでは非常に重要です。0が入ってるときは最大配列数 (1bit==2, 4bit==16, 8bit==256)になります。 biClrImportant は重要な色の数です。具体的には画像内で使用している色数です。0なら全色を使用していることを示します。 |
||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
3.RGBカラーテーブル (パレット) |
||||||||||||||||||||||||||||||||
上述したようにBITMAPINFO構
造体のbmiColors[1]メンバで配列的にアクセスします。 配列数はBITMAPINFOHEADERのbiClrUsedメ ンバです。 BITMAPINFOの 構造体を見て大体の人はbmiColors[1] ってなんじゃいな?っておもうと思います。 配列1個だけなら配列にする必要ないじゃん?って思うはずです。 実はコレbmiColors[16]とか、bmiColors[255]とかやってカラーテーブルに配列的にアクセス するための仕込みです。 不正メモリアクセスになりそう? 確かにメモリがsizeof(BITMAPINFO)しかなければ不正メモリアクセスになりますが、 ここでポイント。 BITMAPINFOの メモリ確保(new でなくmalloc)をするときに ( sizeof(BITMAPINFO) + (色数 -1)*RGBQUAD )という メモリサイズにしてやるのです。 これでカラーテーブルにbmiColors[16]といった風に配列的にアクセスできます。 そういう意味なんですって。ぐふぅ。あほか。 |
||||||||||||||||||||||||||||||||
4.ピクセル・データ(実データ部分) |
||||||||||||||||||||||||||||||||
ピクセルデータはボトムアップ式になっています。 007-001でもやったように8bit以下のビット深度ならパレットの番号がはいっており、16bit以上ならRGB配列がはいっています。 上記biCompressionがBI_RGBである場合(ほとんどコレ、99%以上)配列はただ、闇雲にだら〜と並べて置かれています。 またbiCompressionがBI_RLE4やBI_RLE8の値をとった場合(4bitと8bitの時ね)、配列はRLE圧縮されて格納されます。 |
||||||||||||||||||||||||||||||||
・RLE圧縮 |
||||||||||||||||||||||||||||||||
<RLE圧縮するくらいならPNGで保存する方がいいよ> とまあ、耳タコな事実をアピールしつつも、昨今のあまりにもRLEに対する扱いのひどさから今回の記述に至ったわけで。 筆者も正直「RLEは不毛だな〜」と思ってます。ええ思ってますとも。 RLE圧縮はランレングス(run-length)圧縮ともいい、ビットマップ内部の圧縮方法です。 ただ4bitと、8bitのビットマップにしか適用できず、実際あまり利用価値があるようには思えません。 またbiSizeImageに0がはいってるとWindowsの標準ビューアで読み込めません(マジかよ)。 ランレングスはrun-lengthだから「ランレングス」じゃなくて「ランレンクス」じゃないの?と思うんですが、どの本も辞書もサイト も「グ」なんで詳細不明。とりあえず「ランレングス」で表記します。(アメリカの大学とか行かないとわからない?) RLEの基本的な考え方はこうです。 同じものが続いていたらその繰り返し回数と繰り返す値を使う。 不規則な値が続いていたらその不規則数と、各値をそのまま置く。 これだけです。 圧縮の一番カンタンなやつです。 当然、同じ値が続いたピクセルがないと無圧縮時よりサイズが大きくなります。 まず8bitRLEから説明します(1バイト単位でわかりやすいから)。 RLEは1バイトずつ読み込んでその値により処理を分岐させていきます。
0x05 0x31 というバイトを読み込めば 0x31 0x31 0x31 0x31 0x31 というコードに変換されます 0x00 0x04 0x11 0x22 0x33 0x44 というバイトを読み込めば 0x11 0x22 0x33 0x44 というコードに変換されます 圧縮されているコードは常に2バイトの境界で揃えられます。 つまり圧縮されているコードは常に偶数バイトでなければならず、 もし奇数になってしまった場合0x00 という1バイトが末尾に足されています。 0x00 0x05 0x11 0x22 0x33 0x44 0x55 0x00 というバイトを読み込めば 0x11 0x22 0x33 0x44 0x55 というコードに変換されるということです。 また、0x00 0x02 といったバイトを読み込めば画像に未定義領域(何も書かない領域)が作られます。 (筆者、このコードの存在意味がわかりません。透明とも違うし) 次に4bitRLEを説明します 基本部分は同じなんですがバイトとピクセルが1対1になってないのでちょっと大変です。 第1バイトが0でないとき、第2バイトの値のピクセルの同値連続となりますが、 1バイトに2ピクセル分のデータが入ってるので、 例えば 0x04 0x31 なら 0x31 0x31 というコードに変換されるということです。 8bitのときの半分のピクセル数になります。 また繰り返し数が奇数のとき 例えば 0x05 0x31 なら 0x31 0x31 0x3? というコードに変換されます。末尾4bitは不確定のまま次のピクセルへ進みます。 |
||||||||||||||||||||||||||||||||
・カラーマスク |
||||||||||||||||||||||||||||||||
説明だけ。実装しません。 フォトショップもWindows標準編集ソフトも保存不可能な形式を いったいだれが作ってんのか謎。 読込だけなら対応しているものもあるようです。 でもカラーマスクに対応させるのに下手なコーディングすると処理が遅くなります (ピクセル反復子を拡張してもつらい、ていうかビット深度64bitがでるかもって言う時代に無理にやんなくてもいいでしょ?)。 カラーマスクは16bit , 32bit でbiCompression==BI_BITFIELDSときのみ発動です。 通常RGBは均等なビット数が割り振られています。 24bit, 32bit画像ならRGB各8ビットずつ、16bitも通常はRGB各5ビットずつです。 このビット数の割り振り方を変えて、使いたい色だけ特化させるってことです。 例えば16bit なら通常RGB 5-5-5となっているのを5-6-5にしたりとかするわけです。 このときカラーマスクは
色を取得するときは5-6-5なら R = (color & R_mask) >> 11; G = (color & G_mask) >> 5; B = (color & B_mask) >> 0; これをつかうと理論上は色をどんなふうに割り振ってもいいことになります(0-0-16とか)。 たしかにNTとかでは超変則なカラーマスクもできるらしいんですが、 Win98で使える変則マスクは5-6-5だけです。win98、32bitは8-8-8しかだめです。 実用性は低いです、というか皆無です。 |
||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
下記サンプルソースは6つのファイルから成っています ピクセル反復子や、パレット(色テーブル)クラスを定義するヘッダの gt_img_parts.h と、 定数を定義している resource_04.h と、 画像クラスを定義している gt_img_04.h、 リソーススクリプトの gt_img_04.rc、 メインファイルの gt_img_04.cpp 、 そして実際にBMPファイルを読み書きするgt_img_bmp_io.cpp です。
|
||||||||||||||||||||||||||||||||
サンプルで表示しているBMPとRLEです。→ gt_img_04_bmp_sample.zip
↑メニューからなんかファイルを開いてください | ↑24ビット画像 |
↑8ビット画像 | ↑4ビット画像 |
resource_04.h |
// ビットマップ読込 #define IDM_MAKE_DIB_0 1000 #define IDM_MAKE_DIB_1 1001 #define IDM_MAKE_DIB_4 1004 #define IDM_MAKE_DIB_8 1008 #define IDM_MAKE_DIB_16 1016 #define IDM_MAKE_DIB_24 1024 #define IDM_MAKE_DIB_32 1032 #define IDM_OPEN 2000 #define IDM_LOAD_DIB 2001 #define IDM_SAVE_DIB 2002 #define IDM_SAVE_DIB_RLE 2003 |
gt_img_04.rc |
#include "resource_04.h" //リソーススクリプト GT_DIB_MENU MENU { POPUP "ファイル" { POPUP "作成" { MENUITEM "1 bit" , IDM_MAKE_DIB_1 MENUITEM "4 bit" , IDM_MAKE_DIB_4 MENUITEM "8 bit" , IDM_MAKE_DIB_8 MENUITEM "16 bit", IDM_MAKE_DIB_16 MENUITEM "24 bit", IDM_MAKE_DIB_24 MENUITEM "32 bit", IDM_MAKE_DIB_32 } MENUITEM "ひらく", IDM_OPEN MENUITEM "保存", IDM_SAVE_DIB MENUITEM "ランレングス圧縮で保存", IDM_SAVE_DIB_RLE } } |
gt_img_04.h |
// ビットマップ 読込 #include <stdio.h> #include <windows.h> #include "resource_04.h" #include "gt_img_parts.h" // ======== GT_IMG ================================== class GT_IMG { public : GT_COL_TABLE m_col_table; unsigned long *m_col_mask; char *m_alpha; int m_width, m_height, m_byte_width, m_bit_count; BYTE *m_pbits; GT_IMG(); virtual ~GT_IMG(); // 作成と破棄 virtual destroy(); virtual create(int cx, int cy, int a_bit_count, int a_color_size=0); // ピクセル描画 set_pixel(int x, int y, DWORD col); DWORD get_pixel(int x, int y); // アルファ値 is_alpha(){ return (m_alpha!=NULL); } // カラー set_col_mask(UINT r, UINT g, UINT b); set_pal_col(BYTE index, BYTE r, BYTE g, BYTE b); // 読込 load_dib(char *filename); load_dib_rle4(HANDLE hfile); // 4bit ランレングス圧縮 load_dib_rle8(HANDLE hfile); // 8bit ランレングス圧縮 // 書込み save_dib(char *filename, int rle_compress_flag=0); save_dib_rle4(HANDLE hfile); // 4bit ランレングス圧縮 save_dib_rle8(HANDLE hfile); // 8bit ランレングス圧縮 }; // コンストラクタ GT_IMG::GT_IMG(){ m_col_mask =NULL; m_alpha =NULL; m_width = m_height = m_byte_width = m_bit_count =0; m_pbits =NULL; } // デストラクタ GT_IMG::~GT_IMG(){ destroy(); } // GIMG 破棄 GT_IMG::destroy(){ m_width=0; m_height=0; m_byte_width=0; m_bit_count=0; if (m_pbits) delete[] m_pbits; m_pbits = NULL; if (m_col_mask) delete[] m_col_mask; m_col_mask = NULL; m_col_table.destroy(); return 0; }; // GIMG 作成 GT_IMG::create(int cx, int cy, int a_bit_count, int a_color_size){ int i, palette_len, info_size; destroy(); // ファイルが開けたら既存データ破棄 if (cx<=0 || cy<=0) return -1; if (a_bit_count!=1 && a_bit_count!=4 && a_bit_count!=8 && a_bit_count!=16 && a_bit_count!=24 && a_bit_count!=32) return -1; if (a_bit_count<=8 && a_color_size==0) palette_len=1<<a_bit_count; // 8bit以下でパレット色数指定無しなら else palette_len=a_color_size; // パレット・メモリ確保 m_col_table.make(palette_len); /* バッファの1ラインの長さを計算 */ m_byte_width=4*((cx*a_bit_count+31)/32); m_pbits = new BYTE[m_byte_width*cy]; ZeroMemory(m_pbits, m_byte_width*cy); if (!m_pbits) return -1; m_width= cx; m_height=cy; m_bit_count=a_bit_count; // パレット自動配色 // 適当な基本色 RGBTRIPLE rgb[8]={ {0,0,0},{255, 255, 255}, {0,0,255}, {255,0,0}, {0,255,0}, {255, 255,0}, {255,0,255},{0,255,255} }; for(i=0;i<min(palette_len, 8);i++){ m_col_table.add_col(rgb[i].rgbtRed, rgb[i].rgbtGreen, rgb[i].rgbtBlue); } return 0; } // パレットに色設定 inline GT_IMG::set_pal_col(BYTE index, BYTE r, BYTE g, BYTE b){ return m_col_table.set_pal_col(NULL, index, r, g, b); } // カラーマスク設定 GT_IMG::set_col_mask(UINT r, UINT g, UINT b){ if (!m_col_mask) m_col_mask = new unsigned long[3]; m_col_mask[0] = r; m_col_mask[1] = g; m_col_mask[2] = b; } // 1点ピクセル取得 DWORD GT_IMG::get_pixel(int x, int y){ if (!m_pbits) return 0; if (x<0 || x>=m_width || y<0 || y>=m_height) return 0; BYTE *ppix; ppix=m_pbits+(m_height-y-1)*m_byte_width + (x*m_bit_count>>3); switch(m_bit_count){ case 1: return 0x01 & (*ppix >> (7-(x&7))); case 4: return 0x0f & (*ppix >> ((x&1)?0:4)); case 8: return *ppix ; case 16: return *(WORD*)ppix; case 24: return 0x00FFffFF & *(DWORD*)ppix; case 32: return *(DWORD*)ppix ; } return 0; } // 1点ピクセル書きこみ GT_IMG::set_pixel(int x, int y, DWORD col){ if (!m_pbits) return 0; if (x<0 || x>=m_width || y<0 || y>=m_height) return 0; BYTE *ppix; ppix=m_pbits+(m_height-y-1)*m_byte_width + (x*m_bit_count>>3); switch(m_bit_count){ case 1: *ppix &= ~(1 <<(7-(x&7))); *ppix |= ((bool)col) <<(7-(x&7)); break; case 4: *ppix &= 0x0f << ((x&1)?4:0); *ppix |= (0x0f & col) << ((x&1)?0:4); break; case 8: *ppix = (BYTE)col; break; case 16: *(WORD*)ppix = (WORD)col; break; case 24: *(RGBTRIPLE*)ppix = *(RGBTRIPLE*)&col; break; case 32: *(DWORD*)ppix = *(DWORD*)&col; break; } return 0; } // ======== GT_DIB ================================== class GT_DIB : public GT_IMG{ public : HDC m_hdc; HBITMAP m_hbmp; GT_DIB(); ~GT_DIB(); // DC 適用 apply_dc(HDC hdc); apply_dc(HWND hwnd); // 作成と破棄 create(int cx, int cy, int a_bit_count, int a_color_size=0); virtual destroy(); // カラー apply_palette_all(); set_pal_col(BYTE index, BYTE r, BYTE g, BYTE b); }; // コンストラクタ GT_DIB::GT_DIB(){ m_hdc = NULL; m_hbmp = NULL; } // デストラクタ GT_DIB::~GT_DIB(){ destroy(); } // HDCパレット設定(全て) GT_DIB::apply_palette_all(){ return m_col_table.apply_palette_all(m_hdc); } // パレットに色設定 inline GT_DIB::set_pal_col(BYTE index, BYTE r, BYTE g, BYTE b){ return m_col_table.set_pal_col(m_hdc, index, r, g, b); } // DC有効化 GT_DIB::apply_dc(HDC hdc){ if (m_hdc) { DeleteDC(m_hdc); m_hdc=NULL; } m_hdc = CreateCompatibleDC(hdc); SelectObject(m_hdc, m_hbmp); return 0; } // DC有効化(HWND hwnd) GT_DIB::apply_dc(HWND hwnd){ if (m_hdc) { DeleteDC(m_hdc); m_hdc=NULL; } HDC hwnd_hdc = GetDC(hwnd); m_hdc = CreateCompatibleDC(hwnd_hdc); SelectObject(m_hdc, m_hbmp); ReleaseDC(hwnd, hwnd_hdc); return 0; } // 破棄 GT_DIB::destroy(){ if (m_hdc) { DeleteDC(m_hdc); m_hdc=NULL; } if (m_hbmp) { DeleteObject(m_hbmp); m_hbmp=NULL; } // if (m_pbits) delete[] m_pbits; m_pbits = NULL; m_width=0; m_height=0; m_byte_width=0; m_bit_count=0; if (m_col_mask) delete[] m_col_mask; m_col_mask = NULL; m_col_table.destroy(); return 0; } // 作成 GT_DIB::create(int cx, int cy, int a_bit_count, int a_color_size){ BITMAPINFO *bmp_info; int i, palette_entry, info_size; destroy(); // 作成済みメモリを破棄する if (cx<=0 || cy<=0) return -1; if (a_bit_count!=1 && a_bit_count!=4 && a_bit_count!=8 && a_bit_count!=16 && a_bit_count!=24 && a_bit_count!=32) return -1; palette_entry=1; if (a_color_size!=0) palette_entry=a_color_size; else if (a_bit_count<=8) palette_entry=1<<a_bit_count; // 8bit以下でパレット色数指定無しなら info_size=sizeof(BITMAPINFOHEADER) + (palette_entry-1)*sizeof(RGBQUAD)+1; bmp_info = (BITMAPINFO*)malloc(info_size); if (!bmp_info) return -1; ZeroMemory(bmp_info, info_size); // 適当な基本色 RGBTRIPLE rgb[8]={ {0,0,0},{255, 255, 255}, {0,0,255}, {255,0,0}, {0,255,0}, {255, 255,0}, {255,0,255},{0,255,255} }; m_col_table.make(palette_entry); for(i=0;i<min(palette_entry, 8);i++){ bmp_info->bmiColors[i].rgbBlue = rgb[i].rgbtBlue; bmp_info->bmiColors[i].rgbGreen = rgb[i].rgbtGreen; bmp_info->bmiColors[i].rgbRed = rgb[i].rgbtRed; bmp_info->bmiColors[i].rgbReserved = 0; m_col_table.add_col(rgb[i].rgbtRed, rgb[i].rgbtGreen, rgb[i].rgbtBlue); } /* BITMAPINFO構造体設定 */ bmp_info->bmiHeader.biSize=sizeof(BITMAPINFOHEADER); bmp_info->bmiHeader.biWidth=cx; bmp_info->bmiHeader.biHeight=cy; bmp_info->bmiHeader.biPlanes=1; bmp_info->bmiHeader.biBitCount=a_bit_count; bmp_info->bmiHeader.biCompression=BI_RGB; bmp_info->bmiHeader.biSizeImage=0; bmp_info->bmiHeader.biXPelsPerMeter=0; bmp_info->bmiHeader.biYPelsPerMeter=0; bmp_info->bmiHeader.biClrUsed=palette_entry; bmp_info->bmiHeader.biClrImportant=0; m_hbmp = CreateDIBSection(NULL, bmp_info, DIB_RGB_COLORS, (VOID**)&m_pbits, NULL, 0); if (!m_hbmp) { free(bmp_info); return -1; } m_width= cx; m_height=cy; m_bit_count=a_bit_count; /* バッファの1ラインの長さを計算 */ m_byte_width=4*((m_width*m_bit_count+31)/32); ZeroMemory(m_pbits, m_byte_width*m_height); free(bmp_info); return 0; } |
gt_img_bmp_io.cpp |
#include "gt_img_04.h" // DIB 読込 GT_IMG::load_dib(char *filename){ BITMAPFILEHEADER bmp_file_header; BITMAPINFO *bmp_info; DWORD res_byte; int i, bmp_info_size; destroy(); // 破棄 // ファイルオープン HANDLE hfile = CreateFile(filename, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hfile == INVALID_HANDLE_VALUE) return -1; // 識別子'BM' ReadFile(hfile, &bmp_file_header, sizeof(BITMAPFILEHEADER), &res_byte, NULL); if (bmp_file_header.bfType != 0x4D42) { CloseHandle(hfile); return -1; } // ヘッダタイプ判定・読込 bmp_info_size = bmp_file_header.bfOffBits - res_byte; bmp_info = (BITMAPINFO *) malloc (bmp_info_size); ReadFile(hfile, bmp_info, bmp_info_size, &res_byte, NULL); // 生成 BITMAPINFOHEADER *p_bmih=&bmp_info->bmiHeader; int col_len = bmp_info->bmiHeader.biClrUsed; if (col_len==0 && p_bmih->biBitCount<=8) col_len = 1 << p_bmih->biBitCount; if (create(p_bmih->biWidth, p_bmih->biHeight, p_bmih->biBitCount, p_bmih->biClrUsed)<0) { CloseHandle(hfile); free(bmp_info); return -1; } // カラーテーブル RGBQUAD *prgb=bmp_info->bmiColors; for(i=0;i<col_len;i++) set_pal_col(i, prgb[i].rgbRed, prgb[i].rgbGreen, prgb[i].rgbBlue); if (bmp_info->bmiHeader.biClrImportant==0) bmp_info->bmiHeader.biClrImportant = col_len; m_col_table.m_using_cols_len = bmp_info->bmiHeader.biClrImportant; int res=0; // 圧縮判定・データ読込 switch(p_bmih->biCompression){ case BI_BITFIELDS : if ((m_bit_count==16 || m_bit_count==32) && bmp_info_size>=sizeof(BITMAPV4HEADER)) { BITMAPV4HEADER *p_bmp_v4=(BITMAPV4HEADER*)p_bmih; set_col_mask(p_bmp_v4->bV4RedMask, p_bmp_v4->bV4GreenMask, p_bmp_v4->bV4BlueMask); } break; case BI_RLE4 : // 4bit ランレングス圧縮 res = load_dib_rle4(hfile); break; case BI_RLE8 : // 8bit ランレングス圧縮 res = load_dib_rle8(hfile); break; case BI_RGB : ReadFile(hfile, m_pbits, bmp_file_header.bfSize - bmp_file_header.bfOffBits, &res_byte, NULL); if ((bmp_file_header.bfSize - bmp_file_header.bfOffBits)!=res_byte) res=-1; break; default : res=-1; } CloseHandle(hfile); free(bmp_info); return res; } // DIB 読込 4bit ランレングス圧縮 GT_IMG::load_dib_rle4(HANDLE hfile){ DWORD res_byte; PIXEL_ITR<4,0> pix; int i, ix, iy, loop_flag=1; BYTE code_1, code_2, code_3, code_4; pix.init(m_pbits, m_alpha, m_byte_width, m_width, m_height, &m_col_table); pix.m_pbits = m_pbits; // 先頭ビット合わせ == pix.set_pos(0, m_height-1); ix=0; iy=m_height-1; while(loop_flag){ ReadFile(hfile, &code_1, 1, &res_byte, NULL); if (res_byte<=0) return -1; ReadFile(hfile, &code_2, 1, &res_byte, NULL); if (res_byte<=0) return -1; switch(code_1){ case 0: switch(code_2){ case 0: // 行末 ix=0; iy--; if (iy<0) { loop_flag=0; break; } pix.set_pos(ix, iy); break; case 1: // イメージの末尾 loop_flag = 0; break; case 2: // 座標移動 ReadFile(hfile, &code_3, 1, &res_byte, NULL); if (res_byte<=0) return -1; ReadFile(hfile, &code_4, 1, &res_byte, NULL); if (res_byte<=0) return -1; ix+=code_3; iy-=code_4; pix.set_pos(ix, iy); break; default: // 後ろに続くn ピクセルをそのまま使う for(i=0;i<code_2;i++){ if ((i%2)==0) { ReadFile(hfile, &code_3, 1, &res_byte, NULL); if (res_byte<=0) return -1; } code_4 = (i&1)? code_3 : code_3>>4; pix.set_col(0x0f & code_4); pix++; ix++; } i=(code_2/2)%2; i+=code_2%2; if (i%2) { // 2バイト境界判定 ReadFile(hfile, &code_3, 1, &res_byte, NULL); if (res_byte<=0) return -1; } break; } break; default: // code_1==01〜ff for(i=0;i<code_1;i++){ code_4 = (i&1)? code_2 : code_2>>4; pix.set_col(0x0f & code_4); pix++; ix++; } break; } } return 0; } // DIB 読込 8bit ランレングス圧縮 GT_IMG::load_dib_rle8(HANDLE hfile){ DWORD res_byte; int i, ix, iy, loop_flag=1; // 反復子セット PIXEL_ITR<8,0> pix; pix.init(m_pbits, m_alpha, m_byte_width, m_width, m_height, &m_col_table); pix.m_pbits = m_pbits; // 先頭ビット合わせ == pix.set_pos(0, m_height-1); BYTE code_1, code_2, code_3, code_4; ix=0; iy=m_height-1;; while(loop_flag){ ReadFile(hfile, &code_1, 1, &res_byte, NULL); if (res_byte<=0) return -1; ReadFile(hfile, &code_2, 1, &res_byte, NULL); if (res_byte<=0) return -1; switch(code_1){ case 0: switch(code_2){ case 0: // 行末 ix=0; iy--; if (iy<0) { loop_flag=0; break; } pix.set_pos(ix, iy); break; case 1: // 画像の終端 loop_flag = 0; break; case 2: // 座標移動 ReadFile(hfile, &code_3, 1, &res_byte, NULL); if (res_byte<=0) return -1; ReadFile(hfile, &code_4, 1, &res_byte, NULL); if (res_byte<=0) return -1; ix+=code_3; iy-=code_4; pix.set_pos(ix, iy); break; default: // 後ろに続くn ピクセルをそのまま使う for(i=0;i<code_2;i++){ ReadFile(hfile, &code_3, 1, &res_byte, NULL); if (res_byte<=0) return -1; *pix.m_pbits=code_3; pix++; ix++; } if (code_2%2==1) { // 2バイト境界判定 ReadFile(hfile, &code_3, 1, &res_byte, NULL); if (res_byte<=0) return -1; } break; } break; default: // code_1==01〜ff for(i=0;i<code_1;i++){ pix.set_col(code_2); pix++; ix++; } break; } } return 0; } // DIB 保存 4bit ランレングス圧縮(コード書込み) save_dib_rle4_put_pix(int hold_len, HANDLE hfile, PIXEL_ITR<4,0> &write_pix, int check_mode){ // 書込み int i, total_write_size=0; enum { NO_MODE, SAME_MODE }; DWORD res_byte; BYTE write_code[4]; switch(check_mode){ case NO_MODE : if (hold_len<=2){ // 単純ピクセル書込み write_code[0] = 2; // 第1バイト for(i=0;i<(hold_len/2);i++){ write_code[1] = write_pix.get_index() << 4; // 第2バイト(hi) write_pix++; write_code[1] |= write_pix.get_index(); // 第2バイト(low) write_pix++; if (hfile) WriteFile(hfile, write_code, 2, &res_byte, NULL); total_write_size += 2; } write_code[0] = 1; // 第1バイト if (hold_len%2){ write_code[1] = write_pix.get_index() << 4; // 第2バイト(hi) write_pix++; if (hfile) WriteFile(hfile, write_code, 2, &res_byte, NULL); total_write_size += 2; } } else { // 不規則ピクセルモード(3ピクセル以上) write_code[0] = 0; // 第1バイト write_code[1] = (BYTE)hold_len; // 第2バイト WriteFile(hfile, write_code, 2, &res_byte, NULL); total_write_size += 2; // 第3バイト以降 for(i=0;i<(hold_len/2);i++){ write_code[0] = write_pix.get_index() << 4; // 第2バイト(hi) write_pix++; write_code[0] |= write_pix.get_index(); // 第2バイト(low) write_pix++; if (hfile) WriteFile(hfile, write_code, 1, &res_byte, NULL); total_write_size += 1; } if (hold_len%2){ write_code[0] = write_pix.get_index() << 4; // 第2バイト(hi) write_pix++; if (hfile) WriteFile(hfile, write_code, 1, &res_byte, NULL); total_write_size += 1; } write_code[0] = 0; // 第1バイト i=(hold_len/2)%2; i+=hold_len%2; if (i%2) { if (hfile) WriteFile(hfile, write_code, 1, &res_byte, NULL); // 2バイト境界判定 total_write_size += 1; } } break; case SAME_MODE : // 同値ピクセルモード書込み write_code[0] = (BYTE)hold_len; // 第1バイト write_code[1] = write_pix.get_index() << 4; // 第2バイト(hi) write_pix++; write_code[1] |= write_pix.get_index(); // 第2バイト(low) write_pix++; if (hfile) WriteFile(hfile, write_code, 2, &res_byte, NULL); total_write_size += 2; write_pix += (hold_len-2); break; } return total_write_size; } // DIB 保存 4bit ランレングス圧縮 GT_IMG::save_dib_rle4(HANDLE hfile){ enum { NO_MODE, SAME_MODE }; BYTE c=0, stack_pix[5], stack_len=0, write_code[4]; int i, ix, iy, check_mode, hold_len=0, total_write_size=0; check_mode = NO_MODE; DWORD res_byte; // 反復子セット PIXEL_ITR<4,0> write_pix, check_pix; write_pix.init(m_pbits, m_alpha, m_byte_width, m_width, m_height, &m_col_table); // 書込み用反復子 check_pix.init(m_pbits, m_alpha, m_byte_width, m_width, m_height, &m_col_table); // チェック用反復子 // 圧縮メイン for(iy=m_height-1;iy>=0;iy--){ write_pix.set_pos(0, iy); // 書込み用反復子 check_pix.set_pos(0, iy); // チェック用反復子 hold_len = stack_len = 0; // 保留ピクセル数をゼロにする check_mode = NO_MODE; // スタックも保留ピクセル数もゼロなので "不定ピクセルモード" for(ix=0;ix<(m_width);ix++){ // 第1第2ピクセルゲット stack_pix[stack_len] = check_pix.get_index(); check_pix++; // 反復子インクリメント stack_len++; // スタックに積んでるピクセル数 hold_len++; // 保留ピクセル数 // 255オーバー if (hold_len>=255) { total_write_size += save_dib_rle4_put_pix(hold_len, hfile, write_pix, check_mode); // 保留ピクセル全部 書込み hold_len = stack_len = 0; // 全部書き込んだので保留ピクセル数をゼロにする check_mode = NO_MODE; // スタックも保留ピクセル数もゼロなので "不定ピクセルモード" continue; } if (stack_len<3) continue; // ピクセル判定 switch(check_mode){ case NO_MODE : // 不定ピクセルモード if (stack_pix[0] == stack_pix[2]) { // 不定ピクセルモード終了判定 total_write_size += save_dib_rle4_put_pix(hold_len-3, hfile, write_pix, check_mode); // 決まった分だけ書込み hold_len = 3; // stack_pix[0] 〜 stack_pix[2] は保留ピクセルへ check_mode = SAME_MODE; // モード切替 stack_len = 2; // 判定が済んだのでスタックに積んでるピクセル数を1個だけにもどす break; } stack_pix[0] = stack_pix[1]; // 次回のstack_pix[0] != stack_pix[1] 比較のため stack_pix[1] = stack_pix[2]; stack_len = 2; // 判定が済んだのでスタックに積んでるピクセル数を1個だけにもどす break; case SAME_MODE : // 同値ピクセルモード if ( (((hold_len%2)==0) && (stack_pix[0] != stack_pix[2])) || ((hold_len%2) && (stack_pix[1] != stack_pix[2])) ) { // 同値ピクセルモード終了判定 total_write_size += save_dib_rle4_put_pix(hold_len-1, hfile, write_pix, check_mode); // 決まった分だけ書込み stack_pix[0] = stack_pix[2]; // stack_pix[2] は保留 hold_len = 1; check_mode = NO_MODE; // モード切替 stack_len = 1; } else stack_len = 2; // 判定が済んだのでスタックに積んでるピクセル数を1個だけにもどす break; } } // 行末コード total_write_size += save_dib_rle4_put_pix(hold_len, hfile, write_pix, check_mode); // 保留ピクセル全部 書込み write_code[0] = write_code[1] = 0; // 第1バイト 第2バイト if (hfile) WriteFile(hfile, write_code, 2, &res_byte, NULL); // 行末コード(00 00) total_write_size += 2; } // 画像の終端 write_code[0] = 0; // 第1バイト write_code[1] = 1; // 第2バイト if (hfile) WriteFile(hfile, write_code, 2, &res_byte, NULL); return total_write_size; } // DIB 保存 8bit ランレングス圧縮(コード書込み) save_dib_rle8_put_pix(int hold_len, HANDLE hfile, PIXEL_ITR<8,0> &write_pix, int check_mode){ // 書込み int i; enum { NO_MODE, SAME_MODE }; DWORD res_byte; BYTE write_code[4]; int total_write_size=0; switch(check_mode){ case NO_MODE : if (hold_len<=2){ // 単純ピクセル書込み for(i=0;i<hold_len;i++){ write_code[0] = 1; // 第1バイト write_code[1] = *write_pix.m_pbits; // 第2バイト if (hfile) WriteFile(hfile, write_code, 2, &res_byte, NULL); total_write_size += 2; write_pix++; } } else { // 不規則ピクセルモード(3ピクセル以上) write_code[0] = 0; // 第1バイト write_code[1] = (BYTE)hold_len; // 第2バイト if (hfile) { WriteFile(hfile, write_code, 2, &res_byte, NULL); WriteFile(hfile, write_pix.m_pbits, hold_len, &res_byte, NULL); // 第3バイト以降 if (hold_len%2==1) WriteFile(hfile, write_code, 1, &res_byte, NULL); // 2バイト境界判定 } total_write_size += 2 + hold_len + hold_len%2; write_pix += hold_len; } break; case SAME_MODE : // 同値ピクセルモード書込み write_code[0] = (BYTE)hold_len; // 第1バイト write_code[1] = *write_pix.m_pbits; //stack_pix[0]; // 第2バイト if (hfile) WriteFile(hfile, write_code, 2, &res_byte, NULL); write_pix += hold_len; total_write_size += 2; break; } return total_write_size; } // DIB 保存 8bit ランレングス圧縮 GT_IMG::save_dib_rle8(HANDLE hfile){ enum { NO_MODE, SAME_MODE }; BYTE c=0, stack_pix[5], stack_len=0, write_code[4]; int i, ix, iy, check_mode, hold_len=0; check_mode = NO_MODE; DWORD res_byte; int total_write_size=0; // 反復子セット PIXEL_ITR<8,0> write_pix, check_pix; write_pix.init(m_pbits, m_alpha, m_byte_width, m_width, m_height, &m_col_table); // 書込み用反復子 check_pix.init(m_pbits, m_alpha, m_byte_width, m_width, m_height, &m_col_table); // チェック用反復子 // 圧縮メイン for(iy=m_height-1;iy>=0;iy--){ write_pix.set_pos(0, iy); // 書込み用反復子 check_pix.set_pos(0, iy); // チェック用反復子 hold_len = stack_len = 0; // 全部書き込んだので保留ピクセル数をゼロにする check_mode = NO_MODE; // スタックも保留ピクセル数もゼロなので "不定ピクセルモード" for(ix=0;ix<m_width;ix++){ // 第1第2ピクセルゲット stack_pix[stack_len] = *check_pix.m_pbits; check_pix++; // 反復子インクリメント stack_len++; // スタックに積んでるピクセル数 hold_len++; // 保留ピクセル数 // 255オーバー if (hold_len>=255) { total_write_size += save_dib_rle8_put_pix(hold_len, hfile, write_pix, check_mode); // 保留ピクセル全部 書込み hold_len = stack_len = 0; // 全部書き込んだので保留ピクセル数をゼロにする check_mode = NO_MODE; // スタックも保留ピクセル数もゼロなので "不定ピクセルモード" continue; } if (stack_len<2) continue; // ピクセル判定 switch(check_mode){ case NO_MODE : // 不定ピクセルモード if (stack_pix[0] == stack_pix[1]) { // 不定ピクセルモード終了判定 total_write_size += save_dib_rle8_put_pix(hold_len-2, hfile, write_pix, check_mode); // 決まった分だけ書込み hold_len = 2; // stack_pix[0] stack_pix[1] は保留 check_mode = SAME_MODE; // モード切替 } stack_pix[0] = stack_pix[1]; // 次回のstack_pix[0] != stack_pix[1] 比較のため break; case SAME_MODE : // 同値ピクセルモード if (stack_pix[0] != stack_pix[1]) { // 同値ピクセルモード終了判定 total_write_size += save_dib_rle8_put_pix(hold_len-1, hfile, write_pix, check_mode); // 決まった分だけ書込み stack_pix[0] = stack_pix[1]; // stack_pix[1] は保留 hold_len = 1; check_mode = NO_MODE; // モード切替 } break; } stack_len = 1; // 判定が済んだのでスタックに積んでるピクセル数を1個だけにもどす } // 行末コード total_write_size += save_dib_rle8_put_pix(hold_len, hfile, write_pix, check_mode); // 保留ピクセル全部 書込み write_code[0] = write_code[1] = 0; // 第1バイト 第2バイト if (hfile) WriteFile(hfile, write_code, 2, &res_byte, NULL); // 行末コード(00 00) total_write_size += 2; } // 画像の終端 write_code[0] = 0; // 第1バイト write_code[1] = 1; // 第2バイト if (hfile) WriteFile(hfile, write_code, 2, &res_byte, NULL); total_write_size += 2; return total_write_size; } // DIBセーブ GT_IMG::save_dib(char *filename, int rle_compress_flag){ if (!m_pbits) return -1; BITMAPFILEHEADER bmp_file_header; BITMAPINFOHEADER bmp_info_header; DWORD res_bytes; int i, total_size, pixel_array_size, col_size; total_size=40; // 構造体サイズ==40==ds.dsBmih.biSize if (m_col_mask) total_size+= 3*sizeof(DWORD); // 固有カラーマスク (ds.dsBmih.biCompression==BI_BITFIELDS) // パレットサイズ total_size+=(m_col_table.m_cols_len*sizeof(RGBQUAD)); // ピクセル配列サイズ pixel_array_size=m_byte_width*m_height; // 通常サイズ if (rle_compress_flag) { // RLE 圧縮 ヘッダ・セット switch(m_bit_count){ case 4 : pixel_array_size=save_dib_rle4(NULL); break; case 8 : pixel_array_size=save_dib_rle8(NULL); break; } } total_size+=pixel_array_size; bmp_file_header.bfType = *(WORD*) "BM"; bmp_file_header.bfSize = sizeof(BITMAPFILEHEADER) + total_size; bmp_file_header.bfReserved1=0; bmp_file_header.bfReserved2=0; bmp_file_header.bfOffBits = bmp_file_header.bfSize - pixel_array_size; bmp_info_header.biSize=40; // 構造体サイズ==40 bmp_info_header.biWidth=m_width; // 画像横幅 bmp_info_header.biHeight=m_height; // 画像高さ bmp_info_header.biPlanes=1; // ==1(Windows初期の名残) bmp_info_header.biBitCount=m_bit_count; // カラービット数(1, 4, 8, 16, 24, 32) bmp_info_header.biCompression=BI_RGB; // 圧縮コード(BI_RGB, BI_RLE4, BI_RLE8, BI_BITFIELDSのいずれか) bmp_info_header.biSizeImage=pixel_array_size; // イメージのバイト数 bmp_info_header.biXPelsPerMeter=0; // 水平解像度 bmp_info_header.biYPelsPerMeter=0; // 垂直解像度 bmp_info_header.biClrUsed=m_col_table.m_cols_len; // カラーテーブルの数 bmp_info_header.biClrImportant=m_col_table.m_using_cols_len; // 重要な色の数 HANDLE hfile = CreateFile(filename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (hfile == INVALID_HANDLE_VALUE) return 0; if (rle_compress_flag) { // RLE 圧縮 ヘッダ・セット switch(m_bit_count){ case 4 : bmp_info_header.biCompression=BI_RLE4; break; case 8 : bmp_info_header.biCompression=BI_RLE8; break; } } WriteFile(hfile, &bmp_file_header, sizeof (BITMAPFILEHEADER), &res_bytes, NULL); WriteFile(hfile, &bmp_info_header, sizeof (BITMAPINFOHEADER), &res_bytes, NULL); if (bmp_info_header.biCompression==BI_BITFIELDS) WriteFile(hfile, m_col_mask, 3*sizeof(DWORD), &res_bytes, NULL); col_size = m_col_table.m_cols_len; RGBQUAD rgb; rgb.rgbReserved=0; if (col_size>=0) { for(i=0;i<col_size;i++){ rgb.rgbRed = m_col_table.m_cols[i].m_r; rgb.rgbGreen = m_col_table.m_cols[i].m_g; rgb.rgbBlue = m_col_table.m_cols[i].m_b; WriteFile(hfile, &rgb, sizeof(RGBQUAD), &res_bytes, NULL); } } // ピクセル・ビット書き込み switch(bmp_info_header.biCompression){ case BI_RLE4 : save_dib_rle4(hfile); break; // 4bit RLE 圧縮 case BI_RLE8 : save_dib_rle8(hfile); break; // 8bit RLE 圧縮 default : WriteFile(hfile, m_pbits, pixel_array_size, &res_bytes, NULL); } CloseHandle(hfile); return 0; } |
gt_img_04.cpp |
// ビットマップ 読込 #include <stdio.h> #include <windows.h> #include "resource_04.h" #include "gt_img_04.h" GT_DIB gt_dib_1; static OPENFILENAME ofn = {0}; static char filename[MAX_PATH]; // メニュー on_command(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp){ static CHOOSECOLOR cc = {0}; static DWORD col_costom[8] = {0}; switch(LOWORD(wp)) { case IDM_MAKE_DIB_0: case IDM_MAKE_DIB_1: case IDM_MAKE_DIB_4: case IDM_MAKE_DIB_8: case IDM_MAKE_DIB_16: case IDM_MAKE_DIB_24: case IDM_MAKE_DIB_32: gt_dib_1.create(201, 151, LOWORD(wp)-IDM_MAKE_DIB_0, 0); gt_dib_1.apply_dc(hwnd); InvalidateRect(hwnd, NULL, 0); break; case IDM_OPEN: ofn.Flags = OFN_FILEMUSTEXIST; if (!GetOpenFileName(&ofn)) break; gt_dib_1.load_dib(filename); gt_dib_1.apply_dc(hwnd); gt_dib_1.apply_palette_all(); InvalidateRect(hwnd, NULL, 1); break; case IDM_SAVE_DIB: ofn.Flags = 0; if (!GetSaveFileName(&ofn)) break; gt_dib_1.save_dib(filename); break; case IDM_SAVE_DIB_RLE: ofn.Flags = 0; if (!GetSaveFileName(&ofn)) break; gt_dib_1.save_dib(filename, 1); break; } } LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { HDC hdc; PAINTSTRUCT ps; POINT po; static char s[50]; RECT rc; switch (msg) { case WM_PAINT: hdc = BeginPaint(hwnd, &ps); if (gt_dib_1.m_hdc){ BitBlt( hdc, 0, 0, gt_dib_1.m_width, gt_dib_1.m_height, gt_dib_1.m_hdc, 0, 0, SRCCOPY); } sprintf(s, " ヨコ %d X タテ %d %dビット", gt_dib_1.m_width, gt_dib_1.m_height, gt_dib_1.m_bit_count); GetClientRect(hwnd , &rc); rc.top=gt_dib_1.m_height; DrawText(hdc, s, -1, &rc, DT_WORDBREAK ); EndPaint(hwnd, &ps); break; case WM_COMMAND: on_command(hwnd, msg, wp, lp); break; case WM_DESTROY: PostQuitMessage(0); return 0; case WM_CREATE: ofn.lStructSize = sizeof (OPENFILENAME); ofn.hwndOwner = hwnd; ofn.lpstrFilter = TEXT("bmp files {*.bmp}\0*.bmp\0") TEXT("All files {*.*}\0*.*\0\0"); ofn.lpstrCustomFilter = NULL; ofn.nMaxCustFilter = 0; ofn.nFilterIndex = 0; ofn.lpstrFile = filename; ofn.nMaxFile = MAX_PATH; ofn.Flags = OFN_FILEMUSTEXIST; break; } return DefWindowProc(hwnd, msg, wp, lp); } int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR lpCmdLine, int nCmdShow) { HWND hwnd; MSG msg; WNDCLASS winc; 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)GetStockObject(WHITE_BRUSH); winc.lpszMenuName = TEXT("GT_DIB_MENU"); winc.lpszClassName = TEXT("GT_IMG"); if (!RegisterClass(&winc)) return 1; hwnd = CreateWindow( TEXT("GT_IMG"), TEXT("ビットマップ 読込・保存"), WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, 400, 300, NULL, NULL, hInstance, NULL ); if (hwnd == NULL) return 1; while (GetMessage(&msg, NULL, 0, 0 )) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } |