今後いろいろと加工してみたいので最初に入出力をできるようにしておきます. BitmapファイルからRGB情報と幅と高さの情報だけを抽出し,それをそのままヘッダ部分を付けてBitmapファイルにしてみました.要は同じ画像をもう一枚作ってるだけのプログラムです.これでいろいろと加工することができます.
画像ファイルは基本的にRGB情報(光の三原色です)とヘッダの部分に分かれています.ヘッダとはそのファイルがどんなファイルであるのかとか,画像の高さや幅といった情報が入っている部分です.画像を加工するにあたって,画像の高さと幅,RGB情報があればいいので,入力部分でそれらを読み取っています.また,加工した後Bitmapファイルにするため,出力部分ではヘッダを付け直しています.ところで画像ファイルはサイズを小さくするために殆どのものが圧縮技術が施されています. jpgのような不可逆のものやpngのように可逆のものもありますが, Bitmapは基本的に圧縮されていないためサイズは大きいですが, RGB情報がそのまま並んでいるために読み取るのが簡単です.
まずBitmapは大きく3つの部分に分かれています.
ファイルヘッダ | オフセット(0) | ファイルタイプ | BMといれる |
オフセット(2) | ファイルサイズ | ファイル全体のサイズをバイト単位でいれる(ヘッダ含む) | |
オフセット(6) | 予約領域1 | 常に0を入れておく | |
オフセット(8) | 予約領域2 | 常に0を入れておく | |
オフセット(10) | 画像データまでのオフセット | 24bitのBitmapならカラーパレットがないため54となる | |
情報ヘッダ | オフセット(14) | 情報ヘッダのサイズ | 40を入れる |
オフセット(18) | 画像の幅 | ピクセル単位 | |
オフセット(22) | 画像の高さ | ピクセル単位 | |
オフセット(26) | プレーン数 | 常に1を入れる | |
オフセット(28) | 何ビットのカラー画像であるか | 今回は24と入れる | |
オフセット(30) | 圧縮形式 | 非圧縮なので0を入れる | |
オフセット(34) | 画像データのサイズ | ヘッダ部分を含まない(単純に幅と高さとRGBの3を掛ける) | |
オフセット(38) | 水平解像度 | 0で問題ない | |
オフセット(42) | 垂直解像度 | 0で問題ない | |
オフセット(46) | パレットの色数 | パレットは使わないので0 | |
オフセット(50) | 重要なパレットのインデックス | 使わないので0 |
いろいろなパラメータがありますが,要は読み取り部分で"BM"と24ビットの確認をすれば,横幅と縦幅を読み取りRGB情報を抽出すればいいだけです.具体的には24bitに限ればヘッダ部分は54バイトなので最初に freadで取ってきます.その中から先頭の"BM"とオフセット(28)の24bitを確認し,オフセット(18)とオフセット(22) から画像の幅と高さをとれば,後はRGB情報を取ってくるだけです.データは高さ×幅×3バイトのRGB情報なのですが,実際には1行が4バイトの倍数になる必要があるため,注意が必要です.書き込み部分ではヘッダ部分天下り的に書き込んでRGB情報を書き込んでいくだけです. RGB情報については画像の左下から始まります.少々奇妙な感じはしますが,左下から右へ,下から上へ読み書きすれば良いだけです.
/*----------------------bitmap.h----------------------*/ typedef struct{ unsigned char b; unsigned char g; unsigned char r; }Rgb; typedef struct{ unsigned int height; unsigned int width; Rgb *data; }Image;
ヘッダ部分でRGB情報を格納する構造体と,幅,高さ,RGB情報ヘのポインタを含む構造体を宣言しています. RGB情報へのポインタには動的に領域を確保してアドレスを格納します.
/*--------------------------bitmap.c----------------------*/ //画像の見た目上の幅を取得 memcpy(&width, header_buf + 18, sizeof(width)); //画像の高さを取得 memcpy(&height, header_buf + 22, sizeof(height)); //何bitのBitmapであるかを取得 memcpy(&color, header_buf + 28, sizeof(unsigned int)); //RGB情報は画像の1行分が4byteの倍数で無ければならないためそれに合わせている real_width = width*3 + width%4; //BitmapファイルのRGB情報は左下から右へ、下から上に並んでいる for(i=0; i<height; i++){ fread(bmp_line_data, 1, real_width, fp); for(j=0; j<width; j++){ img->data[(height-i-1)*width + j].b = bmp_line_data[j*3]; img->data[(height-i-1)*width + j].g = bmp_line_data[j*3 + 1]; img->data[(height-i-1)*width + j].r = bmp_line_data[j*3 + 2]; } }
幅や高さなどはmemcpyを使えば簡単です.このとき先頭番地+それぞれのオフセットがそれぞれのデータのアドレスとなります. 1行分が4バイトの倍数でないといけないためにwidthを4で割った余りを足すことで4の倍数にしています.1行分のRGBデータはこのreal_widthバイトからなっています. RGB情報の読み取り部分は分かりづらいですが,bmp_line_dataが最後のデータから取ってくるのに対し,dataには画像の左上から右下への順番でRGB情報を入れようとしている為このようになっています.書き込み部分は天下り的にヘッダ情報を書き込んでいるだけです.下が全体のソースコードです.
これらのファイルを同じディレクトリに入れ,
$gcc main.c bitmap.c
とすれば実行可能ファイルができ,
$./a.out inputfile.bmp outputfile.bmp
で実行できます. makefile作った方がいいのですが,ファイルの数も2つなのでそのままにしています. main関数のRead_BmpとWrite_Bmpの関数の間でRGB情報を弄れば画像が加工できます.