/*
	Twilight Prophecy 3D/Multimedia SDK
	A multi-platform development system for virtual reality and multimedia.

	Copyright (C) 1997-2001 by Twilight 3D Finland Oy Ltd.

	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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

	Please read the file LICENSE.TXT for additional details.


	source: 
		BitmapCodec "jpg"

	revision history:
		Jul/01/1999 - Jukka Liimatta - initial revision
		Jan/24/2001 - Jukka Liimatta - renaissance build
*/
#define XMD_H
#include <prcore/extlib/jpglib/cdjpeg.h>
#include <prcore/prcore.hpp>
using namespace prcore;



//////////////////////////////////////////////////////
// libjpg                                          //
////////////////////////////////////////////////////

	enum OptimizeMode
	{
		JPG_NONE		= 0,
		JPG_DOWNSCALE	= 1,
		JPG_PREVIEW		= 2
	};

	struct AJPG_DECODE
	{
		int		size;		// input: sizeof(AJPG_DECODE)
		int		optimize;	// input: optimize mode
		int		maxkb;		// input: maximum outbuf size
		int     scale;		// output: scale
		int     warnings;	// output: warnings
	};

	struct AJPG_ENCODE
	{
		int		size;		// input: sizeof(AJPG_DECODE)
		int		quality;	// quality, 0 - 100 (0 = lowest, 100 = highest)
	};


extern "C" int	outbytes;
static char*	jvbuf = NULL;
static int		jwidth;
static int		jheight;
static PixelFormat jformat(24,0xff0000,0x00ff00,0x0000ff,0x000000);


static int jpg_decode(AJPG_DECODE *jpg, char *mfbuf, int fsize)
{
	if ( !mfbuf || !fsize ) 
		return 0;

	struct jpeg_decompress_struct cinfo;
	struct jpeg_error_mgr jerr;
	JSAMPARRAY buffer;
	int pitch; int i; int maxkb;

	int jmpret = setjmp( *jerror_env() );


	if ( !jmpret )
	{
		cinfo.err = jpeg_std_error(&jerr);
		jpeg_create_decompress(&cinfo);

		jpeg_stdio_src( &cinfo, mfbuf, fsize );
		(void) jpeg_read_header(&cinfo, TRUE);

		jpeg_calc_output_dimensions(&cinfo);

		if ( ( cinfo.output_components != 3 ) && ( cinfo.out_color_space != JCS_GRAYSCALE ) )
		{
			jpeg_destroy_decompress(&cinfo);
			return 0;
		}

		int jdepth = cinfo.output_components;
		int jbits = cinfo.output_components * 8;

		if ( jpg->optimize == JPG_DOWNSCALE )
		{
			maxkb = jpg->maxkb;

			if ( maxkb < 24 ) maxkb = 24;

			int jscale = 1 << ( (cinfo.output_width * cinfo.output_height * 3) / ( 1024 * maxkb ) );
			if ( jscale > 8 ) jscale = 8;

			cinfo.scale_num = 1;
			cinfo.scale_denom = jscale;

			jpeg_calc_output_dimensions(&cinfo);
		}
		else
		if ( jpg->optimize == JPG_PREVIEW )
		{
			cinfo.scale_num = 1;
			cinfo.scale_denom = 8;

			cinfo.dct_method = JDCT_IFAST;
			cinfo.do_fancy_upsampling = FALSE;

			jpeg_calc_output_dimensions(&cinfo);
		}

		pitch = cinfo.output_width * cinfo.output_components;
		jwidth = cinfo.output_width;
		jheight = cinfo.output_height;

		if ( cinfo.out_color_space == JCS_GRAYSCALE )
		{
			jdepth *= 3;
			jbits *= 3;
		}

		char* tbuffer = new char[jwidth * jheight * jdepth];
		jvbuf = tbuffer;

		if ( !tbuffer )
		{
			jpeg_destroy_decompress(&cinfo);
			return 0;
		}

		buffer = (*cinfo.mem->alloc_sarray)( (j_common_ptr) &cinfo, JPOOL_IMAGE, pitch, 1 );
		(void) jpeg_start_decompress(&cinfo);

		while ( cinfo.output_scanline < cinfo.output_height )
		{
			jpeg_read_scanlines( &cinfo, buffer, 1 );
			if ( cinfo.out_color_space == JCS_GRAYSCALE )
			{
				for ( i = 0; i < pitch; i++ )
				{
					*tbuffer++ = *(buffer[0] + i);
					*tbuffer++ = *(buffer[0] + i);
	 				*tbuffer++ = *(buffer[0] + i);
				}
			}
			else
			{
				memcpy( tbuffer, buffer[0], pitch );
				tbuffer += pitch;
			}
		}

		(void) jpeg_finish_decompress(&cinfo);
		jpeg_destroy_decompress(&cinfo);

		return 1;
	}

	return 0;
}


static int jpg_encode(char* outbuf, int bufsize, int quality, char* image, int width, int height)
{
	int jmpret = setjmp( *jerror_env() );

	if ( !jmpret )
	{
		struct jpeg_compress_struct	cinfo;
		struct jpeg_error_mgr		jerr;
		JSAMPROW	row_pointer[1];
		JSAMPLE*	image_buffer = (JSAMPLE*)image;

		
		cinfo.err = jpeg_std_error( &jerr );
		jpeg_create_compress( &cinfo );

		if ( !outbuf || !bufsize ) 
			return 0;

		jpeg_stdio_dest(&cinfo, outbuf, bufsize);

		cinfo.image_width = width;
		cinfo.image_height = height;
		cinfo.input_components = 3;
		cinfo.in_color_space = JCS_RGB;
		jpeg_set_defaults( &cinfo );

		jpeg_set_quality( &cinfo, quality, TRUE );
		jpeg_start_compress( &cinfo, TRUE );


		while ( cinfo.next_scanline < cinfo.image_height )
		{
			row_pointer[0] = &image_buffer[cinfo.next_scanline * width * 3];
			jpeg_write_scanlines( &cinfo, row_pointer, 1 );
		}

		jpeg_finish_compress( &cinfo );
		jpeg_destroy_compress( &cinfo );

		return outbytes;
	}
	else
	{
		return 0;
	}
}


//////////////////////////////////////////////////////
// codec                                           //
////////////////////////////////////////////////////

static int NumEXT()
{
	return 3;
}


static const char* GetEXT(int index)
{
	switch ( index )
	{
		case 0:		return "jpg";
		case 1:		return "jpeg";
		case 2:		return "jfif";
		default:	return "";
	}
}


static bool IsDecoder()
{
	return true;
}


static bool IsEncoder()
{
	return true;
}


static bool Decode(Bitmap& target, Stream& stream)
{
	// reset stream
	stream.Seek(0,Stream::START);

	// read stream
	int size = stream.GetSize();
	char* data = new char[ size ];
	stream.Read(data,size);

	// setup decode struct
	AJPG_DECODE ujpg;
	ujpg.size = sizeof( ujpg );
	ujpg.optimize = JPG_NONE;
	ujpg.scale = 1;
	ujpg.warnings = 0;

	// decode
	jvbuf = NULL;
	if ( !jpg_decode(&ujpg,data,size) )
	{
		delete[] jvbuf;
		delete[] data;

		return false;
	}

	// setup image
	target.SetImage( jwidth, jheight, jformat, jvbuf );

	// release
	delete[] data;

	return true;
}


static bool Encode(Stream& target, Surface& surface)
{
	// assert
	int width = surface.GetWidth();
	int height = surface.GetHeight();
	if ( width<1 || height<1 || !surface.GetImage() )
		return false;
	
	// temp
	Bitmap temp(width,height,jformat);
	temp.Blit(surface,Surface::BLIT_COPY);
	
	int maxsize = width * height * 4;
	char* buffer = new char[ maxsize ];
	
	// encode
	int quality = 100;
	int bytes = jpg_encode( buffer, maxsize, quality, (char*)temp.GetImage(), width, height );
	if ( !bytes )
	{
		delete[] buffer;
		return false;
	}

	// write stream
	target.Write(buffer,bytes);

	delete[] buffer;

	return true;
}


//////////////////////////////////////////////////////
// factory                                         //
////////////////////////////////////////////////////

BitmapCodec CreateCodecJPG()
{
	BitmapCodec codec;
	
	codec.NumEXT = NumEXT;
	codec.GetEXT = GetEXT;
	codec.IsDecoder = IsDecoder;
	codec.IsEncoder = IsEncoder;
	codec.Decode = Decode;
	codec.Encode = Encode;
	
	return codec;
}
