
#include <stdio.h>
#include <string.h>
#include <stdio.h>
#include "tiffio.h"
#ifdef LINUX
	#include <getopt.h>
#endif

typedef	unsigned char u_char;
typedef	unsigned short u_short;
typedef	unsigned int u_int;
typedef	unsigned long u_long;

#define	streq(a,b)	(strcmp(a,b) == 0)
#define	CopyField(tag, v) \
    if (TIFFGetField(in, tag, &v)) TIFFSetField(out, tag, v)

#define	howmany(x, y)	(((x)+((y)-1))/(y))
#define	roundup(x, y)	(howmany(x,y)*((u_int)(y)))

#define	LumaRed		ycbcrCoeffs[0]
#define	LumaGreen	ycbcrCoeffs[1]
#define	LumaBlue	ycbcrCoeffs[2]

u_short	compression = COMPRESSION_LZW;
long	rowsperstrip = -1;

u_short	horizSubSampling = 2;		/* YCbCr horizontal subsampling */
u_short	vertSubSampling = 1;		/* YCbCr vertical subsampling */
float	ycbcrCoeffs[3] = { .299, .587, .114 };
/* default coding range is CCIR Rec 601-1 with no headroom/footroom */
/*float	refBlackWhite[6] = { 0., 255., 128., 255., 128., 255. };*/
float	refBlackWhite[6] = { 0., 255., 0.0, 128., 0.0, 128.0 };

static int tiffcvt(TIFF *in, FILE *fp);
static	void usage(void);
static	void setupLumaTables(void);

int main(int argc, char **argv)
{
	TIFF *in;
	FILE *fp;
	int c,width,height;
	extern int optind;
	extern char *optarg;
	unsigned char flmheader[64];

	while ((c = getopt(argc, argv, "c:h:r:v:z")) != -1)
	{
		switch (c) 
		{
			case 'c':
				if (streq(optarg, "none"))
					compression = COMPRESSION_NONE;
				else if (streq(optarg, "packbits"))
					compression = COMPRESSION_PACKBITS;
				else if (streq(optarg, "lzw"))
					compression = COMPRESSION_LZW;
				else
					usage();
				break;
			case 'h':
				horizSubSampling = atoi(optarg);
				break;
			case 'v':
				vertSubSampling = atoi(optarg);
				break;
			case 'r':
				rowsperstrip = atoi(optarg);
				break;
			case 'z':	/* CCIR Rec 601-1 w/ headroom/footroom */
				refBlackWhite[0] = 16.0;
				refBlackWhite[1] = 235.0;
				refBlackWhite[2] = 0.0;
				refBlackWhite[3] = 112.0;
				refBlackWhite[4] = 0.0;
				refBlackWhite[5] = 122.0;
				break;
			case '?':
				usage();
				/*NOTREACHED*/
		}
	}
	
	if (argc - optind < 2)
	{
		usage();
	}
	
	setupLumaTables();
	if(!(in = TIFFOpen(argv[optind++], "r")))
	{
		exit(1);
	}
	
	if(!(fp = fopen(argv[optind],"w")))
	{
		exit(1);
	}
	/* Construct header */
	TIFFGetField(in, TIFFTAG_IMAGEWIDTH, &width);
	TIFFGetField(in, TIFFTAG_IMAGELENGTH, &height);
	bcopy("SM1.0",&flmheader[0],5);
	flmheader[5]	= 26;
	flmheader[10]	= width&0xFF;
	flmheader[11]	= (width&0xFF00)>>8;
	flmheader[12]	= height&0xFF;
	flmheader[13]	= (height&0xFF00)>>8;
	flmheader[14]	= 6;
	flmheader[15]	= (width*2) & 0xFF;
	flmheader[16]	= ((width*2) & 0xFF00)>>8;
	flmheader[17]	= 8;
	flmheader[18]	= 8;
	flmheader[19]	= 8;
	flmheader[22]	= 2;
	flmheader[32]	= 31;
	flmheader[33]	= 0;
	flmheader[34]	= 31;
	flmheader[35]	= 0;
	flmheader[36]	= 31;
	flmheader[37]	= 0;
	flmheader[38]	= 0;
	flmheader[39]	= 0;
	flmheader[40]	= 31;
	flmheader[41]	= 0;
	flmheader[42]	= 31;
	flmheader[43]	= 0;
	flmheader[44]	= 31;
	flmheader[45]	= 0;
	/* Write Header */
	if(!fwrite(flmheader,64,1,fp))
	{
		fclose(fp);
		exit(1);
	}
	
	if (!tiffcvt(in, fp))
	{
		fclose(fp);
		exit(1);
	}
	TIFFClose(in);
	fclose(fp);
	exit(0);
}

float	*lumaRed;
float	*lumaGreen;
float	*lumaBlue;
float	D1, D2;
int	Yzero;

static float* setupLuma(float c)
{
	float *v = (float *)malloc(256 * sizeof (float));
	int i;
	for (i = 0; i < 256; i++)
	{
		v[i] = c * i;
	}
	return (v);
}

unsigned char QuantY(float f, float RB, float RW, int CR)
{
	unsigned int c = (unsigned int)((((f)*(RW-RB)/CR)+RB)+.5);
	return (unsigned char) (c > 255 ? 255 : c);
}

char QuantC(float f, float RB, float RW, int CR)
{
	int c = (int)((((f)*(RW-RB)/CR)+RB)+.5);
	return (char) (c > 127 ? 127 : c < -128 ? -128 : c);
}

static void setupLumaTables(void)
{
	lumaRed = setupLuma(LumaRed);
	lumaGreen = setupLuma(LumaGreen);
	lumaBlue = setupLuma(LumaBlue);
	D1 = 1./(2 - 2*LumaBlue);
	D2 = 1./(2 - 2*LumaRed);
	Yzero = QuantY(0, refBlackWhite[0], refBlackWhite[1], 255);
}

static void cvtClump(u_char *op,u_long *raster,u_int ch,u_int cw, u_long w)
{
	float Y[2], Cb = 0, Cr = 0;
	int j,r,b;
	/*
	 * Convert ch-by-cw block of RGB
	 * to YCbCr and sample accordingly.
	 */
	for (j = 0; j < cw; j++) 
	{
		u_long RGB = (raster - w)[j];
		/*u_long RGB = raster[j]; */
		b = TIFFGetB(RGB);
		r = TIFFGetR(RGB);
		Y[j] = lumaRed[r] + lumaGreen[TIFFGetG(RGB)] + lumaBlue[b];
		/* accumulate chrominance */
		Cb += (b - Y[j]) * D1;
		Cr += (r - Y[j]) * D2;
	}
	*op++ = QuantY(Y[0],refBlackWhite[0], refBlackWhite[1], 255);
	(char) *op++ = QuantC(Cb / 2.0, refBlackWhite[2], refBlackWhite[3], 127);
	*op++ = QuantY(Y[1],refBlackWhite[0], refBlackWhite[1], 255);
	(char) *op++ = QuantC(Cr / 2.0, refBlackWhite[4], refBlackWhite[5], 127);
}
#undef LumaRed
#undef LumaGreen
#undef LumaBlue
#undef V2Code

/*
 * Convert a strip of RGB data to YCbCr and
 * sample to generate the output data.
 */
static void cvtStrip(u_char *op, u_long *raster,u_long nrows,u_long width)
{
	long x;
	int clumpSize = vertSubSampling * horizSubSampling + 2;
	u_long *tp;

	for (; nrows >= vertSubSampling; nrows -= vertSubSampling) 
	{
		tp = raster;
		for (x = width; x >= horizSubSampling; x -= horizSubSampling) 
		{
			cvtClump(op, tp, vertSubSampling, horizSubSampling, width);
			op += clumpSize;
			tp += horizSubSampling;
		}
		if (x > 0) 
		{
			cvtClump(op, tp, vertSubSampling, x, width);
			op += clumpSize;
		}
		raster -= vertSubSampling*width;
	}
}

static int cvtRaster(FILE *fp,u_long *raster, u_long width, u_long height)
{
	long y;
	u_int cc;
	u_char *buf;
	u_long rwidth = roundup(width, horizSubSampling);

	cc = rwidth * 2;
	buf = (u_char *)malloc(cc);
	for (y = 0; y < height; y++) 
	{
		cvtStrip(buf, raster + (height-y-1)*width, 1, width);
		if (!fwrite(buf, cc ,1,fp))
		{
			free(buf);
			return (0);
		}
	}
	free(buf);
	return (1);
}

static int tiffcvt(TIFF *in, FILE *fp)
{
	u_long width, height;		/* image width & height */
	u_long *raster;			/* retrieve RGBA image */

	TIFFGetField(in, TIFFTAG_IMAGEWIDTH, &width);
	TIFFGetField(in, TIFFTAG_IMAGELENGTH, &height);
	raster = (u_long *)malloc(width * height * sizeof (u_long));
	if (raster == 0) 
	{
		TIFFError(TIFFFileName(in), "No space for raster buffer");
		return (0);
	}
	if (!TIFFReadRGBAImage(in, width, height, raster, 0)) 
	{
		free(raster);
		return (0);
	}

	return (cvtRaster(fp, raster, width, height));
}

static char* usageMsg[] = {
    "usage: tiff2flm [-z] input output\n",
    " -z\tUser CCIR range for YUV data\n",
    NULL
};

static void usage(void)
{
	int i;
	for (i = 0; usageMsg[i]; i++)
		fprintf(stderr, "%s", usageMsg[i]);
	exit(-1);
}
