/*	MiscPNGDecoder.m

	Copyright 1996 Uwe Hoffmann.

	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.

*/


#import <AppKit/AppKit.h>

#import "MiscPNGDecoder.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))


void misc_read_png_data(png_structp png_ptr, png_bytep data, png_uint_32 length)
{
	NSRange range;
	unsigned int nbytes;
        MiscPNGSource *src_ptr;

        src_ptr = (MiscPNGSource *)png_get_io_ptr(png_ptr);
	if(src_ptr->bytesRead < src_ptr->infileLength){
		nbytes = MIN(length, src_ptr->infileLength - src_ptr->bytesRead);
		range = NSMakeRange(src_ptr->bytesRead, nbytes);
		[src_ptr->infile getBytes:data range:range];
	} else
		nbytes = 0;
	src_ptr->bytesRead += nbytes;
}	

void misc_png_error_fn(png_structp png_ptr, png_const_charp error_msg)
{
    	MiscPNGError *error;

    	error = (MiscPNGError *)png_get_error_ptr(png_ptr);
    	[error->decoder setLastImageCorrupt:YES];
	[error->decoder spewMessage:[NSString stringWithCString:error_msg] 
			withSeverity:MiscDecoderSeverityFailure];
        longjmp(error->setjmp_buffer, 1); // bounce to setjmp() point
}

void misc_png_warning_fn(png_structp png_ptr, png_const_charp warning_msg)
{
    	MiscPNGError *error;

    	error = (MiscPNGError *)png_get_error_ptr(png_ptr);
	[error->decoder spewMessage:[NSString stringWithCString:warning_msg] 
			withSeverity:MiscDecoderSeverityNotice];
}

@implementation MiscPNGDecoder
/*"
  This class implements the internals of the PNG decoder.  It
  basically provides an interface between the #{MiscDecoder} API and the
  libpng API.
"*/

+ sharedInstance
{
	static id sharedInstance = nil;

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

+ (NSArray *)imageUnfilteredFileTypes;
{
	return [NSArray arrayWithObjects:
		@"png",
		@"PNG",
		nil];
}

+ (BOOL)canInitWithData:(NSData *)data
{
	unsigned char header[8];

	NSParameterAssert(data);

	if([data length] < 8)
		return NO;
	
	[data getBytes:header length:8]; 
	return png_check_sig(header,8);
}

- init
{
	[super init];
    	error.decoder = self;
        rows = NULL;
	png_ptr = NULL;
	info_ptr = NULL;
	end_info = NULL;
	return self;
}


- (NSBitmapImageRep *)_internalDecodeImage
{
	NSBitmapImageRep *imageRep;
	char *data;
	int i;

	VERBOSE {
		METHODnl;
	}


	if(info_ptr->color_type == PNG_COLOR_TYPE_PALETTE || 
		(info_ptr->color_type == PNG_COLOR_TYPE_GRAY && info_ptr->bit_depth < 8) ||
		(info_ptr->valid & PNG_INFO_tRNS)){
        	png_set_expand(png_ptr);
		png_read_update_info(png_ptr, info_ptr);
	}

	if(info_ptr->bit_depth == 16){
      		png_set_strip_16(png_ptr);
		png_read_update_info(png_ptr, info_ptr);
	}

	VERBOSE {
		NSLog(@"pixelsWide:%d",info_ptr->width);
		NSLog(@"pixelsHigh:%d",info_ptr->height);
		NSLog(@"bitsPerSample:%d",info_ptr->bit_depth);
		NSLog(@"samplesPerPixel:%d",info_ptr->channels);
		if(info_ptr->color_type & PNG_COLOR_MASK_ALPHA)
			NSLog(@"hasAlpha:YES");
		else
			NSLog(@"hasAlpha:NO");	
		if(info_ptr->color_type & PNG_COLOR_MASK_COLOR)
			NSLog(@"colorSpaceName:NSCalibratedRGBColorSpace");
		else
			NSLog(@"colorSpaceName:NSCalibratedWhiteColorSpace");
		NSLog(@"bytesPerRow:%d",info_ptr->rowbytes);
		NSLog(@"bitsPerPixel:%d",info_ptr->pixel_depth);
	}

	imageRep  = [[[NSBitmapImageRep allocWithZone:[self zone]] initWithBitmapDataPlanes:NULL
	 			pixelsWide:info_ptr->width 
				pixelsHigh:info_ptr->height 
				bitsPerSample:info_ptr->bit_depth 
				samplesPerPixel:info_ptr->channels
				hasAlpha:
	(info_ptr->color_type & PNG_COLOR_MASK_ALPHA) ? YES:NO
				isPlanar:NO 
				colorSpaceName: 
	(info_ptr->color_type & PNG_COLOR_MASK_COLOR) ? NSCalibratedRGBColorSpace:NSCalibratedWhiteColorSpace
				bytesPerRow:info_ptr->rowbytes 
				bitsPerPixel:info_ptr->pixel_depth] autorelease];

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

	data = [imageRep bitmapData];
	
	rows = (char **)NSZoneMalloc([self zone], info_ptr->height * sizeof(char *));
	for(i = 0; i < info_ptr->height; i++)
		rows[i] = (char *)(data + info_ptr->rowbytes * i);

	png_read_image(png_ptr, (png_bytep *)rows);

	NSZoneFree([self zone], rows);
        rows = NULL;

	return imageRep;
}


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


	_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");
	}


	png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 
				(void *)&error, misc_png_error_fn, misc_png_warning_fn);
    	if(!png_ptr)
        	return nil;
    	info_ptr = png_create_info_struct(png_ptr);
    	if(!info_ptr){
        	png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL);
        	return nil;
    	}
    	end_info = png_create_info_struct(png_ptr);
    	if(!end_info){
        	png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
        	return nil;
    	}
	
	png_set_read_fn(png_ptr, (void *)&source, misc_read_png_data);

	source.infile = data;
	source.bytesRead = 0;
	source.infileLength = [data length];

        if(setjmp(error.setjmp_buffer)){
		_SDFlags.lastCorrupt = YES;
            	if(rows)
                    	NSZoneFree([self zone], rows);
		return nil;
	}

	png_read_info(png_ptr, info_ptr);

	// decode image
	pngRep = [self _internalDecodeImage];


	png_read_end(png_ptr, end_info);
	png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);

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

	return pngRep;
}

@end


