/*
 * anaglyph-join.cc
 * 
 * PNG anaglyph image joiner. 
 * Version 0.8
 * 
 * Copyright (c) 2004 by Wolfgang Wieser (wwieser@gmx.de) 
 * 
 * This file may be distributed and/or modified under the terms of the 
 * GNU General Public License version 2 as published by the Free Software 
 * Foundation. 
 * 
 * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
 * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 * 
 */

#include <hlib/prototypes.h>
#include <hlib/pngread.h>
#include <hlib/pngwrite.h>
#include <hlib/valuehandler.h>
#include <hlib/parmanager.h>
#include <hlib/parconsumerovl.h>
#include <hlib/srccmd.h>

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <assert.h>
#include <ctype.h>

//#include <png.h>

#include <Qt/qapplication.h>
#include <Qt/qpainter.h>
#include <Qt/qbrush.h>
#include <Qt/qlayout.h>
#include <Qt/qlabel.h>
#include <Qt/qcdestyle.h>
#include <Qt/qevent.h>

#include <QTX/xpainter.h>
#include <QTX/rgbcolorsource.h>
#include <QTX/ximagewidget.h>

// gcc -W -Wall -O2 anaglyph-join.cc -o anaglyph-join -fno-rtti -fno-exceptions -funroll-loops -ffast-math -finline-functions -march=athlon-xp -mmmx -msse -m3dnow -mfpmath=sse -I. -IQt -fmessage-length=$COLUMNS -lqt-mt -L/opt/Qt/lib libqtxlib.a libhlib.a -lpng -lz

QApplication *qapp=NULL;

template<typename T>union RGB
{
	struct { T r,g,b; };
	T c[3];
};


struct RGB_float_ValueHandler : par::ValueHandler
{
	RGB_float_ValueHandler() : par::ValueHandler() {}
	~RGB_float_ValueHandler() {}
	
	void *alloc(ParamInfo *)
		{  return(LMalloc(sizeof(RGB<float>)));  }
	void free(ParamInfo *,void *val)
		{  LFree(val);  }
	int copy(ParamInfo *,void *dest,const void *src,int op)
	{
		if(op!=SOPCopy) return(-2);
		*(RGB<float>*)dest = *(RGB<float>*)src;
		return(0);
	}
	ParParseState parse(ParamInfo *pi,void *val,par::ParamArg *pa);
	char *print(ParamInfo *,const void *_val,char *dest,size_t len)
	{
		const size_t minlen=64;
		RGB<float> *val=(RGB<float>*)_val;
		if(len<minlen)
		{  dest=ValueHandler::stralloc(minlen);  len=minlen;  }
		if(dest)
		{  snprintf(dest,len,"%g,%g,%g",val->c[0],val->c[1],val->c[2]);  }
		return(dest);
	}
} rgb_float_valuehandler;


static inline par::PAR::ParParseState templ_str2val(RGB<float> *val,
	const char *arg,char **endptr)
{
	errno=0;
	size_t alen=strlen(arg);
	if(*arg=='#')
	{
		// Hex triplet: 
		if(alen!=7)  return(par::PAR::PPSIllegalArg);
		for(int i=0; i<3; i++)
		{
			char tmp[3]={arg[i*2+1],arg[i*2+2],'\0'};
			int tmpv=strtol(tmp,endptr,16);
			if(tmpv<0 || tmpv>0xff)  return(par::PAR::PPSIllegalArg);
			val->c[i]=float(tmpv)/255.0f;
		}
	}
	else
	{
		// Interprete "val,val,val" with val being floats in range 
		// 0..1 or in range 0..255: 
		int ncons=0;
		if(sscanf(arg,"%f%*c%f%*c%f%n",
			&val->c[0],&val->c[1],&val->c[2],&ncons)<3)
		{  return(par::PAR::PPSIllegalArg);  }
		if(ncons>=0) *endptr=(char*)arg+ncons;
		if(val->c[0]>3.0f && val->c[1]>3.0f && val->c[2]>3.0f)
		{
			val->c[0]/=255.0f;
			val->c[1]/=255.0f;
			val->c[2]/=255.0f;
		}
	}
	return(par::PAR::PPSSuccess);
}


RGB_float_ValueHandler::ParParseState RGB_float_ValueHandler::parse(
	ParamInfo *pi,void *valptr,par::ParamArg *arg)
{
	assert(pi->ptype==PAR::PTParameter);
	
	const char *a;
	par::PAR::ParParseState rv=_check_trim_val(&a,arg,/*allow_all=*/0);
	if(rv>0)  return(rv);
	
	RGB<float> val;
	char *endptr;
	rv=templ_str2val(&val,a,&endptr);
	if(endptr==a)  rv=PAR::PPSIllegalArg;
	if(rv>0)  return(rv);   // error
	
	rv=_check_arg_end(rv,endptr,arg);
	// rv<=0 here. 
	
	// other modes than `=' currently unsupported. 
	if(arg->assmode!='=' && arg->assmode!='\0' && rv<=0)
	{  rv=par::PAR::PPSIllegalAssMode;  }
	
	// Store it: 
	*(RGB<float>*)(valptr)=val;
	return(rv);
}

//------------------------------------------------------------------------------

class AnaglyphConverter : 
	public par::ParameterConsumer_Overloaded
{
	private:
		// Preserve color in anaglyph?
		bool preserve_color;
		
		struct LRInfo
		{
			// Foreground color for the side: 
			RGB<float> fg;
			// Gamma value for grayscale to be applied after gray conversion: 
			// (Gamma correction of loaded image.) 
			float g_gamma;
		} l,r;
		
		bool have_g_gamma;
		#if 0
		bool fast_path;
		#endif
		
		// Color weights for grayscale conversion. 
		RGB<float> g_weight;
		
		// Background offset to add: 
		RGB<float> bgoff;
		
		// Convert RGB to grayscale: 
		inline float _CvGray(const RGB<float> &c)
		{  return( g_weight.r*c.r + g_weight.g*c.g + g_weight.b*c.b );  }
		
		// [Overriding virtual:]
		int CheckParams();
	public:
		AnaglyphConverter(par::ParameterManager *parman);
		~AnaglyphConverter();
		
		// Convert integer to float representation and vise versa: 
		static inline float ColI2F(const uchar *ptr,int bpp)
		{
			switch(bpp)
			{
				case 1:  return(float(*ptr)/255.0f);
				case 2:  return(float(*(u_int16_t*)ptr)/65535.0f);
			}
			assert(0);  return(0.0);
		}
		static inline void ColF2I(uchar *ptr,float v,int bpp)
		{
			switch(bpp)
			{
				case 1:  *ptr=uchar(v*255.0f+0.5f);  break;
				case 2:  *((u_int16_t*)ptr)=u_int16_t(v*65535.0f+0.5f);  break;
				default: assert(0);
			}
		}
		static inline void ColI2F(RGB<float> *v,const uchar *ptr,int bpp)
		{
			for(short int i=0; i<3; i++,ptr+=bpp)
				v->c[i]=ColI2F(ptr,bpp);
		}
		static inline void ColF2I(uchar *ptr,const RGB<float> &v,int bpp)
		{
			for(short int i=0; i<3; i++,ptr+=bpp)
				ColF2I(ptr,v.c[i],bpp);
		}
		
		// Do the conversion: join left and right pixel values. 
		// Works for RGB and grayscale images. 
		void Convert(RGB<float> &res,RGB<float> &lv,RGB<float> &rv);
		inline void Convert(RGB<float> &res,float lgray,float rgray)
		{
			#if 0
			if(fast_path)
			{
				res.c[0] = lgray;
				res.c[1] = rgray;
				res.c[2] = rgray;
				return;
			}
			#endif
			if(have_g_gamma)
			{
				lgray=powf(lgray,l.g_gamma);
				rgray=powf(rgray,r.g_gamma);
			}
			for(short int i=0; i<3; i++)
			{
				float restmp = l.fg.c[i]*lgray + r.fg.c[i]*rgray + 
					bgoff.c[i];
				if(restmp>1.0f)  restmp=1.0f;
				else if(restmp<0.0f)  restmp=0.0f;
				res.c[i]=restmp;
			}
		}
};


void AnaglyphConverter::Convert(RGB<float> &res,RGB<float> &lv,RGB<float> &rv)
{
	if(preserve_color)  for(short int i=0; i<3; i++)
	{
		float restmp = l.fg.c[i]*lv.c[i] + r.fg.c[i]*rv.c[i] + bgoff.c[i];

		if(restmp>1.0f)  restmp=1.0f;
		else if(restmp<0.0f)  restmp=0.0f;
		res.c[i]=restmp;
	}
	else
	{  Convert(res,_CvGray(lv),_CvGray(rv));  }
}


int AnaglyphConverter::CheckParams()
{
	int failed=0;
	
	if(l.g_gamma<=0.0 || r.g_gamma<=0.0)
	{  fprintf(stderr,"gamma values must be positive.\n");  ++failed;  }
	
	float gray_sum=g_weight.c[0]+g_weight.c[1]+g_weight.c[2];
	if(fabsf(gray_sum-1.0)>1e-6)
	{  fprintf(stderr,"Warning: gray weight sum %f!=1\n",gray_sum);  }
	
	if(!failed)
	{
		have_g_gamma=(fabsf(l.g_gamma-1.0f)>1e-6f || 
			fabsf(r.g_gamma-1.0f)>1e-6f);
		
		#if 0
		fast_path=0;
		do {
			if(have_g_gamma)  break;
			float sum = fabsf(bgoff.c[0])+fabsf(bgoff.c[1])+fabsf(bgoff.c[2]);
			if(sum>1e-5f)  break;
			// If gray_sum is larger than 1 we can have color values>1: 
			if(gray_sum>1.0f)  break;
			// In the following case we can have color values<0: 
			if(g_weight.c[0]<0.0f || g_weight.c[1]<0.0f || g_weight.c[2]<0.0f)
				break;
			
			// Only works for red/cyan for now: 
			if(fabsf(l.fg.c[0]-1.0f) > 1e-6f || 
			   fabsf(l.fg.c[1]) > 1e-6f || 
			   fabsf(l.fg.c[2]) > 1e-6f || 
			   fabsf(r.fg.c[0]) > 1e-6f || 
			   fabsf(r.fg.c[1]-1.0f) > 1e-6f || 
			   fabsf(r.fg.c[2]-1.0f) > 1e-6f )  break;
			
			fast_path=1;
		} while(0);
		#endif
	}
	
	#if 0
	if(fast_path)
	{  fprintf(stderr,"Using fast path\n");  }
	#endif
	
	return(failed);
}


AnaglyphConverter::AnaglyphConverter(par::ParameterManager *parman) : 
	par::ParameterConsumer_Overloaded(parman)
{
	preserve_color=0;
	l=(LRInfo){ fg: { c: {1.0f, 0.0f, 0.0f} }, g_gamma: 1.0f };
	r=(LRInfo){ fg: { c: {0.0f, 1.0f, 1.0f} }, g_gamma: 1.0f };
	g_weight=(RGB<float>){ c: { 0.30f, 0.59f, 0.11f } };
	bgoff=(RGB<float>){ c: { 0.0f, 0.0f, 0.0f } };
	
	have_g_gamma=0;
	#if 0
	fast_path=0;
	#endif
	
	SetSection(NULL);
	AddParam("c|color",
		"preserve color in anaglyph (useful for red-cyan) [no]",
		&preserve_color);
	ParameterConsumer::AddParam("gw",PTParameter,
		"weights for grayscale conversion of RGB input; sum should be 1 "
		"[0.3,0.59,0.11]",
		&g_weight,&rgb_float_valuehandler);
	ParameterConsumer::AddParam("bg",PTParameter,
		"background color to add to output image [#000000]",
		&bgoff,&rgb_float_valuehandler);
	
	SetSection("l");
	ParameterConsumer::AddParam("c",PTParameter,
		"primary color of left side (color of glasses) [#ff0000]",
		&l.fg,&rgb_float_valuehandler);
	
	SetSection("r");
	ParameterConsumer::AddParam("c",PTParameter,
		"primary color of right side (color of glasses) [#00ffff]",
		&r.fg,&rgb_float_valuehandler);
	
	for(int i=0; i<2; i++)
	{
		LRInfo *p=i ? &r : &l;
		SetSection(i ? "r" : "l");
		
		AddParam("ggma",
			"gray channel gamma value for gamma correction "
			"(only if color is not preserved) [1]",
			&p->g_gamma);
	}
	
	assert(!add_failed);
}

AnaglyphConverter::~AnaglyphConverter()
{
}

//------------------------------------------------------------------------------

class ImageDisplay : 
	public par::ParameterConsumer_Overloaded,
	public QTX::XImageWidget
{
	private:
		QTX::XPainter *any;
		QTX::RGBColorSource *rgb;
		
		bool enabled;
		bool do_wait;
		int last_ye;
		
		void keyPressEvent(QKeyEvent *);
		// [Overriding virtuals:]
		int CheckParams();
	public:
		ImageDisplay(par::ParameterManager *parman);
		~ImageDisplay();
		
		bool must_quit;
		
		void Init(const char *name,int w,int h);
		
		inline void Draw(int x,int y,const RGB<float> &col)
		{
			if(!enabled) return;
			PutPixel(x,y,rgb->color(col.r,col.g,col.b));
		}
		
		inline void DoneRow(int yco,bool last)
		{
			if(!enabled) return;
			//PutImageRow(yco);
			if(!((yco+1)%20))
			{
				PutImage(0,yco-19,ImageWidth(),20);
				last_ye=yco;
				qapp->processEvents();
			}
			else if(last && (++last_ye)<ImageHeight())
			{
				PutImage(0,last_ye,ImageWidth(),ImageHeight()-last_ye);
				last_ye=ImageHeight()-1;
			}
		}
		
		void Done();
};


void ImageDisplay::Init(const char *name,int w,int h)
{
	if(!enabled)  return;
	
	RefString tmp;
	tmp.sprintf(0,"AnaglyphJoin: %s",name);
	setCaption(tmp.str());
	
	if(ImageWidth()!=w || ImageHeight()!=h)
	{
		CreateImage(w,h);
		resize(w,h);
		show();
	}
	
	last_ye=-1;
}


void ImageDisplay::Done()
{
	if(!enabled)  return;
	
	if(do_wait)
	{  qapp->exec();  }
}


void ImageDisplay::keyPressEvent(QKeyEvent *ev)
{
	switch(ev->key())
	{
		case Key_Return:
		case Key_Space:
			qapp->quit();
			break;
		case Key_Q:
			must_quit=1;
			qapp->quit();
			break;
		default:  return;  // Do not accept(). 
	}
	
	ev->accept();
}


int ImageDisplay::CheckParams()
{
	if(do_wait)
	{  enabled=1;  }
	
	return(0);
}


ImageDisplay::ImageDisplay(par::ParameterManager *parman) : 
	par::ParameterConsumer_Overloaded(parman),
	QTX::XImageWidget(0,0)
{
	enabled=0;
	do_wait=0;
	must_quit=0;
	
	last_ye=-1;
	
	QPainter pnt(this);
	pnt.setBrush(QBrush(Qt::black,Qt::SolidPattern));
	any=new QTX::XPainter(&pnt,true);
	assert(any);
	
	rgb=new QTX::RGBColorSource(any);
	assert(rgb);
	
	setBackgroundColor(Qt::black);
	
	SetSection(NULL);
	AddParam("x|dpy","display images being processed [no]",&enabled);
	AddParam("wait","wait for key press between frames (implies -dpy) [no]",
		&do_wait);
	assert(!add_failed);
}

ImageDisplay::~ImageDisplay()
{
	delete any;
	delete rgb;
}

//------------------------------------------------------------------------------

class ImageIO : 
	public par::ParameterConsumer_Overloaded
{
	private:
		RefString lpattern,rpattern,opattern;
		
		int nframes;
		int startframe;
		int framejump;
		
		int compress_level;
		
		int curr_frame;
		
		int _DoOpen(PNGReader *in,const char *side,const RefString &file,
			unsigned int &width,unsigned int &height,int &depth);
		
		// [Overriding virtuals:]
		int CheckParams();
	public:
		ImageIO(par::ParameterManager *parman);
		~ImageIO();
		
		// Return failure code <0 or 1 if finished. 
		int ProcessNextFile(AnaglyphConverter *anacon,ImageDisplay *dpy);
};


// Return value: -1 -> error; 0 -> OK, no %d-spec; 1 -> OK, with %d-spec
static int CheckFramePattern(const char *ptr)
{
	if(!ptr)  return(-1);
	ptr=strchr(ptr,'%');
	if(!ptr)  return(0);
	++ptr;
	while(isdigit(*ptr))  ++ptr;
	if(*ptr!='x' && *ptr!='d' && *ptr!='X')  return(-1);
	if(strchr(ptr,'%'))  return(-1);
	return(1);
}


int ImageIO::_DoOpen(PNGReader *in,const char *side,const RefString &file,
	unsigned int &width,unsigned int &height,int &depth)
{
	int rv=in->Open(file.str());
	if(rv)
	{
		fprintf(stderr,"Failed to open %s image %s: %s\n",
			side,file.str(),
			rv==-10 ? "not a PNG image" : 
				(rv==-3 ? in->GetError() : strerror(errno)));
		return(-2);
	}
	
	rv=in->GetSize(&width,&height,&depth);
	if(rv)
	{
		fprintf(stderr,"Reading %s image %s: %s\n",
			side,file.str(),
			rv==-10 ? "not supported image properties found" : 
				(rv==-3 ? in->GetError() : strerror(errno)));
		return(-2);
	}
	
	if(in->GetInterlaceType()!=PNG_INTERLACE_NONE)
	{
		fprintf(stderr,"Reading %s image %s: interlaced image not supported\n",
			side,file.str());
		return(-2);
	}
	
	rv=in->SetBasicBitOps(
		PNGReader::PNGR_Palette2RGB | 
		PNGReader::PNGR_GrayExpand | 
		PNGReader::PNGR_StripAlpha | 
		PNGReader::PNGR_Unpack124, 
		/*sane=*/1 );
	assert(rv==0);
	
	// Cannot be used when we preserve colors. 
	//rv=in->SetRBG2GrayConversion(30000,59000); // blue=11000
	//assert(rv==0);
	//
	//rv=in->SetGamma(1.0,1.0);
	//assert(rv==0);
	
	if(depth<8)  depth=8;
	assert(depth==8 || depth==16);
	
	rv=in->StartReading();
	assert(rv==0);
	
	return(0);
}


int ImageIO::ProcessNextFile(AnaglyphConverter *anacon,
	ImageDisplay *dpy)
{
	if(nframes>=0 && curr_frame>=startframe+nframes)  return(1);
	
	RefString lfile,rfile,ofile;
	lfile.sprintf(0,lpattern.str(),curr_frame);
	rfile.sprintf(0,rpattern.str(),curr_frame);
	ofile.sprintf(0,opattern.str(),curr_frame);
	curr_frame+=framejump;
	
	if(nframes<0)
	{
		if(access(lfile.str(),R_OK) || access(rfile.str(),R_OK))
		{  printf("No more frames.\n");  return(1);  }
	}
	
	printf("Conversion: %s + %s -> %s\n",lfile.str(),rfile.str(),ofile.str());
	
	PNGReader lin,rin;
	unsigned int width,height,width2,height2;
	int ldepth,rdepth;
	if(_DoOpen(&lin,"left",lfile,width,height,ldepth) || 
	   _DoOpen(&rin,"right",rfile,width2,height2,rdepth))  return(-2);
	
	if(width!=width2 || height!=height2)
	{  fprintf(stderr,"Image sizes not euqal: %dx%d vs. %dx%d\n",
		width,height,width2,height2);  return(-2);  }
	
	PNGWriter out;
	int rv=out.Open(ofile.str(),compress_level);
	if(rv)
	{
		fprintf(stderr,"Failed to open %s: %s\n",
			ofile.str(),
			rv==-3 ? out.GetError() : strerror(errno));
		return(-2);
	}
	
	int odepth8=(ldepth<rdepth ? rdepth : ldepth)/8;
	rv=out.SetSize(width,height,odepth8*8,PNG_COLOR_TYPE_RGB);
	assert(rv==0);
	
	rv=out.WriteHeader();
	if(rv)
	{  fprintf(stderr,"Failed to write header of %s: %s\n",
		ofile.str(),rv==-3 ? out.GetError() : strerror(errno));
		return(-2);  }
	
	dpy->Init(ofile.str(),width,height);
	
	// Actually perform the conversion: 
	ssize_t l_row_size=lin.GetRowSize();
	ssize_t r_row_size=rin.GetRowSize();
	assert(l_row_size>=0 && r_row_size>=0);
	uchar lbuf[l_row_size],rbuf[r_row_size];
	uchar obuf[odepth8*width*3];
	
	int lnc=lin.GetNChannels();
	int rnc=rin.GetNChannels();
	assert((lnc==1 || lnc==3) && (rnc==1 || rnc==3));
	
	int ldepth8=ldepth/8;
	int rdepth8=rdepth/8;
	int lbpp=lnc*ldepth8;
	int rbpp=rnc*rdepth8;
	int obpp=3*odepth8;
	if(ssize_t(lbpp*width)!=l_row_size || 
	   ssize_t(rbpp*width)!=r_row_size )
	{  assert(0);  }
	
	for(int yco=0; yco<(int)height; yco++)
	{
		rv=lin.ReadRow((char*)lbuf);
		if(rv)
		{  fprintf(stderr,"Failed to read %s: %s\n",
			lfile.str(),rv==-3 ? out.GetError() : strerror(errno));
			return(-2);  }
		rv=rin.ReadRow((char*)rbuf);
		if(rv)
		{  fprintf(stderr,"Failed to read %s: %s\n",
			rfile.str(),rv==-3 ? out.GetError() : strerror(errno));
			return(-2);  }
		
		// Convert line: 
		// If both images are grayscale, we can make life easier: 
		uchar *lptr=lbuf,*rptr=rbuf;
		uchar *optr=obuf;
		#if 1  /* 0 for performance testing only. */
		if(lnc==1 && rnc==1)
			for(int xco=0; xco<(int)width;
				xco++,lptr+=lbpp,rptr+=rbpp,optr+=obpp)
		{
			float lgray=AnaglyphConverter::ColI2F(lptr,ldepth8);
			float rgray=AnaglyphConverter::ColI2F(rptr,rdepth8);
			
			RGB<float> res;
			anacon->Convert(res,lgray,rgray);
			
			AnaglyphConverter::ColF2I(optr,res,odepth8);
			
			dpy->Draw(xco,yco,res);
		}
		else for(int xco=0; xco<(int)width;
			xco++,lptr+=lbpp,rptr+=rbpp,optr+=obpp)
		{
			RGB<float> lv,rv;
			
			AnaglyphConverter::ColI2F(&lv,lptr,ldepth8);
			AnaglyphConverter::ColI2F(&rv,rptr,rdepth8);
			
			RGB<float> res;
			anacon->Convert(res,lv,rv);
			
			AnaglyphConverter::ColF2I(optr,res,odepth8);
			
			dpy->Draw(xco,yco,res);
		}
		if(optr!=obuf+odepth8*width*3) assert(0);
		#else
		memcpy(obuf,lbuf,odepth8*width*3);
		#endif
		
		rv=out.WriteRow((char*)obuf);
		if(rv)
		{  fprintf(stderr,"Failed to write %s: %s\n",
			rfile.str(),rv==-3 ? out.GetError() : strerror(errno));
			return(-2);  }
		
		dpy->DoneRow(yco,yco+1==(int)height);
	}
	
	rv=out.WriteTail();
	if(rv)
	{  fprintf(stderr,"Failed to write tail of %s: %s\n",
		ofile.str(),rv==-3 ? out.GetError() : strerror(errno));
		return(-2);  }
	
	rv=out.Close();
	if(rv)
	{  fprintf(stderr,"Failed to close %s: %s\n",
		ofile.str(),rv==-3 ? out.GetError() : strerror(errno));
		return(-2);  }
	
	lin.Close();
	rin.Close();
	
	return(0);
}


int ImageIO::CheckParams()
{
	int failed=0;
	
	int lspec=CheckFramePattern(lpattern.str());
	int rspec=CheckFramePattern(rpattern.str());
	int ospec=CheckFramePattern(opattern.str());
	if(lspec<0 || rspec<0 || ospec<0)  ++failed;
	else if(lspec!=rspec || lspec!=ospec)
	{  fprintf(stderr,"Either all or no frame patterns must "
		"have a %%d-spec.\n");  ++failed;  }
	
	// If only file names without %d-spec were specified. 
	if(!lspec && !rspec && !ospec)  nframes=1;
	
	if(compress_level!=-999 && (compress_level<0 || compress_level>9))
	{  fprintf(stderr,"Illegal compression level %d.\n",compress_level);
		++failed;  }
	
	if(framejump<=0)
	{  fprintf(stderr,"Value for -j must be positive.\n");  ++failed;  }
	
	curr_frame=startframe;
	
	return(failed);
}


ImageIO::ImageIO(par::ParameterManager *parman) : 
	par::ParameterConsumer_Overloaded(parman),
	lpattern(),rpattern()
{
	nframes=-1;   // autodetect
	startframe=0;
	framejump=1;
	
	lpattern.set("l%07d.png");
	rpattern.set("r%07d.png");
	opattern.set("f%07d.png");
	
	compress_level=-999;  // Special and do not change. 
	
	SetSection(NULL);
	AddParam("n","number of frames to convert [leave away for auto detect]",
		&nframes);
	AddParam("f0","frame number to start with [0]",&startframe);
	AddParam("j","process every j-th frame [1]",&framejump);
	AddParam("z","output PNG file compression level (0-9) [6]",&compress_level);
	
	AddParam("f",
		"output frame file name pattern; may contain %d/%x-spec for frame "
		"number [o%07d.png]",
		&opattern);
	
	SetSection("l","left eye side");
	AddParam("f",
		"frame file pattern; may contain %d/%x-spec for frame number [l%07d.png]",
		&lpattern);
	
	SetSection("r","right eye side");
	AddParam("f",
		"frame file pattern; may contain %d/%x-spec for frame number [r%07d.png]",
		&rpattern);
	
	assert(!add_failed);
}

ImageIO::~ImageIO()
{
}

//------------------------------------------------------------------------------

char *prg_name="anaglyph-join";

static void allocfail_abort(size_t size)
{
	fprintf(stderr,"Allocation failure (%u bytes).\n",size);
	_exit(0);
}

int main(int argc,char **arg,char **envp)
{
	prg_name=GetPrgName(arg[0]);
	
	lmalloc_failure_handler=&allocfail_abort;
	
	par::ParameterManager parman;
	parman.SetVersionInfo(
		"0.8",            // version string
		NULL,             // package name
		"anaglyph-join");  // program name
	
	parman.SetLicenseInfo(
		// --lincense--
		"anaglyph-join may be distributed under the terms of the "
		"GNU General Public License version 2 as published by the Free "
		"Software Foundation.\n"
		"This program is provided AS IS with NO WARRANTY OF ANY KIND, "
		"INCLUDING THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS "
		"FOR A PARTICULAR PURPOSE.\n"
		"See the GNU GPL for details.",
		// --author--
		"Anaglyph-Join (c) 2004 by Wolfgang Wieser.");
	
	parman.AdditionalHelpText(
		"All colors may be specified as hex triplett (#rrggbb) or as "
		"three floats in range 0..1 separated by commas.\n\n"
		"Defaults are set up for red-cyan glasses; "
		"example: anaglyph-join -x -c\n");
	
	QApplication qapp(argc,arg);
	::qapp=&qapp;
	qapp.setStyle(new QCDEStyle(TRUE));
	//qapp.setStyle(new QSGIStyle(TRUE));
	
	//MainWindow mainwin(NULL);
	//qapp.setMainWidget(&mainwin);
	
	par::CmdLineArgs cmdline(argc,arg,envp);
	
	ImageIO imgio(&parman);
	AnaglyphConverter anacv(&parman);
	ImageDisplay dpy(&parman);
	
	int fail=0;
	bool will_exit=0;
	do {
		par::ParameterSource_CmdLine cmd_src(&parman);
		if(cmd_src.ReadCmdLine(&cmdline))
		{  fail=1;  break;  }
		if(cmd_src.n_iquery_opts)
		{  will_exit=1;  break;  }
		int rv=cmd_src.WriteParams();
		if(rv)
		{
			fprintf(stderr,"Failed to store the command line "
				"parameters (%d)\n",rv);
			fail=1;  break;
		}
	} while(0);
	
	if(!fail && !will_exit) do {
		if(parman.CheckParams())
		{  fail=1;  break;  }
	} while(0);
	
	while(!fail && !will_exit)
	{
		qapp.processEvents();
		if(dpy.must_quit)  break;
		
		fail=imgio.ProcessNextFile(&anacv,&dpy);
		if(fail==1)  // last image 
		{  fail=0;  break;  }
		
		if(dpy.must_quit)  break;
		dpy.Done();
	}
	
	return(fail ? 1 : 0);
}
