今回は、バイナリファイルの一種であるビットマップファイルについて、色々やってみたいと思います。
コンソールアプリケーションなので、画像を表示する事は出来ませんが、ファイルとして扱う事は問題ありません。
ビットマップ形式のファイルは圧縮がかかっていないため、そのまま画像のデータを扱う事が出来ます。
ただし、色の表現が幅広く1つの「画素」を何ビットで表現するかによって、色のデータの保管方法が異なります。
Windowsに付属の「ペイント」で確認出来るものを使いたいので、今回は↓の画像を使います。
画像を右クリックして「名前を付けて画像を保存・・・」を選び、プロジェクトフォルダへダウンロードしてください。
ダウンロードしたファイルを右クリックして「プロパティ」を確認します。
「詳細」タグを選択した画面が↓です。
画像は「画素(ピクセル)」の集まりで出来ています。
上の図では、幅320ピクセル、高さ240ピクセルと書いてあります。
「画素」が横方向に320個、縦方向に240個集まって二次元のグラフィックを表現しています。
この「画素」1つが何色なのかを表すため、様々なフォーマットがあります。
今回は「ビットの深さ」という項目で、24と書いてあるのが、それに当たります。
24というのは、24ビットの事で「光の三原色」を24ビットで表現する形式です。
「光の三原色」はR(赤)、G(緑)、B(青)の3色です。
それぞれ、8ビットが割り当てられ、全部で24ビットになります。
8ビットあれば「0」から「255」までの「256」段階の色が表現出来ます。
RGBの組み合わせで、1677万種類の色が使えます。
ビットマップファイルは、色情報しか保存していない訳ではありません。
色情報の前に、ヘッダ情報と呼ばれるものが2つあります。
ヘッダは構造体としてアクセスする事ができ、構造体の宣言はWindows.hと言うヘッダファイルで行われています。
他のサイトでさんざん紹介されていますので、ここでは細かい事は省きます。
2つのヘッダを読み込んだ後で、色情報にアクセスする事が出来ます。
では、プログラムを作りましょう。
まずは読み込むまでの準備をします。
<sample program cpp032-01>
#include <iostream>
#include <fstream>
#include <Windows.h>
int main()
{
std::ifstream ifsBitmap;
ifsBitmap.open("omelet.bmp", std::ios::in | std::ios::binary);
if (!ifsBitmap) {
return 1;
}
if (ifsBitmap.is_open()) {
ifsBitmap.close();
}
return 0;
}
|
ヘッダファイルとして「Windows.h」をインクルードしています。
これは、C++とは関係無いヘッダですが、Windowsの各種機能を使うためのヘッダファイルです。
※もちろん、C言語でも使えます。
ここから最初のヘッダを読み込みます。
<sample program cpp032-02>
#include <iostream>
#include <fstream>
#include <Windows.h>
int main()
{
std::ifstream ifsBitmap;
ifsBitmap.open("omelet.bmp", std::ios::in | std::ios::binary);
if (!ifsBitmap) {
return 1;
}
BITMAPFILEHEADER bmFileHeader;
ifsBitmap.read((char*)&bmFileHeader, sizeof(bmFileHeader));
std::cout << bmFileHeader.bfType << std::endl;
if (ifsBitmap.is_open()) {
ifsBitmap.close();
}
return 0;
}
|
<実行結果>
19778 続行するには何かキーを押してください・・・
1つ目のヘッダ「BITMAPFILEHEADER」を読み込みました。
メンバ変数は色々あるのですが、一番重要な「bfType」を表示しました。
10進数で見ても分からないので、2進数(16ビット)にします。
19778(10進) → 0100 1101 0100 0010(2進)
さらに、16進数にします。
0100 1101 0100 0010(2進) → 4D42(16進)
この4Dと42を文字コード表で見ると、
4D(16進) → 'M'(文字) 42(16進) → 'B'(文字)
この文字はファイルの先頭に入っており、「これはビットマップファイル!」という識別のためのコードになっています。
では、次に2つ目のヘッダを読み込みましょう。
※「bfType」の表示は消しておきます。
<sample program cpp032-03>
#include <iostream>
#include <fstream>
#include <Windows.h>
int main()
{
std::ifstream ifsBitmap;
ifsBitmap.open("omelet.bmp", std::ios::in | std::ios::binary);
if (!ifsBitmap) {
return 1;
}
BITMAPFILEHEADER bmFileHeader;
ifsBitmap.read((char*)&bmFileHeader, sizeof(bmFileHeader));
BITMAPINFOHEADER bmInfoHeader;
ifsBitmap.read((char*)&bmInfoHeader, sizeof(bmInfoHeader));
std::cout << "width = " << bmInfoHeader.biWidth << std::endl;
std::cout << "height = " << bmInfoHeader.biHeight << std::endl;
std::cout << "bitcount = " << bmInfoHeader.biBitCount << std::endl;
if (ifsBitmap.is_open()) {
ifsBitmap.close();
}
return 0;
}
|
<実行結果>
width = 320 height = 240 bitcount = 24 続行するには何かキーを押してください・・・
2番目のヘッダは「BITMAPINFOHEADER」と言います。
ここには重要な情報が入っています。
その中で3つの情報を表示しました。
画像の幅と高さ、ビットカウント(ビットの深さ)です。
ビットマップファイルのプロパティを見た時に書いてあった3つの情報です。
なぜこれが重要かと言うと、ビットマップ画像の大きさや色情報によって画像に必要なメモリの容量が違うからです。
ヘッダはどのビットマップファイルでも同じ大きさですが、画像の大きさや色情報はファイルによって異なります。
色情報を全て読み込もうとした場合、ヘッダの情報を読み込んで動的にメモリを確保して読み込まなければならないのです。
さて、今回の色情報は24ビットだと書きました。
この形式の色情報を扱う構造体がWindowsには用意されています。
RGBTRIPLEという構造体です。
typedef struct tagRGBTRIPLE { BYTE rgbtBlue; BYTE rgbtGreen; BYTE rgbtRed; } RGBTRIPLE, *PRGBTRIPLE, NEAR *NPRGBTRIPLE, FAR *LPRGBTRIPLE; |
メンバ変数はBYTE(符号無し8ビット)型でRGBそれぞれに割り当てられています。
この構造体を、ビットマップの幅×高さ分用意すれば色情報を読み込む事が出来ます。
それでは、色情報を読み込むプログラムを追加しましょう。
※幅、高さ、ビットカウントを表示するプログラムは削除しておきます。
<sample program cpp032-04>
#include <iostream>
#include <fstream>
#include <Windows.h>
int main()
{
std::ifstream ifsBitmap;
ifsBitmap.open("omelet.bmp", std::ios::in | std::ios::binary);
if (!ifsBitmap) {
return 1;
}
BITMAPFILEHEADER bmFileHeader;
ifsBitmap.read((char*)&bmFileHeader, sizeof(bmFileHeader));
BITMAPINFOHEADER bmInfoHeader;
ifsBitmap.read((char*)&bmInfoHeader, sizeof(bmInfoHeader));
RGBTRIPLE *pColor = NULL;
pColor = new RGBTRIPLE[bmInfoHeader.biWidth * bmInfoHeader.biHeight];
if (!pColor) {
if (ifsBitmap.is_open()) {
ifsBitmap.close();
}
return 1;
}
ifsBitmap.read((char*)pColor, sizeof(RGBTRIPLE) * bmInfoHeader.biWidth * bmInfoHeader.biHeight);
if (pColor) {
delete[] pColor;
pColor = NULL;
}
if (ifsBitmap.is_open()) {
ifsBitmap.close();
}
return 0;
}
|
<実行結果>
続行するには何かキーを押してください・・・
実行結果は何も表示されませんが、多分大丈夫でしょう・・・
pColor = new RGBTRIPLE[bmInfoHeader.biWidth * bmInfoHeader.biHeight]; |
ここで、構造体をビットマップの幅(320)×高さ(240)分確保しました。
ifsBitmap.read((char*)pColor, sizeof(RGBTRIPLE) * bmInfoHeader.biWidth * bmInfoHeader.biHeight); |
この一行で確保したアドレスから必要なバイト数分データを読み込んでいます。
必要なデータは、
1つの画素(RGBTRIPLE)のバイト数 × ビットマップの幅 × ビットマップの高さ
ですが、具体的な数値で書くと、
3バイト(24ビット) × 320 × 240 = 230400バイト
になります。
今回はここまでです。
結局読み込めたかどうかは分かりませんでしたが、次回で検証してみましょう。