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

#include "common.h"
#include "bmphed.h"
#if PNG_LIBPNG_VER_MINOR > 4
#include <pngpriv.h>
#endif
#define PNG2BMP_VER		"1.52 (Feb 13 2001)"

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

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

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

static int read_file( char *, IMAGE * );
static int read_stdin( IMAGE * );
static int write_file( char *, IMAGE * );
static int write_stdout( IMAGE * );
static int read_png( char *, FILE *, IMAGE * );
static int skip_macbinary( png_structp );
static void to4bpp( png_structp, png_row_infop, png_bytep );
static char *write_bmp( char *, FILE *, IMAGE * );
static void mputdwl( void *, unsigned long );
static void mputwl( void *, unsigned int );
static void usage( 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 __RSXNT__
	_wildcard( &argc, &argv );
#endif
	envargv( &argc, &argv, "B2P" );

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

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

		case 'F':
			/* '-F' 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 ){
					xprintf( 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 ){
					xprintf( wrn_mkdirfail, outnam, 'O' );
					outnam[0] = '\0';
				}else{
					*p = c;
				}
			}
			break;

		case 0x00:		/* input file spec */
			if( r_stdin ) break;
			if( w_stdout ){
				if( !read_file(arg,&image) ) return 1;
				if( !write_stdout(&image)  ) return 1;
				if( deletesrc ) remove( arg );
				return 0;
			}
			/* ---------------------- */
			if( outnam[0]!='\0' ){
				strcpy( outf, outnam );
				outnam[0] = '\0';
			}else{
				if( outdir[0]!='\0' ){
					strcpy( outf, outdir );
					strcat( outf, 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_file(arg,&image) ){
				failure++;
				break;
			}
			renbak( outf );
			if( !write_file(outf,&image) ){
				failure++;
				break;
			}
			/* ---------------------- */
			if( copytime ) cpyftime( arg, outf );
			if( deletesrc ) remove( arg );
			/* ---------------------- */
			success++;
			break;

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

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


/*
**		ディスクファイルから画像データを読む
*/
static int read_file( char *fn, IMAGE *img )
{
	FILE *fp;
	int b;

	fp = fopen( fn, "rb" );
	if( fp==NULL ){
		xprintf( err_ropenfail, fn );
		return 0;
	}
	b = read_png( fn, fp, img );
	fclose( fp );

	return b;
}


/*
**		標準入力から画像データを読む
*/
static int read_stdin( IMAGE *img )
{
	static char fn[] = " (stdin)";
	FILE *fp;
	int b;

#ifdef USE_FDOPEN
	fp = fdopen( fileno(stdin), "rb" );
	if( fp==NULL ){
		xprintf( err_ropenfail, fn );
		return 0;
	}
	b = read_png( fn, fp, img );
	fclose( fp );
#else
#ifdef USE_SETMODE
	setmode( fileno(stdin), O_BINARY );
#endif
	fp = stdin;
	b = read_png( fn, fp, img );
#endif
	return b;
}


/*
**		ディスクファイルへ画像データを書く
*/
static int write_file( char *fn, IMAGE *img )
{
	FILE *fp;
	char *p;

	fp = fopen( fn, "wb" );
	if( fp==NULL ){
		xprintf( err_wopenfail, fn );
		imgfree( img );
		return 0;
	}
	p = write_bmp( fn, fp, img );
	fclose( fp );
	imgfree( img );
	if( p!=NULL ){
		xprintf( p, fn );
		return 0;
	}

	return 1;
}


/*
**		標準出力へ画像データを書く
*/
static int write_stdout( IMAGE *img )
{
	static char fn[] = " (stdout)";
	FILE *fp;
	char *p;

#ifdef USE_FDOPEN
	fp = fdopen( fileno(stdout), "wb" );
	if( fp==NULL ){
		xprintf( err_wopenfail, fn );
		imgfree( img );
		return 0;
	}
	p = write_bmp( fn, fp, img );
	fclose( fp );
#else
#ifdef USE_SETMODE
	setmode( fileno(stdout), O_BINARY );
#endif
	fp = stdout;
	p = write_bmp( fn, fp, img );
#endif
	imgfree( img );
	if( p!=NULL ){
		xprintf( p, fn );
		return 0;
	}

	return 1;
}


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

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

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

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

	png_ptr = png_create_read_struct( PNG_LIBPNG_VER_STRING, fn,
	                                    png_my_error, png_my_warning );
	if( png_ptr==NULL ){
		xprintf( err_outofmemory, fn );
		return 0;
	}
	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 );
		xprintf( err_outofmemory, fn );
		return 0;
	}
#if PNG_LIBPNG_VER_MINOR > 4
	if( setjmp(png_jmpbuf(png_ptr)) ){
#else
	if( setjmp(png_ptr->jmpbuf) ){
#endif
		/* If we get here, we had a problem writing the file */
		png_destroy_read_struct( &png_ptr, &info_ptr, &end_info );
		imgfree( img );
		return 0;
	}
	png_init_io( png_ptr, fp );
	memset( img, 0, sizeof(IMAGE) );
	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 );

	img->width  = (LONG)width;
	img->height = (LONG)height;

	if( color_type==PNG_COLOR_TYPE_RGB ||
		color_type==PNG_COLOR_TYPE_RGB_ALPHA ){
		img->pixdepth = 24;		/* bit_depths 8, 16 */
		img->palnum   = 0;
	}else{
		switch( bit_depth ){	/* bit depths 1, 2, 4, 8, 16 */
			case 2:  img->pixdepth = 4;		break;
			case 16: img->pixdepth = 8;		break;
			default: img->pixdepth = bit_depth;
		}
		img->palnum = 1 << img->pixdepth;
	}
	if( !imgalloc(img) ){
		xprintf( err_outofmemory, fn );
#if PNG_LIBPNG_VER_MINOR > 4
		longjmp( png_jmpbuf(png_ptr), 1 );
#else
		longjmp( png_ptr->jmpbuf, 1 );
#endif
	}

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

	if( color_type & PNG_COLOR_MASK_ALPHA )
						png_set_strip_alpha( png_ptr );
	if( 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 );
	if( color_type==PNG_COLOR_TYPE_RGB ||
	    color_type==PNG_COLOR_TYPE_RGB_ALPHA ){
		png_set_bgr( png_ptr );
	}
	png_read_update_info( png_ptr, info_ptr );

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

	if( img->palnum > 0 ){
		if( color_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 ;
			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 );
#if PNG_LIBPNG_VER_MINOR > 4
	png_ptr->transformations |= PNG_INTERLACE;
#endif
	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 %s", basname(fn) );

	return 1;
}


/*
**		ファイル先頭にあるマックバイナリを読み跳ばす
*/
static int skip_macbinary( png_structp png_ptr )
{
	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;

	xprintf( err_not_a_png, (char *)png_get_error_ptr(png_ptr) );
#if PNG_LIBPNG_VER_MINOR > 4
	longjmp( png_jmpbuf(png_ptr), 1 );
#else
	longjmp( png_ptr->jmpbuf, 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 char *write_bmp( char *fn, FILE *fp, IMAGE *img )
{
	BYTE bfh[FILEHED_SIZE+INFOHED_SIZE];
	BYTE * const bih = bfh + FILEHED_SIZE;
	BYTE rgbq[RGBQUAD_SIZE];
	DWORD offbits;
	DWORD filesize;
	PALETTE *pal;
	UINT i;

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

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

	offbits  = FILEHED_SIZE + INFOHED_SIZE + 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      , INFOHED_SIZE );
	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( fwrite(bfh,(FILEHED_SIZE+INFOHED_SIZE),1,fp)!=1 ) return 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 ) return err_writeerr;
	}

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

	if( fwrite(img->bmpbits,img->imgbytes,1,fp)!=1 ) return err_writeerr;

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

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

	return NULL;	/* success */
}


/*
**	メモリへ 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( char *argv0, int status )
{
	static const char str_usage[] =
#ifdef JAPANESE /* -------------------------- */
#ifdef NON_SHIFT_JIS	/* euc_jp etc. */
#define SJ_ESC(esc,raw)	raw
#else /* SHIFT_JIS */
#define SJ_ESC(esc,raw)	esc
#endif
		"png2bmp, PNG -> BMP コンバータ - version " PNG2BMP_VER "\n"
		"   Copyright (C) 1999-2001 MIYASAKA Masaru\n"
		"   Compiled with libpng " PNG_LIBPNG_VER_STRING " and zlib " ZLIB_VERSION ".\n"
		"\n"
		"使い方 : %s [-スイッチ] 入力ファイル名 ...\n"
		"       : ... | %s [-スイッチ] | ...\n"
		"\n"
		"入力ファイル名にはワイルドカードが使えます (* と ?)\n"
		"出力ファイル名は入力ファイル名の拡張子を .bmp に変えた名前になります\n"
		"\n"
		"スイッチオプション (小文字でも可) :\n"
		"   -O name  出力ファイル名を指定する\n"
		"   -D dir   ファイルを出力するディレクトリを指定する\n"
		"   -E       変換が成功した場合には入力ファイルを削除する\n"
		"   -T       入力ファイルのタイムスタンプを出力ファイルに設定する\n"
		"   -Q       処理中, 一切の" SJ_ESC("\x95\x5C\x8E\xA6","表示") "をしない\n"
		"   -L       処理中のエラーをログファイル(%s)に記録する\n";
#else  /* ----------------------------------- */
		"png2bmp, a PNG-to-BMP converter - version " PNG2BMP_VER "\n"
		"   Copyright (C) 1999-2001 MIYASAKA Masaru\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"
		"   -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";
#endif /* ----------------------------------- */
#if defined(WIN32) || defined(MSDOS)
	char c,*p,exef[FILENAME_MAX];

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

	exit( status );
}

