/*
**  png2bmp --- conversion from PNG to (Windows style) BMP
**
**  Copyright (C) 1999-2005 MIYASAKA Masaru
**
**  For conditions of distribution and use,
**  see copyright notice in common.h.
*/

#include "common.h"
#include "bmphed.h"

#define PNG2BMP_VERSION		"1.62 (Sep 4, 2005)"
#define PNG2BMP_COPYRIGHT	"Copyright (C) 1999-2005 MIYASAKA Masaru"

char outnam[FILENAME_MAX];
char outdir[FILENAME_MAX];
int  deletesrc = 0;
int  copytime  = 0;

#define P2B_ALPHABMP_NONE		0
#define P2B_ALPHABMP_ARGB		1	/* -a option; 32bit ARGB(RGB) BMP */
#define P2B_ALPHABMP_BITFIELD	2	/* -b option; 32bit Bitfield BMP  */

int alpha_format = P2B_ALPHABMP_NONE;
int expand_trans = 0;

#if defined(WIN32) || defined(MSDOS)
const char errlogfile[] = ".\\P2BERROR.LOG";
#else
const char errlogfile[] = "./p2berror.log";
#endif

	/* error messages */
#ifdef JAPANESE /* ---------- */
const char wrn_mkdirfail[]   =
        "WARNING: 出力先ディレクトリを作れません - %s\n"
        "WARNING:   -> -%c オプション での出力先指定は無視されます\n";
const char err_ropenfail[]   = "SKIPPED: 該当するファイルがありません - %s\n";
const char err_wopenfail[]   = "SKIPPED: 出力ファイルを作成できません - %s\n";
const char err_outofmemory[] = "SKIPPED: 作業用メモリが確保できません - %s\n";
	/* -- */
const char err_writeerr[]    = "SKIPPED: 書き込みエラーが発生しました - %s\n";
const char err_not_a_png[]   = "SKIPPED: PNG ファイルではありません - %s\n";
#else  /* ------------------- */
const char wrn_mkdirfail[]   =
        "WARNING: Cannot create a directory - %s\n"
        "WARNING:   -> Output directory specified by '-%c' will be ignored.\n";
const char err_ropenfail[]   = "SKIPPED: No such file or directory - %s\n";
const char err_wopenfail[]   = "SKIPPED: Cannot create - %s\n";
const char err_outofmemory[] = "SKIPPED: Out of memory - %s\n";
	/* -- */
const char err_writeerr[]    = "SKIPPED: Write operation failed - %s\n";
const char err_not_a_png[]   = "SKIPPED: Not a PNG file - %s\n";
#endif /* ------------------- */

static BOOL read_png(char *, IMAGE *);
static int skip_macbinary(png_structp);
static void to4bpp(png_structp, png_row_infop, png_bytep);
static BOOL write_bmp(char *, IMAGE *);
static const char *write_rgb_bits(IMAGE *, FILE *);
static void mputdwl(void *, unsigned long);
static void mputwl(void *, unsigned int);
static void usage_exit(char *, int);



/*
**		メイン
*/
int main(int argc, char *argv[])
{
	char outf[FILENAME_MAX];
	IMAGE image;
	int opt;
	char *arg;
	char *p, c;
	int r_stdin, w_stdout;
	int failure = 0, success = 0;

#ifdef __LCC__					/* lcc-win32 */
	char **envp;
	void _GetMainArgs(int *, char ***, char ***, int);
	_GetMainArgs(&argc, &argv, &envp, 1);
#endif
#ifdef __EMX__
	_wildcard(&argc, &argv);
#endif
	envargv(&argc, &argv, "B2P");

	r_stdin  = !isatty(fileno(stdin));
	w_stdout = !isatty(fileno(stdout));

	while (parsearg(&opt, &arg, argc, argv, "DdOoFfPp")) {
		switch (toupper(opt)) {
		case 'E':  deletesrc ^= 1;  break;
		case 'T':  copytime  ^= 1;  break;
		case 'Q':  quietmode ^= 1;  break;
		case 'L':  errorlog  ^= 1;  break;

		case 'X':
			r_stdin  = 0;
			w_stdout = 0;
			break;

		case 'A':
			alpha_format = (alpha_format == P2B_ALPHABMP_ARGB) ?
			                P2B_ALPHABMP_NONE : P2B_ALPHABMP_ARGB;
			break;

		case 'B':
			alpha_format = (alpha_format == P2B_ALPHABMP_BITFIELD) ?
			                P2B_ALPHABMP_NONE : P2B_ALPHABMP_BITFIELD;
			break;

		case 'R':
			expand_trans ^= 1;
			break;

		case 'F':
			/* '-F' option of bmp2png (ignored on png2bmp) */
			break;

		case 'P':
			/* '-P' option of bmp2png (ignored on png2bmp) */
			break;

		case 'D':				/* output directory */
			if (*arg == '-') arg = NULL;
			if (arg == NULL) {
				outdir[0] = '\0';
			} else {
				strcpy(outdir, arg);
				addslash(outdir);
				if (makedir(outdir) != 0) {
					xxprintf(wrn_mkdirfail, outdir, 'D');
					outdir[0] = '\0';
				}
			}
			break;

		case 'O':				/* output filename */
			if (arg == NULL) {
				outnam[0] = '\0';
			} else {
				strcpy(outnam, arg);
				p = basname(outnam);
				c = *p;  *p = '\0';
				if (makedir(outnam) != 0) {
					xxprintf(wrn_mkdirfail, outnam, 'O');
					outnam[0] = '\0';
				} else {
					*p = c;
				}
			}
			break;

		case 0x00:				/* input file spec */
			if (outnam[0] != '\0') {
				strcpy(outf, outnam);
				outnam[0] = '\0';
			} else if (w_stdout) {
				if (!read_png(arg, &image)) return 1;
				if (!write_bmp(NULL, &image)) return 1;
				if (deletesrc) remove(arg);
				return 0;
			} else {
				if (outdir[0] != '\0') {
					strcat(strcpy(outf, outdir), basname(arg));
				} else {
					strcpy(outf, arg);
				}
#ifdef WIN32_LFN
				strcpy(suffix(outf), is_dos_filename(outf) ? ".BMP" : ".bmp");
#else
				strcpy(suffix(outf), ".bmp");
#endif
			}
			/* ---------------------- */
			if (!read_png(arg, &image)) {
				failure++;
				break;
			}
			renbak(outf);
			if (!write_bmp(outf, &image)) {
				failure++;
				break;
			}
			/* ---------------------- */
			if (copytime) cpyftime(arg, outf);
			if (deletesrc) remove(arg);
			/* ---------------------- */
			success++;
			break;

		default:
			;		/* Ignore unknown option */
		}
	}
	if (failure == 0 && success == 0) {
		if (!r_stdin) usage_exit(argv[0], 255);
		if (!read_png(NULL, &image)) return 1;
		if (outnam[0] != '\0') {
			renbak(outnam);
			return !write_bmp(outnam, &image);
		} else if (w_stdout) {
			return !write_bmp(NULL, &image);
		} else {
			strcat(strcpy(outf, outdir), "___stdin.bmp");
			renbak(outf);
			return !write_bmp(outf, &image);
		}
	}

	return (failure > 255) ? 255 : failure;
}


/* -----------------------------------------------------------------------
**		PNG ファイルの読み込み
*/

#define ERROR_ABORT(s) do { errmsg = (s); goto error_abort; } while (0)

/*
**		.pngファイルからの読み込み
*/
static BOOL read_png(char *fn, IMAGE *img)
{
	png_structp png_ptr;
	png_infop info_ptr, end_info;
	png_uint_32 width, height;
	int bit_depth, color_type;
	int xbit_depth, xcolor_type, xchannels;
	const char *errmsg;
	FILE *fp;

	imgbuf_init(img);

	if (fn == NULL) {
		fn = " (stdin)";
		fp = binary_stdio(fileno(stdin));
	} else {
		fp = fopen(fn, "rb");
	}
	if (fp == NULL) ERROR_ABORT(err_ropenfail);

	set_status("Reading %.80s", basname(fn));

	/* ------------------------------------------------------ */

	png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, fn,
	                                   png_my_error, png_my_warning);
	if (png_ptr == NULL) {
		ERROR_ABORT(err_outofmemory);
	}
	info_ptr = png_create_info_struct(png_ptr);
	end_info = png_create_info_struct(png_ptr);
	if (info_ptr == NULL || end_info == NULL) {
		png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
		ERROR_ABORT(err_outofmemory);
	}
	if (setjmp(png_jmpbuf(png_ptr))) {
		/* If we get here, we had a problem writing the file */
		png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
		ERROR_ABORT(NULL);
	}
	png_init_io(png_ptr, fp);
	png_set_sig_bytes(png_ptr, skip_macbinary(png_ptr));

	/* ------------------------------------------------------ */

	png_read_info(png_ptr, info_ptr);

	png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth,
	             &color_type, NULL, NULL, NULL);

	/* ------------------------------------------------------ */

	if (color_type & PNG_COLOR_MASK_ALPHA) {
		if (alpha_format == P2B_ALPHABMP_NONE) {
			png_set_strip_alpha(png_ptr);
			color_type &= ~PNG_COLOR_MASK_ALPHA;
		}
	} else if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
		if (alpha_format != P2B_ALPHABMP_NONE && expand_trans) {
			png_set_tRNS_to_alpha(png_ptr);
			color_type |=  PNG_COLOR_MASK_ALPHA;
			color_type &= ~PNG_COLOR_MASK_PALETTE;
		}
	}
	if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
		png_set_gray_to_rgb(png_ptr);
	}
	if (color_type == PNG_COLOR_TYPE_RGB ||
		color_type == PNG_COLOR_TYPE_RGB_ALPHA) {
		png_set_bgr(png_ptr);
	}
	if (!(color_type & PNG_COLOR_MASK_ALPHA) && bit_depth == 2) {
		png_set_user_transform_info(png_ptr, NULL, 4, 1);
		png_set_read_user_transform_fn(png_ptr, to4bpp);
	}
	if (bit_depth == 16)
		png_set_strip_16(png_ptr);

	png_read_update_info(png_ptr, info_ptr);

	/* ------------------------------------------------------ */

	png_get_IHDR(png_ptr, info_ptr, &width, &height, &xbit_depth,
	             &xcolor_type, NULL, NULL, NULL);
	xchannels = png_get_channels(png_ptr, info_ptr);

	img->width    = (LONG)width;
	img->height   = (LONG)height;
	img->pixdepth = (UINT)xbit_depth * xchannels;
	img->palnum   = (img->pixdepth <= 8) ? (1 << img->pixdepth) : 0;
	img->topdown  = FALSE;
	img->alpha    = (xcolor_type & PNG_COLOR_MASK_ALPHA) ? TRUE : FALSE;

	if (!imgbuf_alloc(img)) {
		png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
		ERROR_ABORT(err_outofmemory);
	}

	if (img->palnum > 0) {
		if (xcolor_type == PNG_COLOR_TYPE_PALETTE) {
			png_colorp palette;
			int num_palette;
			png_get_PLTE(png_ptr, info_ptr, &palette, &num_palette);
			if (num_palette > (int)img->palnum) num_palette = img->palnum;
			memset(img->palette,       0, img->palnum * sizeof(png_color));
			memcpy(img->palette, palette, num_palette * sizeof(png_color));
		} else {
			int depth = (bit_depth == 16) ? 8 : bit_depth;
			memset(img->palette, 0, img->palnum * sizeof(png_color));
			png_build_grayscale_palette(depth, img->palette);
		}
	}

	/* ------------------------------------------------------ */

	png_set_read_status_fn(png_ptr, row_callback);
	init_progress_meter(png_ptr, img->width, img->height);

	png_read_image(png_ptr, img->rowptr);

	png_read_end(png_ptr, end_info);
	png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);

	/* ------------------------------------------------------ */

	set_status("Read OK %.80s", basname(fn));

	if (fp != stdin) fclose(fp);

	return TRUE;

error_abort:				/* error */
	if (errmsg != NULL) xxprintf(errmsg, fn);
	if (fp != stdin && fp != NULL) fclose(fp);
	imgbuf_free(img);

	return FALSE;
}


/*
**		ファイル先頭にあるマックバイナリを読み跳ばす
*/
static int skip_macbinary(png_structp png_ptr)
{
#if 0
	void png_read_data(png_structp, png_bytep, png_size_t);
	enum { PNG_BYTES_TO_CHECK = 8, MACBIN_SIZE = 128 };	/* ^ in pngrio.c */
	png_byte buf[MACBIN_SIZE];
	png_bytep sig;

	png_read_data(png_ptr, buf, PNG_BYTES_TO_CHECK);
	if (png_sig_cmp(buf, 0, PNG_BYTES_TO_CHECK) == 0)
								return PNG_BYTES_TO_CHECK;

	png_read_data(png_ptr, buf, MACBIN_SIZE);
	sig = buf + MACBIN_SIZE - PNG_BYTES_TO_CHECK;
	if (png_sig_cmp(sig, 0, PNG_BYTES_TO_CHECK) == 0)
								return PNG_BYTES_TO_CHECK;

	xxprintf(err_not_a_png, (char *)png_get_error_ptr(png_ptr));
	longjmp(png_jmpbuf(png_ptr), 1);

#endif
	return 0;	/* to quiet compiler warnings */
}


/*
**		4色形式 -> 16色形式への変換
*/
static void to4bpp(png_structp png_ptr, png_row_infop row_info, png_bytep data)
{
	static const png_byte pix[] = {
		0x00, 0x01, 0x02, 0x03,  0x10, 0x11, 0x12, 0x13,
		0x20, 0x21, 0x22, 0x23,  0x30, 0x31, 0x32, 0x33,
	};
	png_uint_32 rowb;
	png_bytep p, q;
	png_byte c;

	rowb = (row_info->width + 1) / 2;
	q = data + rowb;
	p = data + rowb / 2;

	if (rowb % 2 == 1) {
		c = *p;
		*(--q) = pix[c >> 4];
	}
	while (p > data) {
		c = *(--p);
		*(--q) = pix[c & 0x0F];
		*(--q) = pix[c >> 4];
	}
	row_info->bit_depth   = 4;
	row_info->pixel_depth = 4;
	row_info->rowbytes    = rowb;
}


/* -----------------------------------------------------------------------
**		BMP ファイルの書き込み
*/

/*
**		.bmp ファイルの書き込み
*/
static BOOL write_bmp(char *fn, IMAGE *img)
{
	BYTE bfh[FILEHED_SIZE + BMPV4HED_SIZE];
	BYTE *const bih = bfh + FILEHED_SIZE;
	BYTE rgbq[RGBQUAD_SIZE];
	BOOL alpha_bitfield;
	DWORD bihsize, offbits, filesize;
	PALETTE *pal;
	const char *errmsg;
	FILE *fp;
	UINT i;

	if (fn == NULL) {
		fn = " (stdout)";
		fp = binary_stdio(fileno(stdout));
	} else {
		fp = fopen(fn, "wb");
	}
	if (fp == NULL) ERROR_ABORT(err_wopenfail);

	set_status("Writing %.80s", basname(fn));

	/* ------------------------------------------------------ */

	alpha_bitfield = (img->alpha && alpha_format == P2B_ALPHABMP_BITFIELD);
	bihsize = (alpha_bitfield) ? BMPV4HED_SIZE : INFOHED_SIZE;
	offbits = FILEHED_SIZE + bihsize + RGBQUAD_SIZE * img->palnum;
	filesize = offbits + img->imgbytes;

	memset(bfh, 0, sizeof(bfh));

	mputwl( bfh + BFH_WTYPE   , BMP_SIGNATURE);
	mputdwl(bfh + BFH_DSIZE   , filesize);
	mputdwl(bfh + BFH_DOFFBITS, offbits);

	mputdwl(bih + BIH_DSIZE     , bihsize);
	mputdwl(bih + BIH_LWIDTH    , (DWORD)img->width);
	mputdwl(bih + BIH_LHEIGHT   , (DWORD)img->height);
	mputwl( bih + BIH_WPLANES   , 1);
	mputwl( bih + BIH_WBITCOUNT , img->pixdepth);
	mputdwl(bih + BIH_DSIZEIMAGE, img->imgbytes);

	if (alpha_bitfield) {
		mputdwl(bih + BIH_DCOMPRESSION, BI_BITFIELDS);
		mputdwl(bih + B4H_DALPHAMASK, 0xFF000000);
		mputdwl(bih + B4H_DREDMASK  , 0x00FF0000);
		mputdwl(bih + B4H_DGREENMASK, 0x0000FF00);
		mputdwl(bih + B4H_DBLUEMASK , 0x000000FF);
	}

	if (fwrite(bfh, (FILEHED_SIZE + bihsize), 1, fp) != 1)
		ERROR_ABORT(err_writeerr);

	/* ------------------------------------------------------ */

	memset(rgbq, 0, sizeof(rgbq));

	for (pal = img->palette, i = img->palnum; i > 0; i--, pal++) {
		rgbq[RGBQ_RED]   = pal->red;
		rgbq[RGBQ_GREEN] = pal->green;
		rgbq[RGBQ_BLUE]  = pal->blue;
		if (fwrite(rgbq, RGBQUAD_SIZE, 1, fp) != 1)
			ERROR_ABORT(err_writeerr);
	}

	/* ------------------------------------------------------ */

	if ((errmsg = write_rgb_bits(img, fp)) != NULL) ERROR_ABORT(errmsg);

	/* ------------------------------------------------------ */

	set_status("OK      %.80s", basname(fn));
	feed_line();

	fflush(fp);
	if (fp != stdout) fclose(fp);
	imgbuf_free(img);

	return TRUE;

error_abort:				/* error */
	xxprintf(errmsg, fn);
	if (fp != stdout && fp != NULL) fclose(fp);
	imgbuf_free(img);

	return FALSE;
}


/*
**		BI_RGB (無圧縮) 形式の画像データを書く
*/
static const char *write_rgb_bits(IMAGE *img, FILE *fp)
{
#if 1
	DWORD wr  = 16*1024*1024;
	DWORD num = img->imgbytes;
	BYTE *ptr = img->bmpbits;

	while (num > 0) {
		if (wr > num) wr = num;

		if (fwrite(ptr, wr, 1, fp) != 1)
			return err_writeerr;

		ptr += wr; num -= wr;
	}
#else
	if (fwrite(img->bmpbits, img->imgbytes, 1, fp) != 1)
		return err_writeerr;
#endif
	return NULL;
}


/*
**	メモリへ little-endien 形式 4バイト無符号整数を書く
*/
static void mputdwl(void *ptr, unsigned long val)
{
	unsigned char *p = ptr;

	p[0] = (unsigned char)(val       & 0xFF);
	p[1] = (unsigned char)(val >>  8 & 0xFF);
	p[2] = (unsigned char)(val >> 16 & 0xFF);
	p[3] = (unsigned char)(val >> 24 & 0xFF);
}


/*
**	メモリへ little-endien 形式 2バイト無符号整数を書く
*/
static void mputwl(void *ptr, unsigned int val)
{
	unsigned char *p = ptr;

	p[0] = (unsigned char)(val      & 0xFF);
	p[1] = (unsigned char)(val >> 8 & 0xFF);
}


/* -----------------------------------------------------------------------
**		ヘルプスクリーンの表示
*/

/*
**		使用法表示
*/
static void usage_exit(char *argv0, int status)
{
	static const char str_usage[] =
#ifdef JAPANESE /* -------------------------- */
#ifdef SJIS_ESCAPE
#define SJ_ESC(esc,raw)	esc
#else
#define SJ_ESC(esc,raw)	raw
#endif
		"png2bmp, PNG -> BMP コンバータ - version " PNG2BMP_VERSION "\n"
		"   " PNG2BMP_COPYRIGHT "\n"
		"   Compiled with libpng " PNG_LIBPNG_VER_STRING " and zlib " ZLIB_VERSION ".\n"
		"\n"
		"使い方 : %s [-スイッチ] 入力ファイル名 ...\n"
		"       : ... | %s [-スイッチ] | ...\n"
		"\n"
		"入力ファイル名にはワイルドカードが使えます (* と ?)\n"
		"出力ファイル名は入力ファイル名の拡張子を .bmp に変えた名前になります\n"
		"\n"
		"スイッチオプション (小文字でも可) :\n"
		"   -A       アルファチャネルを保存する (32bit ARGB BMP 形式で)\n"
		"   -B       アルファチャネルを保存する (32bit Bitfield BMP 形式で)\n"
		"   -R       透明色をアルファチャネルに変換する (-A か -B と共に使う)\n"
		"   -O name  出力ファイル名を指定する\n"
		"   -D dir   ファイルを出力するディレクトリを指定する\n"
		"   -E       変換が成功した場合には入力ファイルを削除する\n"
		"   -T       入力ファイルのタイムスタンプを出力ファイルに設定する\n"
		"   -Q       処理中, 一切の" SJ_ESC("表\示","表示") "をしない\n"
		"   -L       処理中のエラーをログファイル(%s)に記録する\n"
		"   -X       標準入力／標準出力を介した変換を無効にする\n";
#else  /* ----------------------------------- */
		"png2bmp, a PNG-to-BMP converter - version " PNG2BMP_VERSION "\n"
		"   " PNG2BMP_COPYRIGHT "\n"
		"   Compiled with libpng " PNG_LIBPNG_VER_STRING " and zlib " ZLIB_VERSION ".\n"
		"\n"
		"Usage: %s [-switches] inputfile(s) ...\n"
		"   or: ... | %s [-switches] | ...\n"
		"\n"
		"List of input files may use wildcards (* and ?)\n"
		"Output filename is same as input filename, but extension .bmp\n"
		"\n"
		"Switches (case-insensitive) :\n"
		"   -A       Preserve alpha channel (save in 32bit ARGB BMP format)\n"
		"   -B       Preserve alpha channel (save in 32bit Bitfield BMP format)\n"
		"   -R       Convert transparent color to alpha channel (use with -A or -B)\n"
		"   -O name  Specify name for output file\n"
		"   -D dir   Output files into dir\n"
		"   -E       Delete input files after successful conversion\n"
		"   -T       Set the timestamp of input file on output file\n"
		"   -Q       Quiet mode\n"
		"   -L       Log errors to %s file\n"
		"   -X       Disable conversion through standard input/output\n";
#endif /* ----------------------------------- */
#if defined(WIN32) || defined(MSDOS)
	char exename[FILENAME_MAX];
	char *p;

	argv0 = strcpy(exename, basname(argv0));
	for (p = argv0; *p != '\0'; p++) *p = tolower(*p);
#endif
	fprintf(stdout, str_usage, argv0, argv0, errlogfile);

	exit(status);
}

