ページ 11

BMPファイルの作成方法について

Posted: 2011年6月13日(月) 20:52
by MoNoQLoREATOR
BMPファイルを作成するプログラムをつくっているのですが、うまく作成できませんでした。

とりあえず、10×10の大きさの真っ白なBMPファイルを作成しようとして下記のようなソースコードを書いてみました。

コード:

#include <stdio.h>
#include <string.h>

int main(){
	FILE *fp;
	char header_buf[54];
	int height=10, width=10;
  
	header_buf[0] = 'B';
	header_buf[1] = 'M';
	int real_width = width*3 + width%4;
	int file_size = height * real_width + 54;
	memcpy(header_buf + 2, &file_size, sizeof(file_size));
	header_buf[6] = 0;
	header_buf[7] = 0;
	header_buf[8] = 0;
	header_buf[9] = 0;
	header_buf[10] = 54;
	header_buf[11] = 0;
	header_buf[12] = 0;
	header_buf[13] = 0;
	header_buf[14] = 40;
	header_buf[15] = 0;
	header_buf[16] = 0;
	header_buf[17] = 0;
	memcpy(header_buf + 18, &width, sizeof(width));
	memcpy(header_buf + 22, &height, sizeof(height));
	int planes = 1;
	memcpy(header_buf + 26, &planes, sizeof(planes));
	int color = 24;
	memcpy(header_buf + 28, &color, sizeof(color));
	long compress = 0;
	memcpy(header_buf + 30, &compress, sizeof(compress));
	long data_size = height * real_width;
	memcpy(header_buf + 34, &data_size, sizeof(data_size));
	long xppm = 1, yppm = 1;
	memcpy(header_buf + 38, &xppm, sizeof(xppm));
	memcpy(header_buf + 42, &yppm, sizeof(yppm));
	header_buf[46] = 0;
	header_buf[47] = 0;
	header_buf[48] = 0;
	header_buf[49] = 0;
	header_buf[50] = 0;
	header_buf[51] = 0;
	header_buf[52] = 0;
	header_buf[53] = 0;
  
	fp = fopen("outPut.bmp","wb");
	fwrite(header_buf,sizeof(char),54,fp);
  
	fclose( fp );

	char str[31];
	
	for(int i=0;i<30;i++) str[i] = 255;
	str[30] = '\0';
	
	fp = fopen("outPut.bmp","ab");
	for(int i=0;i<10;i++) fputs(str,fp);

	fclose( fp );
  
	printf("END!");
}
Windows Picture and Fax Viewerで表示させようとすると、「描画できませんでした」と出ました。
「ペイント」で開いてみると、右上に黒い部分がある画像が表示されました。


おそらくヘッダ部分のどこかが間違っているのだと思うのですが、私にはわかりません。
ご教授よろしくお願いいたします。

Re: BMPファイルの作成方法について

Posted: 2011年6月13日(月) 21:02
by MoNoQLoREATOR
もちろん、ヘッダー部分を自動で作成してくれたり、ファイル出力までやってくれる関数をもつライブラリ等がございましたら、ぜひ教えてください。

Re: BMPファイルの作成方法について

Posted: 2011年6月13日(月) 21:40
by box
「BMP フォーマット」あたりでググると、どういうフォーマットであるかが具体的にわかると思います。

Re: BMPファイルの作成方法について

Posted: 2011年6月13日(月) 21:56
by softya(ソフト屋)
ライブラリとしてはGDIがありますが、自分で作るほうが勉強になるのでこのまま続けられたらどうでしょうか?
ところで出来たファイルはバイナリエディタで調べられましたか?

「bmp ファイルフォーマット」
http://www.kk.iij4u.or.jp/~kondo/bmp/

Re: BMPファイルの作成方法について

Posted: 2011年6月13日(月) 22:20
by a5ua
1行分のデータは4の倍数(バイト)でないといけません。
今回の場合、1ピクセルあたり3バイトで、1行あたり10ピクセルですから、
1行分のデータは30バイトになります。
よって、4の倍数にするには、1行分の30バイトを出力したあと、適当なデータを2バイト書きこむ必要があります。

ところで、10×10画素のデータを作るには、
char p[10][10][3]; // 1画素(3バイト)×10×10
分のデータが必要ではないですか?

Re: BMPファイルの作成方法について

Posted: 2011年6月14日(火) 07:15
by MoNoQLoREATOR
以下のように修正してみました。「ペイント」を使って10*10の真っ白な画像を作り、そのデータと見比べて修正しました。

コード:

#include <stdio.h>
#include <string.h>

int main(){
	FILE *fp;
	char header_buf[54];
	int height=10, width=10;
  
	header_buf[0] = 'B';
	header_buf[1] = 'M';
	int real_width = width*3 + width%4;
	int file_size = height * real_width + 54;
	memcpy(header_buf + 2, &file_size, sizeof(file_size));
	header_buf[6] = 0;
	header_buf[7] = 0;
	header_buf[8] = 0;
	header_buf[9] = 0;
	header_buf[10] = 54;
	header_buf[11] = 0;
	header_buf[12] = 0;
	header_buf[13] = 0;
	header_buf[14] = 40;
	header_buf[15] = 0;
	header_buf[16] = 0;
	header_buf[17] = 0;
	memcpy(header_buf + 18, &width, sizeof(width));
	memcpy(header_buf + 22, &height, sizeof(height));
	int planes = 1;
	memcpy(header_buf + 26, &planes, sizeof(planes));
	int color = 24;
	memcpy(header_buf + 28, &color, sizeof(color));
	long compress = 0;
	memcpy(header_buf + 30, &compress, sizeof(compress));
	long data_size = height * real_width;
	memcpy(header_buf + 34, &data_size, sizeof(data_size));
	header_buf[38] = 0;
	header_buf[39] = 0;
	header_buf[40] = 0;
	header_buf[41] = 0;
	header_buf[42] = 0;
	header_buf[43] = 0;
	header_buf[44] = 0;
	header_buf[45] = 0;
	header_buf[46] = 0;
	header_buf[47] = 0;
	header_buf[48] = 0;
	header_buf[49] = 0;
	header_buf[50] = 0;
	header_buf[51] = 0;
	header_buf[52] = 0;
	header_buf[53] = 0;
  
	fp = fopen("outPut.bmp","wb");
	fwrite(header_buf,sizeof(char),54,fp);
  
	fclose( fp );

	char str[32];
	
	for(int i=0;i<30;i++) str[i] = 255;
	str[30] = 0;
	str[31] = 0;
	
	fp = fopen("outPut.bmp","ab");
	for(int i=0;i<10;i++) fputs(str,fp);

	fclose( fp );
  
	printf("END!");
}
しかし、
str[30] = 0;
str[31] = 0;
の部分が、なぜか書き込まれていません。どうしてなのでしょうか。

※fputsを10回呼んで、一行分のデータを10回出力させているので、そこらへんは大丈夫のはずです。

Re: BMPファイルの作成方法について

Posted: 2011年6月14日(火) 07:24
by bitter_fox
MoNoQLoREATOR さんが書きました:

コード:

	for(int i=0;i<30;i++) str[i] = 255;
	str[30] = 0;
	str[31] = 0;

	fp = fopen("outPut.bmp","ab");
	for(int i=0;i<10;i++) fputs(str,fp);
}
しかし、
str[30] = 0;
str[31] = 0;
の部分が、なぜか書き込まれていません。どうしてなのでしょうか。

※fputsを10回呼んで、一行分のデータを10回出力させているので、そこらへんは大丈夫のはずです。
fputsが原因です。
fputsは文字列を出力する関数ですので'\0'(=0)は原理的に出力されません。
そもそもバイナリファイルへの書き出しですのでfwriteを使用ください。
[hr][追記]
ところで、ヘッダ部にBITMAPFILEHEADER構造体やBITMAPINFOHEADER構造体を使用しないのでしょうか?

Re: BMPファイルの作成方法について

Posted: 2011年6月14日(火) 19:09
by MoNoQLoREATOR
正常に表示されるようになりました。ありがとうございました。

ヘッダ部に構造体を使わないことに関してですが、情報を順番に書いていけば良いだけなのですから、わざわざ構造体を使わなくても良いのではないかと思います。

Re: BMPファイルの作成方法について

Posted: 2011年6月14日(火) 20:27
by box
MoNoQLoREATOR さんが書きました: ヘッダ部に構造体を使わないことに関してですが、情報を順番に書いていけば良いだけなのですから、わざわざ構造体を使わなくても良いのではないかと思います。
配列の要素番号
構造体のメンバー名

必要な情報にアクセスしやすいのは前者でしょうか。それとも後者でしょうか。
個人的には、後者じゃないかなぁ、なんて思います。
ま、人それぞれですけどね。

Re: BMPファイルの作成方法について

Posted: 2011年6月14日(火) 21:16
by bitter_fox
MoNoQLoREATOR さんが書きました: ヘッダ部に構造体を使わないことに関してですが、情報を順番に書いていけば良いだけなのですから、わざわざ構造体を使わなくても良いのではないかと思います。
確かに順番にchar型配列にぶっこんで行っても正常に動作するプログラムを書くことができるでしょう。
ただその時はどういう意図があって書いたか分かるかもしれませんが、3か月後の自分が読んですぐに分かるでしょうか?(可読性の問題)
それに、読み込みの時に数値を指定したり、char型より大きい値を代入するときにmemsetを使用するのは煩わしいと思いませんか?(ユーザビリティーの問題)

構造体を利用するメリットとすれば。
名前でアクセスするので可読性が上がる。
配列にぶっこんで行く方法よりも名前を指定して代入するので安全であり、memsetをかます必要がないので楽である。
などなどいっぱいあります。

また今回のケースで言えば既に出来上がった構造体(BITMAPFILEHEADER, BITMAPINFOHEADER)があるのでそれを再利用した方がスマートで楽です。

取りあえず、次のプログラムとどっちが読みやすいですか?(関数化などをしているので単純に比較できないですが・・・)
ヘッダを作成している部分はInitBitmapFileHeaderとInitBitmapInfoHeaderです。特にこの二つの関数に注目して読んでみてください。(他は大して注目しなくて大丈夫です。)

コード:


#include <stdio.h>
#include <windows.h>

#define WIDTH 10
#define HEIGHT 10

typedef struct RGB
{
	BYTE red, green, blue;
} RGB;

void InitBitmapFileHeader(BITMAPFILEHEADER *fileHeader, int width, int height);
void InitBitmapInfoHeader(BITMAPINFOHEADER *infoHeader, int width, int height);
void InitBitmapHeader(BITMAPFILEHEADER *fileHeader, BITMAPINFOHEADER *infoHeader, int width, int height);
void WriteBitmapHeader(FILE *fp, BITMAPFILEHEADER fileHeader, BITMAPINFOHEADER infoHeader);
void WriteBitmapLine(FILE *fp, RGB *rgb, int width);

void main()
{
	BITMAPFILEHEADER fileHeader;
	BITMAPINFOHEADER infoHeader;
	RGB rgb[WIDTH];
	FILE *fp = fopen("outPut.bmp", "wb"); // 面倒くさいのでエラーチェックしていません。
	int counter;

	InitBitmapHeader(&fileHeader, &infoHeader, WIDTH, HEIGHT); // ヘッダを作成

	WriteBitmapHeader(fp, fileHeader, infoHeader); // ヘッダを出力

	// ビットマップデータを作成
	for (counter = 0; counter < WIDTH; counter++)
	{
		rgb[counter].red   = 255;
		rgb[counter].green = 255;
		rgb[counter].blue  = 255;
	}

	// 10行分出力
	for (counter = 0; counter < HEIGHT; counter++)
	{
		WriteBitmapLine(fp, rgb, WIDTH);
	}

	fclose(fp);
}

//
// BITMAPFILEHEADERを引数に従って初期化
////////////////////////////////////////////////////////////////////////////////
void InitBitmapFileHeader(BITMAPFILEHEADER *fileHeader, int width, int height)
{
	int headerSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER); // BITMAPINFOHEADERと共に使用されることを前提

	fileHeader->bfType      = 'B' + 'M' * 0x100;
	fileHeader->bfSize      = width * height * 3 + headerSize;
	fileHeader->bfReserved1 = fileHeader->bfReserved2 = 0;
	fileHeader->bfOffBits   = headerSize;
}

//
// BITMAPINFOHEADERを引数に従って初期化
////////////////////////////////////////////////////////////////////////////////
void InitBitmapInfoHeader(BITMAPINFOHEADER *infoHeader, int width, int height)
{
	memset(infoHeader, 0, sizeof(BITMAPINFOHEADER));
	infoHeader->biSize              = 40;
	infoHeader->biWidth             = width;
	infoHeader->biHeight            = height;
	infoHeader->biPlanes            = 1;
	infoHeader->biBitCount          = 32; // true color(4byte)
}

//
// BITMAPFILEHEADERとBITMAPINFOHEADERを引数に従って初期化
////////////////////////////////////////////////////////////////////////////////
void InitBitmapHeader(BITMAPFILEHEADER *fileHeader, BITMAPINFOHEADER *infoHeader, int width, int height)
{
	InitBitmapFileHeader(fileHeader, width, height);
	InitBitmapInfoHeader(infoHeader, width, height);
}

//
// ビットマップヘッダをファイルに書き出す
////////////////////////////////////////////////////////////////////////////////
void WriteBitmapHeader(FILE *fp, BITMAPFILEHEADER fileHeader, BITMAPINFOHEADER infoHeader)
{
	fwrite(&fileHeader, sizeof(BITMAPFILEHEADER), 1, fp);
	fwrite(&infoHeader, sizeof(BITMAPINFOHEADER), 1, fp);
}

//
// 一行分のRGBデータをファイルに出力
////////////////////////////////////////////////////////////////////////////////
void WriteBitmapLine(FILE *fp, RGB *rgb, int width)
{
	int counter;
	BYTE padding = 0;

	for (counter = 0; counter < width; counter++)
	{
		fwrite(&rgb[counter].blue,  sizeof(BYTE), 1, fp);
		fwrite(&rgb[counter].green, sizeof(BYTE), 1, fp);
		fwrite(&rgb[counter].red,   sizeof(BYTE), 1, fp);
		fwrite(&padding,            sizeof(BYTE), 1, fp);
	}
}