Gobble up pudding

プログラミングの記事がメインのブログです。

MENU

プログラムからBitmap画像を扱う(VC++) 1回目

スポンサードリンク

今回使う画像です。使う画像は背景をキャプチャして切り取ってビットマップ(Bitmap)として保存したものです。ここからDLしたものを使わせていただきました。

f:id:fa11enprince:20140424021303p:plain
何回かに分けてビットマップ画像を扱う特集をやろうと思います。Windowsな話です。
扱いが簡単なビットマップなので、次回以降とか自力で読むとか書こうかな…と気まぐれに思っております。
勉強のためということで、まずは今回は簡単なレガシーなAPIのGDIを使って読込みのサンプルです。ほかにGDI+やWIC(Windows Imaging Component)があります。まともなものに使うならWICを使うのがいいと思われます。
最近、あまりビットマップ形式のファイル自体を目にすることがめっきり減りました。写真ならJPG、アニメ絵とかjpgが適してないような画像はpngみたいにすみ分けされちゃってますよね。PNGは可逆圧縮だし便利。ちなみにPNGもBitmapの一種です。あと、これは個人的な感想なのですがBitmapと聞くと汚い画像のイメージがあります。でもそんなことはありません。たぶん、Bitmapがよく使われていたころは256色ビットマップがごろごろあったせいだと思います。

さて、プログラムから扱う話ですが、プログラムで画像を読込むというのはC++以外の言語ではそれはそれはもう簡単に読込できて超楽勝で例えばC#ならPNGやJPEG画像読みたいよーっていうのはたった1~2行くらいで実現できてしまうわけです。

//ビットマップを読み込んで、Bitmap型にキャストする
string bmpPath = @"C:\test.jpg";
System.Drawing.Bitmap bmp =
    (System.Drawing.Bitmap)System.Drawing.Image.FromFile(bmpPath);

このSystem.Drawing.Image.FromFileというのはCOMのWICというのを使っているのですが、VC++だとハイパーめんどくさい。
次のリンク先のは以前書いたものです。ただし、これはWICを使った場合でlibpngを使うともっとめんどくさい。さらに自分で読もうなんてするともはや狂気です。
WICの以前書いたサンプル
Direct2DとWindows Imaging Component(WIC)で透過PNGを表示 - Gobble up pudding
libpngの以前書いたサンプル
libpngライブラリを使ってみた。 - Gobble up pudding
そんなわけで、VC++からビットマップを扱ってみよう!

ビットマップというのは画像形式でして、C++プログラムからどんな方法でも比較的簡単に扱うことができます。
PNGだとライブラリ使ってもちょっと苦しいし、使わないと死ねというのかという感じですが。そもそもフォーマットをよく知っていないと扱える代物ではない感じです。
話を戻しまして、ビットマップをプログラムで扱う場合はDDBとDIBというものがあります。
前者はネット依存オタクというもので、僕のことです。
後者はネット非依存リア充というもので、平たく言うとFaceBookにいる人たちです。
というのは冗談で、ざっくり説明

DDB(Device Dependent Bitmap) デバイス依存ビットマップ

デバイスの性能や現在の状態によって、扱うことのできる色の種類や数などが制限されていて、
ピクセルのビット値がどのような意味を持つのかに関してもデバイスに依存するため
そのデバイス用に変換したBitmapのデータってことで、簡単にいえばディスプレイに画像を表示するときに使う形式…と理解していただければ。

DIB(Device Independent Bitmap) デバイス非依存ビットマップ

データにピクセルの色情報をもっていて、ファイルに保存するときに使う形式。
他のOSでもある程度可搬性があるよってもの。
DDBのままじゃファイルとして保存したときにほかのデバイスから見たら意味のない情報になるからね!

とりあえずお手軽に使えるDDBの説明

ざっくりDDBは以下のような構造になっています。

ファイル構造 備考 サイズ
ファイルヘッダ BITMAPFILEHEADER構造体 14
情報ヘッダ BITMAPINFOHEADER構造体 40
パレット 24bit、32bitBMPには存在しない biClrUsedによる
画像データ ビット配列 画像による

余談ですが、このデバイス依存とかデバイス非依存とかいう用語はDirectXでも出てきます。

プロジェクト作成&リソース追加

Visual C++でプロジェクトをBitmapSample01としてWin32プロジェクトで作成して、ひな形は作らせるようにします。
つまり「空のプロジェクト」のところにチェックを入れずに作成してください。
プロジェクトのディレクトリのところ(既定ならC:\Users\(省略)\visual studio 2013\Projects\BitmapSample01\BitmapSample01)
のところでBMPというフォルダを作り、読み込む画像を置きます。ここではGreen.bmpとします。
f:id:fa11enprince:20140424021511p:plain
BMP 画像をリソースとして取り込みます。※すぐ下の注意点を先に参照してください。
ソリューションエクスプローラーでプロジェクトを選択した状態で、メニューの
プロジェクト > リソースの追加 > Bitmap > インポート
で画像ファイルを取り込みます。
f:id:fa11enprince:20140424021535p:plain
f:id:fa11enprince:20140424021545p:plain
注意点がこのとき3つあります。
1. このときなぜか絞込みの部分を「すべてのファイル」にしてあげないと選べない
2. GIMP2.8.0以降で保存したビットマップだと互換性がないようで、
開いた時に「認識できないビットマップです」とエラーが出ます。
対策方法はgimpで作成した画像をペイントで一時的に編集し、保存します。
3. リソースを読み込むときにMAKEINTRESOURCEというマクロを使いたくない場合は
BITMAP リソースを表示して IDB_BITMAP1 の両側を "" で囲って下さい。

上記が終わるとリソースファイルにビットマップが追加されているのがわかるはずです。
f:id:fa11enprince:20140424021609p:plain
f:id:fa11enprince:20140424021921p:plain

ソースコード作成

DDBとして読込むコードを書きます。
使うAPIは主にLoadBitmap, CreateCompatibleDC/DeleteDC, SelectObject/DeleteObject, BitBltあたりです。
BitBltって何の略かずっと知りませんでした。Blt。。。Belt?ベルトがなんか関係あるのかな?ベルトコンベアのように頑張って転送を…と思ってましたが、関係ありません。Bit Block Transferの略でした。ああ、なるほど。
ちなみにCreateCompatibleDCで作成したメモリデバイスコンテキストはDeleteDCで破棄してください。DeleteDCじゃなくてReleaseDCと書いたら間違いっぽいですよ。まぎらわしー。
ちなみにCreateCompatibleDCのCompatibleというのは互換性のあるという意味で、ここではhdcと互換性のあるメモリデバイスコンテキストということです。言葉だけだとなんだか意味不明ですね。(´・ω・`)

※補足ですがWindowsサイズのクライアント領域を840 x 640ぴったりにするために

int width = 640
	+ GetSystemMetrics(SM_CXEDGE) // 横の縁の幅
	+ GetSystemMetrics(SM_CXBORDER) // 横の縁の幅
	+ GetSystemMetrics(SM_CXFIXEDFRAME); // タイトルバー&固定サイズウィンドウのフレームの幅
int height = 480
	+ GetSystemMetrics(SM_CYEDGE) // 縦の縁の高さ
	+ GetSystemMetrics(SM_CYBORDER) // 縦の縁の高さ
	+ GetSystemMetrics(SM_CXFIXEDFRAME) // タイトルバー&固定サイズウィンドウのフレームの高さ
	+ GetSystemMetrics(SM_CYCAPTION); // タイトルバーの高さ
hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
	CW_USEDEFAULT, 0, width, height,
	NULL, NULL, hInstance, NULL); 

とやっているところがありますが、
こうもかけます。お好みで。

RECT rc = {
	0, 0, 640, 480
};
AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW , FALSE);
 
hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
	CW_USEDEFAULT, 0, rc.right - rc.left, rc.bottom - rc.top,
	NULL, NULL, hInstance, NULL);


「識別子 "IDB_BITMAP1" が定義されていません」
とエラーが出ますが、ガン無視してください。コンパイルが通って動くのでたぶんVisual Studioのバグです。
f:id:fa11enprince:20140424021850p:plain
僕が何かやらかしてるかもしれませんが、MSに報告しておきました。

実行して画像を表示

f:id:fa11enprince:20140424021823p:plain
画像がかっこいいとしょぼいプログラムでも冴えますね。

追記

2回目は続きませんでした。
ちなみにVS 2019でコードを作成するときは、Windows デスクトップ アプリケーションですね。
そして自動生成されるコードがすべてワイド文字(死語?)前提になってました。いいことです。
int APIENTRY _tWinMainではなく
int APIENTRY wWinMainになっていて、
LoadStringWとかこっちを使ってるのでTEXT()、_T()みたいな変なやつは使わなくてよくなりそうです。
これからはL""でよさそうです。え??C++がchar16_t, char32_tを出してるって…知らんがなw
c++ - char vs wchar_t vs char16_t vs char32_t (c++11) - Stack Overflow
カオス…。