bmpファイルの入出力

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
24736
記事: 2
登録日時: 5年前

bmpファイルの入出力

#1

投稿記事 by 24736 » 5年前

bmpファイルの入出力(+画像の色変換)をしたいです。
ファイルは出力されるものの、出力されたファイルが破損しているらしく開くことができません。
エラー等も出ないのでどこを直せばいいのか分かりません。
プログラミングを始めたばかりで理解が追い付いていないためおかしな点が多々あると思いますが、どうか教えていただければと思います。
以下コードです。

コード:

 
#include<stdio.h>
#include<stdlib.h>

typedef struct {
	unsigned short  bfType;
	unsigned long   bfSize;
	unsigned short  bfReserved1;
	unsigned short  bfReserved2;
	unsigned long   bfOffBits;
} BITMAPFILEHEADER;

typedef struct {
	unsigned long   biSize;
	long            biWidth;
	long            biHeight;
	unsigned short  biPlanes;
	unsigned short  biBitCount;
	unsigned long   biCompression;
	unsigned long   biSizeImage;
	long            biXPixPerMeter;
	long            biYPixPerMeter;
	unsigned long   biClrUsed;
	unsigned long   biClrImportant;
} BITMAPINFOHEADER;

typedef struct {
	unsigned char rgbBlue;
	unsigned char rgbGreen;
	unsigned char rgbRed;
	unsigned char rgbReserved;
} RGBQUAD;

typedef struct {
	BITMAPFILEHEADER    bitmapFileHeader;
	BITMAPINFOHEADER    bitmapInfoHeader;
	RGBQUAD             rgbQuad[256];
	unsigned char       *imageData;
} BMPINFO;

int readBMP(char* filename, BMPINFO* bmpInfo);
int writeBMP(char* filename, BMPINFO* bmpInfo);

int readBMP(char* filename, BMPINFO* bmpInfo) {
	FILE *fp;
	if ((fopen_s(&fp, filename, "rb")) != 0)
		printf("ファイルを開けませんでした\n");
	return -1;

	// BITMAPFILEHEADER/bfType読み込み
	fread_s(&bmpInfo->bitmapFileHeader.bfType, sizeof(unsigned short), sizeof(unsigned short), 1, fp);
	// BMPであることを確認
	if (bmpInfo->bitmapFileHeader.bfType != 0x4d42)
		return -2;
	// その他情報を読み込む
	fread_s(&bmpInfo->bitmapFileHeader.bfSize, sizeof(unsigned long), sizeof(unsigned long), 1, fp);        // bfSize
	fread_s(&bmpInfo->bitmapFileHeader.bfReserved1, sizeof(unsigned short), sizeof(unsigned short), 1, fp); // bfReserved1
	fread_s(&bmpInfo->bitmapFileHeader.bfReserved2, sizeof(unsigned short), sizeof(unsigned short), 1, fp); // bfReserved2
	fread_s(&bmpInfo->bitmapFileHeader.bfOffBits, sizeof(unsigned long), sizeof(unsigned long), 1, fp);     // bfOffBits
	// BITMAPINFOHEADER読み込み
	fread_s(&bmpInfo->bitmapInfoHeader, sizeof(BITMAPINFOHEADER), sizeof(BITMAPINFOHEADER), 1, fp);
	// BMPINFOHEADERの種類を確認
	long test = bmpInfo->bitmapInfoHeader.biSize;
	if (bmpInfo->bitmapInfoHeader.biSize != 40) 
		return -3;
	// カラーテーブル取得
	fread_s(&bmpInfo->rgbQuad, sizeof(RGBQUAD) * 256, sizeof(RGBQUAD), 256, fp);
	// 画像データ格納用領域確保
	bmpInfo->imageData = (char*)malloc(sizeof(char) * bmpInfo->bitmapInfoHeader.biWidth * bmpInfo->bitmapInfoHeader.biHeight);
	// 画像データ取得
	fread_s(&bmpInfo->imageData,
		bmpInfo->bitmapInfoHeader.biWidth * bmpInfo->bitmapInfoHeader.biHeight,
		sizeof(char),
		bmpInfo->bitmapInfoHeader.biWidth * bmpInfo->bitmapInfoHeader.biHeight,
		fp);	

	fclose(fp);
	return 0;
}
	
int writeBMP(char* filename, BMPINFO* bmpInfo){
	FILE *fp;
	if ((fopen_s(&fp, filename, "w+b")) != 0)
		return -1;

	// BMPファイル書き出し
	fwrite(&bmpInfo->bitmapFileHeader.bfType, sizeof(unsigned short), 1, fp);
	fwrite(&bmpInfo->bitmapFileHeader.bfSize, sizeof(unsigned long), 1, fp);
	fwrite(&bmpInfo->bitmapFileHeader.bfReserved1, sizeof(unsigned short), 1, fp);
	fwrite(&bmpInfo->bitmapFileHeader.bfReserved2, sizeof(unsigned short), 1, fp);
	fwrite(&bmpInfo->bitmapFileHeader.bfOffBits, sizeof(unsigned long), 1, fp);
	fwrite(&bmpInfo->bitmapInfoHeader, sizeof(BITMAPINFOHEADER), 1, fp);
	fwrite(&bmpInfo->rgbQuad, sizeof(RGBQUAD), 256, fp);
	fwrite(&bmpInfo->imageData, 
		sizeof(unsigned char), 
		bmpInfo->bitmapInfoHeader.biWidth * bmpInfo->bitmapInfoHeader.biHeight, 
		fp);

	fclose(fp);
	return 0;
}

int main() {
	char rFilename[64] = "test.bmp";
	char wFilenmane[64] = "test_.bmp";

	BMPINFO bitmapInfo;

	readBMP(rFilename, &bitmapInfo);
	writeBMP(wFilenmane, &bitmapInfo);

	return 0;
}
  

アバター
みけCAT
記事: 6734
登録日時: 13年前
住所: 千葉県
連絡を取る:

Re: bmpファイルの入出力

#2

投稿記事 by みけCAT » 5年前

これはカラーテーブルが256要素あり、画素が下のものから並んでいる無圧縮256色bmpファイル専用ですね。
解析の結果、少なくとも以下の問題があることがわかりました。
  • readBMP関数において、ファイルを開いた直後に必ずreturn -1;しているので、
    読み込み処理が行われず、writeBMPに未初期化のデータが渡されてしまいます。
    複文({}で囲まれたブロック)を用いてファイルが開けなかった場合のみreturn -1;するようにするか、
    return -1;を削除するといいでしょう。
  • せっかくbmpInfo->imageDataに確保した領域のアドレスを入れているのに、
    その後の読み込みでそれを破壊する上に、通常4~8バイト程度しかない場所に
    大きいことがある画像データを書き込んでいるため、
    ここが実行されると範囲外アクセスを起こす可能性が高く、危険です。
    画像データの読み込み先は、
    &bmpInfo->imageData(画像データ格納用領域へのポインタを格納する変数)ではなく、
    bmpInfo->imageData(画像データ格納用領域)にしないといけません。
    writeBMPで書き出す画像データの指定も同様に間違っています。
  • 画像の横幅(ピクセル数)が4の倍数ではないとき、
    1~3バイトのパディングを入れて1行のバイト数を4の倍数にしないといけないのに、
    この処理がありません。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

24736
記事: 2
登録日時: 5年前

Re: bmpファイルの入出力

#3

投稿記事 by 24736 » 5年前

パディングは読み書き両方で行うのでしょうか?それとも書きだす際に幅を整えればよいのでしょうか?
とんちんかんな質問で申し訳ありません。

アバター
みけCAT
記事: 6734
登録日時: 13年前
住所: 千葉県
連絡を取る:

Re: bmpファイルの入出力

#4

投稿記事 by みけCAT » 5年前

読み書き両方です。
書き込み時にパディングを入れたら(そして他のソフトウェアは入れるので)、
読み込み時もパディングが入っている前提で読み込まないと、おかしくなりますよね。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

Math

Re: bmpファイルの入出力

#5

投稿記事 by Math » 5年前

24736さん
https://www.setsuki.com/hsp/ext/bmp.htm

https://meteoricstream.com/tips/doc/bitmap.html

http://www.ruche-home.net/program/bmp

を見るとこれらを網羅したプログラムは相当大きなものになるでしょう。

もともとWindows はIBM が開発を始めたものですが 1995年に Microsoftに
譲ってIBM では OS/2 という名称に変えて開発を始めた時期にきめられた規格の
ようです。

各処理系ではバイナリエディタを持っているはずなので とりあえず見本のbmpファイル
バイナリエディタで読みそれをもとにDebugするのがいいでしょう。

VS2017ではTEXTファイルで読んだりバイナリで読んだりできます。
http://www2.koyoen.birdview.co.jp/~abcx ... 21-02-.PNG

かずま

Re: bmpファイルの入出力

#6

投稿記事 by かずま » 5年前

パディングを含めた画像のサイズが biSizeImage に入っているから
それを使えばよいのではありませんか?

コード:

#include <stdio.h>
#include <stdlib.h>

typedef struct {
	unsigned short  bfType;
	unsigned long   bfSize;
	unsigned short  bfReserved1;
	unsigned short  bfReserved2;
	unsigned long   bfOffBits;
} BITMAPFILEHEADER;

typedef struct {
	unsigned long   biSize;
	long            biWidth;
	long            biHeight;
	unsigned short  biPlanes;
	unsigned short  biBitCount;
	unsigned long   biCompression;
	unsigned long   biSizeImage;
	long            biXPixPerMeter;
	long            biYPixPerMeter;
	unsigned long   biClrUsed;
	unsigned long   biClrImportant;
} BITMAPINFOHEADER;

typedef struct {
	unsigned char rgbBlue;
	unsigned char rgbGreen;
	unsigned char rgbRed;
	unsigned char rgbReserved;
} RGBQUAD;

typedef struct {
	BITMAPFILEHEADER    bitmapFileHeader;
	BITMAPINFOHEADER    bitmapInfoHeader;
	RGBQUAD             rgbQuad[256];
	unsigned char       *imageData;
} BMPINFO;

int readBMP(const char* filename, BMPINFO* bmpInfo)
{
	int rv = 0;  // return value
	FILE *fp;
	if ((fopen_s(&fp, filename, "rb")) != 0) {
		printf("ファイルを開けませんでした\n");
		return -1;
	}

	// BITMAPFILEHEADER/bfType読み込み
	BITMAPFILEHEADER *bf = &bmpInfo->bitmapFileHeader;
	size_t n = fread_s(&bf->bfType, 2, 2, 1, fp);
	if (n != 1) { rv = -2; goto end; }
	if (bf->bfType != 0x4d42) { rv = -3; goto end; }

	// その他情報を読み込む
	const size_t bfSize = sizeof(BITMAPFILEHEADER) - 4;
	n = fread_s(&bf->bfSize, bfSize, bfSize, 1, fp);
	if (n != 1) { rv = -4; goto end; }

	// BITMAPINFOHEADER読み込み
	const size_t biSize = sizeof(BITMAPINFOHEADER);
	BITMAPINFOHEADER *bi = &bmpInfo->bitmapInfoHeader;
	n = fread_s(bi, biSize, biSize, 1, fp);
	if (n != 1) { rv = -5; goto end; }
	if (bi->biSize != 40) { rv = -6; goto end; }
	if (bi->biBitCount != 8) { rv = -7; goto end; }

	// カラーテーブル取得
	n = fread_s(bmpInfo->rgbQuad, sizeof(RGBQUAD) * 256, sizeof(RGBQUAD), 256, fp);
	if (n != 256) { rv = -7; goto end; }

	// 画像データ格納用領域確保
	bmpInfo->imageData = (unsigned char *)malloc(bi->biSizeImage);
	if (!bmpInfo->imageData) { rv = -8; goto end; }
	// 画像データ取得
	n = fread_s(bmpInfo->imageData, bi->biSizeImage, bi->biSizeImage, 1, fp);
	if (n != 1) rv = -9;
end:
	fclose(fp);
	return rv;
}
	
int writeBMP(const char* filename, const BMPINFO* bmpInfo)
{
	FILE *fp;
	if ((fopen_s(&fp, filename, "w+b")) != 0)
		return -1;

	// BMPファイル書き出し
	fwrite(&bmpInfo->bitmapFileHeader.bfType, 2, 1, fp);
	const size_t bfSize = sizeof(BITMAPFILEHEADER) - 4;
	fwrite(&bmpInfo->bitmapFileHeader.bfSize, bfSize, 1, fp);
	fwrite(&bmpInfo->bitmapInfoHeader, sizeof(BITMAPINFOHEADER), 1, fp);
	fwrite(bmpInfo->rgbQuad, sizeof(RGBQUAD), 256, fp);
	fwrite(bmpInfo->imageData, 1, bmpInfo->bitmapInfoHeader.biSizeImage, fp);
	fclose(fp);
	return 0;
}

int main(void)
{
	static const char rFilename[] = "test.bmp";
	static const char wFilenmane[] = "test_.bmp";

	BMPINFO bitmapInfo;

	int rv = readBMP(rFilename, &bitmapInfo);
	if (rv < 0)
		printf("readBMP failed (%d)\n", rv);
	else
		writeBMP(wFilenmane, &bitmapInfo);

	free(bitmapInfo.imageData);
	return rv;
}
BITMAPFILEHEADER を読み込むときに
bfType とそれ以外を分けて読むのは、
bfType の後に 2バイトのパディングがあるからです。
ファイルの中にはそれはありません。

Math

Re: bmpファイルの入出力

#7

投稿記事 by Math » 5年前

#5 のサイトにbmp サンプルがあります。

コード:

BMPファイルの一覧
----------------------------------------

os-1		OS/2 Bitmap
os-4
os-8
os-24

win-1
win-4
win-4-rle	ランレングス圧縮
win-8
win-8-rle	ランレングス圧縮
win-8-td	トップダウンDIB
win-16-1	4bitの倍数でないサイズ
win-16
win-16-bf-324	ビットフィート R:3 G:2 B:4
win-16-t	透過付きビットマップ(16bit) ※BITMAPV4HEADER 使用
win-24
win-32
win-32-bf-833	ビットフィート R:8 G:3 B:3
win-32-bf-888	ビットフィート R:8 G:8 B:8
win-32-bf-td	ビットフィート R:12 G:10 B:10 + トップダウンDIB
win-32-t	透過付きビットマップ(32bit) ※BITMAPV4HEADER 使用
win-jpeg	JPEG圧縮
win-png		PNG圧縮

----------------------------------------
win-1 を バイナリ・エディタで読むと

http://www2.koyoen.birdview.co.jp/~abcx ... -22-b-.PNG

これを test.bmp にリネームします。

Math

Re: bmpファイルの入出力

#8

投稿記事 by Math » 5年前

これを プログラムをコンパイル・実行します。

http://www2.koyoen.birdview.co.jp/~abcx ... -22-a-.PNG

Math

Re: bmpファイルの入出力

#9

投稿記事 by Math » 5年前

test_.bmp はバイナリ・エディタで読めて


http://www2.koyoen.birdview.co.jp/~abcx ... -22-c-.PNG

こうなります。(^^;

デバッガーでデバッグすれば 入力値が分かっているのだから簡単にわかりますよね。

データのしめせない空論は無意味ですよ。

Math

Re: bmpファイルの入出力

#10

投稿記事 by Math » 5年前

まず ウキペディア を参照することは 必須です。

https://ja.wikipedia.org/wiki/Windows_bitmap

Math

Re: bmpファイルの入出力

#11

投稿記事 by Math » 5年前

Hex Editor をみれば bmp の構成は 一目瞭然です。

http://www2.koyoen.birdview.co.jp/~abcx ... -23-a-.PNG

かずま

Re: bmpファイルの入出力

#12

投稿記事 by かずま » 5年前

かずま さんが書きました:
5年前
パディングを含めた画像のサイズが biSizeImage に入っているから
BITMAPINFOHEADER の biSizeImage は 0 の場合もあるようです。
次のようにサイズを計算して、それを使いましょう。

コード:

	// 画像データ格納用領域確保
	size_t sizeImage = ((bi->biWidth + 3) & ~3) * bi->biHeight;
	bmpInfo->imageData = (unsigned char *)malloc(sizeImage);
	if (!bmpInfo->imageData) { rv = -8; goto end; }
	// 画像データ取得
	n = fread_s(bmpInfo->imageData, sizeImage, sizeImage, 1, fp);	

コード:

	size_t sizeImage = ((bmpInfo->bitmapInfoHeader.biWidth + 3) & ~3)
		* bmpInfo->bitmapInfoHeader.biHeight;
	fwrite(bmpInfo->imageData, 1, sizeImage, fp);
また、readBMP の先頭に、bmpInfo->imageData = NULL; を追加して、
malloc をせずに main に戻った場合の free に備えてください。


返信

“C言語何でも質問掲示板” へ戻る