ビットマップファイルフォーマット

ビットマップファイル(*.bmp)のファイルフォーマットです。

 

ビットマップ全体の構造

BITMAPFILEHEADER 14Byte
BITMAPINFOHEADER 40Byte
カラーテーブル(無い場合もあり) 4Byte*Index数
画像データ

 

各構造体について

■BITMAPFILEHEADER

typedef struct tagBITMAPFILEHEADER { 
	WORD bfType; 
	DWORD bfSize; 
	WORD bfReserved1; 
	WORD bfReserved2; 
	DWORD bfOffBits; 
} BITMAPFILEHEADER, *PBITMAPFILEHEADER;

 

bfType ファイルタイプ ‘BM’
bfSize ファイル全体のサイズ(バイト数)
bfReserved1 予約領域 常に0
bfReserved2 予約領域 常に0
bfOffBits ファイル先頭から画像データまでのオフセット数(バイト単位)

 

■BITMAPINFO

typedef struct tagBITMAPINFO { 
	BITMAPINFOHEADER bmiHeader; 
	RGBQUAD bmiColors[1]; 
} BITMAPINFO, *PBITMAPINFO;

●BITMAPINFOHEADER

typedef struct tagBITMAPINFOHEADER{
	DWORD  biSize;
	LONG   biWidth;
	LONG   biHeight;
	WORD   biPlanes;
	WORD   biBitCount;
	DWORD  biCompression;
	DWORD  biSizeImage;
	LONG   biXPelsPerMeter;
	LONG   biYPelsPerMeter;
	DWORD  biClrUsed;
	DWORD  biClrImportant;
} BITMAPINFOHEADER, *PBITMAPINFOHEADER;
biSize 構造体のサイズ 40
biWidth 画像の幅(ピクセル数)
biHeight 画像の高さ(ピクセル数)
値が負の場合、画像の上下が逆になる
biPlanes プレーン数 常に1
biBitCount 1画素あたりのビット数 1,4,8,16,24,32
biCompression 圧縮形式 BI_RGB, BI_RLE8, BI_RLE4
BI_BITFIELDS, BI_JPEG, BI_PNG
biSizeImage 画像データのサイズ(バイト数) BI_RGBの場合0でも可
biXPelsPerMeter 水平方向の1Mあたりの画素数 0でも可
biYPelsPerMeter 垂直方向の1Mあたりの画素数 0でも可
biClrUsed カラーテーブルの色数 0でも可
biClrImportant 表示に必要なカラーテーブルの色数 0でも可

 

●RGBQUAD(カラーテーブル)

typedef struct tagRGBQUAD {
	BYTE    rgbBlue;
	BYTE    rgbGreen;
	BYTE    rgbRed;
	BYTE    rgbReserved;
} RGBQUAD;
rgbBlue 青の輝度値 0~255
rgbGreen 緑の輝度値 0~255
rgbRed 赤の輝度値 0~255
rgbReserved 予約領域 常に0

 

biBitCountが1,4,8のとき、RGBQUAD構造体のカラーテーブルが指定されます。
biBitCountが24,32のときは存在しません。
ただし、biCompressionがBI_BITFIELDS かつ、biBitCountが16,32の場合、
多ビット(10Bit,12Bitなど)が表示可能となり、ビットフィールドが指定されます。

 

■画像データ

モノクロ画像の場合、輝度値が格納されています。

24Bitカラーの場合、B,G,R,B,G,R・・・の順で各輝度値が格納されています。
32Bitカラーの場合、B,G,R,A,B,G,R,A・・・の順で各輝度値が格納されています。

 

画像データは、画像の左から右、下から上へ向かう順番で格納されます。

 

 

また、1行あたりのメモリのサイズは4の倍数バイトになるように調整されています。
この値は以下のようにして計算しています。

 

VBの場合

((biWidth * biBitCount + 31) \ 32) * 4

Cの場合

((biWidth * biBitCount + 31) / 32) * 4

 

4の倍数バイト(32ビットの倍数)になるように調整しています。

 

ビットマップファイルを開く場合の注意点について

ビットマップファイルを開く場合、ヘッダ情報をもとに画像データ格納用のメモリを確保し、
そのメモリにデータを格納しますが、ヘッダの値はすべて正しく記載されているとは限りません。私の場合、以下の値を信じてメモリの確保などを行っています。

biWidth, biHeight, biBitCountを用いて画像データ格納用メモリの確保
biOffBitsを用いて、画像データまでのファイルのシークを行う。

biSizeやbiSizeImageなどは信じない方が良いと思います。

 

【C++/CLI】Graphicsオブジェクトの作成

.NETではピクチャボックスに画像や線、文字などを描画するには、Graphicsオブジェクトを作成し、このGraphicsオブジェクトに対して描画を行います。

 

Graphicsオブジェクトを作成する方法は3つ。
1.Imageオブジェクトから作成(オススメ!)
2.CreateGraphicsメソッドを使う方法(使わないことをオススメ!)
3.PaintイベントのPaintEventArgsから取得する方法

 

Graphiscオブジェクトを作成したら、画像の描画は

g->DrawImage(bmp, 0, 0);

線の描画は

g->DrawLine(Pens::Blue,0, 0, 100, 100);

というような具合で。

 

Imageオブジェクトから作成

(コード例)

//PictureBoxと同じ大きさのBitmapクラスを作成する。
Bitmap^ bmpPicBox = gcnew Bitmap(pictureBox1->Width, pictureBox1->Height);
//空のBitmapをPictureBoxのImageに指定する。
pictureBox1->Image = bmpPicBox;
//Graphicsオブジェクトの作成(FromImageを使う)
Graphics^ g = Graphics::FromImage(pictureBox1->Image);

 

上記コードをフォームのResizeイベントなどで処理を行い、作成したGraphicsオブジェクトを使い回せばよいかと思います。
この手法だと再描画は勝手にやってくれる上に、CreateGraphicsを使うよりも
格段に描画が速くなります。
.NET(GDI+)による描画が遅い!と思っている方は、まずはこの方法をお試し下さい。
(でも個人的には、それでも遅く感じます)

 

CreateGraphicsメソッドを使う

(コード例)

//Graphicsオブジェクトの作成(CreateGraphicsを使う)
Graphics^ g = pictureBox1->CreateGraphics();

 

この手法だと簡単ですが描画がかなり遅くなります。
しかも再描画してくれません...
説明が簡単なので、使っちゃう場合もありますが。

 

PaintイベントのPaintEventArgsから取得する

(コード例)

private: System::Void pictureBox1_Paint(System::Object^  sender, System::Windows::Forms::PaintEventArgs^  e) {
//PaintEventArgsから取得
Graphics^ g = e->Graphics;
}

この方法が一番高速ですが、ちょっと扱いにくい。

 

ImageオブジェクトからGraphicsオブジェクトを作成する方法とCreateGraphisメソッドを使った方法の処理時間の比較などを「モノクロ画像の上に線などを描画」のページで紹介しています。
もしよろしければご参照下さい。

【C++/CLI】モノクロ画像の上に線などを描画

モノクロ画像の上に線などを描画する方法をまとめました。

 

【目標】

  • モノクロ/カラー区別なく画像の上に描画できること
  • 高速に描画できること
  • 再描画すること

     

    作成したサンプルプログラムはこんな感じ↓です。

     

    (サンプルプログラムの使い方)
    各ボタンをクリックするとファイルを開くダイアログボックスが開きますので、ビットマップファイルを指定して下さい。
    (ダウンロード)MonoImageDisp2005.zip(Visual Studio 2005 Express Edition版)

     

    準備

    線の描画は条件を同じにするため、以下の関数を使い回しました。

    void DrawGraphics(Graphics^ g){
    	//ギザギザ模様を画像の上に描画
    
    	int i, j;
    
    	//ギザギザの線の描画
        for (j = 0; j < 256; j+= 3){
    	    for (i = 0; i < 25; i++){ g->DrawLine(Pens::Blue, i * 10, j, i * 10 + 5, j - 5);
    		    g->DrawLine(Pens::Blue, i * 10 + 5, j - 5, i * 10 + 10, j);
    	    }
        }
    }

    よくあるサンプルプログラム

    private: System::Void btnBitmap_Click(System::Object^  sender, System::EventArgs^  e) {
    		 //ビットマップファイルからBitmapクラスを作成し、ピクチャボックスへ渡す方法
    		 //モノクロ画像だとエラーになります。
    
    	     try{
    			//描画時間計測用
    		    Diagnostics::Stopwatch^ sw = gcnew Diagnostics::Stopwatch;
    
    			//ピクチャボックスの画像クリア
    			pictureBox1->Image = nullptr;
    
    			//画像ファイル名の取得
    			String^ FileName = GetImageFilename();
    			//Bitmapクラスの作成
    			Bitmap^ bmp = gcnew Bitmap(FileName);
    			//PictureBoxへBitmapを表示する。
    			pictureBox1->Image = bmp;
    
    			//Graphicsクラスの作成
    			//※モノクロ画像ファイルを開くと、ここでエラーになります。
    			Graphics^ g = Graphics::FromImage(bmp);
    			//画像の上にギザギザ模様の描画
    			sw->Start();
    			DrawGraphics(g);
    			sw->Stop();
    			//描画時間表示
    			MessageBox::Show("描画時間 = " + sw->ElapsedMilliseconds.ToString() + " msec");
    
    		 }catch(Exception^ err){
    			//エラーの表示
    			MessageBox::Show(err->Message);
    		 }
    	 }

    このプログラムではカラー画像では問題ないのですが、8ビットモノクロ画像ファイルを開くとFromImageメソッドでエラーとなります。

     

    CreateGraphicsを使ってGraphicsクラスを作成する方法

    private: System::Void btnCreateGraphics_Click(System::Object^  sender, System::EventArgs^  e) {
    		 //CreateGraphicsを使ってGraphicsクラスを作成し描画する。
    		 //モノクロ画像の上にも描画できますが、描画時間がかなり遅くなります。
    		 //描画したギザギザ模様は再描画されません。
    
    		 try{
    			//描画時間計測用
    		    Diagnostics::Stopwatch^ sw = gcnew Diagnostics::Stopwatch;
    
    			//ピクチャボックスの画像クリア
    			pictureBox1->Image = nullptr;
    
    			//Graphicsクラスの作成(CreateGraphicsを使う)
    			Graphics^ g = pictureBox1->CreateGraphics();
    
    			//画像ファイル名の取得
    			String^ FileName = GetImageFilename();
    			//Bitmapクラスの作成
    			Bitmap^ bmp = gcnew Bitmap(FileName);
    			//PictureBoxへBitmapを表示する。
    			pictureBox1->Image = bmp;
    			pictureBox1->Refresh();
    
    			//画像の上にギザギザ模様の描画
    			sw->Start();
    			DrawGraphics(g);
    			sw->Stop();
    			//描画時間表示
    			MessageBox::Show("描画時間 = " + sw->ElapsedMilliseconds.ToString() + " msec");
    
    		 }catch(Exception^ err){
    			//エラーの表示
    			MessageBox::Show(err->Message);
    		 }
    	 }

    このプログラムでは、モノクロ画像ファイルを開いても、モノクロ画像の上に線を描画することができます。
    ただし、遅い!!!
    再描画もされません。

    PictureBoxと同じ大きさのBitmapクラスを作成し、ピクチャボックスのImageへ。画像データはDrawImageで描画

    /private: System::Void btnDrawImage_Click(System::Object^  sender, System::EventArgs^  e) {
    		 //ピクチャボックス表示用と画像データ用と別のBitmapクラスを作成し、
    		 //ピクチャボックスにはDrawImageを使って画像を描画する。
    		 //モノクロ画像でも表示可能
    		 //再描画可能、CreateGraphicsを使うより高速
    
    		 try{
    			//描画時間計測用
    		    Diagnostics::Stopwatch^ sw = gcnew Diagnostics::Stopwatch;
    
    			//ピクチャボックスの画像クリア
    			//pictureBox1->Image = nullptr;
    
    			//画像ファイル名の取得
    			String^ FileName = GetImageFilename();
    			//Bitmapクラスの作成
    			Bitmap^ bmpFileImage = gcnew Bitmap(FileName);
    
    			//PictureBoxの大きさを画像の大きさに合わせる
    			pictureBox1->Width  = bmpFileImage->Width;
    			pictureBox1->Height = bmpFileImage->Height;
    
    			//PictureBoxと同じ大きさのBitmapクラスを作成する。
    			Bitmap^ bmpPicBox = gcnew Bitmap(pictureBox1->Width, pictureBox1->Height);
    			//空のBitmapをPictureBoxのImageに指定する。
    			pictureBox1->Image = bmpPicBox;
    			//Graphicsクラスの作成(CreateGraphicsを使う)
    			Graphics^ g = Graphics::FromImage(pictureBox1->Image);
    
    			//PictureBoxへBitmapを描画する。
    			g->DrawImage(bmpFileImage, 0, 0);
    			pictureBox1->Invalidate();
    
    			//画像の上にギザギザ模様の描画
    			sw->Start();
    			DrawGraphics(g);
    			sw->Stop();
    			//描画時間表示
    			MessageBox::Show("描画時間 = " + sw->ElapsedMilliseconds.ToString() + " msec");
    
    		 }catch(Exception^ err){
    			//エラーの表示
    			MessageBox::Show(err->Message);
    		 }
    	 }

    最初にピクチャボックスと同じ大きさのBitmapクラスを作成し、ピクチャボックスのImageクラスへ渡す。ピクチャボックスのImageからFromImageメソッドでGraphicsクラスを作成。
    このようにすると、モノクロ画像の上にも線を描画することができ、再描画も勝手にしてくれます。
    しかも、CreateGraphicsを使ってGraphicsクラスを作成した場合よりも、描画速度は10倍くらい速くなります。

    【C++/CLI】大文字/小文字、全角/半角、ひらがな/カタカナを区別せず比較する

    字列を比較する際に、文字の大文字/小文字、全角/半角、ひらがな/カタカナを区別せず比較したい場合があります。

    大文字/小文字を区別せずに比較するのは、比較的、情報も多く、

    System.StringクラスのCompareメソッド

    を使います。
    使用例はこんな感じ↓

     

    String^ strA = "Labeling";
    String^ strB = "labeling";
    
    if (String::Compare(strA, strB, true) == 0){
    MessageBox::Show("strAとstrBは同じ");
    }

     

    ただ、実際には、日本語を比較する場合は、全角/半角、ひらがな/カタカナを区別しないで比較したい場合が多く、この場合はSystem.Globalization.CompareInfoクラスCompareメソッドを使います。

     

    (使用例)

    System::Globalization::CompareInfo^ ci =
    System::Globalization::CultureInfo::CurrentCulture->CompareInfo;
    
    String^ strA = "ラベリング";
    String^ strB = "らべりんぐ";
    
    if (ci->Compare(strA, strB,
    System::Globalization::CompareOptions::IgnoreWidth |         //全角/半角を無視
    System::Globalization::CompareOptions::IgnoreCase |          //大文字/小文字を無視
    System::Globalization::CompareOptions::IgnoreKanaType    //ひらがな/カタカナを無視
    ) == 0){
    MessageBox::Show("strAとstrBは同じ");
    }

     

    詳しくはDOBON.NETの

    2つの文字列が等しいかを調べる
    大文字小文字、半角全角、ひらがなカタカナの区別をしないで文字列を比較する

    が参考になります。

     

    実は”ラベリング”と”ラベリング”の文字列を比較するのに大はまりして、
    ひらがなの”ベ” と カタカナの ”ベ” がある事に気が付くまで、とても苦労してしまいました...

    【C++/CLI】VB.NET固有の関数を使用する方法

    .NET Framework ではVisual Basic.NETであっても、ただのVisualBasicという名前空間であるだけなので、C#やC++/CLIからVB.NET固有の関数を使うことが可能です。

     

    以下、VB固有の関数の代表格のInputBox関数の使用方法を紹介します。

     

    まず、VisualBasicの名前空間を参照できるように参照設定を行います。

     

    プロジェクトの名前の部分で右ボタンをクリックし、参照を選択します。

     

     

    次に新しい参照の追加ボタンをクリックします。

     

     

    すると、使用可能な.NETの名前空間を表示されるので、この中からMicrosoft.VisualBaicを選択しOKボタンをクリックします。

     

     

    すると、現在参照設定している名前空間の一覧にMicrosoft.VisualBasicの名前か追加されます。

     

     

    これで準備は完了です。

     

    実際にInputBoxを呼び出すのは、こんな感じ↓です。

     

    String^ str = Microsoft::VisualBasic::Interaction::InputBox(“入力文字?”, “タイトル”, “初期値”, -1, -1);

     

    とすると、このよう↓に表示されます。

     

     

    C++からVBの関数が呼べてしまうのは不思議な感じもしますが、他にも使いたいVBの関数があったので、今回わざわざ記事にしてみました。

    【C++/CLI】大文字/小文字、全角/半角、ひらがな/カタカナ変換

    大文字/小文字の変換をするには System.String クラスToLowerToUpper メソッドを用います。

     

    【コード例】

    String^ str;
    
    //大文字→小文字変換
    str = "ImagingSolution";    // → imagingsolution
    str = str->ToLower();
    //小文字→大文字変換
    str = "Imagingsolution";    // → IMAGINGSOLUTION
    str = str->ToUpper();

    さらにVBの関数を用いると全角/半角、ひらがな/カタカナの変換も行う事が出来ます。
    VBの関数をC++/CLIから使う方法は VB.NET固有の関数を使用する方法 を参照願います。

     

    【コード例】

    using namespace Microsoft::VisualBasic;
    
    String^ str;
    
    //大文字→小文字変換
    str = "ImagingSolution";    //→imagingsolution
    str = Strings::StrConv(str, VbStrConv::Lowercase, 0);
    //小文字→大文字変換
    str = "Imagingsolution";    //→IMAGINGSOLUTION
    str = Strings::StrConv(str, VbStrConv::Uppercase, 0);
    
    //全角→半角変換
    str = "画像処理ソリューション";    //→画像処理ソリューション
    str = Strings::StrConv(str, VbStrConv::Narrow, 0);
    //半角→全角変換
    str = "画像処理ソリューション";            //→画像処理ソリューション
    str = Strings::StrConv(str, VbStrConv::Wide, 0);
    
    //ひらがな→カタカナ
    str = "画像処理そりゅーしょん";    //→画像処理ソリューション
    str = Strings::StrConv(str, VbStrConv::Katakana, 0);
    //カタカナ→ひらがな
    str = "画像処理ソリューション";    //→画像処理そりゅーしょん
    str = Strings::StrConv(str, VbStrConv::Hiragana, 0);
    

    (参考)
    http://msdn.microsoft.com/ja-jp/library/7wtc81z6%28v=VS.80%29.aspx
    http://msdn.microsoft.com/ja-jp/library/microsoft.visualbasic.vbstrconv.aspx

    【C++/CLI】ファイルパス(フルパス)からファイル名、拡張子、ディレクトリの取得

    ファイルを開くダイアログボックスなどから取得したファイル名(フルパス)からファイル名や拡張子、
    ディレクトリなどを取得する場合には

    System::IOクラスのGet×××メソッド

    を用います。

     

    以下、サンプルプログラムです。

    //ファイル名(フルパス)
    String^ FullPath = "c:\\Dir1\\Dir2\\Bitmap.bmp";
    String ^FileName, ^Extension, ^Directory;
    
    //ファイル名の取得(拡張子を含む)        → "Bitmap.bmp"
    FileName = IO::Path::GetFileName(FullPath);
    //ファイル名の取得(拡張子を含まない) → "Bitmap"
    FileName = IO::Path::GetFileNameWithoutExtension(FullPath);
    //拡張子の取得                         → ".bmp"  ※ピリオド"."を含みます。
    Extension = IO::Path::GetExtension(FullPath);
    //ディレクトリの取得                    → "c:\Dir1\Dir2"
    Directory = IO::Path::GetDirectoryName (FullPath);
    //相対パスから絶対パスを取得            → "c:\Dir1\Dir2\Bitmap.bmp"
    FullPath = IO::Path::GetFullPath("Bitmap.bmp");

     

    相対パスから絶対パスを取得する場合、指定したファイル名がカレントディレクトリに無い場合は
    カレントディレクトリにファイル名が追加された文字列が返されます。

    【C++/CLI】数値の書式(フォーマット)指定文字列

    数値の表示桁数などの設定にはFormatメソッドかToStringメソッドなどで可能ですが、ここではToStringメソッドによる設定方法を紹介します。

     

    【コード例】
    int Val = 123;
    String^ Txt = Val.ToString(“D5”);

     

    とすると
    Txt = “00123”
    となります。

     

    以下、主なフォーマットの設定例です。

     

    標準書式指定文字列

    書式 書式設定文字列 データ型 出力
    10進数 D Int32 123 123
    10進数(桁数設定) D5 Int32 123 00123
    固定小数点 F Double 123.456 123.46
    固定小数点(桁数設定) F6 Double 123.456 123.456000
    パーセント P Double 0.12345 12.35%
    パーセント(桁数設定) P3 Double 0.123456 12.346%
    16進数(小文字) x Int32 123456 1e240
    16進数(大文字) X Int32 123456 1E240
    通貨 C Double 1234567 \1,234,567
    数字 N Double 123456 123,456.00
    数字(桁数設定) N1 Double 12345678 123,456.8

    詳細は下記ページを参照願います。

    http://msdn.microsoft.com/ja-jp/library/241ad66z(VS.80).aspx

     

    カスタム数値書式指定文字列

    書式 書式設定文字列 データ型 出力
    ゼロプレースホルダ 00000 Double 123 00123
    000.000 Double 12.3456 012.346
    000.000 Double 123.45 123.450
    桁プレースホルダ ##### Double 123 123
    #.# Double 1.2 1.2
    #.# Double 1 1
    #.# Double 123.456 123.5
    #,# Double 123456789 123,456,789
    (00)####-#### Double 312345678 (03)1234-5678
    正、負、0別表示 +#;-#;±0 Double 123 +123
    +#;-#;±0 Double -123 -123
    +#;-#;±0 Double 0 ±0
    #;(#) Double 123 123
    #;(#) Double -123 (123)

    詳細は下記ページを参照願います

    http://msdn.microsoft.com/ja-jp/library/7x5bacwt(VS.80).aspx

     

    【右寄せ】
    int Val = 123;
    String^ Txt = Val.ToString()->PadLeft(5);
    とすると
    Txt = ”  123″
    となります。

     

    【左寄せ】
    int Val = 123;
    String^ Txt = Val.ToString()->PadRight(5);
    とすると
    Txt = “123  ”
    となります。

     

    右寄せ/左寄せは、少し紛らわしいですが、左側を穴埋めして右寄せ(PadLeft)、右側を穴埋めして左寄せ(PadRight)となります。
    カッコ内の数値が文字数になります。