BMP画像の表示

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
アバター
Hiragi(GKUTH)
記事: 167
登録日時: 13年前
住所: 大阪府
連絡を取る:

BMP画像の表示

#1

投稿記事 by Hiragi(GKUTH) » 9年前

いつもお世話になっております。

今回は、BMP画像の表示について少し行き詰っている部分があるのでアドバイスなどを与えてくれれば幸いです。

環境
OS:Windows 8.1
Compiler:VC++
Library:DXライブラリ


今、プログラミングでの画像についての扱いなどで、折角だし簡易的な圧縮でも試してみようかと、初めに
手動によるBMP画像の読み込み、二値化やグレースケール化などを行っています。
とりあえず基本的にできたと思っていたのですが、表示が若干おかしくなってしまいます。

元の画像がコレ
画像
で、
私が組んだプログラムで表示させたモノが
画像
です。

なんというか、ところどころに左に90度回転させたかのように引き伸ばされた画像がうっすらと見えるような点が
打たれてしまっています。

なお、今回私が組んだプログラムの大部分はこのサイト様のこちらのページを参考にさせていただいています。


以下ソースコード
(無駄に分割されていて見づらいかもしれませんがよろしくお願いします。)


Main.cpp

コード:

#include "DxLib.h"
#include "BMPMgr.h"		//マネージャーのinclude

int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
	ChangeWindowMode(TRUE), SetDrawScreen(DX_SCREEN_BACK), DxLib_Init();


	BMPMgr_Load();		//読み込みさせて
	BMPMgr_Convert();	//変換させて

	while (ProcessMessage() == 0 && ClearDrawScreen() == 0 && CheckHitKey(KEY_INPUT_ESCAPE) == 0)
	{
		BMPMgr_Draw();	//描画


		ScreenFlip();
	}
	DxLib_End();
	return 0;
}

BMPmgr.h

コード:

	//多重インクルード防止
#ifndef DEF_BMPMGR_H

#define DEF_BMPMGR_H

bool BMPMgr_Load();

void BMPMgr_Convert();

void BMPMgr_Draw();


#endif

BMPmgr.cpp

コード:

#include "DxLib.h"
#include "BMP.h"
	//描画するオブジェクトの量
static const int NUM = 1;
	//画像サイズ分の量のデータ確保
unsigned char data[TOTAL];

	//オブジェクト分のイメージ情報を確保
img_t img[NUM];

	//現在は使っていない
//bool Pixel[BMP_WEIGHT][BMP_HEIGHT];


	//ロードさせる
bool BMPMgr_Load()
{
	for (int i = 0; i < NUM; i++)
	{
		//描画に失敗すればfalseを返す
		if (BMP_Load(&data[TOTAL])) return false;
	}
	return true;
}

	//変換させる
void BMPMgr_Convert()
{
	for (int i = 0; i < NUM; i++)
	{
		BMP_Convert(&img[i],&data[TOTAL]);
	}
}

	//描画させる
void BMPMgr_Draw()
{
	for (int i = 0; i < NUM;)
	{
		BMP_Draw(&img[i],GLAY);
		i++;
	}
}

BMP.h

コード:

	//多重インクルード防止
#ifndef DEF_BMP_H

#define DEF_BMP_H


	//いろいろ定数を宣言
#define BMP_HEADER 54	//ヘッダの大きさ(54byte)
	//とりあえず画像サイズは固定、ヘッダも無視する
#define BMP_WEIGHT 640
#define BMP_HEIGHT 480
#define BMP_BLACKORWHITE 128 //白と黒に分ける場合の境目
#define TOTAL (BMP_WEIGHT * BMP_HEIGHT * 3 + BMP_HEADER)	//読み込むべきサイズ


	//モードの宣言
enum
{
	MONO,
	GLAY,
	FULL,
};

	//一つのピクセルが持つ情報
typedef struct
{
	unsigned char color[3];
}pixel_t;

	//一つの画像が持つ情報
typedef struct
{
		//画素を画像の大きさ分確保
	pixel_t pixel[BMP_WEIGHT][BMP_HEIGHT];
}img_t;


	//ロードさせる関数
bool BMP_Load(unsigned char data[TOTAL]);
	//扱いやすいように変換する関数
void BMP_Convert(img_t *img,unsigned char data[TOTAL]);
	//描画させる関数
void BMP_Draw(img_t *img,int MODE);


#endif DEF_BMP_H

BMP.cpp
たぶんこれが問題

コード:

#include "DxLib.h"
#include "BMP.h"

	//あらかじめ白と黒は宣言しておく
int black = GetColor(0, 0, 0);
int white = GetColor(255, 255, 255);


	//画像のロード
bool BMP_Load(unsigned char data[TOTAL])
{
	char name[256] = "BMP.bmp";	//画像の名前
	FILE *fp;					//ファイルの構造体
	fp = fopen(name, "rb");		//読み込み
	if (fp == NULL)				//失敗すれば
	{
		printfDx("%sが見つかりませんでした", name);
		fclose(fp);				//閉じて
		return false;			//falseを返す
	}
	fread(data, TOTAL, 1, fp);	//そうでなければロードさせる
	fclose(fp);					//閉じて
	return true;				//終了
}


	//データを扱いやすく変換
void BMP_Convert(img_t *img,unsigned char data[TOTAL])
{
	int x, y, c, t;
	t = BMP_HEADER;
		//y方向へのロード
	for (y = BMP_HEIGHT - 1; y >= 0; y--)
	{
			//x方向へのロード
		for (x = 0; x < BMP_WEIGHT; x++)
		{
				//RGB分ロード
			for (c = 0; c < 3; c++)
			{
				img->pixel[x][y].color[c] = data[t];	//ヘッダ以降のデータを実際に代入
				t++;									//次のデータにイクリメント(unsigned char分足す)
			}
		}
	}
}

	//描画
void BMP_Draw(img_t *img,int MODE)
{
	int x, y;
		//y方向へ描画
	for (y = BMP_HEIGHT - 1; y >= 0; y--)
	{
			//x方向へ描画
		for (x = 0; x < BMP_WEIGHT; x++)
		{
				//モードによって異なる描画方法
			switch (MODE)
			{

			case FULL:
				DrawPixel(x, y, GetColor(img->pixel[x][y].color[2], img->pixel[x][y].color[1], img->pixel[x][y].color[0]));
				break;

			case GLAY:
				DrawPixel(x, y, GetColor(img->pixel[x][y].color[0], img->pixel[x][y].color[0], img->pixel[x][y].color[0]));
				break;

			case MONO:
					//RGBの平均値が黒か白かの境目より大きければ
				if (img->pixel[x][y].color[2] + img->pixel[x][y].color[1] + img->pixel[x][y].color[0] / 3 > BMP_BLACKORWHITE)
					DrawPixel(x, y, white);	//白く描画
				else //そうでなければ
					DrawPixel(x, y, black);	//黒く描画

				break;
			}
		}
	}
}


突っ込みどころしかないかもしれませんが、よろしくお願いします。
突っ込むところがあればどんどん突っ込んでくれれば幸いです。



何も触っていない(ハズ)にもかかわらず、動作がかなり変化してしまいました。
※コメントをつける作業と BMPmgr.cppの40行目をGLAYからFULLに変更。ただしGLAYに戻しても戻らず。
加えて終了時に「XXXXは動作を終了しました」の表示
実行中にOSの動きが不安定になることから大分致命的なミス?

画像
だいがくせい!

アバター
h2so5
副管理人
記事: 2212
登録日時: 13年前
住所: 東京
連絡を取る:

Re: BMP画像の表示

#2

投稿記事 by h2so5 » 9年前

BMPmgr.cppの21行目と31行目で思いっきりバッファオーバーランしてますね。
あと幅はWIDTHです。WEIGHTは重さです。

アバター
Hiragi(GKUTH)
記事: 167
登録日時: 13年前
住所: 大阪府
連絡を取る:

Re: BMP画像の表示

#3

投稿記事 by Hiragi(GKUTH) » 9年前

>>h2so5
思いっきり終端位置を指定していますね...
なんで呼び出し時と仮宣言時とを同じにしたのだろうか。
それと高校生なのに英語力の無さがあらわになってしまっている。
変なミス、申し訳ありませんでした。

他にも様々指摘されるべき点があるかもしれないのでもう少しだけ解決とはせずに置いておくことにします。

BMP画像自体は無事に、しっかりと表示してくれています。
だいがくせい!

kry

Re: BMP画像の表示

#4

投稿記事 by kry » 9年前

問題解決済みなんだよかった、と思ったのですが一つ気になりますのでお伝えしておきます。

BMP.cppの72行目ですが平均値を求めるのであればこのままでは正しくないです。
除算の優先順位が高いのでカッコで括るか変数に入れておかないと正しい結果が得られません。

if ( ( img->pixel[x][y].color[2] + img->pixel[x][y].color[1] + img->pixel[x][y].color[0] ) / 3 > BMP_BLACKORWHITE)
{
// 処理
}
もしくは
{
int tmp = img->pixel[x][y].color[2] + img->pixel[x][y].color[1] + img->pixel[x][y].color[0];
if ( tmp / 3 > BMP_BLACKORWHITE)
{
// 処理
}
}
ですね。
細かい指摘で申し訳ありません。

アバター
h2so5
副管理人
記事: 2212
登録日時: 13年前
住所: 東京
連絡を取る:

Re: BMP画像の表示

#5

投稿記事 by h2so5 » 9年前

効率に関するアドバイスを一つ。
次のコードはどちらのほうが速いでしょうか。

コード:

#include <stdio.h>

int main(void) {
	int i, x, y;
	static int pixel[40000][40000];
	for (y = 0; y < 40000; ++y) {
		for (x = 0; x < 40000; ++x) {
			pixel[y][x] = 1;
		}
	}
	return 0;
}

コード:

#include <stdio.h>

int main(void) {
	int i, x, y;
	static int pixel[40000][40000];
	for (y = 0; y < 40000; ++y) {
		for (x = 0; x < 40000; ++x) {
			pixel[x][y] = 1;
		}
	}
	return 0;
}

アバター
Hiragi(GKUTH)
記事: 167
登録日時: 13年前
住所: 大阪府
連絡を取る:

Re: BMP画像の表示

#6

投稿記事 by Hiragi(GKUTH) » 9年前

kry さんが書きました:問題解決済みなんだよかった、と思ったのですが一つ気になりますのでお伝えしておきます。

BMP.cppの72行目ですが平均値を求めるのであればこのままでは正しくないです。
除算の優先順位が高いのでカッコで括るか変数に入れておかないと正しい結果が得られません。

if ( ( img->pixel[x][y].color[2] + img->pixel[x][y].color[1] + img->pixel[x][y].color[0] ) / 3 > BMP_BLACKORWHITE)
{
// 処理
}
もしくは
{
int tmp = img->pixel[x][y].color[2] + img->pixel[x][y].color[1] + img->pixel[x][y].color[0];
if ( tmp / 3 > BMP_BLACKORWHITE)
{
// 処理
}
}
ですね。
細かい指摘で申し訳ありません。
返信ありがとうございます。
計算順序を考えていないとか何やってんでしょうねこの高校生。
ミスの指摘、ありがとうございました。

#ある色の成分だけでの二値化ができるのかなぁ
だいがくせい!

アバター
Hiragi(GKUTH)
記事: 167
登録日時: 13年前
住所: 大阪府
連絡を取る:

Re: BMP画像の表示

#7

投稿記事 by Hiragi(GKUTH) » 9年前

h2so5 さんが書きました:効率に関するアドバイスを一つ。
次のコードはどちらのほうが速いでしょうか。

コード:

#include <stdio.h>

int main(void) {
	int i, x, y;
	static int pixel[40000][40000];
	for (y = 0; y < 40000; ++y) {
		for (x = 0; x < 40000; ++x) {
			pixel[y][x] = 1;
		}
	}
	return 0;
}

コード:

#include <stdio.h>

int main(void) {
	int i, x, y;
	static int pixel[40000][40000];
	for (y = 0; y < 40000; ++y) {
		for (x = 0; x < 40000; ++x) {
			pixel[x][y] = 1;
		}
	}
	return 0;
}

返信ありがとうございます。

うーん?たぶん前者なのでしょうが、こちらのサイト様もこの用にしていたので、
なんかいつも x -> yの順番で処理する感じがあるのでなんでかなぁ、とは考えつつ自分のわかりやすいように順番を
入れ替えたのですが...

さて、何なのでしょうか、二次配列の扱いについては「二次元的な扱いができる」としか解釈していないので、アクセス方法などはちょいと思いつきません。
例えば各々の要素にアクセスするとき、式の外側からのほうが早い、とか?

コード:

#include <stdio.h>

int main(void) {
	int i, x, y;
	static int pixel[40000][40000];
	for (y = 0; y < 40000; ++y) {
		for (x = 0; x < 40000; ++x) {
			pixel[y][x] = 1; //外側「x」を連続的に書き換えている。
		}
	}
	return 0;
}
うーむ、まったく見当が付きません。
時間の計測をしてみようかとも思いましたが早すぎてできませんでした。
(Windows OSだしある程度の計測制度の限界が?加えて巨大な配列の確保ができませんでした。)
それとイクリメント、デクリメントの

args++;
++args;
などの違いとかも理解していませんねぇ...


:::追記:::

配列の大きさを大き目にしたら図れました。
単位は不明ですが、clock()で図りました。

前者*5
 14
 15
 14
 18
 14

後者*5
 142
 120
 108
 105
 146

はい、後者のほうが圧倒的に遅いです。10倍近く。
なんでだろう...(配列は2048^2で計測)

:::さらに追記:::

二次元配列がメモリ上に実際にどのように配置されているのかは知りませんが、たぶん式の右側のほうが順番に並んでるんじゃないでしょうか?
それで、右側を書き換えるときはポインタに対してイクリメントするだけでいいので処理が高速になる、とか(CPUにもイクリメント専用の命令があるようですし。)
左側の値を書き換えるときは右側の要素分スキップして書き換えないといけないので、毎回加算器を使う必要がある、とか?
アセンブリレベルでの話はほとんど理解できないレベルのプログラミングスキルですのでほぼ憶測ですが...
だいがくせい!

アバター
Hiragi(GKUTH)
記事: 167
登録日時: 13年前
住所: 大阪府
連絡を取る:

Re: BMP画像の表示

#8

投稿記事 by Hiragi(GKUTH) » 9年前

しばらく反応がなかったので解決とさせていただきます。

問題の解決への御指摘、及び高速化への御指摘、ありがとうございました。


問題があったコードのみと、指摘してくださった二次配列の部分だけを投稿します。


BMPmgr.cpp

コード:

/*                              
 BMPの手動なロード及び二値化、圧縮   
 解凍、表示を行うモジュール(?)      
 
               Hiragi */



#include "DxLib.h"
#include "BMP.h"


	//描画するオブジェクトの量
static const int NUM = 2;
	//画像サイズ分の量のデータ確保
unsigned char data[TOTAL];	
	//オブジェクト分のイメージ情報を確保
img_t img[NUM];




	//ロードさせる
bool BMPMgr_Load()
{
	for (int i = 0; i < NUM; i++)
	{
		//描画に失敗すればfalseを返す
		if (BMP_Load(&data[0])) return false;
	}
	return true;
}

	//変換させる
void BMPMgr_Convert()
{
	for (int i = 0; i < NUM; i++)
	{
		BMP_Convert(&img[i],&data[0]);
	}
}

	//描画させる
void BMPMgr_Draw()
{
	for (int i = 0; i < NUM;)
	{

		BMP_Draw(&img[i], MONO);
		i++;
	}
}

	//エンコード(未実装)
void BMPMgr_Encode()
{
	for (int i = 0; i < NUM; i++)
	{
		BMP_Encode(&img[i]);
	}
}

	//デコード(未実装)
void BMPMgr_Decode()
{
	for (int i = 0; i < NUM; i++)
	{
		BMP_Decode();
	}
}

BMP.cpp(指摘されたところ修正)

コード:

#include "DxLib.h"
#include "BMP.h"

	//あらかじめ白と黒は宣言しておく
int black = GetColor(0, 0, 0);
int white = GetColor(255, 255, 255);


	//画像のロード
bool BMP_Load(unsigned char data[TOTAL])
{
	char name[256] = "542.bmp";	//画像の名前
	FILE *fp;					//ファイルの構造体
	fp = fopen(name, "rb");		//読み込み
	if (fp == NULL)				//失敗すれば
	{
		printfDx("%sが見つかりませんでした", name);
		fclose(fp);				//閉じて
		return false;			//falseを返す
	}
	fread(data, TOTAL, 1, fp);	//そうでなければロードさせる
	fclose(fp);					//閉じて
	return true;				//終了
}


	//データを扱いやすく変換
void BMP_Convert(img_t *img,unsigned char data[TOTAL])
{
	int x, y, c, t;
	t = BMP_HEADER;
		//y方向へのロード
	for (y = BMP_HEIGHT - 1; y >= 0;y--)
	{
			//x方向へのロード
		for (x = 0; x < BMP_WIDTH;x++)
		{
				//RGB分ロード
			for (c = 0; c < 3; c++)
			{
				img->pixel[y][x].color[c] = data[t];	//ヘッダ以降のデータを実際に代入
				t++;									//次のデータにイクリメント(unsigned char分足す)
			}

				//ついでに二値化も行ってしまう
			if ((img->pixel[y][x].color[0] + img->pixel[y][x].color[1] + img->pixel[y][x].color[2]) / 3 > BMP_BLACKORWHITE)
				img->bin_pixel[y][x] = true;
			else
				img->bin_pixel[y][x] = false;
		}
	}
}

	//描画
void BMP_Draw(img_t *img,int MODE)
{
	int x, y;
		//y方向へ描画
	for (y = 0; y < BMP_HEIGHT;y++)
	{
			//x方向へ描画
		for (x = 0; x < BMP_WIDTH; x++)
		{
				//モードによって異なる描画方法
			switch (MODE)
			{

			case FULL:
				DrawPixel(x, y, GetColor(img->pixel[y][x].color[2], img->pixel[y][x].color[1], img->pixel[y][x].color[0]));
				break;

			case GLAY:
				DrawPixel(x, y, GetColor(img->pixel[y][x].color[0], img->pixel[y][x].color[0], img->pixel[y][x].color[0]));
				break;

			case MONO:
					//白か?黒か?
				if (img->bin_pixel[y][x])
					DrawPixel(x, y, white);	//白く描画
				else //そうでなければ
					DrawPixel(x, y, black);	//黒く描画

				break;
			}
		}
	}
}

	//未実装でーす
void BMP_Encode(img_t *img)
{
	int x, y;

	for (y = BMP_HEIGHT - 1; y >= 0; y--)
	{
		for (x = 0; x < BMP_WIDTH; x++)
		{
			//ココに圧縮する処理を書く?(たぶんzipと同じ方式にするか? 0 or 1)
		}
	}
}

	//未実装でーす
void BMP_Decode()
{
	//デコードの処理
}

だいがくせい!

アバター
h2so5
副管理人
記事: 2212
登録日時: 13年前
住所: 東京
連絡を取る:

Re: BMP画像の表示

#9

投稿記事 by h2so5 » 9年前

Hiragi(GKUTH) さんが書きました: 二次元配列がメモリ上に実際にどのように配置されているのかは知りませんが、たぶん式の右側のほうが順番に並んでるんじゃないでしょうか?
それで、右側を書き換えるときはポインタに対してイクリメントするだけでいいので処理が高速になる、とか(CPUにもイクリメント専用の命令があるようですし。)
左側の値を書き換えるときは右側の要素分スキップして書き換えないといけないので、毎回加算器を使う必要がある、とか?
アセンブリレベルでの話はほとんど理解できないレベルのプログラミングスキルですのでほぼ憶測ですが...
参照の局所性 - Wikipedia

閉鎖

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