/*	Copyright (c) 1992 Linus Upson & The Geometry Center
	University of Minnesota
	1300 South Second Street
	Minneapolis, MN  55454, USA
	lupson@geom.umn.edu
*/

#import "Scale.h"
#import <math.h>

@implementation Scale

static unsigned char fourToEight_upper[256];
static unsigned char fourToEight_lower[256];
static unsigned char twoToEight_upper[256];
static unsigned char twoToEight_upperMiddle[256];
static unsigned char twoToEight_lowerMiddle[256];
static unsigned char twoToEight_lower[256];

static void print_info (const char *string, NXBitmapImageRep *bitmap)
{
	printf ("%s\n", string);
	printf (" bitsPerPixel               %d\n", (int)[bitmap bitsPerPixel]);
	printf (" samplesPerPixel            %d\n", (int)[bitmap samplesPerPixel]);
	printf (" bitsPerSample (NXImageRep) %d\n", (int)[bitmap bitsPerSample]);
	printf (" isPlanar                   %d\n", (int)[bitmap isPlanar]);
	printf (" numPlanes                  %d\n", (int)[bitmap numPlanes]);
	printf (" numColors (NXImageRep)     %d\n", (int)[bitmap numColors]);
	printf (" hasAlpha (NXImageRep)      %d\n", (int)[bitmap hasAlpha]);
	printf (" bytesPerPlane              %d\n", (int)[bitmap bytesPerPlane]);
	printf (" bytesPerRow                %d\n", (int)[bitmap bytesPerRow]);
	printf (" colorSpace                 %d\n", (int)[bitmap colorSpace]);
	printf (" pixelsWide (NXImageRep)    %d\n", (int)[bitmap pixelsWide]);
	printf (" pixelsHigh (NXImageRep)    %d\n", (int)[bitmap pixelsHigh]);
	printf ("\n");
}

static void convertFourBPSMeshed (NXBitmapImageRep *inBitmap,
	NXBitmapImageRep *outBitmap)
{
	int i, j, width, height, samplesPerPixel;
	int inBytesPerRow, outBytesPerRow, inWordsPerRow, inRemainderSamples;
	unsigned char *inData, *inPtr, *outData, *outPtr;
	
	width = [inBitmap pixelsWide];
	height = [inBitmap pixelsHigh];
	samplesPerPixel = [inBitmap samplesPerPixel];
	
	inData = [inBitmap data];
	outData = [outBitmap data];
	inBytesPerRow = [inBitmap bytesPerRow];
	outBytesPerRow = [outBitmap bytesPerRow];
	
	inWordsPerRow = inBytesPerRow / 4;
	inRemainderSamples = (width * samplesPerPixel - inWordsPerRow * 8);
	
	for (i = 0; i < height; i++) {
		outPtr = outData + i * outBytesPerRow;
		inPtr = inData + i * inBytesPerRow;
		for (j = 0; j < inWordsPerRow; j++) {
			*outPtr++ = fourToEight_upper[*inPtr];
			*outPtr++ = fourToEight_lower[*inPtr++];
			*outPtr++ = fourToEight_upper[*inPtr];
			*outPtr++ = fourToEight_lower[*inPtr++];
			*outPtr++ = fourToEight_upper[*inPtr];
			*outPtr++ = fourToEight_lower[*inPtr++];
			*outPtr++ = fourToEight_upper[*inPtr];
			*outPtr++ = fourToEight_lower[*inPtr++];
		}
		for (j = 0; j < inRemainderSamples; j++) {
			*outPtr++ = fourToEight_upper[*inPtr];
			if (++j < inRemainderSamples)
				*outPtr++ = fourToEight_lower[*inPtr++];
		}
	}
}

static void convertTwoBPSPlanar (NXBitmapImageRep *inBitmap,
	NXBitmapImageRep *outBitmap)
{
	int i, j, k, width, height;
	int inBytesPerRow, inWordsPerRow, inRemainderSamples;
	int numPlanes, outBytesPerRow;
	unsigned char *inPlanes[5], *inPlanePtr[5], *outData, *outPtr;

	width = [inBitmap pixelsWide];
	height = [inBitmap pixelsHigh];
	numPlanes = [inBitmap numPlanes];
	
	[inBitmap getDataPlanes:inPlanes];
	inBytesPerRow = [inBitmap bytesPerRow];
	inWordsPerRow = inBytesPerRow / 4;
	inRemainderSamples = width - inWordsPerRow * 16;
	
	outData = [outBitmap data];
	outBytesPerRow = [outBitmap bytesPerRow];

	for (i = 0; i < height; i++) {
		outPtr = outData + i * outBytesPerRow;
		for (k = 0; k < numPlanes; k++)
			inPlanePtr[k] = inPlanes[k] + i * inBytesPerRow;
		for (j = 0; j < inWordsPerRow * 4; j++) {
			for (k = 0; k < numPlanes; k++)
				*outPtr++ = twoToEight_upper[*inPlanePtr[k]];
			for (k = 0; k < numPlanes; k++)
				*outPtr++ = twoToEight_upperMiddle[*inPlanePtr[k]];
			for (k = 0; k < numPlanes; k++)
				*outPtr++ = twoToEight_lowerMiddle[*inPlanePtr[k]];
			for (k = 0; k < numPlanes; k++)
				*outPtr++ = twoToEight_lower[*inPlanePtr[k]++];
		}
		for (j = 0; j < inRemainderSamples; j++) {
			for (k = 0; k < numPlanes; k++)
				*outPtr++ = twoToEight_upper[*inPlanePtr[k]];
			if (++j < inRemainderSamples)
				for (k = 0; k < numPlanes; k++)
					*outPtr++ = twoToEight_upperMiddle[*inPlanePtr[k]];
			if (++j < inRemainderSamples)
				for (k = 0; k < numPlanes; k++)
					*outPtr++ = twoToEight_lowerMiddle[*inPlanePtr[k]];
			if (++j < inRemainderSamples)
				for (k = 0; k < numPlanes; k++)
					*outPtr++ = twoToEight_lower[*inPlanePtr[k]++];
		}
	}
}

static void convertEightBPSPlanar (NXBitmapImageRep *inBitmap,
	NXBitmapImageRep *outBitmap)
{
	int i, j, k, width, height, numPlanes;
	int inBytesPerRow, outBytesPerRow;
	unsigned char *inPlanes[5], *inPlanePtr[5], *outData, *outPtr;

	width = [inBitmap pixelsWide];
	height = [inBitmap pixelsHigh];
	numPlanes = [inBitmap numPlanes];
	
	[inBitmap getDataPlanes:inPlanes];
	inBytesPerRow = [inBitmap bytesPerRow];
	
	outData = [outBitmap data];
	outBytesPerRow = [outBitmap bytesPerRow];

	for (i = 0; i < height; i++) {
		outPtr = outData + i * outBytesPerRow;
		for (k = 0; k < numPlanes; k++)
			inPlanePtr[k] = inPlanes[k] + i * inBytesPerRow;
		for (j = 0; j < inBytesPerRow; j++) {
			for (k = 0; k < numPlanes; k++)
				*outPtr++ = *inPlanePtr[k]++;
		}
	}
}

- init
{
	[super init];
	scratchZone = NXCreateZone (vm_page_size, vm_page_size, YES);
	return self;
}

- buildTables:(int)seed
{
	int i;
	double dx, dy, dz, rr;
	
	// Build the different bitmap conversion tables.
	for (i = 0; i < 256; i++) {
		fourToEight_upper[i] = ((i >> 4) * 17);
		fourToEight_lower[i] = ((i & 0x0F) * 17);
		twoToEight_upper[i] = ((i >> 6) * 85);
		twoToEight_upperMiddle[i] = (((i & 0x30) >> 4) * 85);
		twoToEight_lowerMiddle[i] = (((i & 0x0C) >> 2) * 85);
		twoToEight_lower[i] = ((i & 0x03) * 85);
	}
	
	// Build a table of points whose density falls off as:
	// 1./sqrt (2.*M_PI*sigma) * exp (r*r/(2.*sigma*sigma))
	srandom (seed);
	for (i = 0; i < NUM_POINTS; i++) {
		do {
			dx = 1. - 4. * (double)random () / (double)(0xFFFFFFFF);
			dy = 1. - 4. * (double)random () / (double)(0xFFFFFFFF);
			dz = (double)random () / (double)(0xFFFFFFFF);
			rr = dx * dx + dy * dy;
		} while (dz > exp (-2. * rr) || rr > 1.);

		jitterPoint[i].x = dx;
		jitterPoint[i].y = dy;
	}
	
	jit = jitMin = jitterPoint;
	jitMax = jitterPoint + NUM_POINTS;
	
	tablesBuilt = YES;
	return self;
}

- resetJitterIndex
{
	jit = jitMin;
	return self;
}

- (NXBitmapImageRep *)convertBitmap:(NXBitmapImageRep *)inBitmap
{
	int bps;
	BOOL planar;
	NXBitmapImageRep *standardBitmap;
	
	bps = [inBitmap bitsPerSample];
	planar = [inBitmap isPlanar];
	
	// If the bitmap is already 8 bits per sample and in meshed
	// configuration, we don't need to do any conversion.
	if (bps == 8 && !planar) {
		freeStandardBitmap = NO;
		return inBitmap;
	}
	
	// Make space for a bitmap that is one byte for each sample and meshed.
	// Be sure to remember to free it.
	freeStandardBitmap = YES;
	standardBitmap = [[NXBitmapImageRep allocFromZone:scratchZone]
		initData:NULL
		pixelsWide:inWidth
		pixelsHigh:inHeight
		bitsPerSample:8
		samplesPerPixel:[inBitmap samplesPerPixel]
		hasAlpha:[inBitmap hasAlpha]
		isPlanar:NO
		colorSpace:[inBitmap colorSpace]
		bytesPerRow:0
		bitsPerPixel:0];

	// Currently the Scale filter only supports the following bitmap formats:
	// 4 bits per sample meshed (NeXT Station Color).
	// 2 bits per sample planar (NeXT Station & Cube).
	// 8 bits per sample planar (Eight bit Gray).
	// 8 bits per sample meshed (NeXT Dimension) -- no conversion needed.
	if (bps == 4 && !planar)
		convertFourBPSMeshed (inBitmap, standardBitmap);
	else if (bps == 2 && planar)
		convertTwoBPSPlanar (inBitmap, standardBitmap);
	else if (bps == 8 && planar)
		convertEightBPSPlanar (inBitmap, standardBitmap);
	else {
		[standardBitmap free];
		standardBitmap = nil;
	}
	
	return standardBitmap;
}

- (NXBitmapImageRep *)scaleBitmap:(NXBitmapImageRep *)inBitmap
{
	int i, j, k, l, x, y;
	int inBytesPerRow, outBytesPerRow, spp, accbuf[5];
	float s, t;
	unsigned char *inData, *inPtr, *outData, *outPtr;
	NXBitmapImageRep *outBitmap;
	
	inData = [inBitmap data];
	inBytesPerRow = [inBitmap bytesPerRow];
	spp = [inBitmap samplesPerPixel];

	outBitmap = [[NXBitmapImageRep allocFromZone:scratchZone]
		initData:NULL
		pixelsWide:outWidth
		pixelsHigh:outHeight
		bitsPerSample:8
		samplesPerPixel:spp
		hasAlpha:[inBitmap hasAlpha]
		isPlanar:NO
		colorSpace:[inBitmap colorSpace]
		bytesPerRow:0
		bitsPerPixel:0];

	outData = [outBitmap data];
	outBytesPerRow = [outBitmap bytesPerRow];
	
	for (i = 0; i < outHeight; i++) {
	
		outPtr = outData + i * outBytesPerRow;
		
		for (j = 0; j < outWidth; j++) {
		
			for (l = 0; l < spp; l++)
				accbuf[l] = 0.;
			
			for (k = 0; k < samples; k++) {
			
				if (++jit == jitMax) jit = jitMin;
				s = ((float)j + .5 + focus * jit->x) / scale;
				t = ((float)i + .5 + focus * jit->y) / scale;
				if (++jit == jitMax) jit = jitMin;
				x = (int)(s + .5 + focus * jit->x); 
				y = (int)(t + .5 + focus * jit->y); 
				
				if (x < 0)
					x = 0;
				else if (x >= inWidth)
					x = inWidth - 1;
				if (y < 0)
					y = 0;
				else if (y >= inHeight)
					y = inHeight - 1;
					
				inPtr = inData + y * inBytesPerRow  + x * spp;
					
				for (l = 0; l < spp; l++)
					accbuf[l] += inPtr[l];
			}
			
			for (l = 0; l < spp; l++)
				*outPtr++ = (unsigned char)(accbuf[l] / samples);
		}
	}
	
	return outBitmap;
}

- filter:(NXBitmapImageRep *)inBitmap
{
	NXBitmapImageRep *standardBitmap, *scaledBitmap;
	
	// Do a quick reality check.
	if (![inBitmap pixelsWide] || ![inBitmap pixelsHigh]) return nil;
	
	// Read relevant parameters out of the text fields.
	focus = [focusField floatValue];
	samples = 256;
	
	// Determine the input and output sizes in pixels
	inWidth = [inBitmap pixelsWide];
	inHeight = [inBitmap pixelsHigh];
	outWidth = outHeight= [outSizeField intValue];
	
	// Figure out the scale factor given the input and output sizes.
	scale = (float)outWidth / (float)inWidth;
	
	// If this is the first time the filter has been called, build the tables
	// used in jittering and bitmap format conversion.
	if (!tablesBuilt)
		[self buildTables:1];
	
	// Convert the bitmap into one that is eight bits per sample
	// and in meshed configuration.
	if (!(standardBitmap = [self convertBitmap:inBitmap])) {
		print_info ("Unknown bitmap format.", inBitmap);
		return nil;
	}
	
	// Actually do the scaling.
	scaledBitmap = [self scaleBitmap:standardBitmap];
	
	// If we had to do conversion of the input bitmap, free the tmp one
	// in the standard format we created.
	if (freeStandardBitmap) [standardBitmap free];
	
	[inBitmap free];
	
	// Return the 8 bit per sample bitmap.
	return scaledBitmap;
}

@end
