/* 
 NSImageRep.m
 
 Image representaions NSCachedImageRep NSCustomImageRep NSBitmapImageRep
 
 Copyright (C) 1996 Free Software Foundation, Inc.
 
 Author:  Adam Fedor <fedor@colorado.edu>
 Date: 	Feb 1996
 Author:  Felipe A. Rodriguez <farz@mindspring.com>
 Date: 	March 1999
 Author:  H. Nikolaus Schaller <hns@computer.org>
 Date: 	March 2006
 
 This file is part of the mySTEP Library and is provided
 under the terms of the GNU Library General Public License.
 */ 

#import <tiff.h>
#import <tiffio.h>

#import <Foundation/NSObject.h>
#import <Foundation/NSArray.h>
#import <Foundation/NSData.h>
#import <Foundation/NSSet.h>
#import <Foundation/NSException.h>
#import <Foundation/NSNotification.h>

#import <AppKit/NSColor.h>
#import <AppKit/NSGraphics.h>
#import <AppKit/NSGraphicsContext.h>
#import <AppKit/NSImageRep.h>
#import <AppKit/NSBitmapImageRep.h>
#import <AppKit/NSCachedImageRep.h>
#import <AppKit/NSCustomImageRep.h>
#import <AppKit/NSPasteboard.h>
#import <AppKit/NSWindow.h>
#import <AppKit/NSView.h>
#import <AppKit/NSAffineTransform.h>

#import "NSBackendPrivate.h"

// Class variables
static NSMutableArray *__imageRepClasses;
static NSArray *__types = nil;
static NSArray *__pbTypes = nil;
static NSCountedSet *__img;
static NSCountedSet *__pb;


//*****************************************************************************
//
// 		NSImageRep 
//
//*****************************************************************************

@class GSBitmapImageRepPNG;
@class GSBitmapImageRepGIF;
@class GSBitmapImageRepJPEG;
@class GSBitmapImageRepICNS;

@implementation NSImageRep

// initialize for imageRep subclasses defined in GSBitmapImageReps.m

+ (void) initialize	
{ // in practice only one of them can load in data from a given external source
	if(self == [NSImageRep class])
		{ // not if we initialize a subclass
		__imageRepClasses = [[NSMutableArray alloc] initWithCapacity: 2];
		__img = [NSCountedSet new];
		__pb = [NSCountedSet new];
		
		// make GSBitmapImageReps register themselves as additional classes (they are not initialized automatically!)
#if 0
		NSLog(@"NSImageRep initialize");
#endif
		[NSBitmapImageRep version];	// handles TIFF
		[GSBitmapImageRepICNS version];
		[GSBitmapImageRepPNG version];
		[GSBitmapImageRepGIF version];
		[GSBitmapImageRepJPEG version];
		}
}

+ (BOOL) canInitWithData:(NSData *)data			{ SUBCLASS return NO; }
+ (BOOL) canInitWithPasteboard:(NSPasteboard*)p { SUBCLASS return NO; }
+ (NSArray *) imageUnfilteredFileTypes			{ return [self imageFileTypes]; }
+ (NSArray *) imageUnfilteredPasteboardTypes	{  return [self imagePasteboardTypes]; }

+ (NSArray *) imageFileTypes					
{
	return (__types) ? __types : (__types = [[__img allObjects] retain]);
}

+ (NSArray *) imagePasteboardTypes
{
	return (__pbTypes) ? __pbTypes : (__pbTypes = [[__pb allObjects] retain]);
}

+ (NSArray *) registeredImageRepClasses			
{ 
	return (NSArray*)__imageRepClasses;
}

+ (void) registerImageRepClass:(Class)imageRepClass
{
#if 0
	NSLog(@" register %@", [imageRepClass description]);
#endif
	NSDebugLog(@" register %@", [imageRepClass description]);
	if (![imageRepClass respondsToSelector: @selector(imageFileTypes)])
		[NSException raise: NSInvalidArgumentException
					format: @"imageRep does not respond to imageFileTypes"];
	
	[__imageRepClasses addObject: imageRepClass];
	[__img addObjectsFromArray: [imageRepClass imageUnfilteredFileTypes]];
	[__pb addObjectsFromArray: [imageRepClass imageUnfilteredPasteboardTypes]];
	ASSIGN(__types, nil);						// regenerate types on next access 
	ASSIGN(__pbTypes, nil);
}

+ (void) unregisterImageRepClass:(Class)imageRepClass
{
	[__imageRepClasses removeObject: imageRepClass];
	ASSIGN(__types, nil);						// recache types on next access 
	ASSIGN(__pbTypes, nil);
	[[NSNotificationCenter defaultCenter] postNotificationName:NSImageRepRegistryChangedNotification 
						object: self];
}

+ (id) imageRepWithContentsOfFile:(NSString *)filename
{ // Creating an NSImageRep
	NSArray *array = [self imageRepsWithContentsOfFile: filename];
	
	return ([array count]) ? [array objectAtIndex: 0] : nil;	// return first
}

+ (NSArray *) imageRepsWithContentsOfFile:(NSString *)filename
{
	NSString *ext = [filename pathExtension];
	Class rep;
	
	if (ext && (rep = [self imageRepClassForFileType: ext]))
		{
		NSData *data = [NSData dataWithContentsOfFile: filename];
		if(!data)
			return nil;	// can't open
		if([rep respondsToSelector: @selector(imageRepsWithData:)])
			return [rep imageRepsWithData: data];
		else if ([rep respondsToSelector: @selector(imageRepWithData:)])
			return [NSArray arrayWithObject:[rep imageRepWithData:data]];
		}
	
	return nil;
}

+ (id) imageRepWithPasteboard:(NSPasteboard *)pasteboard
{
	NSArray *array = [self imageRepsWithPasteboard: pasteboard];
	
	return ([array count]) ? [array objectAtIndex: 0] : nil;
}

+ (NSArray *) imageRepsWithPasteboard:(NSPasteboard *)pasteboard
{
	int i, count = [__imageRepClasses count];
	NSMutableArray *array = [NSMutableArray arrayWithCapacity:1];
	
	for (i = 0; i < count; i++)
		{
		NSString *t;
		Class rep = [__imageRepClasses objectAtIndex: i];
		
		if ([rep respondsToSelector: @selector(imagePasteboardTypes)] && 
			(t=[pasteboard availableTypeFromArray:[rep imagePasteboardTypes]]))
			{
			NSData *data = [pasteboard dataForType: t];
			
			if ([rep respondsToSelector: @selector(imageRepsWithData:)])
				[array addObjectsFromArray: [rep imageRepsWithData: data]];
			else 
				if ([rep respondsToSelector: @selector(imageRepWithData:)])
					[array addObject: [rep imageRepWithData: data]];
			}	}
	
	return (NSArray *)array;
}

+ (Class) imageRepClassForData:(NSData *)data
{
	int i, count = [__imageRepClasses count];
	Class rep;
	
	for (i = 0; i < count; i++)
		if([(rep = [__imageRepClasses objectAtIndex: i]) canInitWithData:data])
			return rep;
	
	return Nil;
}

+ (Class) imageRepClassForFileType:(NSString *)type
{
	int i, count = [__imageRepClasses count];
	
	for (i = 0; i < count; i++)
		{
		Class rep = [__imageRepClasses objectAtIndex: i];
		
		if ([[rep imageUnfilteredFileTypes] indexOfObject:type] != NSNotFound)
			return rep;
		}
	
	return Nil;
}

+ (Class) imageRepClassForPasteboardType:(NSString *)type
{
	int i, count = [__imageRepClasses count];
	
	for (i = 0; i < count; i++)
		{
		Class rep = [__imageRepClasses objectAtIndex: i];
		
		if ([rep respondsToSelector: @selector(imagePasteboardTypes)]
			&& [[rep imagePasteboardTypes] indexOfObject: type] != NSNotFound)
			return rep;
		}
	
	return Nil;
}

- (void) dealloc
{
	[_colorSpace release];
	[super dealloc];
}

- (NSSize) size									{ return _size; }
- (void) setSize:(NSSize)aSize					{ _size = aSize; }
- (void) setAlpha:(BOOL)flag					{ _irep.hasAlpha = flag; }
- (BOOL) hasAlpha								{ return _irep.hasAlpha; }
- (BOOL) isOpaque								{ return _irep.isOpaque; }
- (void) setOpaque:(BOOL)flag					{ _irep.isOpaque = flag; }
- (void) setPixelsWide:(int)anInt				{ _pixelsWide = anInt; }
- (void) setPixelsHigh:(int)anInt				{ _pixelsHigh = anInt; }
- (void) setBitsPerSample:(int)anInt			{ _irep.bitsPerSample = anInt;}
- (int) pixelsWide								{ return _pixelsWide; }
- (int) pixelsHigh								{ return _pixelsHigh; }
- (int) bitsPerSample							{ return _irep.bitsPerSample; }
- (NSString *) colorSpaceName					{ return _colorSpace; }
- (void) setColorSpaceName:(NSString *)aString	{ ASSIGN(_colorSpace,aString);}

- (BOOL) draw { return NO; } // default

- (BOOL) drawAtPoint:(NSPoint)aPoint
{ // draw translated
	if(aPoint.x == 0.0 && aPoint.y == 0.0)
		return [self draw];
	else
		{
		BOOL r;
		NSGraphicsContext *ctx=[NSGraphicsContext currentContext];
		NSAffineTransform *atm=[NSAffineTransform transform];
		[ctx saveGraphicsState];
		[atm translateXBy:aPoint.x yBy:aPoint.y];
		[atm concat];	// modify CTM as needed
		r=[self draw];
		[ctx restoreGraphicsState];
		return r;
		}
}

- (BOOL) drawInRect:(NSRect)aRect
{ // draw translated and scaled to rect
	BOOL r;
	NSGraphicsContext *ctx=[NSGraphicsContext currentContext];
	NSAffineTransform *atm=[NSAffineTransform transform];
	[ctx saveGraphicsState];
	[atm translateXBy:aRect.origin.x yBy:aRect.origin.y];
	if(!NSEqualSizes(aRect.size, _size))
		[atm scaleXBy:aRect.size.width/_size.width yBy:aRect.size.height/_size.height];	// scale to rect
	[atm concat];	// modify CTM as needed
	r=[self draw];
	[ctx restoreGraphicsState];
	return r;
}

- (id) copyWithZone:(NSZone *) zone;
{
	NSImageRep *copy = (NSImageRep*)[isa allocWithZone:zone];
	if(copy)
		{
		copy->_size = _size;
		copy->_irep = _irep;
		copy->_pixelsWide = _pixelsWide;
		copy->_pixelsHigh = _pixelsHigh;
		copy->_colorSpace = [_colorSpace retain];
		}
	return copy;
}

- (void) encodeWithCoder:(NSCoder *)aCoder
{														// NSCoding protocol
	[aCoder encodeObject: _colorSpace];
	[aCoder encodeSize: _size];
	[aCoder encodeValueOfObjCType: @encode(unsigned int) at: &_irep];
	[aCoder encodeValueOfObjCType: @encode(int) at: &_pixelsWide];
	[aCoder encodeValueOfObjCType: @encode(int) at: &_pixelsHigh];
}

- (id) initWithCoder:(NSCoder *)aDecoder
{
	if([aDecoder allowsKeyedCoding])
		{
		NSData *data=[aDecoder decodeObjectForKey:@"NSTIFFRepresentation"];
		[self release];
		return [[NSBitmapImageRep alloc] initWithData:data];
		}
	_colorSpace = [[aDecoder decodeObject] retain];
	_size = [aDecoder decodeSize];
	[aDecoder decodeValueOfObjCType:@encode(unsigned int) at: &_irep];
	[aDecoder decodeValueOfObjCType: @encode(int) at: &_pixelsWide];
	[aDecoder decodeValueOfObjCType: @encode(int) at: &_pixelsHigh];
	
	return self;
}

@end /* NSImageRep */

//*****************************************************************************
//
// 		NSCachedImageRep -- maintain an image rep in an off screen window
//
//*****************************************************************************

@implementation NSCachedImageRep

- (id) initWithSize:(NSSize)aSize
			  depth:(NSWindowDepth)aDepth
		   separate:(BOOL)separate
			  alpha:(BOOL)alpha
{
	if(separate)
		self=[self initWithWindow:nil rect:(NSRect) { NSZeroPoint, _size }];	// use shared window
	else
		NIMP;		//self=[self initWithWindow:shared window rect:(NSRect) { unused area, _size }];	// use shared window
	if(self)
		{
		_irep.hasAlpha = alpha;
		[_window setDepthLimit:aDepth];
		}
	return self;
}

- (id) initWithWindow:(NSWindow *)win rect:(NSRect)rect
{
	if((self=[super init]))
		{
		if(NSIsEmptyRect(rect))
			{
			if(!_window) 
				[NSException raise: NSInvalidArgumentException
							format: @"NSCachedImageRep window and rect are both nil"];		
			_size = [_window frame].size;
			}
		else
			{
			_origin = rect.origin;
			_size = rect.size;
			}	
		if (win)
			_window = [win retain];
		else
			// FIXME: eh... shouldn't we make an OFFSCREEN window?
			_window = [[NSWindow alloc] initWithContentRect: rect
												  styleMask: NSBorderlessWindowMask
													backing: NSBackingStoreRetained
													  defer: NO];
		}
	return self;
}

- (id) initWithCoder:(NSCoder *)aDecoder
{
	self=[super initWithCoder:aDecoder];
	if([aDecoder allowsKeyedCoding])
		{
		BOOL isPlanar=[aDecoder decodeBoolForKey:@"NSCacheWindowIsPlanar"];
		int bitsPerPlane=[aDecoder decodeBoolForKey:@"NSCacheWindowBPP"];
		int bitsPerS=[aDecoder decodeBoolForKey:@"NSCacheWindowBPS"];
		id colorSpace=[aDecoder decodeObjectForKey:@"NSCacheWindowColorSpace"];
		/* create in cache window and store self (which is now an NSBitmapImageRep) */
		abort();
		return self;
		}
	return NIMP;
}

- (id) copyWithZone:(NSZone *) zone;
{
	NSCachedImageRep *copy = (NSCachedImageRep*)[super copyWithZone:zone];
	if(copy)
		{
		copy->_window = [_window retain];
		copy->_origin = _origin;
		}
	return copy;
}

- (void) dealloc
{
	[_window release];
	[super dealloc];
}

- (NSRect) rect								{ return (NSRect){_origin,_size}; }
- (NSWindow *) window						{ return _window; }

- (BOOL) draw
{ // copy (clipped) area from cached window to current locked focus
	NSCopyBits([_window gState], [self rect], NSZeroPoint);
	return YES;
}

@end /* NSCachedImageRep */

	//*****************************************************************************
	//
	// 		NSCustomImageRep 
	//
	//*****************************************************************************

@implementation NSCustomImageRep

- (id) initWithDrawSelector:(SEL)aSelector delegate:(id)anObject
{
	if((self=[super init]))
		{
		_delegate = [anObject retain];
		_selector = aSelector;
		}
	return self;
}

- (id) copyWithZone:(NSZone *) zone;
{
	NSCustomImageRep *copy = (NSCustomImageRep*)[super copyWithZone:zone];
	if(copy)
		{
		copy->_delegate = [_delegate retain];
		copy->_selector = _selector;
		}
	return copy;
}

- (void) dealloc
{
	[_delegate release];
	[super dealloc];
}

- (id) delegate							{ return _delegate; }
- (SEL) drawSelector					{ return _selector; }

- (BOOL) draw
{
	// FIXME: we should use the IMP mechanism to properly get the BOOL return value!!!
	return ([_delegate performSelector: _selector]) ? YES : NO;
}

@end /* NSCustomImageRep */


//*****************************************************************************
//
//		NSBitmapImageRep natively understands TIFF
// 		Private tiff functions and structures (read/write data from libtiff)
//
//*****************************************************************************

#define MAX_PLANES 5							// Maximum number of planes

typedef struct {								// Structure to store common 
    u_long  imageNumber;						// information about a tiff.
    u_long  subfileType;
    u_long  width;
    u_long  height;
    u_short bitsPerSample;		// number of bits per data channel
    u_short samplesPerPixel;	// number of channels per pixel
    u_short planarConfig;		// meshed or separate
    u_short photoInterp;		// photometric interpretation of bitmap data
	NSString *space;
    u_short compression;
    int     numImages;			// number of images in tiff
    int     error;
} NSTiffInfo; 

typedef struct {
    u_int size;
    u_short *red;
    u_short *green;
    u_short *blue;
} NSTiffColormap;

typedef struct {
	char *data;
	long size;
	long position;
	const char *mode;
	
} chandle_t;

static tsize_t
TiffHandleRead(thandle_t handle, tdata_t buf, tsize_t count)
{
	chandle_t *chand = (chandle_t *)handle;
	
	NSDebugLog (@"TiffHandleRead\n");
	if (chand->position >= chand->size)
		return 0;
	if (chand->position + count > chand->size)
		count = chand->size - chand->position;
	memcpy(buf, chand->data + chand->position, count);
	
	return count;
}

static tsize_t
TiffHandleWrite(thandle_t handle, tdata_t buf, tsize_t count)
{
	chandle_t *chand = (chandle_t *)handle;
	
	NSDebugLog (@"TiffHandleWrite\n");
	if (chand->mode == "r" || (chand->position + count > chand->size))
		return 0;
	memcpy(chand->data + chand->position, buf, count);
	
	return count;
}

static toff_t
TiffHandleSeek(thandle_t handle, toff_t offset, int mode)
{
	chandle_t *chand = (chandle_t *)handle;
	
	NSDebugLog (@"TiffHandleSeek\n");
	switch(mode) 
		{
		case SEEK_SET: chand->position = offset;  break;
		case SEEK_CUR: chand->position += offset; break;
		case SEEK_END: 
			if (offset > 0 && chand->mode == "r")
				return 0;
			chand->position += offset; 
			break;
		}
	
	return chand->position;
}

static int
TiffHandleClose(thandle_t handle)
{
	NSDebugLog (@"TiffHandleClose\n");
	free((chandle_t *)handle);
	
	return 0;
}

static toff_t
TiffHandleSize(thandle_t handle)	{ return ((chandle_t *)handle)->size; }

static void
TiffHandleUnmap(thandle_t handle, tdata_t data, toff_t size)			{}

static int
TiffHandleMap(thandle_t handle, tdata_t *data, toff_t *size)
{
	chandle_t *chand = (chandle_t *)handle;
	
	NSDebugLog (@"TiffHandleMap\n");
	*data = chand->data;
	*size = chand->size;
	
	return 1;
}

TIFF * 
GSTiffOpenData(char *data, long size, const char *mode)
{												// Open a tiff from a stream. 
	chandle_t *handle;								// Returns NULL if can't read 
													// the tiff info.
	NSDebugLog (@"GSTiffOpenData\n");
	if (!(handle = malloc(sizeof(chandle_t))))
		return NULL;
	handle->data = data;
	handle->position = 0;
	handle->size = size;
	handle->mode = mode;
	
	return TIFFClientOpen("NSData", mode, (thandle_t)handle, TiffHandleRead, 
						  TiffHandleWrite, TiffHandleSeek, TiffHandleClose,
						  TiffHandleSize, TiffHandleMap, TiffHandleUnmap);
}

NSTiffInfo *      
GSTiffGetInfo(int imageNumber, TIFF *image)			// Read some information 
{													// about the image. Note 
	NSTiffInfo *info;									// that currently we don't
														// determine numImages.
	if (imageNumber >= 0 && !TIFFSetDirectory(image, imageNumber)) 
		return NULL;
	if (!(info = calloc(1, sizeof(NSTiffInfo))))
		return NULL;
	if (imageNumber >= 0)
		info->imageNumber = imageNumber;
	
	TIFFGetField(image, TIFFTAG_IMAGEWIDTH,  &info->width);
	TIFFGetField(image, TIFFTAG_IMAGELENGTH, &info->height);
	TIFFGetField(image, TIFFTAG_COMPRESSION, &info->compression);
	TIFFGetField(image, TIFFTAG_SUBFILETYPE, &info->subfileType);
	
	// If any tags are missing use TIFF defaults
	TIFFGetFieldDefaulted(image, TIFFTAG_BITSPERSAMPLE, &info->bitsPerSample);
	TIFFGetFieldDefaulted(image,TIFFTAG_SAMPLESPERPIXEL,&info->samplesPerPixel);	TIFFGetFieldDefaulted(image, TIFFTAG_PLANARCONFIG, &info->planarConfig);
	
	if (!TIFFGetField(image, TIFFTAG_PHOTOMETRIC, &info->photoInterp)) 
		{									// If TIFFTAG_PHOTOMETRIC is not 
		switch (info->samplesPerPixel) 		// present then assign a reasonable 
			{								// default. TIFF 5.0 spec doesn't 
			case 1:							// give a default.
				info->photoInterp = PHOTOMETRIC_MINISBLACK; break;
			case 3: 
			case 4:
				info->photoInterp = PHOTOMETRIC_RGB; break;
			default:
				TIFFError(TIFFFileName(image),
						  "Missing needed \"PhotometricInterpretation\" tag");
				return (0);
			}
		TIFFError(TIFFFileName(image),
				  "No \"PhotometricInterpretation\" tag, assuming %s\n",
				  info->photoInterp == PHOTOMETRIC_RGB ? "RGB" : "min-is-black");
		}
	// 8-bit RGB will be converted to 24-bit by 
	switch(info->photoInterp) 		// the tiff routines, so account for this.
		{
		case PHOTOMETRIC_MINISBLACK: 
			info->space = NSDeviceWhiteColorSpace; break;
		case PHOTOMETRIC_MINISWHITE: 
			info->space = NSDeviceBlackColorSpace; break;
		case PHOTOMETRIC_RGB: 		 
			info->space = NSCalibratedRGBColorSpace;   break;
		case PHOTOMETRIC_PALETTE: 
			info->space = NSCalibratedRGBColorSpace; 
			info->samplesPerPixel = 3;
		default:
			break;
		}
	
	return info;
}

NSTiffColormap *							// Gets the colormap for the image 
GSTiffGetColormap(TIFF *tif)				// if there is one. Returns a
{											// NSTiffColormap if one was found.
	register int i;
	NSTiffInfo *info = GSTiffGetInfo(-1, tif);	// Re-read the tiff info.  Pass -1
	int colorMapSize = 8;						// as the image number which means
	NSTiffColormap *map;						// just read the current image.
	
	if (info->photoInterp != PHOTOMETRIC_PALETTE)
		return NULL;
	
    if (!(map = malloc(sizeof(NSTiffColormap))))
		return NULL;
	map->size = 1 << info->bitsPerSample;
	
	if (!TIFFGetField(tif,TIFFTAG_COLORMAP,&map->red,&map->green,&map->blue)) 
		{
		TIFFError(TIFFFileName(tif), "Missing required \"Colormap\" tag");
		free(map);
		
		return NULL;
		}
	
	for (i = 0; i < map->size; i++)	
		if((map->red[i] > 255) ||(map->green[i] > 255) || (map->blue[i] > 255))
			{
			colorMapSize = 16;				// Many programs get TIFF colormaps 
			break;							// wrong.  They use 8-bit colormaps 
			}								// instead of 16-bit colormaps.  
											// This is a heuristic to detect 
			if (colorMapSize == 8)					// and correct this.
				{
				TIFFWarning(TIFFFileName(tif), "Assuming 8-bit colormap");
				for (i = 0; i < map->size; i++) 
					{
					map->red[i] = ((map->red[i] * 255) / ((1L << 16) - 1));
					map->green[i] = ((map->green[i] * 255) / ((1L << 16) - 1));
					map->blue[i] = ((map->blue[i] * 255) / ((1L << 16) - 1));
					}	}
			
			free(info);
	
	return map;
}

int
GSTiffRead(TIFF *tif, NSTiffInfo *info, char *data)
{
	u_char *outp = (u_char *)data;
	int i, row, col, sl_size;	// Read a tiff into a data array. The data array
	u_char *buf;				// is assumed to have been allocated to the correct
								// size.  Note that palette images are implicitly
	if (data == NULL)		// coverted to 24-bit contig direct color images.
		return -1;			// Data array should be large enough to hold this.
	
    if (!(buf = (u_char *) malloc((sl_size = TIFFScanlineSize(tif)))))
		return -1;
	
	switch (info->photoInterp) 
		{
		case PHOTOMETRIC_MINISBLACK:
		case PHOTOMETRIC_MINISWHITE:
			if (info->planarConfig == PLANARCONFIG_CONTIG)
				{
				for (row = 0; row < info->height; row++) 
					{
					if (TIFFReadScanline(tif, outp, row, 0) < 0)
						{
						NSLog(@"tiff: line %d bad data\n", row);
						return 1;
						}
					outp += sl_size;
					}	}
			else 
				{
				for (i = 0; i < info->samplesPerPixel; i++)
					{
					for (row = 0; row < info->height; row++) 
						{
						if (TIFFReadScanline(tif, buf, row, i) < 0)
							{
							NSLog(@"tiff: line %d bad data\n", row);
							return 1;
							}
						
						for (col = 0; col < sl_size; col++)
							{
							*outp = *(buf + col);
							outp += info->samplesPerPixel;
							}	}
					
					outp = (u_char *)data + i + 1;
					}	}
			break;
			
		case PHOTOMETRIC_PALETTE:
			{
				NSTiffColormap *map;
				u_char *inp;
				
				if (!(map = GSTiffGetColormap(tif)))
					return -1;
				
				for (row = 0; row < info->height; ++row) 
					{
					if (TIFFReadScanline(tif, buf, row, 0) < 0)
						{
						NSLog(@"tiff: line %d bad data\n", row);
						return 1;
						}
					for (inp = buf, col = 0; col < info->width; col++) 
						{
						*outp++ = map->red[*inp] / 256;
						*outp++ = map->green[*inp] / 256;
						*outp++ = map->blue[*inp] / 256;
						inp++;
						}	}
				
				free(map->red);
				free(map->green);
				free(map->blue);
				free(map);
			}
			break;
			
		case PHOTOMETRIC_RGB:
			if (info->planarConfig == PLANARCONFIG_CONTIG) 
				{
				for (row = 0; row < info->height; row++) 
					{
					if (TIFFReadScanline(tif, outp, row, 0) < 0)
						{
						NSLog(@"tiff: line %d bad data\n", row);
						return 1;
						}
					outp += sl_size;
					}	}
			else 
				{
				for (i = 0; i < info->samplesPerPixel; i++)
					{
					for (row = 0; row < info->height; row++) 
						{
						if (TIFFReadScanline(tif, buf, row, i) < 0)
							{
							NSLog(@"tiff: line %d bad data\n", row);
							return 1;
							}
						
						for (col = 0; col < sl_size; col++)
							{
							*outp = *(buf + col);
							outp += info->samplesPerPixel;
							}	}
					
					outp = (u_char *)data + i + 1;
					}	}
			break;
			
		default:
			TIFFError(TIFFFileName(tif), "unknown photometric %d\n",
					  info->photoInterp);
			break;
		}
	
	free (buf);
	
	return 0;
}


//*****************************************************************************
//
// 		NSBitmapImageRep 
//
//*****************************************************************************

@implementation NSBitmapImageRep 

// BitmapImageRep Class variables
static NSArray *__bitmapImageReps;
static NSArray *__pbBitmapImageReps;

+ (void) initialize
{
	__bitmapImageReps = [[NSArray arrayWithObjects: @"tiff", @"tif", nil] retain];
	__pbBitmapImageReps = [[NSArray arrayWithObjects: NSTIFFPboardType, nil] retain];
	[NSImageRep registerImageRepClass: [self class]];
}

+ (BOOL) canInitWithData:(NSData *)data
{
	TIFF *tif = GSTiffOpenData((char *)[data bytes], [data length], "r");
	TIFFClose(tif);
	return (tif) ? YES : NO;
}

+ (NSArray *) imageUnfilteredFileTypes			{ return __bitmapImageReps; }
+ (NSArray *) imagePasteboardTypes				{ return __pbBitmapImageReps; }
+ (NSArray *) imageUnfilteredPasteboardTypes	{ return __pbBitmapImageReps; }

+ (id) imageRepWithData:(NSData *)data
{
	return [[self alloc] initWithData:data];
}

+ (NSArray *) imageRepsWithData:(NSData *)data
{
	TIFF *tif;							
	int image = 0;
	NSTiffInfo *info;
	NSMutableArray *array = [NSMutableArray arrayWithCapacity:1];
	
	if (!(tif = GSTiffOpenData((char*)[data bytes], [data length], "r")))
		[NSException raise:NSTIFFException format: @"Read invalid TIFF data"];
	
	while ((info = GSTiffGetInfo(image, tif))) 
		{
		NSBitmapImageRep *imageRep;
		
		imageRep=[[[[self class] alloc] initWithBitmapDataPlanes: NULL
								pixelsWide: info->width
								pixelsHigh: info->height
							 bitsPerSample: info->bitsPerSample
						   samplesPerPixel: info->samplesPerPixel
								  hasAlpha:(info->samplesPerPixel > 3)
								  isPlanar:(info->planarConfig == PLANARCONFIG_SEPARATE)
							colorSpaceName: info->space
							   bytesPerRow: 0
							  bitsPerPixel: 0] autorelease];
		imageRep->_compression = info->compression;
	//	imageRep->_factor = info->_factor;
		// read tiff into data array
		if (GSTiffRead(tif, info, (char *) [imageRep bitmapData]))
			[NSException raise:NSTIFFException format: @"invalid TIFF image"];
		
		free(info);
		[array addObject: imageRep];
		image++;
		}
	TIFFClose(tif);
	
	return array;
}

- (id) initWithData:(NSData *)data				// Loads default (first) image
{										 		// from TIFF contained in data
	TIFF *tif = GSTiffOpenData((char*)[data bytes], [data length], "r");							
	NSTiffInfo *info = GSTiffGetInfo(-1, tif);
	
	if (!tif || !info)
		{
		// FIXME: we must also be able to load all supported formats here!
		[NSException raise:NSTIFFException format: @"Read invalid TIFF data"];
		}
	self=[self initWithBitmapDataPlanes: NULL
						pixelsWide: info->width
						pixelsHigh: info->height
					 bitsPerSample: info->bitsPerSample
				   samplesPerPixel: info->samplesPerPixel
						  hasAlpha:(info->samplesPerPixel > 3)
						  isPlanar:(info->planarConfig == PLANARCONFIG_SEPARATE)
					colorSpaceName: info->space
					   bytesPerRow: 0
					  bitsPerPixel: 0];
	if(self)
		{
		_compression = info->compression;
	//	_factor = info->compression;
		// read tiff into data array
		if (GSTiffRead(tif, info, (char *) [self bitmapData]))
			[NSException raise:NSTIFFException format:@"Read invalid TIFF image"];
	
		TIFFClose(tif);
		}
	return self;
}

- (id) initWithBitmapDataPlanes:(unsigned char **)planes
					 pixelsWide:(int)width
					 pixelsHigh:(int)height
				  bitsPerSample:(int)bitsPerSample
				samplesPerPixel:(int)samplesPerPixel
					   hasAlpha:(BOOL)alpha
					   isPlanar:(BOOL)isPlanar
				 colorSpaceName:(NSString *)colorSpaceName
					bytesPerRow:(int)rowBytes
				   bitsPerPixel:(int)pixelBits;
{
	return [self initWithBitmapDataPlanes:planes
							   pixelsWide:width
							   pixelsHigh:height
							bitsPerSample:bitsPerSample
						  samplesPerPixel:samplesPerPixel
								 hasAlpha:alpha
								 isPlanar:isPlanar
						   colorSpaceName:colorSpaceName
							 bitmapFormat:0
							  bytesPerRow:rowBytes
							 bitsPerPixel:pixelBits];
}

- (id) initWithBitmapDataPlanes:(unsigned char **)planes	// designated init
					 pixelsWide:(int)width
					 pixelsHigh:(int)height
				  bitsPerSample:(int)bitsPerSample
				samplesPerPixel:(int)samplesPerPixel
					   hasAlpha:(BOOL)alpha
					   isPlanar:(BOOL)isPlanar
				 colorSpaceName:(NSString *)colorSpaceName
					bitmapFormat:(NSBitmapFormat)bitmapFormat
					bytesPerRow:(int)rowBytes
				   bitsPerPixel:(int)pixelBits;
{
	if (bitsPerSample <= 0 || samplesPerPixel <= 0 || samplesPerPixel > 5 || width <= 0 || height <= 0) 
		[NSException raise: NSInvalidArgumentException
					format: @"initWithBitmapDataPlanes missing required arguments"];
	if((self=[super init]))
		{
		_pixelsWide = width;
		_pixelsHigh = height;
		_size = NSMakeSize(width, height);
		_irep.bitsPerSample = bitsPerSample;
		_brep.numColors = samplesPerPixel;
		_irep.hasAlpha = alpha;  
		_brep.isPlanar = isPlanar;
		_format=bitmapFormat;
		_compression=NSTIFFCompressionLZW;	// default compression
		_factor=1.0;
		_colorSpace = [colorSpaceName retain];
		if(!pixelBits)
			pixelBits = bitsPerSample * ((_brep.isPlanar) ? 1 : samplesPerPixel);
		_brep.bitsPerPixel = pixelBits;
		if(!rowBytes) 
			bytesPerRow = ((unsigned long) width * _brep.bitsPerPixel + 7)/8;
		else
			bytesPerRow = rowBytes;
		if(planes) 
			{
			int i, np = ((_brep.isPlanar) ? _brep.numColors : 1);
			_imagePlanes = calloc(np, sizeof(unsigned char *));
			if(!_imagePlanes)
				{ [self release]; return nil; }
			for(i = 0; i < np; i++)
				_imagePlanes[i] = planes[i];	// copy data
			}
		}
	return self;
}

- (id) copyWithZone:(NSZone *) zone;
{
	NSBitmapImageRep *copy = (NSBitmapImageRep*)[super copyWithZone:zone];
	if(copy)
		{
		/*
		 unsigned char **_imagePlanes;
		 NSMutableData *_imageData;
		 unsigned int bytesPerRow;
		 unsigned short compression;
		 NSBitmapFormat _format;
		 struct __bitmapRepFlags {
			 unsigned int bitsPerPixel:8;	
			 unsigned int isPlanar:1;
			 unsigned int numColors:4;
			 unsigned int cached:1;
			 unsigned int cachedPixel:1;
			 unsigned int reserved:17;
		 } _brep;
		 */
		}
	return NIMP;
	return copy;
}

- (NSString *) description;
{
	return [NSString stringWithFormat:@"%@: WxH=%dx%d, size=%@, bit/sample=%d, planes=%d, alpha=%d, planar=%d, colorspace=%@, bit/px=%d, byte/row=%d",
		NSStringFromClass([self class]),
		_pixelsWide, _pixelsHigh,
		NSStringFromSize(_size),
		_irep.bitsPerSample,
		_brep.numColors,
		_irep.hasAlpha,
		_brep.isPlanar,
		_colorSpace,
		_brep.bitsPerPixel,
		bytesPerRow
		];
}

- (int) incrementalLoadFromData:(NSData *) data complete:(BOOL) complete;
{
	if(!complete)
		return NSImageRepLoadStatusWillNeedAllData;	// we don't implement it really
	return [self initWithData:data]?NSImageRepLoadStatusCompleted:NSImageRepLoadStatusUnexpectedEOF;
}

- (int) bitsPerPixel			{ return _brep.bitsPerPixel; }
- (int) samplesPerPixel			{ return _brep.numColors; }
- (int) numberOfPlanes			{ return _brep.isPlanar ? _brep.numColors : 1;}
- (int) bytesPerPlane			{ return bytesPerRow * _pixelsHigh; }
- (int) bytesPerRow				{ return bytesPerRow; }
- (BOOL) isPlanar				{ return _brep.isPlanar; }

- (BOOL) draw
{ // draw scaled by size
	BOOL r;
	NSGraphicsContext *ctx=[NSGraphicsContext currentContext];
	NSAffineTransform *atm=[NSAffineTransform transform];
	[ctx saveGraphicsState];
	[atm scaleXBy:_size.width yBy:_size.height];
	[atm concat];
	// [ctx _setFraction:1.0];	// we ignore fraction anyway
	r=[ctx _draw:self];
	[ctx restoreGraphicsState];
	return r;
}

- (unsigned char *) bitmapData
{
	if (!_imagePlanes || !_imagePlanes[0])
		{ // allocate data planes
		int i, planeSize = (bytesPerRow * _pixelsHigh);
		long length = _brep.numColors * planeSize * sizeof(unsigned char);
		
		_imagePlanes = objc_calloc(MAX_PLANES, sizeof(unsigned char*));
		_imageData = [[NSMutableData dataWithLength: length] retain];
		_imagePlanes[0] = [_imageData mutableBytes];
		
		if (_brep.isPlanar) 
			for (i = 1; i < _brep.numColors; i++) 
				_imagePlanes[i] = _imagePlanes[0] + (i * planeSize);
		}									
	
	return _imagePlanes[0];
}

- (NSBitmapFormat) bitmapFormat;	{ return _format; }

- (void) getBitmapDataPlanes:(unsigned char **)data
{
	int i;
	
	if (!_imagePlanes || !_imagePlanes[0])
		[self bitmapData];
	
	if (data)
		for (i = 0; i < _brep.numColors; i++)
			data[i] = _imagePlanes[i];
}

- (void) getPixel:(unsigned int[]) pixelData atX:(int) x y:(int) y;
{
	int i;
	int offset;
	// FIXME: handle _format
	if(_brep.isPlanar)
		{ // planar
		offset=x + bytesPerRow*(_pixelsHigh-1-y);
		for(i=0; i<_brep.numColors; i++)
			pixelData[i]=_imagePlanes[i][offset];
		}
	else
		{ // meshed
		offset=_brep.numColors*x + bytesPerRow*(_pixelsHigh-1-y);
		for(i=0; i<_brep.numColors; i++)
			pixelData[i]=_imagePlanes[0][offset+i];
		}
}

- (void) setPixel:(unsigned int[]) pixelData atX:(int) x y:(int) y;
{
	int i;
	int offset;
	if (!_imagePlanes || !_imagePlanes[0])
		[self bitmapData];	// allocate plane memory
	// FIXME: handle _format
	if(_brep.isPlanar)
		{ // planar
		offset=x + bytesPerRow*(_pixelsHigh-1-y);
		for(i=0; i<_brep.numColors; i++)
			_imagePlanes[i][offset]=pixelData[i];
		}
	else
		{ // meshed
		offset=_brep.numColors*x + bytesPerRow*(_pixelsHigh-1-y);
		for(i=0; i<_brep.numColors; i++)
			_imagePlanes[0][offset+i]=pixelData[i];
		}
}

- (NSColor *) colorAtX:(int) x y:(int) y;
{
	unsigned int pixelData[5];
	if(x < 0 || y < 0 || x >= _pixelsWide || y >= _pixelsHigh)
		return nil;	// outside
	[self getPixel:pixelData atX:x y:y];
	if([_colorSpace isEqualToString:NSCalibratedRGBColorSpace])
		{
		if(_irep.hasAlpha)
			return [NSColor colorWithCalibratedRed:pixelData[0]/255.0 green:pixelData[1]/255.0 blue:pixelData[2]/255.0 alpha:pixelData[3]/255.0];
		return [NSColor colorWithCalibratedRed:pixelData[0]/255.0 green:pixelData[1]/255.0 blue:pixelData[2]/255.0 alpha:1.0];
		}
	else if([_colorSpace isEqualToString:NSDeviceRGBColorSpace])
		{
		if(_irep.hasAlpha)
			return [NSColor colorWithDeviceRed:pixelData[0]/255.0 green:pixelData[1]/255.0 blue:pixelData[2]/255.0 alpha:pixelData[3]/255.0];
		return [NSColor colorWithDeviceRed:pixelData[0]/255.0 green:pixelData[1]/255.0 blue:pixelData[2]/255.0 alpha:1.0];
		}
	NSLog(@"unexpected colorspace");
	return nil;
}

- (void) setColor:(NSColor *)color atX:(int) x y:(int) y;
{
	unsigned int pixelData[5];
	if(x < 0 || y < 0 || x >= _pixelsWide || y >= _pixelsHigh)
		return;	// outside
	if([_colorSpace isEqualToString:NSCalibratedRGBColorSpace])
		{
		float red, green, blue, alpha;
		[color getRed:&red green:&green blue:&blue alpha:&alpha];
		pixelData[0]=255*red;
		pixelData[1]=255*green;
		pixelData[2]=255*blue;
		if(_irep.hasAlpha)
			pixelData[3]=255*alpha;
		}
	else if([_colorSpace isEqualToString:NSDeviceRGBColorSpace])
		{
		float red, green, blue, alpha;
		[color getRed:&red green:&green blue:&blue alpha:&alpha];
		pixelData[0]=255*red;
		pixelData[1]=255*green;
		pixelData[2]=255*blue;
		if(_irep.hasAlpha)
			pixelData[3]=255*alpha;
		}
	else
		return;	// not implemented (e.g. NSCalibratedCMYKColorSpace)
	[self setPixel:pixelData atX:x y:y];
}

- (id) initWithFocusedViewRect:(NSRect)rect
{
	if((self=[self initWithBitmapDataPlanes:NULL
								 pixelsWide:rect.size.width
								 pixelsHigh:rect.size.height
							  bitsPerSample:24	// get from screen depth
							samplesPerPixel:8
								   hasAlpha:NO
								   isPlanar:NO	// what is better?
							 colorSpaceName:NSDeviceRGBColorSpace
								bytesPerRow:0
							   bitsPerPixel:0]))
		{ // fill bitmap from screen
		[[NSGraphicsContext currentContext] _initBitmap:self withFocusedViewRect:rect];
		}
	return self;
}

+ (void) getTIFFCompressionTypes:(const NSTIFFCompression **)list
						   count:(int *)numTypes
{
	NIMP;
}

+ (NSData *) representationOfImageRepsInArray:(NSArray *)imageReps usingType:(NSBitmapImageFileType)storageType properties:(NSDictionary *)properties
{
	switch(storageType)
		{ // pass down to appropriate subclass - but usingType:-1 to avoid recursion if not implemented
		case NSBMPFileType:
			return nil;	// not available
		case NSGIFFileType:
			return [GSBitmapImageRepGIF representationOfImageRepsInArray:imageReps usingType:-1 properties:properties];
		case NSJPEGFileType:
		case NSJPEG2000FileType:
			return [GSBitmapImageRepJPEG representationOfImageRepsInArray:imageReps usingType:-1 properties:properties];
		case NSPNGFileType:
			return [GSBitmapImageRepPNG representationOfImageRepsInArray:imageReps usingType:-1 properties:properties];
		case NSICNSFileType:
			return [GSBitmapImageRepICNS representationOfImageRepsInArray:imageReps usingType:-1 properties:properties];
		case NSTIFFFileType:
			{
				id factor=[properties objectForKey:NSImageCompressionFactor];
				if(!factor)
					return nil;
				// fixme: get compression type
				return [self TIFFRepresentationOfImageRepsInArray:imageReps usingCompression:0 factor:[factor floatValue]];
			}
		}
	return nil;	// invalid storage type
}

- (NSData *) representationUsingType:(NSBitmapImageFileType) storageType properties:(NSDictionary *) properties
{ // single file
	return [isa representationOfImageRepsInArray:[NSArray arrayWithObject:self] usingType:storageType properties:properties];
}

+ (NSData *) TIFFRepresentationOfImageRepsInArray:(NSArray *)anArray
{
	// loop through all image reps and store [element TIFFRepresentation]
	return NIMP;
}

+ (NSData *) TIFFRepresentationOfImageRepsInArray:(NSArray *)anArray
								 usingCompression:(NSTIFFCompression)compressionType
										   factor:(float)factor
{
	// loop through all image reps and store [element TIFFRepresentationUsingCompression:compressionType factor:factor]
	return NIMP;
}

- (NSData *) TIFFRepresentation
{
	return [self TIFFRepresentationUsingCompression:_compression factor:_factor];
}

- (NSData *) TIFFRepresentationUsingCompression:(NSTIFFCompression)comp factor:(float)f
{ // this is the primitive method for TIFFs
	return NIMP;
}

- (BOOL) canBeCompressedUsing:(NSTIFFCompression)compression
{
	NIMP return NO;
}

- (void) getCompression:(NSTIFFCompression *)comp factor:(float *)factor
{
	*comp=_compression;
	*factor=_factor;
}

- (void) setCompression:(NSTIFFCompression)comp factor:(float)factor
{
	_compression=comp;
	_factor=factor;
}

- (void) colorizeByMappingGray:(float) midPoint
					   toColor:(NSColor *) midPointColor
				  blackMapping:(NSColor *) shadowColor
				  whiteMapping:(NSColor *) lightColor;
{ // FIXME: very slow algorithm! Should be a CoreImage filter kernel...
	int x, y;
	NSColor *col;
	for(y=0; y<_pixelsHigh; y++)
		{
		for(x=0; x<_pixelsWide; x++)
			{
			col=[self colorAtX:x y:y];
			// get grey value
			// decide if we are above or below mid point
			// if below: interpolate between mitPointColor and shadowColor
			// if above: interpolate between mitPointColor and lightColor
			[self setColor:col atX:x y:y];
			}
		}
}

- (void) encodeWithCoder:(NSCoder*)coder		{ NIMP; }		// NSCoding Protocol

- (id) initWithCoder:(NSCoder*)coder
{
	if([coder allowsKeyedCoding])
		{
		NSData *data=[coder decodeObjectForKey:@"NSTIFFRepresentation"];
		[self release];
		return [NSBitmapImageRep imageRepWithData:data];	// should be TIFF
		}
	NSLog(@"NSBitmapImageRep: can't initWithCoder");
	return nil;
}

@end /* NSBitmapImageRep */
