/*----------------------------------------------------------------------
Copyright (c) 1998,1999 Gipsysoft. All Rights Reserved.
Please see the file "licence.txt" for licencing details.
File:	DecodeJPEG.cpp
Owner:	rich@woodbridgeinternalmed.com
Purpose:	Decode a JPEG file into a frame array.
			The only exported function is:
			bool DecodeJPEG( CDataSourceABC &ds, CFrameArray &arrFrames, int &nWidth, int &nHeight )
----------------------------------------------------------------------*/
#include "stdafx.h"
#include "DataSourceABC.h"
#include <jpeg-6b\jpeglib.h>
#include <jpeg-6b\jerror.h>
#include "ImgLibInt.h"
#include <ImgLib.h>
#include "Config.h"

#ifdef TRACING
	#define ITRACE TRACEA
#else	//	TRACING
	#define ITRACE 
#endif	//	TRACING

#ifdef IMGLIB_JPG
/////////////////////////////////////////////////////////////
// Here are the JPEG error handling routines. 
// On a error an int is thrown.
/////////////////////////////////////////////////////////////

static void win_error_exit (j_common_ptr cinfo)
{
	/* cinfo->err really points to a my_error_mgr struct, so coerce pointer */
	//jpeg_error_mgr* myerr = (jpeg_error_mgr*) cinfo->err;

#ifdef _DEBUG
	char buffer[JMSG_LENGTH_MAX];

	/* Create the message */
	(*cinfo->err->format_message) (cinfo, buffer);

	/* Output it */
	TRACEA( buffer );
#else
	UNREFERENCED_PARAMETER( cinfo );
#endif
	throw 1;
}



static void win_emit_message (j_common_ptr cinfo, int msg_level)
{
	/* cinfo->err really points to a my_error_mgr struct, so coerce pointer */
	//jpeg_error_mgr* myerr = (jpeg_error_mgr*) cinfo->err;
#ifdef _DEBUG
	char buffer[JMSG_LENGTH_MAX];

	/* Create the message */
	(*cinfo->err->format_message) (cinfo, buffer);

	/* Send it to stderr, adding a newline */
	ITRACE(buffer);
#else
	UNREFERENCED_PARAMETER( cinfo );
#endif

	/* Return control to the setjmp point */
	if (msg_level < 0)
		throw 1;
}

static void win_output_message (j_common_ptr cinfo)
{
#ifdef _DEBUG
	char buffer[JMSG_LENGTH_MAX];
	/* Create the message */
	(*cinfo->err->format_message) (cinfo, buffer);

	/* Send it to stderr, adding a newline */
	ITRACE(buffer);
#else
	UNREFERENCED_PARAMETER( cinfo );
#endif
}

static void win_format_message (j_common_ptr /* cinfo */, char * /* buffer */)
{
}

static void win_reset_error_mgr (j_common_ptr cinfo)
{
	cinfo->err->num_warnings = 0;
	/* trace_level is not reset since it is an application-supplied parameter */
	cinfo->err->msg_code = 0;	/* may be useful as a flag for "no error" */
}

static jpeg_error_mgr * jpeg_win_error (jpeg_error_mgr * err)
{
  err->error_exit = win_error_exit;
  err->emit_message = win_emit_message;
  err->output_message = win_output_message;
  err->format_message = win_format_message;
  err->reset_error_mgr = win_reset_error_mgr;

  err->trace_level = 0;		/* default = no tracing */
  err->num_warnings = 0;	/* no warnings emitted yet */
  err->msg_code = 0;		/* may be useful as a flag for "no error" */

  /* Initialize message table pointers */
  err->jpeg_message_table = 0;
  err->last_jpeg_message = 0;

  err->addon_message_table = NULL;
  err->first_addon_message = 0;	/* for safety */
  err->last_addon_message = 0;

  return err;
}

/////////////////////////////////////////////////////////////
// JPEG DataSourceABS source routines. 
/////////////////////////////////////////////////////////////

typedef struct {
	struct jpeg_source_mgr pub;	/* public fields */

	CDataSourceABC* ds;		// The data source
	JOCTET * buffer;		// start of buffer */
	bool start_of_file;	/* have we gotten any data yet? */
	int buffer_size;	// Available data at initialization
} win_source_mgr;

typedef win_source_mgr * win_src_ptr;

#define INPUT_BUF_SIZE  0x8000	/* choose an efficiently fread'able size */

static void win_init_source (j_decompress_ptr cinfo)
{
  win_src_ptr src = (win_src_ptr) cinfo->src;

  /* We reset the empty-input-file flag for each image,
   * but we don't clear the input buffer.
   * This is correct behavior for reading a series of images from one source.
   */
  src->start_of_file = true;
  src->buffer_size = src->ds->GetSize();	// Do this here for efficiency
}


static boolean win_fill_input_buffer (j_decompress_ptr cinfo)
{
	win_src_ptr src = (win_src_ptr) cinfo->src;
	int nbytes;
 	
	// Do not cause an EOF, only read what is available.
	nbytes = min(INPUT_BUF_SIZE, src->buffer_size - src->ds->GetCurrentPos());
	if (!src->ds->ReadBytes(src->buffer, nbytes))
		nbytes = 0;

	if (nbytes <= 0) 
	{
		if (src->start_of_file)	/* Treat empty input file as fatal error */
			ERREXIT(cinfo, JERR_INPUT_EMPTY);
		WARNMS(cinfo, JWRN_JPEG_EOF);
		/* Insert a fake EOI marker */
		src->buffer[0] = (JOCTET) 0xFF;
		src->buffer[1] = (JOCTET) JPEG_EOI;
		nbytes = 2;
	}

	src->pub.next_input_byte = src->buffer;
	src->pub.bytes_in_buffer = nbytes;
	src->start_of_file = false;

	return TRUE;
}


static void win_skip_input_data (j_decompress_ptr cinfo, long num_bytes)
{
	win_src_ptr src = (win_src_ptr) cinfo->src;

	/* Just a dumb implementation for now.  Could use fseek() except
	 * it doesn't work on pipes.  Not clear that being smart is worth
	 * any trouble anyway --- large skips are infrequent.
	 */
	if (num_bytes > 0)
	{
		if (num_bytes < (long)src->pub.bytes_in_buffer)
		{
			src->pub.bytes_in_buffer -= num_bytes;
			src->pub.next_input_byte += num_bytes;
			return;
		}
		num_bytes -= src->pub.bytes_in_buffer;

		if (num_bytes > 0)
		{
			src->ds->SetRelativePos(num_bytes);
		}
		win_fill_input_buffer(cinfo);
	}
}


static void win_term_source (j_decompress_ptr /* cinfo */)
{
  /* no work necessary here */
}


static void jpeg_win_src (j_decompress_ptr cinfo, CDataSourceABC* ds)
{
	win_src_ptr src;

  /* The source object and input buffer are made permanent so that a series
   * of JPEG images can be read from the same file by calling jpeg_win_src
   * only before the first one.  (If we discarded the buffer at the end of
   * one image, we'd likely lose the start of the next one.)
   * This makes it unsafe to use this manager and a different source
   * manager serially with the same JPEG object.  Caveat programmer.
   */
	if (cinfo->src == NULL) /* first time for this JPEG object? */
	{
		cinfo->src = (struct jpeg_source_mgr *)
			(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
			 sizeof(win_source_mgr));
		src = (win_src_ptr) cinfo->src;
		src->buffer = (JOCTET *)
			(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
			INPUT_BUF_SIZE * sizeof(JOCTET));
	}

	src = (win_src_ptr) cinfo->src;
	src->pub.init_source = win_init_source;
	src->pub.fill_input_buffer = win_fill_input_buffer;
	src->pub.skip_input_data = win_skip_input_data;
	src->pub.resync_to_restart = jpeg_resync_to_restart; /* use default method */
	src->pub.term_source = win_term_source;
	src->ds = ds;
	src->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */
	src->pub.next_input_byte = NULL; /* until buffer loaded */
}

bool DecodeJPEG( CDataSourceABC &ds, CFrameArray &arrFrames, SIZE &size )
{
	/* This struct contains the JPEG decompression parameters and pointers to
	 * working space (which is allocated as needed by the JPEG library).
	 */
	struct jpeg_decompress_struct cinfo;
	jpeg_error_mgr jerr;
	/* More stuff */
	JSAMPARRAY buffer;		/* Output row buffer */
	int row_stride;		/* physical row width in output buffer */
	CDib * pDib = NULL;

	try {
		// Setup the jpeg error routines to use ours
		cinfo.err = jpeg_win_error(&jerr);
		// Initialize the decompression object
		jpeg_create_decompress(&cinfo);

		// Specify the data source
		jpeg_win_src(&cinfo, &ds);

		// Read the file paramters
		jpeg_read_header(&cinfo, TRUE);
		// Start the decompressor
		jpeg_start_decompress(&cinfo);
		// Get needed info...
		size.cx = cinfo.output_width;
		size.cy = cinfo.output_height;

		// Bits will be 8 for grey scale, 24 otherwise
		int bits = cinfo.out_color_components * 8;

		pDib = new CDib( size.cx, size.cy, bits );

		RGBQUAD *pct = pDib->GetColorTable();

		// A colormapped image may not have a palette... then it is gray scale
		
		if (cinfo.out_color_components == 1)		// colormapped...
		{
			// Create a colormap...
			if (cinfo.actual_number_of_colors && cinfo.colormap)
			{
				for (int i=0; i < cinfo.actual_number_of_colors; i++)
				{
					pct->rgbReserved = 0xFF;
					pct->rgbRed = cinfo.colormap[0][i];
					pct->rgbGreen = cinfo.colormap[1][i];
					pct->rgbBlue = cinfo.colormap[2][i];
					pct++;
				}
			}
			else
			{
				// No colormap supplied... create grayscale
				int NumColors = 1<<(bits);
				for (int i=0; i<NumColors; i++)
				{
					BYTE CurColor = static_cast<BYTE>( (i*255)/(NumColors-1) );
					pct->rgbReserved = pct->rgbRed = pct->rgbGreen = pct->rgbBlue = CurColor;
					pct++;
				}
			}
		}
		
		// Get lengthof row data to be read, and allocate a buffer for it
		row_stride = cinfo.output_width * cinfo.output_components;
		buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1);

		// Get line array from CDib
		Container::CArray< BYTE * > arrLines;

		pDib->GetLineArray( arrLines );

		// Read each scan line

		for (unsigned int line = 0; line < cinfo.output_height; line++)
		{
			jpeg_read_scanlines(&cinfo, buffer, 1);

			// Need to convert RGB color space to BGR colorspace
			// used by windows bmps. Swap Red and blue entries
			if (cinfo.output_components == 3)
			{
				for (unsigned int _i = 0; _i < cinfo.output_width; _i++)
				{
					BYTE temp = buffer[0][_i * 3];
					buffer[0][_i * 3] = buffer[0][(_i * 3) + 2];
					buffer[0][(_i * 3) + 2] = temp;
				}
			}
			// Copy it into the bit array of the bitmap
			::CopyMemory(arrLines[line], buffer[0], row_stride);
  		}

		// Finish decompression
		jpeg_finish_decompress(&cinfo);
		// Release JPEG decompression object */
		jpeg_destroy_decompress(&cinfo);

		// Save our work!
		if( pDib )
		{
			CFrame *pFrame = new CFrame( pDib, 0 );
			arrFrames.Add( pFrame );
		}
	}
	catch( ... )
	{
		if( pDib )
		{
			delete pDib;
			pDib = NULL;
		}
	}

	if( pDib )
		return true;
	return false;
}

#endif	//	IMGLIB_JPG

