/*
 *	dpbmppp - ビットマッププリプロセッサ
 *	Copyright (C) 2007 Naoyuki Sawa
 *
 *	* Thu Jan 11 21:03:55 JST 2007 Naoyuki Sawa
 *	- 1st リリース。
 *	* Sat Jan 13 19:45:45 JST 2007 Naoyuki Sawa
 *	- 3ピクセルの元画像を"-s50%"で2ピクセル画像に縮小するような場合の処理を変更しました。
 *	- これまでは、単純に3ピクセル→2ピクセルに縮小していました。
 *	  しかし、これでは66%縮小となり、厳密にはユーザーの期待する結果と異なります。
 *	- そこで、出力画像のキャンバスサイズは2ピクセルですが、3ピクセル→1.5ピクセルと考えて縮小するよう、処理を変更しました。
 *	  元画像を4ピクセルと考えて、4ピクセル→2ピクセルの50%縮小と言い換えることもできます。
 *	  本来、元画像には含まれていない4ピクセル目からカラーを取得する際にはは、透明と考えて処理します。
 *	  すなわち、"-b"を指定すればそのカラーと考えて加重平均し、"-b"を指定しなければ加重平均に含めません。
 *	- 以上の変更により、9x9ピクセルフォント画像を50%縮小するような場合に、これまでよりも厳密に50%縮小の結果が期待できます。
 *	  出力画像サイズは5x5ピクセルでこれまでと同じですが、画像の内容が4.5x4.5ピクセル相当へのリサンプルとなるからです。
 *	- "-w??%","-h??%","-s??%"を使わず、"-w??","-h??","-s??"で直接ピクセル数を指定した場合は、これまでと同じ結果となります。
 *	* Sun Jan 14 00:01:23 JST 2007 Naoyuki Sawa
 *	- 画像情報の表示を追加しました。
 *	- 画像情報を表示しない、"-q"オプションを追加しました。
 *	* Thu Jan 18 01:34:05 JST 2007 Naoyuki Sawa
 *	 - resample()関数のアルゴリズムチェックコード自体が誤っていたので、チェックコードを削除しました。
 *	   前バージョンでは、正しい処理に対してもアサートしてしまうという不具合が発生していました。
 *	   今回のチェックコード削除により、無用なアサートを回避しました。
 *	* Mon Feb 05 12:44:07 JST 2007 Naoyuki Sawa
 *	- -w,-h,-sオプションに、分数指定を追加しました。
 *	  分数指定を追加した理由は、以下のとおりです。
 *	- これまで、VGA解像度の入力画像を、P/ECE解像度に変換する場合、ピクセル指定や%指定では高さが割り切れず、厳密に正確な指定ができませんでした。
 *	  たとえば、256x256ピクセルの入力画像に上述の変換を行う場合、100×88/480=46.933となり、ピクセル指定では厳密に正確な指定ができません。
 *	  また、88/480=0.18333となり、パーセント指定でも厳密に正確な指定ができません。
 *	- 今回追加した分数指定を使えば、"-h88/480"と指定することにより、厳密に正確な指定が可能となりました。
 */
#define STRICT
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <math.h>

#define VERSION	"20070205"

/****************************************************************************
 *	
 ****************************************************************************/

char src_path[_MAX_PATH];	/* 入力ファイル名 */
char dst_path[_MAX_PATH];	/* 出力ファイル名 */
int opt_w1;	/* 幅指定                                            */
int opt_w2;	/*   指定無し      w1=0         , w2=0               */
		/*   ピクセル指定  w1=ピクセル数, w2=0               */
		/*   %指定         w1=%         , w2=100             */
		/*   分数指定      w1=分子      , w2=分母            */
		/*   (どの場合も、解析後に分子/分母に設定し直します) */
int opt_h1;	/* 高さ指定                                          */
int opt_h2;	/*   (opt_w1,w2と同様。上コメントを参照してください) */
int opt_b;	/* カラー0と未設定を区別するために、+1しておきます */
int opt_c;	/* 不透明色数。256ならば透明色無しです */
int opt_q;	/* 画像情報を表示しません */

#define MAX_SIZE	4096
unsigned char src_bits[MAX_SIZE][MAX_SIZE];	/* 入力画像 */
unsigned char dst_bits[MAX_SIZE][MAX_SIZE];	/* 出力画像 */
RGBQUAD pal[256];				/* パレット */
int trans;					/* これ以上の色は、透明色と見なします */

int src_w;	/* 入力キャンバスの幅   (ピクセル単位) */
int src_h;	/* 入力キャンバスの高さ (ピクセル単位) */
int dst_w;	/* 出力キャンバスの幅   (ピクセル単位) */
int dst_h;	/* 出力キャンバスの高さ (ピクセル単位) */

void err(const char* fmt, ...);
void usage();
void cmdline(int argc, char* argv[]);
void loadbmp();
void resample();
void savebmp();

/****************************************************************************
 *	main
 ****************************************************************************/

int
main(int argc, char* argv[])
{
	/* コマンドラインを解析します。 */
	cmdline(argc, argv);

	/* 入力画像をロードします。 */
	loadbmp();

	/* 出力画像サイズを決定します。 */
	if(!opt_w2) { /* 指定無し or ピクセル指定 */
		opt_w2 = src_w;
		if(!opt_w1) { /* 指定無し */
			opt_w1 = src_w;
		}
	}
	dst_w = (src_w * opt_w1 + (opt_w2 - 1)) / opt_w2;
	if(dst_w > MAX_SIZE) err("出力画像が大きすぎます");
	//
	if(!opt_h2) { /* 指定無し or ピクセル指定 */
		opt_h2 = src_h;
		if(!opt_h1) { /* 指定無し */
			opt_h1 = src_h;
		}
	}
	dst_h = (src_h * opt_h1 + (opt_h2 - 1)) / opt_h2;
	if(dst_h > MAX_SIZE) err("出力画像が大きすぎます");

	/* 画像情報を表示します。 */
	if(!opt_q) {
		printf("■入力画像\n");
		printf("　ﾌｧｲﾙ名    : %s\n", src_path);
		printf("　ｷｬﾝﾊﾞｽｻｲｽﾞ: %d x %d\n", src_w, src_h);
		printf("　ｲﾒｰｼﾞ ｻｲｽﾞ: %f x %f\n", (double)src_w * opt_w2 / opt_w2,
						  (double)src_h * opt_h2 / opt_h2);
		printf("■出力画像\n");
		printf("　ﾌｧｲﾙ名    : %s\n", dst_path);
		printf("　ｷｬﾝﾊﾞｽｻｲｽﾞ: %d x %d\n", dst_w, dst_h);
		printf("　ｲﾒｰｼﾞ ｻｲｽﾞ: %f x %f\n", (double)src_w * opt_w1 / opt_w2,
						  (double)src_h * opt_h1 / opt_h2);
	}

	/* リサンプル処理を行います。 */
	resample();

	/* 出力画像をセーブします。 */
	savebmp();

	return 0;
}

/****************************************************************************
 *	err
 ****************************************************************************/

void
err(const char* fmt, ...)
{
	va_list ap;
	va_start(ap, fmt);
	 fprintf(stderr, "### ");
	vfprintf(stderr, fmt, ap);
	 fprintf(stderr, "\n");
	va_end(ap);
	abort();
}

/****************************************************************************
 *	usage
 ****************************************************************************/

void
usage()
{
	fprintf(stderr, "dpbmppp - ﾋﾞｯﾄﾏｯﾌﾟﾌﾟﾘﾌﾟﾛｾｯｻ (%s)\n", VERSION);
	fprintf(stderr, "Copyright (C) 2007 Naoyuki Sawa\n");
	fprintf(stderr, "\n");
	fprintf(stderr, "使い方:\n");
	fprintf(stderr, "    dpbmppp [ｵﾌﾟｼｮﾝ] 入力ﾌｧｲﾙ名[.bmp] 出力ﾌｧｲﾙ名[.bmp]\n");
	fprintf(stderr, "\n");
	fprintf(stderr, "ｵﾌﾟｼｮﾝ:\n");
	fprintf(stderr, "    -w横ﾋﾟｸｾﾙ数   <例> -w128  横ｻｲｽﾞを128ﾋﾟｸｾﾙにﾘｻﾝﾌﾟﾙします｡\n");
	fprintf(stderr, "    -w横拡大率%%   <例> -w200%% 横ｻｲｽﾞを入力画像の200%%にﾘｻﾝﾌﾟﾙします｡\n");
	fprintf(stderr, "    -w分子/分母   <例> -w128/640 横ｻｲｽﾞを128/640の割合でﾘｻﾝﾌﾟﾙします｡\n");
	fprintf(stderr, "    -h縦ﾋﾟｸｾﾙ数   <例> -h88   縦ｻｲｽﾞを88ﾋﾟｸｾﾙにﾘｻﾝﾌﾟﾙします｡\n");
	fprintf(stderr, "    -h縦拡大率%%   <例> -h75%%  縦ｻｲｽﾞを入力画像の75%%にﾘｻﾝﾌﾟﾙします｡\n");
	fprintf(stderr, "    -h分子/分母   <例> -w88/480 縦ｻｲｽﾞを88/480の割合でﾘｻﾝﾌﾟﾙします｡\n");
	fprintf(stderr, "    -s縦横ﾋﾟｸｾﾙ数 <例> -s256  縦横ｻｲｽﾞを256ﾋﾟｸｾﾙにﾘｻﾝﾌﾟﾙします｡\n");
	fprintf(stderr, "    -s縦横拡大率%% <例> -s50%%  縦横ｻｲｽﾞを入力画像の50%%にﾘｻﾝﾌﾟﾙします｡\n");
	fprintf(stderr, "    -s分子/分母   <例> -s2/3  縦横ｻｲｽﾞを2/3の割合でﾘｻﾝﾌﾟﾙします｡\n");
	fprintf(stderr, "    -b境界背景色  <例> -b0    ﾘｻﾝﾌﾟﾙ時に透明ﾋﾟｸｾﾙと不透明ﾋﾟｸｾﾙを混合する場合､\n");
	fprintf(stderr, "                              不透明ﾋﾟｸｾﾙをこの色と仮定して加重平均を取ります｡\n");
	fprintf(stderr, "                              透明背景に黒一色で描いたﾃｷｽﾄ画像を縮小する場合\n");
	fprintf(stderr, "                              などにこのｵﾌﾟｼｮﾝを使って縮小すると､実行時､明るい\n");
	fprintf(stderr, "                              背景画像にうまく溶け込むﾃｷｽﾄ画像を作成できます｡\n");
	fprintf(stderr, "                              このｵﾌﾟｼｮﾝを指定しなければ､透明ﾋﾟｸｾﾙは加重0です｡\n");
	fprintf(stderr, "                       -b3    白一色のﾃｷｽﾄ画像と暗い背景色の組合せの場合はこう｡\n");
	fprintf(stderr, "                       -b15   同上の組合せで､16階調用の白ﾃｷｽﾄ画像の場合は､こう｡\n");
	fprintf(stderr, "    -c不透明色数  <例> -c2    ｶﾗｰ2とそれ以降を透明色と見なします｡(1bitﾏｽｸ形式)\n");
	fprintf(stderr, "                       -c4    ｶﾗｰ4とそれ以降を透明色と見なします｡(2bitﾏｽｸ形式)\n");
	fprintf(stderr, "                       -c16   ｶﾗｰ16とそれ以降を透明色と見なします｡(4bitﾏｽｸ形式)\n");
	fprintf(stderr, "                       -c256  透明色無しと見なします。(1bit､2bit､4bit形式)\n");
	fprintf(stderr, "                              このｵﾌﾟｼｮﾝを指定しなければ､ｸﾞﾚｰｽｹｰﾙでない最初の\n");
	fprintf(stderr, "                              色とそれ以降を透明色と見なして､自動検出します｡\n");
	fprintf(stderr, "                              通常は､このｵﾌﾟｼｮﾝを指定する必要はありません｡\n");
	fprintf(stderr, "                              何らかの理由で､不透明色に仮にｸﾞﾚｰｽｹｰﾙ以外の色を\n");
	fprintf(stderr, "                              設定しているような､特殊な場合に指定してください｡\n");
	fprintf(stderr, "    -q            <例> -q     画像情報を表示しません｡\n");
	exit(1);
}

/****************************************************************************
 *	cmdline
 ****************************************************************************/

static void
cmdline_whs(char* arg, int* p1, int* p2)
{
	if(*p1 || *p2) usage();
	*p1 = strtol(arg, &arg, 10);
	if(*p1 <= 0) usage();
	if(*arg) {
		switch(*arg) {
		case '%':
			arg++;
			*p2 = 100;
			break;
		case '/':
			arg++;
			*p2 = strtol(arg, &arg, 10);
			if(*p2 <= 0) usage();
			break;
		//default:
		//	//↓すぐ下でまとめてエラー扱い
		}
		if(*arg) usage(); /* 余分な後続文字 */
	}
}

void
cmdline(int argc, char* argv[])
{
	int i;
	char opt;
	char* arg;
	char drive[_MAX_DRIVE];
	char dir[_MAX_DIR];
	char fname[_MAX_FNAME];
	char ext[_MAX_EXT];

	for(i = 1; i < argc; i++) {
		arg = argv[i];
		if(*arg == '-' ||
		   *arg == '/') {
			arg++;
			opt = *arg++;
			switch(opt) {
			case 'w':
				cmdline_whs(arg, &opt_w1, &opt_w2);
				break;
			case 'h':
				cmdline_whs(arg, &opt_h1, &opt_h2);
				break;
			case 's':
				cmdline_whs(arg, &opt_w1, &opt_w2);
				cmdline_whs(arg, &opt_h1, &opt_h2);
				break;
			case 'b':
				if(opt_b) usage();
				opt_b = strtol(arg, &arg, 10) + 1;	/* カラー0と未設定を区別するために、+1しておきます */
				if((opt_b <= 0) || *arg) usage();
				break;
			case 'c':
				if(opt_c) usage();
				opt_c = strtol(arg, &arg, 10);
				if((opt_c <= 0) || *arg) usage();
				break;
			case 'q':
				if(opt_q) usage();
				opt_q = 1;
				if(*arg) usage();
				break;
			default:
				usage();	/* 不明なオプション */
			}
		} else {
			_splitpath(arg, drive, dir, fname, ext);
			if(!*src_path) {							/* 入力ファイル名が未設定ならば... */
				_makepath(src_path, drive, dir, fname, *ext ? ext : ".bmp");	/* 入力ファイル名に格納します。 */
			} else if(!*dst_path) {							/* 出力ファイル名が未設定ならば... */
				_makepath(dst_path, drive, dir, fname, *ext ? ext : ".bmp");	/* 出力ファイル名に格納します。 */
			} else {
				usage();	/* ファイル名が三つ以上指定されてました。不正なコマンドライン書式です。 */
			}
		}
	}
	if(!*src_path || !*dst_path) usage();	/* 入力ファイル名、または、出力ファイル名が指定されていません。 */
	if(!strcmpi(src_path, dst_path)) err("入力ﾌｧｲﾙ名と出力ﾌｧｲﾙ名に同じﾌｧｲﾙ名を指定しないでください");
}

/****************************************************************************
 *	loadbmp
 ****************************************************************************/

void
loadbmp()
{
	FILE* fp;

	int retval;
	int i;
	int y;
	int pitch;
	BITMAPFILEHEADER bf;
	BITMAPINFOHEADER bi;

	/* 入力ファイルを開きます。 */
	fp = fopen(src_path, "rb");
	if(!fp) err("入力ﾌｧｲﾙが開けません");

	/* ファイルヘッダを読み込みます。 */
	retval = fread(&bf, 1, sizeof(BITMAPFILEHEADER), fp);
	if(retval != sizeof(BITMAPFILEHEADER)) err("ﾌｧｲﾙﾍｯﾀﾞが不正です");
	if(memcmp(&bf.bfType, "BM", 2)) err("ﾌｧｲﾙﾍｯﾀﾞが不正です");

	/* ビットマップヘッダを読み込みます。 */
	retval = fread(&bi, 1, sizeof(BITMAPINFOHEADER), fp);
	if(retval != sizeof(BITMAPINFOHEADER)) err("ﾋﾞｯﾄﾏｯﾌﾟﾍｯﾀﾞが不正です");
	if(bi.biSize < sizeof(BITMAPINFOHEADER)) err("ﾋﾞｯﾄﾏｯﾌﾟﾍｯﾀﾞが不正です");
	if(bi.biPlanes != 1) err("256色BMP形式以外は未対応です");
	if(bi.biBitCount != 8) err("256色BMP形式以外は未対応です");
	if(bi.biCompression != BI_RGB) err("256色BMP形式以外は未対応です");

	/* パレットを読み込みます。 */
	i = bi.biClrUsed != 0 ? bi.biClrUsed
	                      : bi.biPlanes << bi.biBitCount;
	if(i < 0 || 256 < i) err("ﾊﾟﾚｯﾄが不正です");
	retval = fread(pal, sizeof(RGBQUAD), i, fp);
	if(retval != i) err("ﾊﾟﾚｯﾄが不正です");

	/* グレイスケールでない最初の色とそれ以降を、透明色と見なします。 */
	if(opt_c) {
		trans = opt_c;
	} else {
		for(i = 0; i < 256; i++) {
			if(pal[i].rgbRed != pal[i].rgbGreen ||
			   pal[i].rgbRed != pal[i].rgbBlue) {
				break;
			}
		}
		trans = i; /* 透明色無しならば256になります */
	}

	/* 入力画像サイズを求めます。 */
	src_w = bi.biWidth;
	src_h = bi.biHeight;
	if((src_w > MAX_SIZE) || (src_h > MAX_SIZE)) err("入力画像が大きすぎます");

	/* ピッチを求めます。 */
	pitch = (src_w + 3) & ~3; /* 仕様 */

	/* 入力画像を読み込みます。 */
	retval = fseek(fp, bf.bfOffBits, SEEK_SET);
	if(retval) err("ｼｰｸｴﾗｰです");
	for(y = src_h - 1; y >= 0; y--) { /* ボトムアップ */
		retval = fread(&src_bits[y][0], 1, pitch, fp);
		if(retval != pitch) err("読み込みｴﾗｰです");
	}

	/* 入力ファイルを閉じます。 */
	fclose(fp);
}

/****************************************************************************
 *	resample
 ****************************************************************************/

/* 伸縮アルゴリズムは、CLiPライブラリのsurface_stretch()関数と同じです。
 * surface_stretch()関数のコメントを参照してください。
 * ↓Sat Jan 13 19:45:45 JST 2007 Naoyuki Sawa
 * 3ピクセル→50%→1.5ピクセル縮小のような、サブピクセル単位の処理に変更したため、
 * surface_stretch()関数とは、やや、アルゴリズムが異なるようになりました。
 */
void
resample()
{
	unsigned __int64 dx;
	unsigned __int64 dy;
	unsigned __int64 sx;
	unsigned __int64 sy;
	unsigned __int64 x1;
	unsigned __int64 x2;
	unsigned __int64 x3;
	unsigned __int64 y1;
	unsigned __int64 y2;
	unsigned __int64 y3;
	unsigned __int64 xy;
	unsigned __int64 c1;
	unsigned __int64 c2;
	unsigned __int64 save_sx;
	unsigned __int64 save_sy;
	unsigned __int64 save_x1;
	unsigned __int64 save_y1;
	int opaque;
	int c;

	/*{{転送先の垂直ループ(転送先1ピクセルづつ)*/
	sy = 0;
	y1 = opt_h1;
	for(dy = 0; dy < dst_h; dy++) {
		/*{{転送先の水平ループ(転送先1ピクセルづつ)*/
		sx = 0;
		x1 = opt_w1;
		save_sy = sy;
		save_y1 = y1;
		for(dx = 0; dx < dst_w; dx++) {
			sy = save_sy;
			y1 = save_y1;
			opaque = 0;
			c1 = 0;
			c2 = 0;
			/*{{転送元の垂直ループ(転送先1ピクセルにかかる範囲の転送元ピクセルの分)*/
			y2 = opt_h2;
			save_sx = sx;
			save_x1 = x1;
			for(;;) {
				sx = save_sx;
				x1 = save_x1;
				y3 = y1 < y2 ? y1 : y2;
				/*{{転送元の水平ループ(転送先1ピクセルにかかる範囲の転送元ピクセルの分)*/
				x2 = opt_w2;
				for(;;) {
					x3 = x1 < x2 ? x1 : x2;
					xy = x3 * y3;
					if((sx < src_w) && (sy < src_h)) {
						c = src_bits[sy][sx];
					} else {
						//{{2007/01/18削除
						///* たとえば、3ピクセル画像を"-s50%"で2ピクセルに縮小する場合に、
						// * 元画像の4ピクセル目を参照するので、透明と見なします。
						// * 元画像の5ピクセル目以降を参照することは有り得ません。
						// * もしそうなったら、アルゴリズムが誤っています。
						// */
						//if(((int)sx > src_w) || ((int)sy > src_h)) err("resample()関数の内部ｴﾗｰです");
						//}}2007/01/18削除
						// * Thu Jan 18 01:34:05 JST 2007 Naoyuki Sawa
						// - resample()関数のアルゴリズムチェックコード自体が誤っていたので、チェックコードを削除しました。
						//   前バージョンでは、正しい処理に対してもアサートしてしまうという不具合が発生していました。
						//   今回のチェックコード削除により、無用なアサートを回避しました。
						// - 具体的には、上述のアルゴリズムに関する考察が間違っていました。
						//   2ピクセル以上外側を参照することも、充分に有り得ます。
						//   たとえば、32ピクセル→20%縮小の場合、有効イメージ6.4ピクセルに対して7ピクセルのキャンバスとなりますから、
						//   32×(7÷6.4)=35、すなわち、元画像の35ピクセル目まで参照します。(これは正しい動作です)
						// - そんなわけで、このチェックコード自体が誤りであり、正しい処理に対してアサートしてしまうことがありました。
						//   よって、チェックコードを削除しました。
						c = trans;
					}
					if(c < trans) {
						opaque = 1;
						c1 += xy;
						c2 += xy * c;
					} else {
						if(opt_b) {
							c1 += xy;
							c2 += xy * (opt_b - 1);
						}
					}
					if(!(x1 -= x3)) {
						x1 += opt_w1;
						sx++;
					}
					if(!(x2 -= x3)) {
						break;
					}
				}
				/*}}転送元の水平ループ(転送先1ピクセルにかかる範囲の転送元ピクセルの分)*/
				if(!(y1 -= y3)) {
					y1 += opt_h1;
					sy++;
				}
				if(!(y2 -= y3)) {
					break;
				}
			}
			/*}}転送元の垂直ループ(転送先1ピクセルにかかる範囲の転送元ピクセルの分)*/
			if(opaque) {
				c = (int)((c2 + (c1 / 2)) / c1);
			} else {
				c = trans;
			}
			dst_bits[dy][dx] = c;
		}
		/*}}転送先の水平ループ(転送先1ピクセルづつ)*/
	}
	/*}}転送先の垂直ループ(転送先1ピクセルづつ)*/
}

/****************************************************************************
 *	savebmp
 ****************************************************************************/

void
savebmp()
{
	FILE* fp;

	int retval;
	int y;
	int pitch;
	BITMAPFILEHEADER bf;
	BITMAPINFOHEADER bi;

	/* 出力ファイルを作成します。 */
	fp = fopen(dst_path, "wb");
	if(!fp) err("出力ﾌｧｲﾙが作成できません");

	/* ピッチを求めます。 */
	pitch = (dst_w + 3) & ~3; /* 仕様 */

	/* ファイルヘッダを書き込みます。 */
	memset(&bf, 0, sizeof(BITMAPFILEHEADER));
	memcpy(&bf.bfType, "BM", 2);
	bf.bfSize = sizeof(BITMAPFILEHEADER)
		  + sizeof(BITMAPINFOHEADER)
		  + sizeof(RGBQUAD) * 256
		  + pitch * dst_h;
	bf.bfOffBits = sizeof(BITMAPFILEHEADER)
		     + sizeof(BITMAPINFOHEADER)
		     + sizeof(RGBQUAD) * 256;
	retval = fwrite(&bf, 1, sizeof(BITMAPFILEHEADER), fp);
	if(retval != sizeof(BITMAPFILEHEADER)) err("書き込みｴﾗｰです");

	/* ビットマップヘッダを書き込みます。 */
	memset(&bi, 0, sizeof(BITMAPINFOHEADER));
	bi.biSize = sizeof(BITMAPINFOHEADER);
	bi.biWidth = dst_w;
	bi.biHeight = dst_h;
	bi.biPlanes = 1;
	bi.biBitCount = 8;
	retval = fwrite(&bi, 1, sizeof(BITMAPINFOHEADER), fp);
	if(retval != sizeof(BITMAPINFOHEADER)) err("書き込みｴﾗｰです");

	/* パレットを書き込みます。 */
	retval = fwrite(pal, sizeof(RGBQUAD), 256, fp);
	if(retval != 256) err("書き込みｴﾗｰです");

	/* 出力画像を書き込みます。 */
	for(y = dst_h - 1; y >= 0; y--) { /* ボトムアップ */
		retval = fwrite(&dst_bits[y][0], 1, pitch, fp);
		if(retval != pitch) err("書き込みｴﾗｰです");
	}

	/* 出力ファイルを閉じます。 */
	fclose(fp);
}

