/*--------------------------------------------------------------
	Make Circumscribing Rectangles    makecr  v1.8
		Written  by H.Goto , Sep 1994
		Modified by H.Goto , Jan 1995
			 by H.Goto , May 1995
			 by H.Goto , Dec 1995
			 by H.Goto , May 1996
			 by H.Goto , Feb 1997
			 by H.Goto , May 2002
--------------------------------------------------------------*/

/*--------------------------------------------------------------------
  Copyright (C) 1994-2002  Hideaki Goto

        All Rights Reserved

  Permission to use, copy, modify, and distribute this software and
  its documentation for any purpose is hereby granted without fee,
  provided that (i) the above copyright notice and this permission
  notice appear in all copies and in supporting documentation, (ii)
  the name of the author, Hideaki Goto, may not be used in any
  advertising or otherwise to promote the sale, use or other
  dealings in this software without prior written authorization
  from the author, (iii) this software may not be used for
  commercial products without prior written permission from the
  author, and (iv) the notice of modification is specified in cases
  where modified copies of this software are distributed.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
  THE AUTHOR WILL NOT BE RESPONSIBLE FOR ANY DAMAGE CAUSED BY THIS
  SOFTWARE.
--------------------------------------------------------------------*/


#define		FASTALG

#include	<stdio.h>
#include	<stdlib.h>
#include	<string.h>
#include	<unistd.h>

#include        "ufilep.h"
#include        "imgobj.h"




/*------------------------------------------------------
	Global definitions
------------------------------------------------------*/

class RLpac {
    public:
	int	gid;
	int	pos;
	int	len;
	int	endpos(){ return(pos + len -1); }
	int	overlap(RLpac *rl2);
};


int RLpac :: overlap(RLpac *rl){
	int	left2;
	int	dif;
	left2 = rl->pos;
	dif = left2 - (pos + len -1);
	if ( dif > 1 )  return(-2);	/* Appart */
	if ( dif == 1 )  return(-1);	/* Touching */
	dif = pos - (left2 + rl->len -1);
	if ( dif > 1 )  return(2);	/* Appart */
	if ( dif == 1 )  return(1);	/* Touching */
	return(0);			/* Overlapping */
}




/*------------------------------------------------------
	CC list file export class
------------------------------------------------------*/

class CCEXP : public D4SAVE{
    private:
	d4file		d4header;
	d4pac		d4p;
    public:
	int		open(char *fname,int x0,int x1,int y0,int y1, \
				int width0,int width1,int height0,int height1);
	int		close(void);
	int		writeccdata(char *fname,int x0,int x1,int y0,int y1, \
				int width0,int width1,int height0,int height1, \
				int ccs,CRects *CR);
};


int CCEXP :: writeccdata(char *fname,int x0,int x1,int y0,int y1, \
			int width0,int width1,int height0,int height1, \
			int ccs,CRects *CR){
	int	ix;
	CRect	*ccr;
	if ( fname == 0 )  return(0);
	if ( 0 != open(fname,x0,x1,y0,y1,width0,width1,height0,height1) ){
		fprintf(stderr,"File creation failed : %s\n",fname);
		return (-1);
	}
	for ( ix=0 ; ix<ccs ; ix++ ){
		if ( ! CR->isactive(ix) )  continue;

		ccr = CR->getrect(ix);
		d4p.d4_data[0] = ccr->x1;
		d4p.d4_data[1] = ccr->y1;
		d4p.d4_data[2] = ccr->x2 - ccr->x1 +1;
		d4p.d4_data[3] = ccr->y2 - ccr->y1 +1;
		if ( 0 != putdata(&d4p) ){
			fprintf(stderr,"File write error : %s\n",fname);
			close();
			unlink(fname);
			return (-1);
		}
	}
	if ( 0 != close() ){
		fprintf(stderr,"File write error : %s\n",fname);
		return (-1);
	}
	return(0);
}


int CCEXP :: open(char *fname,int x0,int x1,int y0,int y1, \
			int width0,int width1,int height0,int height1){
	d4header.d4_llimit[0] = x0;		d4header.d4_hlimit[0] = x1;
	d4header.d4_llimit[1] = y0;		d4header.d4_hlimit[1] = y1;
	d4header.d4_llimit[2] = width0;		d4header.d4_hlimit[2] = width1;
	d4header.d4_llimit[3] = height0;	d4header.d4_hlimit[3] = height1;
	return ( filecreat(fname,&d4header) );
}


int CCEXP :: close(void){
	return ( fileclose() );
}




/*------------------------------------------------------
	Image processor class
------------------------------------------------------*/

class IMPR {
    private:
	uchar		*imgbuffer;
	int		imgwidth,imgheight;
    public:
	void		setbuffer(uchar *buf,int width,int height);
	void		uchar2int(uchar *src,int *dst,int size);
	void		dif1(int *src,int *dst,int size,int limit);
	void		bstretch(int *buf,int size);
	void		revcol(uchar *src,uchar *dst,int size);
};


void IMPR :: setbuffer(uchar *buf,int width,int height){
	imgbuffer = buf;
	imgwidth  = width;
	imgheight = height;
}


void IMPR :: uchar2int(uchar *src,int *dst,int size){
	int	ix;
	for ( ix=0 ; ix<size ; ++ix ){
		*(dst++) = (int)(unsigned int)*(src++);
	}
}


void IMPR :: dif1(int *src,int *dst,int size,int limit){
	int	ix,d;
	if ( size <= 0 )  return;
	for ( ix=0 ; ix<(size-1) ; ++ix ){
		d = src[ix+1] - src[ix];
		if ( limit >= abs(d) )  d = 0;
		dst[ix] = d;
	dst[size-1] = 0;
	}
}


void IMPR :: bstretch(int *buf,int size){
	int	c0,ix;
	c0 = buf[0];
	for ( ix=1 ; ix<size ; ix++ ){
		if ( buf[ix] != 0 ){
			if ( c0 == 0 )  buf[ix-1] = -1;
			c0 = -1;
		}
		else{	if ( c0 != 0 )  buf[ix  ] = -1;
			c0 = 0;
		}
	}
}


void IMPR :: revcol(uchar *src,uchar *dst,int size){
	int	ix;
	for ( ix=0 ; ix<size ; ++ix ){
		*(dst++) = 255 - *(src++);
	}
}




/*------------------------------------------------------
	Message class
------------------------------------------------------*/

class MSG {
    public:
	void		usage(void);
	void		switcherr(void);
	void		readerr(char *name);
	void		writeerr(char *name);
	void		nonsupported(char *name);
	void		memoryover(void);
};
    	
    	
void MSG :: usage(void){		// Display Usage
	fputs("makecr  v1.8,  Copyright (C) 1994-2002 Hideaki Goto \n",stderr);
	fputs("Usage : makecr [-options]  in_file|stdin  out_file \n",stderr);
//	fputs("		-reverse : reverse B/W \n",stderr);
	fputs("		-bstr N  : black pixel stretching \n",stderr);
	fputs("		-rls  N  : run length smoothing \n",stderr);
//	fputs("		-verbose : display conversion-info. \n",stderr);
//	fputs("		-quiet   : no conversion-info. display \n",stderr);
}


void MSG :: switcherr(void){
	fputs("Switch error.\n",stderr);
}


void MSG :: readerr(char *name){
	fprintf(stderr,"Can't read %s.\n",name);
}


void MSG :: writeerr(char *name){
	fprintf(stderr,"Can't write %s.\n",name);
}


void MSG :: nonsupported(char *name){
	fprintf(stderr,"Non-supported file %s.\n",name);
}


void MSG :: memoryover(void){
	fputs("Not enough memory.\n",stderr);
}




/*------------------------------------------------------
	Argument / Option class
------------------------------------------------------*/

class ARGS {
    private:
	char		*fpath[16];
	int		fpcount;
    	int		sw_verbose;
    	int		sw_reverse;
	int		sw_bstr;
	int		sw_rls;
    public:
	int		scanargv(int ac,char **av);
	int		SWverbose(void);
	int		SWreverse(void);
	int		SWbstr(void) {  return sw_bstr;  }
	int		SWrls(void) {  return sw_rls;  }
	char		*infile(void);
	char		*outfile(void);
			ARGS(void);
};


ARGS :: ARGS(void){
	sw_verbose = 0;
	sw_reverse = 0;
	sw_bstr = 0;
	sw_rls = 0;
}


int ARGS :: scanargv(int ac,char **av){
	int	i1,err,skip;
	char	*sw;
	fpcount = err = 0;
	skip = 0;
	--ac;  if( ac >= 16 )  ac = 16;
	for( i1 = 0 ; i1 < ac ; ++i1 ){
		if ( skip ){  skip = 0; continue;  }
		if( *( sw = av[i1+1] ) == '-' ){
			++sw;
			if(0==strcmp(sw,"bstr")){
				if((i1+1)<ac){
					sw_bstr = atoi(av[i1+2]);
					if ( sw_bstr < 0 )  sw_bstr = 0;
					skip = -1;
					continue;
				}
				else{	err=-1; break;  }
			}
			if(0==strcmp(sw,"rls")){
				if((i1+1)<ac){
					sw_rls = atoi(av[i1+2]);
					if ( sw_rls < 0 )  sw_rls = 0;
					skip = -1;
					continue;
				}
				else{	err=-1; break;  }
			}
			if( 0 == strcmp(sw,"reverse") ){
				sw_reverse = -1;
				continue;
			}
			if( 0 == strcmp(sw,"quiet") ){
				sw_verbose = 0;
				continue;
			}
			if( 0 == strcmp(sw,"verbose") ){
				sw_verbose = -1;
				continue;
			}
			err = -1;  break;
		}
		else{	fpath[fpcount++] = av[i1+1];
		}
	}
	return(err);
}


int ARGS :: SWverbose(void){
	return(sw_verbose);
}


int ARGS :: SWreverse(void){
	return(sw_reverse);
}


char * ARGS :: infile(void){
	if ( fpcount < 1 )  return( "" );
	return( fpath[0] );
}


char * ARGS :: outfile(void){
	if ( fpcount < 2 )  return(0);
	return( fpath[1] );
}




/*------------------------------------------------------
	Main routine
------------------------------------------------------*/

int main(int ac,char **av){
	ARGS		ARGS;
	MSG		MSG;
	PBMFILE		PBMLD;
	D4SAVE		D4SV;
	IMPR		IMPR;
	CRects		CRects(512);
	CCEXP		CCEXP;
	int		retcode,errflag;
	int		width,height;
	int		maxrls;
	int		ix,iy;
	uchar		*lbuf;
	int		*rlbuf,*rlbuf0;
	int		RL,RLcounts;
	RLpac		*RLlist,*RLlist0;
	int		currccn;
	int		cc1;
	int		wgap;

	if ( ac < 2 ){
		MSG.usage();
		exit(0);
	}
	if ( 0 != ARGS.scanargv(ac,av) ){
		MSG.switcherr();
		exit(1);
	}

	retcode = PBMLD.open(ARGS.infile(),"r");
	switch ( retcode ){
	  case 0:	break;
	  case -5:	MSG.nonsupported(ARGS.infile());  exit(2);
	  case -6:	MSG.memoryover();  exit(3);
	  default:	MSG.readerr(ARGS.infile());  exit(2);
	}

	width  = PBMLD.getwidth();
	height = PBMLD.getheight();
	if ( ARGS.SWverbose() ){
		fprintf(stderr,"Input : %s is a %dx%d ", \
					ARGS.infile(),width,height );
		if ( PBMLD.getfiletype() ==0 ){
			fputs("RawBits PBM image\n",stderr);
		}
		else{	fputs("RawBits PGM image\n",stderr);
		}
	}

	maxrls = width + 2;
	if ( 0 == (RLlist = new RLpac[ 2 * maxrls ]) ){
		MSG.memoryover();
		exit(3);
	}
	RLlist0 = RLlist + maxrls;
	if ( 0 == (lbuf = new uchar[width]) ){
		MSG.memoryover();
		exit(3);
	}
	if ( 0 == (rlbuf = new int[ width + 2 ]) ){
		MSG.memoryover();
		exit(3);
	}
	rlbuf[0] = rlbuf[width+1] = 0;
	if ( 0 == (rlbuf0 = new int[ width + 2 ]) ){
		MSG.memoryover();
		exit(3);
	}
	for ( ix=0 ; ix<(width+2) ; ix++ )  rlbuf0[ix] = 0;
	RLlist0[0].pos = -1;

	retcode = 0;

	errflag = 0;
	for ( iy=0 ; iy<height ; ++iy ){
		if ( 0 != PBMLD.readline_gray(-1,lbuf) ){
			errflag = -1;
			break;
		}
		if ( ARGS.SWreverse() ){
			IMPR.revcol(lbuf,lbuf,width);
		}
		for ( ix=0 ; ix<width ; ix++ ){
			if ( lbuf[ix] != 0 )  rlbuf[ix+1] =  0;
			else                  rlbuf[ix+1] = -1;
		}


/* Black pixel stretching */

		for ( ix=0 ; ix<ARGS.SWbstr() ; ix++ ){
			IMPR.bstretch(rlbuf+1,width);
		}


/* Runlength encode */

//		printf("Line %d :\n",iy);
		for ( ix = RLcounts = 0 ; ix<(width + 1) ; ){
			RL = 0;
			for ( ; ix<(width+1) ; ix++ ){
				if ( rlbuf[ix+1] != 0 )  break;
			}
			if ( ix >= (width+1) )  break;
//			printf("	Pos %d , ",ix);
			RLlist[ RLcounts ].pos = ix;
			for ( ; ix<(width+1) ; ix++ ){
				if ( rlbuf[ix+1] == 0 )  break;
				++RL;
			}
//			printf("Len %d\n",RL);
			RLlist[ RLcounts ].len = RL;
			RLlist[ RLcounts ].gid = -1;
			if ( (++RLcounts) >= (maxrls-1) ){
				fprintf(stderr,\
					"Warning : RunLength buffer overflow.\n");
				break;
			}
		}
		RLlist[ RLcounts ].pos = -1;


/* Runlength Smoothing */

		if ( ARGS.SWrls() && (RLlist[0].pos != -1) && (RLcounts >= 2) ){
			RLcounts = ix = 0;
			while (1){
				if ( RLlist[RLcounts].pos == -1 )  break;
				RLlist[RLcounts] = RLlist[ix];
				while (1){
					++ix;
					if ( RLlist[ix].pos == -1 ){
						RLlist[++RLcounts].pos = -1;
						break;
					}
					wgap = RLlist[RLcounts].pos + RLlist[RLcounts].len;
					wgap = RLlist[ix].pos - wgap;
					if ( wgap > ARGS.SWrls() ){
						++RLcounts;
						break;
					}
					RLlist[RLcounts].len = RLlist[ix].pos + RLlist[ix].len - RLlist[RLcounts].pos;
				}
			}
		}


/* Labeling */
#ifdef FASTALG
		{
		int	*rlptr;
		int	pos;
		for ( RLcounts=0 ; RLlist[ RLcounts ].pos != -1 ; RLcounts++ ){
			currccn = -1;
			pos = RLlist[ RLcounts ].pos;
			for ( ix=-1 ; ix<(RLlist[ RLcounts ].len+1) ; ix++ ){
				cc1 = rlbuf0[ ix +pos +1 ];
				if ( cc1 == 0 )  continue;
				--cc1;
				if ( currccn != -1 ){
					if ( currccn != cc1 ){
						CRects.cat(currccn,cc1);
					}
				}
				else{	currccn = CRects.findparent(cc1);
					CRects.expand(currccn,pos,iy, \
						pos + RLlist[ RLcounts ].len -1,iy);
				}
				
			}
			if ( currccn == -1 ){
				currccn = CRects.create(pos,iy, \
					pos + RLlist[ RLcounts ].len -1,iy);
				if ( currccn < 0 ){
					fprintf(stderr,"Warning : Too many cc\'s.");
					break;
				}
			}
			for ( ix=0 ; ix<RLlist[ RLcounts ].len ; ix++ ){
				rlbuf[ ix +pos +1 ] = currccn +1;
			}
		}
						//  rlbuf <--> rlbuf0
		rlptr = rlbuf;  rlbuf = rlbuf0 ;  rlbuf0 = rlptr;
		}
	}

#else

		{
		RLpac	*RLlist_tmp;
		int	i,j;
		for ( i=0 ; RLlist[i].pos != -1 ; i++ ){
			currccn = -1;
			for ( j=0 ; RLlist0[j].pos != -1 ; j++ ){
				RL = RLlist[i].overlap(RLlist0 + j);
				if ( RL < -1 )  break;
				if ( abs(RL) > 1 )  continue;
				cc1 = RLlist0[j].gid;
				if ( currccn != -1 ){
					if ( currccn != cc1 ){
						CRects.cat(currccn,cc1);
					}
				}
				else{	currccn = CRects.findparent(cc1);
					CRects.expand(currccn,RLlist[i].pos, \
						iy,RLlist[i].endpos(),iy);
				}
			}
			if ( currccn == -1 ){
				currccn = CRects.create(RLlist[i].pos,iy, \
							RLlist[i].endpos(),iy);
				if ( currccn < 0 ){
					fprintf(stderr,"Warning : Too many cc\'s.");
					break;
				}
			}
			RLlist[i].gid = currccn;
		}
						//  RLlist <--> RLlist0
		RLlist_tmp = RLlist;  RLlist = RLlist0;  RLlist0 = RLlist_tmp;
		}
	}
#endif

//	printf("%d cc\'s created.\n",CRects.counts);
	CCEXP.writeccdata(ARGS.outfile(),0,width-1,0,height-1, \
			1,width,1,height,CRects.counts,&CRects);

	return(0);
}


