/*
  *  yuvdeinterlace.c
  *  deinterlace  2004 Mark Heath <mjpeg at silicontrip.org>
  *  converts interlaced source material into progressive by halving 
  *  the vertical resolution and doubling the frame rate.
  *
  *  based on code:
  *  Copyright (C) 2002 Alfonso Garcia-Patiņo Barbolani <barbolani at jazzfree.com>
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License as published by
  *  the Free Software Foundation; either version 2 of the License, or
  *  (at your option) any later version.
  *
  *  This program is distributed in the hope that it will be useful,
  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  *  GNU General Public License for more details.
  *
  *  You should have received a copy of the GNU General Public License
  *  along with this program; if not, write to the Free Software
  *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>

#include "yuv4mpeg.h"
#include "mpegconsts.h"

#define YUVDE_VERSION "0.1"

static void print_usage() 
{
  fprintf (stderr,
	   "usage: yuvdeinterlace [-v -t -b -d -h]\n"
	   "yuvdeinterlace  deinterlaces source material by\n"
           "doubling the frame rate and halving the vertical resolution.\n"
           "\n"
		"\t -t force top field first\n"
		"\t -b force bottom field first\n"
		"\t -n half vertical height (not duplicating fields)\n"
		"\t -i interlace (rather than deinterlace -n has no meaning)\n"
	   "\t -v Verbosity degree : 0=quiet, 1=normal, 2=verbose/debug\n"
	   "\t -h print this help\n"
         );
}


static void deinterlace(  int fdIn 
                      , y4m_stream_info_t  *inStrInfo
                      , int fdOut
                      , y4m_stream_info_t  *outStrInfo
                    )
{
	y4m_frame_info_t   in_frame ;
	uint8_t            *yuv_data[3] ;
	uint8_t            *yuv_o1data[3] ;
	uint8_t            *yuv_o2data[3] ;
	int                frame_data_size ;
	int                read_error_code ;
	int                write_error_code ;
	int interlaced = -1;            //=Y4M_ILACE_NONE for not-interlaced scaling, =Y4M_ILACE_TOP_FIRST or Y4M_ILACE_BOTTOM_FIRST for interlaced scaling
	int w,ih,oh,y;

// Allocate memory for the YUV channels
	interlaced = y4m_si_get_interlace(inStrInfo);

	ih = y4m_si_get_height(inStrInfo) ; w = y4m_si_get_width(inStrInfo);
	frame_data_size = ih * w;
	yuv_data[0] = (uint8_t *)malloc( frame_data_size );
	yuv_data[1] = (uint8_t *)malloc( frame_data_size >> 2);
	yuv_data[2] = (uint8_t *)malloc( frame_data_size >> 2);

	oh = y4m_si_get_height(outStrInfo) ; w = y4m_si_get_width(outStrInfo);
	frame_data_size = oh * w;
	yuv_o1data[0] = (uint8_t *)malloc( frame_data_size );
	yuv_o1data[1] = (uint8_t *)malloc( frame_data_size >> 2);
	yuv_o1data[2] = (uint8_t *)malloc( frame_data_size >> 2);
	yuv_o2data[0] = (uint8_t *)malloc( frame_data_size );
	yuv_o2data[1] = (uint8_t *)malloc( frame_data_size >> 2);
	yuv_o2data[2] = (uint8_t *)malloc( frame_data_size >> 2);

	if( !yuv_o1data[0] || !yuv_o1data[1] || !yuv_o1data[2] )
		mjpeg_error_exit1 ("Could'nt allocate memory for the YUV4MPEG data!");
	if( !yuv_o2data[0] || !yuv_o2data[1] || !yuv_o2data[2] )
		mjpeg_error_exit1 ("Could'nt allocate memory for the YUV4MPEG data!");
	if( !yuv_data[0] || !yuv_data[1] || !yuv_data[2] )
		mjpeg_error_exit1 ("Could'nt allocate memory for the YUV4MPEG data!");

/* Initialize counters */

	write_error_code = Y4M_OK ;

	y4m_init_frame_info( &in_frame );
	read_error_code = y4m_read_frame(fdIn, inStrInfo,&in_frame,yuv_data );


	while( Y4M_ERR_EOF != read_error_code && write_error_code == Y4M_OK ) {

// de interlace frame.
		if (read_error_code == Y4M_OK) {

		for (y=0; y<(ih/2); y++) 
		{
			if (oh == ih) {
				memcpy(&yuv_o1data[0][2*y*w],&yuv_data[0][2*y*w],w);
				memcpy(&yuv_o1data[0][(2*y+1)*w],&yuv_data[0][2*y*w],w);

				memcpy(&yuv_o2data[0][2*y*w],&yuv_data[0][(2*y+1)*w],w);
				memcpy(&yuv_o2data[0][(2*y+1)*w],&yuv_data[0][(2*y+1)*w],w);
			} else {
				memcpy(&yuv_o1data[0][y*w],&yuv_data[0][2*y*w],w);
				memcpy(&yuv_o2data[0][y*w],&yuv_data[0][(2*y+1)*w],w);
			}


			if (y % 2) {
				if (oh == ih) {
					memcpy(&yuv_o2data[1][y*w/2],&yuv_data[1][y*w/2],w/2);
					memcpy(&yuv_o2data[1][(y-1)*w/2],&yuv_data[1][y*w/2],w/2);
					memcpy(&yuv_o2data[2][y*w/2],&yuv_data[2][y*w/2],w/2);
					memcpy(&yuv_o2data[2][(y-1)*w/2],&yuv_data[2][y*w/2],w/2);
				} else {
					memcpy(&yuv_o2data[1][y/2*w/2],&yuv_data[1][y*w/2],w/2);
					memcpy(&yuv_o2data[2][y/2*w/2],&yuv_data[2][y*w/2],w/2);
				}
			} else {
				if (oh == ih) {
					memcpy(&yuv_o1data[1][y*w/2],&yuv_data[1][y*w/2],w/2);
					memcpy(&yuv_o1data[1][(y+1)*w/2],&yuv_data[1][y*w/2],w/2);
					memcpy(&yuv_o1data[2][y*w/2],&yuv_data[2][y*w/2],w/2);
					memcpy(&yuv_o1data[2][(y+1)*w/2],&yuv_data[2][y*w/2],w/2);
				} else {
					memcpy(&yuv_o1data[1][y/2*w/2],&yuv_data[1][y*w/2],w/2);
					memcpy(&yuv_o1data[2][y/2*w/2],&yuv_data[2][y*w/2],w/2);
				}
			}
		}

		if (interlaced == Y4M_ILACE_BOTTOM_FIRST)
		{
			write_error_code = y4m_write_frame( fdOut, outStrInfo, &in_frame, yuv_o1data );
			write_error_code = y4m_write_frame( fdOut, outStrInfo, &in_frame, yuv_o2data );
		} else {
			// assume top field first
			// other possibility is non interlaced so why use this program
			// We've already printed a warning.
			write_error_code = y4m_write_frame( fdOut, outStrInfo, &in_frame, yuv_o2data );
			write_error_code = y4m_write_frame( fdOut, outStrInfo, &in_frame, yuv_o1data );
		}
		}

		y4m_fini_frame_info( &in_frame );
		y4m_init_frame_info( &in_frame );
		read_error_code = y4m_read_frame(fdIn, inStrInfo,&in_frame,yuv_data );
  }
  // Clean-up regardless an error happened or not
  y4m_fini_frame_info( &in_frame );

  free( yuv_data[0] );
  free( yuv_data[1] );
  free( yuv_data[2] );
  free( yuv_o1data[0] );
  free( yuv_o1data[1] );
  free( yuv_o1data[2] );
  free( yuv_o2data[0] );
  free( yuv_o2data[1] );
  free( yuv_o2data[2] );

  if( read_error_code != Y4M_ERR_EOF )
    mjpeg_error_exit1 ("Error reading from input stream!");
  if( write_error_code != Y4M_OK )
    mjpeg_error_exit1 ("Error writing output stream!");

}

static void depro(  int fdIn 
                      , y4m_stream_info_t  *inStrInfo
                      , int fdOut
                      , y4m_stream_info_t  *outStrInfo
                    )
{
	y4m_frame_info_t   in_frame ;
	uint8_t            *yuv_data[3] ;
	uint8_t            *yuv_o1data[3] ;
	uint8_t            *yuv_o2data[3] ;
	int                frame_data_size ;
	int                read_error_code ;
	int                write_error_code ;
	int interlaced = -1;            //=Y4M_ILACE_NONE for not-interlaced scaling, =Y4M_ILACE_TOP_FIRST or Y4M_ILACE_BOTTOM_FIRST for interlaced scaling
	int w,h,y;

// Allocate memory for the YUV channels
	interlaced = y4m_si_get_interlace(outStrInfo);
	h = y4m_si_get_height(inStrInfo) ; w = y4m_si_get_width(inStrInfo);
	frame_data_size = h * w;
	yuv_data[0] = (uint8_t *)malloc( frame_data_size );
	yuv_data[1] = (uint8_t *)malloc( frame_data_size >> 2);
	yuv_data[2] = (uint8_t *)malloc( frame_data_size >> 2);

	h = y4m_si_get_height(outStrInfo) ; w = y4m_si_get_width(outStrInfo);
	frame_data_size = h * w;
	yuv_o1data[0] = (uint8_t *)malloc( frame_data_size );
	yuv_o1data[1] = (uint8_t *)malloc( frame_data_size >> 2);
	yuv_o1data[2] = (uint8_t *)malloc( frame_data_size >> 2);
	yuv_o2data[0] = (uint8_t *)malloc( frame_data_size );
	yuv_o2data[1] = (uint8_t *)malloc( frame_data_size >> 2);
	yuv_o2data[2] = (uint8_t *)malloc( frame_data_size >> 2);

	if( !yuv_o1data[0] || !yuv_o1data[1] || !yuv_o1data[2] )
		mjpeg_error_exit1 ("Could'nt allocate memory for the YUV4MPEG data!");
	if( !yuv_o2data[0] || !yuv_o2data[1] || !yuv_o2data[2] )
		mjpeg_error_exit1 ("Could'nt allocate memory for the YUV4MPEG data!");
	if( !yuv_data[0] || !yuv_data[1] || !yuv_data[2] )
		mjpeg_error_exit1 ("Could'nt allocate memory for the YUV4MPEG data!");

/* Initialize counters */

	write_error_code = Y4M_OK ;

	y4m_init_frame_info( &in_frame );
	read_error_code = y4m_read_frame(fdIn, inStrInfo,&in_frame,yuv_o1data );
	read_error_code = y4m_read_frame(fdIn, inStrInfo,&in_frame,yuv_o2data );


	while( Y4M_ERR_EOF != read_error_code && write_error_code == Y4M_OK ) {

		if (read_error_code == Y4M_OK) {

// de interlace frame.

		for (y=0; y<h; y+=2) 
		{

				if (interlaced == Y4M_ILACE_BOTTOM_FIRST ) {
					/* copy the luminance scan line from the odd frame */
					memcpy(&yuv_data[0][y*w],&yuv_o2data[0][y*w],w);

					/* copy the alternate luminance scan line from the even frame */
					memcpy(&yuv_data[0][(y+1)*w],&yuv_o1data[0][(y+1)*w],w);
				} else if (interlaced == Y4M_ILACE_TOP_FIRST) {
					/* assume Y4M_ILACE_TOP_FIRST */
					/* outputting in progressive is pointless */
					memcpy(&yuv_data[0][y*w],&yuv_o1data[0][y*w],w);
					memcpy(&yuv_data[0][(y+1)*w],&yuv_o2data[0][(y+1)*w],w);
				} else {
					fprintf (stderr,"Error cannot determine interlace order\n");
				}

				/* average the chroma data */
				unsigned int x;
				for (x=0; x<(w>>1); x++) {
					yuv_data[1][(y>>1)*(w>>1)+x]= (yuv_o1data[1][(y>>1)*(w>>1)+x]+yuv_o2data[1][(y>>1)*(w>>1)+x])/2 ;
					yuv_data[2][(y>>1)*(w>>1)+x]= (yuv_o1data[2][(y>>1)*(w>>1)+x]+yuv_o2data[2][(y>>1)*(w>>1)+x])/2 ;
				}
		}

		write_error_code = y4m_write_frame( fdOut, outStrInfo, &in_frame, yuv_data );

		}

		y4m_fini_frame_info( &in_frame );
		y4m_init_frame_info( &in_frame );
		read_error_code = y4m_read_frame(fdIn, inStrInfo,&in_frame,yuv_o1data );
		read_error_code = y4m_read_frame(fdIn, inStrInfo,&in_frame,yuv_o2data );

  }
  // Clean-up regardless an error happened or not
  y4m_fini_frame_info( &in_frame );

  free( yuv_data[0] );
  free( yuv_data[1] );
  free( yuv_data[2] );
  free( yuv_o1data[0] );
  free( yuv_o1data[1] );
  free( yuv_o1data[2] );
  free( yuv_o2data[0] );
  free( yuv_o2data[1] );
  free( yuv_o2data[2] );

  if( read_error_code != Y4M_ERR_EOF )
    mjpeg_error_exit1 ("Error reading from input stream!");
  if( write_error_code != Y4M_OK )
    mjpeg_error_exit1 ("Error writing output stream!");

}


// *************************************************************************************
// MAIN
// *************************************************************************************
int main (int argc, char *argv[])
{

	int verbose = LOG_ERROR ;
	int top_field =0, bottom_field = 0,double_height=1;
	int fdIn = 0 ;
	int fdOut = 1 ;
	y4m_stream_info_t in_streaminfo, out_streaminfo ;
	y4m_ratio_t frame_rate;
	int interlaced,ilace=0;
	int height;
	int c ;
	const static char *legal_flags = "vnitbh";

  while ((c = getopt (argc, argv, legal_flags)) != -1) {
    switch (c) {
      case 'v':
        verbose = atoi (optarg);
        if (verbose < 0 || verbose > 2)
          mjpeg_error_exit1 ("Verbose level must be [0..2]");
        break;
        
        case 'h':
        case '?':
          print_usage (argv);
          return 0 ;
          break;
        case 't':
          top_field = 1 ;
	  break;
        case 'b':
		bottom_field = 1;
		break;
	case 'n':
		double_height = 0;
		break;
	case 'i':
		ilace = 1;
		break;
    }
  }

	if ((bottom_field==1) && (top_field==1)) {
		fprintf (stderr,"top field and bottom field specified\n");
		exit(1);
	}
  // mjpeg tools global initialisations
  mjpeg_default_handler_verbosity (verbose);

  // Initialize input streams
  y4m_init_stream_info (&in_streaminfo);
  y4m_init_stream_info (&out_streaminfo);

  // ***************************************************************
  // Get video stream informations (size, framerate, interlacing, aspect ratio).
  // The streaminfo structure is filled in
  // ***************************************************************
  // INPUT comes from stdin, we check for a correct file header
  if (y4m_read_stream_header (fdIn, &in_streaminfo) != Y4M_OK)
    mjpeg_error_exit1 ("Could'nt read YUV4MPEG header!");

// Check input parameters
	height = y4m_si_get_height (&in_streaminfo);
	frame_rate = y4m_si_get_framerate( &in_streaminfo );

	interlaced = y4m_si_get_interlace (&in_streaminfo);

	if ((height%2) && (ilace == 0))
		mjpeg_error_exit1("material is not even frame height");

	if ((interlaced == Y4M_ILACE_NONE) && (ilace == 0))
		mjpeg_warn("source material not interlaced");

	if ((interlaced != Y4M_ILACE_NONE) && (ilace == 1))
		mjpeg_warn("source material is interlaced");

	if (ilace == 1) {
		if (top_field == 1)
			interlaced = Y4M_ILACE_TOP_FIRST;
		if (bottom_field == 1)
			interlaced = Y4M_ILACE_BOTTOM_FIRST;

		frame_rate.n /= 2;
	}
	if (ilace == 0) {
		if (top_field == 1) 
			y4m_si_set_interlace(&in_streaminfo, Y4M_ILACE_TOP_FIRST);
		if (bottom_field == 1) 
			y4m_si_set_interlace(&in_streaminfo, Y4M_ILACE_BOTTOM_FIRST);
		if (! double_height) height /=2;
		frame_rate.n *= 2 ;
		interlaced = Y4M_ILACE_NONE;
	}


	
  // Prepare output stream
  // De interlacer doubles framerate but halves vertical resolution.

		

  y4m_copy_stream_info( &out_streaminfo, &in_streaminfo );

  
  // Information output
  mjpeg_info ("yuvdeinterlace (version " YUVDE_VERSION
              ") is a general deinterlace/interlace utility for yuv streams");
  mjpeg_info ("(C) 2005 Mark Heath <mjpeg@silicontrip.org>");
  mjpeg_info ("yuvdeinterlace -h for help");

	y4m_si_set_framerate( &out_streaminfo, frame_rate );                
	y4m_si_set_height (&out_streaminfo, height);
	y4m_si_set_interlace(&out_streaminfo, interlaced);
	y4m_write_stream_header(fdOut,&out_streaminfo);
    
  /* in that function we do all the important work */
	if (ilace == 0) 
		deinterlace( fdIn,&in_streaminfo,  fdOut,&out_streaminfo);

	if (ilace == 1)
		depro( fdIn,&in_streaminfo,  fdOut,&out_streaminfo);

  y4m_fini_stream_info (&in_streaminfo);
  y4m_fini_stream_info (&out_streaminfo);

  return 0;
}
/*
 * Local variables:
 *  tab-width: 8
 *  indent-tabs-mode: nil
 * End:
 */
