/*************************************************************************
	File:	tga_out.c

	FormZ image bitmap translator sample.  Imports and exports Targa images.

	COPYRIGHT (C) 2001.  All Rights Reserved.
	auto*des*sys, Inc., Columbus, Ohio, USA.
*************************************************************************/


#include "tga_prvt.h"




/************************************************************************
	Open the output file of a targa file and initialize working data.
*************************************************************************/
fzrt_error_td tga_export_init(fzrt_int windex, fz_ffmt_ref_td ffmt_id, void **plugin_data,  fzrt_floc_ptr floc)
{
	fzrt_error_td		err = FZRT_NOERR;
	char				fname[256];
	tga_out_prvt_td*	tga_out;
	char				tmp_name[256];
	
	/* Allocate and initialize working data */
	err = _fzrt_mem_funcs.fzrt_new_ptr(sizeof(tga_out_prvt_td), (fzrt_ptr*)&tga_out);
	if(tga_out == NULL || err != FZRT_NOERR)
	{
		err = _fzrt_base_funcs.fzrt_error_set_with_detail(TGA_MEM_ERR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _tga_plugin_runtime_id, 102);
	}
	else
	{
		tga_out->fp = NULL;
		tga_out->scanline = NULL;
		tga_out->rle_buffer = NULL;

		/* Assign working data to plugin data */
		*plugin_data = (void *)tga_out;

		/* Get the file name from the floc and open the file. The output directory will have been set by formZ. */
		_fzrt_file_funcs.fzrt_file_floc_get_name(floc, fname, 256);
		_fzrt_file_funcs.fzrt_file_cnvt_name(fname, tmp_name, 256);
		if(!(tga_out->fp = fopen(tmp_name,"wb")))
		{
			err = _fzrt_base_funcs.fzrt_error_set_with_detail(errno, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_ANSI, 0, 18 );
		}
	}
	
	return err;
}

/************************************************************************
	Close the output file and free any memory allocated for working data.
*************************************************************************/
fzrt_error_td tga_export_finit(fzrt_int windex, fz_ffmt_ref_td ffmt_id, void **plugin_data,  fzrt_floc_ptr floc, fzrt_error_td err)
{
	tga_out_prvt_td*	tga_out;

	if(plugin_data != NULL)
	{
		/* Get working data from plugin data. */
		tga_out = *((tga_out_prvt_td**)plugin_data);
		if ( tga_out != NULL )
		{
			/* Close the file. */
			if(tga_out->fp != NULL)
			{
				fclose(tga_out->fp);
				tga_out->fp = NULL;
			}
			
			/* free working data. */
			if(tga_out->scanline != NULL)
			{
				_fzrt_mem_funcs.fzrt_dispose_ptr((fzrt_ptr)tga_out->scanline);
				tga_out->scanline = NULL;
			}
			if(tga_out->rle_buffer != NULL)
			{
				_fzrt_mem_funcs.fzrt_dispose_ptr((fzrt_ptr)tga_out->rle_buffer);
				tga_out->rle_buffer = NULL;
			}
			_fzrt_mem_funcs.fzrt_dispose_ptr((fzrt_ptr)tga_out);	
			
			/* Set plugin data to NULL. */
			*plugin_data = NULL;
		}
	}

	return (FZRT_NOERR);
}

/************************************************************************
	Write the TARGA image header
*************************************************************************/
static fzrt_error_td	tga_write_header(	tga_out_prvt_td*	tga_out,
									short				img_type,
									short				width,
									short				height,
									short				depth,
									short				col_map,
									short				map_len)
{
	char			data[18];
	fzrt_error_td	err = FZRT_NOERR;
	
	
	data[0] = 0;													/* IMAGE ID FIELD 			*/
	data[1] = (col_map) ? 1 : 0;									/* IS THERE A COLOR MAP ?	*/
	data[2] = (char)img_type;												/* IMAGE TYPE				*/
	*(short*) &data[3] = 0;			TGA_OUT_SWAP_SHORT(data[3]);	/* FIRST ENTRY				*/
	*(short*) &data[5] = map_len;	TGA_OUT_SWAP_SHORT(data[5]);	/* COLOR MAP LENGTH			*/
	data[7] = (col_map) ? 24 : 0;									/* COLOR MAP ENTRY SIZE 	*/

	*(short*) &data[8]  = 0;		TGA_OUT_SWAP_SHORT(data[8]);	/* IMAGE X ORIGIN			*/
	*(short*) &data[10] = 0;		TGA_OUT_SWAP_SHORT(data[10]);	/* IMAGE Y ORIGIN			*/
	*(short*) &data[12] = width;	TGA_OUT_SWAP_SHORT(data[12]);	/* IMAGE WIDTH				*/
	*(short*) &data[14] = height;	TGA_OUT_SWAP_SHORT(data[14]);	/* IMAGE HEIGHT				*/
	data[16] = (char)depth;												/* PIXEL DEPTH 				*/
	data[17] = 0x20 | ((depth == 32) ? 0x08 : 0x00);				/* IMAGE DESCRIPTION		*/

	if ( fwrite(data,18,1,tga_out->fp) < 1 )
	{
		err = _fzrt_base_funcs.fzrt_error_set_with_detail(errno, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_ANSI, 0, 18 );
	}
	
	return err;
}



/************************************************************************
	Compress a Targa image scanline.
*************************************************************************/
static void	tga_compress_scanline(	unsigned char	*buff_in,
									unsigned short	width,
									short 			pack_size,
									unsigned char	*buff_out,
									fzrt_int			*nbytes)
{
	unsigned char	*input, *output, *length, *start, *end ;
	fzrt_int			pixel;
	short			i;

	input  = buff_in ;
	output = buff_out ;

	start  = input ;
	end    = start + pack_size;
	length = output++ ;
	pixel  = width ;
	while( pixel )
	{
		if( pixel == 1 )
		{
			*length = 0 ;
			for(i = 0; i < pack_size; i++) *(output++) = start[i];
			output++ ;
			pixel-- ;
		}
		else
		{
			for(i = 0; i < pack_size; i++)
			{
				if ( start[i] != end[i] ) break;
			}

			if( i == pack_size )
			{
				*length = 2 ;
				pixel -= 2 ;
				for(i = 0; i < pack_size; i++)
				{
					*(output++) = start[i];
				}
         		end += pack_size;

				for(i = 0; i < pack_size; i++)
				{
					if ( start[i] != end[i] ) break;
				}

				while( *length != 128 && pixel && i == pack_size )
				{
					(*length)++ ;
					end += pack_size;
					pixel--;

					for(i = 0; i < pack_size; i++)
					{
						if ( start[i] != end[i] ) break;
					}
				}

				start = end ;
				end   = start + pack_size ;

				*length = *length - 1 + 128 ;
				length  = output++ ;
			}
			else
			{
				*length = 1 ;
				pixel-- ;
				for(i = 0; i < pack_size; i++)
				{
					*(output++) = start[i];
				}

				start += pack_size ;
 				end    = start + pack_size ;

				for(i = 0; i < pack_size; i++)
				{
					if ( start[i] != end[i] )	break;
				}

				while( *length != 128 && pixel > 1 && i < pack_size )
				{
					(*length)++ ;
					for(i = 0; i < pack_size; i++) *(output++) = start[i];
					start += pack_size ;
					end   += pack_size ;
					pixel-- ;
					
					for(i = 0; i < pack_size; i++)
					{
						if ( start[i] != end[i] ) break;
					}
				}

				if ( pixel == 1 && *length < 128 )
				{
					(*length)++ ;
					for(i = 0; i < pack_size; i++) *(output++) = start[i];
					pixel-- ;
				}

				*length = *length - 1 ;
				length = output++ ;
			}
		}
	}

   *nbytes = output - buff_out - 1;
}

/************************************************************************
	Write a Targa image scanline.
*************************************************************************/
static fzrt_error_td	tga_write_scanline(tga_out_prvt_td* tga_out, unsigned char *scanline)
{
	unsigned char		*ptr;
	fzrt_error_td		err = FZRT_NOERR;
	fzrt_int				size,nbytes;
	
	if ( tga_out->compressed )
	{
		tga_compress_scanline(scanline,tga_out->ncolms,tga_out->bytes_per_pixel,tga_out->rle_buffer,&nbytes);
		ptr  = tga_out->rle_buffer;
		size = nbytes;
	}
	else	
	{
		ptr  = scanline;
		size = tga_out->scan_size;
	}
	
	if ( fwrite(ptr,size,1,tga_out->fp) < 1 )
	{
		err = _fzrt_base_funcs.fzrt_error_set_with_detail((short)errno, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_ANSI, 0, 18 );
	}
	
	return err;
}


/************************************************************************
	Write the Targa image footer.
*************************************************************************/
static fzrt_error_td	tga_write_footer(tga_out_prvt_td* tga_out)
{
	char			data[495];
	fzrt_int			ext_offset, i;
	fzrt_error_td	err = FZRT_NOERR;
	
	for(i=0;i<495;i++)	data[i]=0;
	*(short*) &data[478] = (short)(tga_out->gamma * 100);	/* Gamma Numerator. */
	*(short*) &data[480] = 100;								/* Gamma Denominator. */
	ext_offset = ftell(tga_out->fp);
	if ( fwrite(data,495,1,tga_out->fp) < 1 )
	{
		err = _fzrt_base_funcs.fzrt_error_set_with_detail((short)errno, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_ANSI, 0, 18 );
	}
	
	*(fzrt_int*) &data[0] = ext_offset;		TGA_OUT_SWAP_LONG(data[0]);	/* EXTENSION AREA OFFSET 		*/
	*(fzrt_int*) &data[4] = 0;		TGA_OUT_SWAP_LONG(data[4]);			/* DEVELOPER DIRECTORY OFFSET	*/
	strcpy(&data[8],"TRUEVISION-XFILE");							/* VERSION STRING 				*/
	data[24] = '.';													/* RESERVED CHARACTER 			*/
	data[25] = '\0';												/* STRING TERMINATOR 			*/

	if ( fwrite(data,26,1,tga_out->fp) < 1 )
	{
		err = _fzrt_base_funcs.fzrt_error_set_with_detail((short)errno, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_ANSI, 0, 18 );
	}
	
	return err;
}

/*************************************************************************
	Set a string to display in an error dialog.
 *************************************************************************/
fzrt_error_td tga_write_err_lable(fzrt_int windex, fz_ffmt_ref_td ffmt_id, char *str, fzrt_int max_len, short *err_id)
{
	char str1[256];

	if(str != NULL && max_len > 0)
	{
		/* Read string from resource file. */
		_fzrt_base_funcs.fzrt_fzr_get_string(_fz_rsrc_ref, TGA_STRINGS, TGA_READ_ERR_STR, str1);
		strncpy(str, str1, max_len);
	}
	
	if(err_id != NULL)
	{
		*err_id = TGA_READ_ERR_ID;
	}
	
	return(FZRT_NOERR);
}

/*************************************************************************
	Set a string to display in a progress bar. 
 *************************************************************************/
fzrt_error_td tga_write_progress_str(fzrt_int windex, fz_ffmt_ref_td ffmt_id, char *str, fzrt_int max_len)
{
	char str1[256];

	if(str != NULL && max_len > 0)
	{
		/* Read string from resource file. */
		_fzrt_base_funcs.fzrt_fzr_get_string(_fz_rsrc_ref, 1, WRITING_TGA_FILE, str1);
		strncpy(str, str1, max_len);
	}
	
	return(FZRT_NOERR);
}

/************************************************************************
	Initialize the targa export working data.
*************************************************************************/
static fzrt_error_td tga_out_init_raw(tga_out_prvt_td* tga_out, short width, short height, short pixelSize, 
						 fzrt_rgb_color_td *back_color, float gamma)
{
	short				img_type,do_col_map,map_len;
	fzrt_error_td		err = FZRT_NOERR;

	if ( width > 0 && height > 0)
	{	
		if(tga_out->fp)rewind(tga_out->fp);

		tga_out->rle_buffer = NULL;
		tga_out->scanline = NULL;
		tga_out->pixsize = pixelSize;
		tga_out->im_init = TRUE;
		tga_out->back_color = *back_color;
		tga_out->gamma = gamma;
		
		tga_out->nbits = 8;

		tga_out->ncolms    = width;
		tga_out->nrows     = height;

		switch (tga_out->pixsize)
		{
			case 24 :
				tga_out->bytes_per_pixel = 3;
			break;
			
			case 32 :
				tga_out->bytes_per_pixel = 4;
			break;
		}
		tga_out->shift = 0; tga_out->mask = 0x00FF;
		
		/* Allocate memory for a scanline. */
		tga_out->scan_size		 = width * tga_out->bytes_per_pixel * sizeof(unsigned char);
		err = _fzrt_mem_funcs.fzrt_new_ptr(tga_out->scan_size, (fzrt_ptr*)&tga_out->scanline);
		if ( tga_out->scanline == NULL || err != FZRT_NOERR)
		{
			err = _fzrt_base_funcs.fzrt_error_set_with_detail ( TGA_MEM_ERR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _tga_plugin_runtime_id, 109 );
		}
		else
		{	
			img_type	= (_write_flags & TGA_COMPRESS_FLAG) ? TGA_IMAGE_TYPE_TRUECOL_RLE : TGA_IMAGE_TYPE_TRUECOL;
			do_col_map	= FALSE;
			map_len 	= 0;
			
			tga_out->compressed = ((_write_flags & TGA_COMPRESS_FLAG)?1:0);
			if ( tga_out->compressed )
			{
				err = _fzrt_mem_funcs.fzrt_new_ptr(tga_out->scan_size + (width / 128 + 1) * sizeof(unsigned char), (fzrt_ptr*)&tga_out->rle_buffer);
				if ( tga_out->rle_buffer == NULL || err != FZRT_NOERR)
				{
					err = _fzrt_base_funcs.fzrt_error_set_with_detail ( TGA_MEM_ERR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _tga_plugin_runtime_id, 110 );
				}
			}
			
			/* Write the file header. */
			if(err == FZRT_NOERR)
			{
				err = tga_write_header(tga_out, img_type,width,height,(short)(tga_out->bytes_per_pixel * 8),do_col_map,map_len);
			}
		}
	}
	else
	{
		err = _fzrt_base_funcs.fzrt_error_set_with_detail(TGA_ZERO_SIZE_ERR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _tga_plugin_runtime_id, 111);
	}

	return err;
}

/************************************************************************
	Write a buffer of scanlines to the TARGA file.
*************************************************************************/
static fzrt_int	tga_out_buffer_2_file_raw(short nrows, short top, fz_rgb_uchar_td *pixels, unsigned char *alpha, fzrt_int* counter, tga_out_prvt_td* tga_out)
{
	char					shift,bitcnt;
	unsigned short			mask;
	short					nbits;
	short					pixsize,ncolms;
	fzrt_rgb_color_td		the_color;
	register short			i,j,k,kk;
	register fz_rgb_uchar_td	*ptr,*ptr2;
	register unsigned char	*aptr,*aptr2;
	register unsigned char	*scanline;
	fzrt_int					err = FZRT_NOERR;
		
	shift     = tga_out->shift;
	mask      = tga_out->mask;
	nbits     = tga_out->nbits;
	pixsize	  = tga_out->pixsize;
	ncolms    = tga_out->ncolms;
	scanline  = tga_out->scanline;
	ptr 	  = pixels;
	aptr 	  = alpha;
	bitcnt    = 0;
	
	/* Pad top with background color. */

	if ( tga_out->im_init && top > 0 )
	{
		the_color = tga_out->back_color;
		k = 0;
		
		switch ( pixsize )
		{
			case 24 :
				scanline[k++] = the_color.blue  >> 8;
				scanline[k++] = the_color.green >> 8;
				scanline[k++] = the_color.red   >> 8;
			break;
			
			case 32 :
				scanline[k++] = the_color.blue  >> 8;
				scanline[k++] = the_color.green >> 8;
				scanline[k++] = the_color.red   >> 8;
				scanline[k++] = 0;
			break;
		}
		
		for(i = 1; i < ncolms; i++)
		{
			for(kk = 0; kk < tga_out->bytes_per_pixel; kk++)
			{
				scanline[k++] = scanline[kk++];
			}
		}
		
		for(i = 0; i < top; i++)
		{
			if((err = tga_write_scanline(tga_out, scanline)) != FZRT_NOERR )	goto EXIT;		
			if(counter)(*counter)++;
		}
	}
	
	tga_out->im_init = FALSE;

	for(i = 0; i < nrows; i++)
	{
		_fuim_funcs.fz_fuim_curs_wait(FZ_FUIM_CURS_WAIT_TURN);
		ptr2 = ptr;
		if(alpha != NULL)	aptr2 = aptr;
		k = 0;
	
		if ( pixsize == 24L )
		{
			for(j = 0; j < ncolms; j++) 
			{
				scanline[k++] = ptr2->blue;
				scanline[k++] = ptr2->green;
				scanline[k++] = ptr2->red;
				ptr2++; 
			}
		}
		else if ( pixsize == 32 )
		{	
			for(j = 0; j < ncolms; j++) 
			{
				scanline[k++] = ptr2->blue;
				scanline[k++] = ptr2->green;	
				scanline[k++] = ptr2->red;		
				if(alpha == NULL)	scanline[k++] = 1.0;
				else				scanline[k++] = *aptr2++;
				ptr2++;
			}
		}		
		
		if ((err = tga_write_scanline(tga_out, scanline)) != FZRT_NOERR ) 	goto EXIT;						
		
		if(counter)(*counter)++;
	
		ptr += ncolms;
		if(alpha != NULL)	aptr += ncolms;
	}

EXIT :	
	return err;
}

/************************************************************************
	Finish the output of a targa file.
*************************************************************************/
static fzrt_int	tga_out_finit_raw(tga_out_prvt_td* tga_out)
{
	fzrt_int		err = FZRT_NOERR;

	err = tga_write_footer(tga_out);	
	return err;
}

/************************************************************************
	Check the size of the image to be exported.
*************************************************************************/
static fzrt_error_td tga_check_image_size(short pix_depth, fzrt_int width, fzrt_int height)
{
	fzrt_error_td	err = FZRT_NOERR;
	fzrt_unsigned_int	size;
	
	size = width * height * pix_depth;
	
	if(width > 0x00007fff ||
	   height > 0x00007fff ||
	   size > 0x7fffffff)
	{
		err = _fzrt_base_funcs.fzrt_error_set_with_detail(TGA_IMAGE_TOO_BIG_ERR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _tga_plugin_runtime_id, 2044 );
	}
	
	return(err);
}


/************************************************************************
	Start exporting an image.
*************************************************************************/
fzrt_error_td tga_out_init_image(fzrt_int windex, fz_ffmt_ref_td ffmt_id, void *plugin_data, fzrt_int width, fzrt_int height, fzrt_boolean has_alpha)
{
	fzrt_error_td 		err = FZRT_NOERR;
	fz_rgb_float_td		back_color;
	short				pix_depth;
	float				gamma;
	fzrt_rgb_color_td	back_clr;
	tga_out_prvt_td		*tga_out;
	fz_type_td 			fz_type;
	
	/* Get working data from plugin data. */
	tga_out = (tga_out_prvt_td*)plugin_data;

	/* Get background color. */
	_proj_funcs.fz_proj_color_get_rgb(windex, FZ_PROJ_COLR_BACK, &back_color);
	back_clr.red = (short)(back_color.red * 65535.0);
	back_clr.green = (short)(back_color.green * 65535.0);
	back_clr.blue = (short)(back_color.blue * 65535.0);

	/* Get information about the image from the image buffer. */
	
	/* Number of bits per sample. */
	if(has_alpha)	pix_depth = 32;
	else			pix_depth = 24;
	
	/* Gamma correction value for the image. */
	_rndr_funcs.fz_rndr_ibuf_get_parm(windex, FZRT_UUID_NULL, FZ_RNDR_IBUF_PARM_GAMMA, &fz_type);
	_type_funcs.fz_type_get_float(&fz_type, &gamma);

	/* Make sure the image in not too big for the Targa format. */
	if((err = tga_check_image_size(pix_depth, width, height)) == FZRT_NOERR)
	{
		/* Initialize the targa export working data. */
		err = tga_out_init_raw(tga_out, (short)(width), 
								 (short)(height), /*(short)(pix_depth > 8 ? 16 : 0),*/
								 pix_depth, /*8, row_bytes, clut,*/ &back_clr, gamma);
	}

	return err;
}



/************************************************************************
	Export some of the image.
*************************************************************************/
fzrt_error_td tga_out_buffer_2_file_image(fzrt_int windex, fz_ffmt_ref_td ffmt_id, void *plugin_data, fz_rgb_uchar_td *pixels, unsigned char *alpha, fzrt_int row)
{
	fzrt_error_td 		err = FZRT_NOERR;
	tga_out_prvt_td		*tga_out;
	static fzrt_int x;
	
	x = row;
	
	/* Get working data from plugin data. */
	tga_out = (tga_out_prvt_td*)plugin_data;

	/* Write the pixel data to the file. */
	err = tga_out_buffer_2_file_raw(1, 0, pixels, alpha, NULL/*, apply_alpha*/, tga_out);
		
	return err;
}


/************************************************************************
	Finish exporting an image.
*************************************************************************/
fzrt_error_td tga_out_finit_image(fzrt_int windex, fz_ffmt_ref_td ffmt_id, void *plugin_data)
{
	fzrt_int 				err = FZRT_NOERR;
	tga_out_prvt_td		*tga_out;
	
	/* Get working data from plugin data. */
	tga_out = (tga_out_prvt_td*)plugin_data;

	/* Finish the output of a targa file. */
	err = tga_out_finit_raw(tga_out);

	return err;
}
