/*	MiscJPEGDecoder.m

	Copyright 1996 Netsurfer Inc.

	This notice may not be removed from this source code.
	The use and distribution of this software is governed by the
	terms of the MiscKit license agreement.  Refer to the license
	document included with the MiscKit distribution for the terms.

	Author: <bbum@friday.com>
	Converted to OpenStep, September 1996, Uwe Hoffmann.
*/


#import <AppKit/AppKit.h>

#import "MiscJPEGDecoder.h"

#import <mach/mach.h>

#ifdef VERBOSE
#undef VERBOSE
#undef METHOD
#undef METHODnl
#endif

#define VERBOSE 		if(_SDFlags.verboseMode)
#define METHOD  		NSLog(@"[%@ %s%@] ", \
                                [[self class] description], \
                 		        (self == (id)[self class]) ? "+" : "-", \
				                NSStringFromSelector(_cmd))
#define METHODnl  		NSLog(@"[%@ %s%@]\n", \
                                [[self class] description], \
                 		        (self == (id)[self class]) ? "+" : "-", \
				                NSStringFromSelector(_cmd))

#define INPUT_BUF_SIZE  vm_page_size

typedef struct {
	struct jpeg_source_mgr pub;	/* public fields */
	NSData *infile;			/* source stream */
	JOCTET *buffer;			/* start of buffer */
	boolean start_of_file;		/* have we gotten any data yet? */
	unsigned int bytesRead;		/* bytes already read */
	unsigned int infileLength;	/* length of infile */
} misc_source_mgr;

typedef misc_source_mgr * misc_src_ptr;


@implementation MiscJPEGDecoder
/*"
  This class implements the internals of the JPEG/JFIF decoder.  It
  basically provides an interface between the #{MiscDecoder} API and the
  IJG libjpeg API.
"*/

+ sharedInstance
{
	static id sharedInstance = nil;

	if(!sharedInstance)
		sharedInstance = [[self alloc] init];
	return sharedInstance;
}

+ (NSArray *)imageUnfilteredFileTypes;
{
	return [NSArray arrayWithObjects:
		@"jpeg",
		@"JPEG",
		@"jpg",
		@"JPG",
		@"jfif",
		@"JFIF",
		nil];
}

+ (BOOL)canInitWithData:(NSData *)data
{
	unsigned index;
	unsigned char header[11];
	unsigned short matchbuf[] = {
				0xff, // marker
				0xd8, // start-of-input mark
				0xff, // marker
				0xe0, // app0 block follows
				0xfff,// block length (ignored)
				0xfff,// block length (ignored)
				0x4a, // 'J'
				0x46, // 'F'
				0x49, // 'I'
				0x46, // 'F'
				0x00  // NULL 'JFIF' terminator
	};

	NSParameterAssert(data);

	if([data length] < 11)
		return NO;
	
	[data getBytes:header length:11]; 

	for(index = 0; index < 11;index++)
		if((matchbuf[index] != 0xfff) && (header[index] != matchbuf[index]))
			return NO;
	return YES;
}

- init
{
	[super init];
	[self setReadCount:5];
	return self;
}

- (void)dealloc
{
	free(rowPtrBuf);
	return [super dealloc];
}

- (void) setReadCount:(unsigned)aNum
/*"Sets the readCount to aNum and reallocates %rowPtrBuf, as necessary."*/
{
	if(aNum)
		readCount = aNum;
	else
		readCount = 5;
	if(rowPtrBuf)
		rowPtrBuf = (unsigned char **)NSZoneRealloc([self zone], rowPtrBuf,sizeof(unsigned char *) * readCount);
	else
		rowPtrBuf = (unsigned char **) NSZoneMalloc([self zone], sizeof(unsigned char *) * readCount);
}

- (unsigned) readCount
/*"Returns the number of rows that will be read during each decoding pass."*/
{	
	return readCount;
}

void _init_source(j_decompress_ptr cinfo)
{
	misc_src_ptr src = (misc_src_ptr)cinfo->src;
	src->start_of_file = TRUE;
	src->bytesRead = 0;
}

boolean _fill_input_buffer(j_decompress_ptr cinfo)
{
	NSRange range;
	misc_src_ptr src = (misc_src_ptr) cinfo->src;
	size_t nbytes;
	
	if(src->bytesRead < src->infileLength){
		nbytes = MIN(INPUT_BUF_SIZE, src->infileLength - src->bytesRead);
		range = NSMakeRange(src->bytesRead, nbytes);
		[src->infile getBytes:src->buffer range:range];
	} else
		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;
	src->bytesRead += nbytes;
	return TRUE;
}

static void _skip_input_data(j_decompress_ptr cinfo, long num_bytes)
{
  	misc_src_ptr src = (misc_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){
    		while(num_bytes > (long) src->pub.bytes_in_buffer){
      			num_bytes -= (long) src->pub.bytes_in_buffer;
      			(void) _fill_input_buffer(cinfo);
      			/* note we assume that fill_input_buffer will never return FALSE,
       			* so suspension need not be handled.
       			*/
    		}
    		src->pub.next_input_byte += (size_t) num_bytes;
    		src->pub.bytes_in_buffer -= (size_t) num_bytes;
  	}
}

void _term_source(j_decompress_ptr cinfo)
{
  	/* no work necessary here */

  	/* no work necessary here */
}
	
void _nsdata_stdio_src(j_decompress_ptr cinfo, NSData *infile)
{
  	misc_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_stdio_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(misc_source_mgr));
	  	src = (misc_src_ptr) cinfo->src;
	  	src->buffer = (JOCTET *)
      		(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
								  INPUT_BUF_SIZE * sizeof(JOCTET));
  	}

  	src = (misc_src_ptr) cinfo->src;
  	src->pub.init_source = _init_source;
  	src->pub.fill_input_buffer = _fill_input_buffer;
  	src->pub.skip_input_data = _skip_input_data;
  	src->pub.resync_to_restart = jpeg_resync_to_restart; /* use default method */
  	src->pub.term_source = _term_source;

  	src->infile = infile;
	src->infileLength = [infile length];
  	src->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */
  	src->pub.next_input_byte = NULL; /* until buffer loaded */
}

void _update_row_ptr_buf(unsigned char **rowPtrBuf,
						 unsigned totalRows,
						 unsigned rowsRead,
						 unsigned readCount,
						 unsigned bytesPerRow,
						 unsigned char *data)
{
	unsigned row;
	unsigned bufIndex;
	unsigned maxRow = (rowsRead + readCount);
		
	maxRow = (maxRow < totalRows) ? maxRow : totalRows; 

	for(row=rowsRead, bufIndex = 0; row < maxRow; row++, bufIndex++)
			rowPtrBuf[bufIndex] = &(data[row * bytesPerRow]);
}
	


- (NSBitmapImageRep *)_internalDecodeImage
{
	NSBitmapImageRep *imageRep;
	void *data;
	int bytesPerRow;

	VERBOSE {
		METHODnl;
	}

	totalRows = jpegDecompInfo.pub.output_height;
	imageRep  = [[NSBitmapImageRep allocWithZone:[self zone]] initWithBitmapDataPlanes:NULL
	 			pixelsWide:jpegDecompInfo.pub.output_width 
				pixelsHigh:totalRows 
				bitsPerSample:8 
				samplesPerPixel:jpegDecompInfo.pub.output_components 
				hasAlpha:NO 
				isPlanar:NO 
				colorSpaceName:NSCalibratedRGBColorSpace 
				bytesPerRow:0 
				bitsPerPixel:0];

	if(!imageRep) {
		VERBOSE {
			METHOD;
			NSLog(@"Failed to allocate imageRep.\n");
		}
		return nil;
	}

	data = [imageRep bitmapData];
	bytesPerRow = [imageRep bytesPerRow];
	rowsRead = 0;
	while(jpegDecompInfo.pub.output_scanline < jpegDecompInfo.pub.output_height){
		_update_row_ptr_buf(rowPtrBuf,
					totalRows,
					rowsRead,
					readCount,
					bytesPerRow,
					data);
		rowsRead += jpeg_read_scanlines(&jpegDecompInfo.pub,(JSAMPARRAY) rowPtrBuf,1);
	}
	return [imageRep autorelease];
}

void miscjpeg_error_exit(j_common_ptr cinfo)
{
  	miscjpeg_error_ptr theErr = (miscjpeg_error_ptr) cinfo->err;

  	/* Always display the message. */

  	longjmp(theErr->setjmp_buffer, 1); // bounce to setjmp() point
}

void miscjpeg_error_msg(j_common_ptr cinfo)
{
	char buffer[JMSG_LENGTH_MAX];

	miscjpeg_decompress_ptr decompInfo = (miscjpeg_decompress_ptr) cinfo;

	/* Create the message */
	(*cinfo->err->format_message) (cinfo, buffer);
	
	[decompInfo->jpegDecoder setLastImageCorrupt:YES];
	if(decompInfo->jpegDecoder)
		[decompInfo->jpegDecoder 
			spewMessage:[NSString stringWithCString:buffer] 
			withSeverity:MiscDecoderSeverityFailure];
}

- (NSBitmapImageRep *)decodeFromData:(NSData *)data
{
	NSBitmapImageRep *jpegRep;


	_SDFlags.lastCorrupt = NO;
	
	if(![[self class] canInitWithData:data]) {
		VERBOSE {
			METHOD;
			NSLog(@"+canInitWithData: returned NO");
		}
		_SDFlags.lastCorrupt = YES;
		return nil;
	} else	VERBOSE {
		METHOD;
		NSLog(@"Data validated");
	}

	// initialize decompressor 
	jpegDecompInfo.pub.err = jpeg_std_error(&jpegErrMgr.pub);
	jpegDecompInfo.jpegDecoder = self;
	jpeg_create_decompress(&jpegDecompInfo.pub);

	// set source to theStream
	_nsdata_stdio_src(&jpegDecompInfo.pub, data);

	// set up jpeg error handlers
	jpegErrMgr.pub.output_message = miscjpeg_error_msg;
	jpegErrMgr.pub.error_exit = miscjpeg_error_exit;

	if(setjmp(jpegErrMgr.setjmp_buffer)){
		_SDFlags.lastCorrupt = YES;
		jpeg_destroy_decompress(&jpegDecompInfo.pub);
		return nil;
	}

	// read header and start decompression
	jpeg_read_header(&jpegDecompInfo.pub, TRUE);
	jpeg_start_decompress(&jpegDecompInfo.pub);

	VERBOSE {
		METHOD;
		NSLog(@"(%d x %d); %d components\n",
				jpegDecompInfo.pub.image_width,
				jpegDecompInfo.pub.image_height,
				jpegDecompInfo.pub.num_components);
	}

	// decode image
	jpegRep = [self _internalDecodeImage];

	// finish decompression
	jpeg_finish_decompress(&jpegDecompInfo.pub);

	// destroy decompression context
	jpeg_destroy_decompress(&jpegDecompInfo.pub);

	VERBOSE {
		METHOD;
		NSLog(@"Image was %s corrupted.",
				_SDFlags.lastCorrupt ? "" : "not");
	}

	return jpegRep;
}

@end


