/*
 * Copyright 1990 by Baylor College of Medicine ALL RIGHTS RESERVED. 
 *
 * This program is subject to a license agreement between 
 * Baylor College of Medicine and MIT. Any use inconsistent with
 * said license and any use by persons other than the faculty, 
 * students and staff at MIT or any use on a computer not operated 
 * as part of the Athena Computing Environment (ACE) is expressly 
 * prohibited.
 */
/*
 *		Routines to read an write raster files to and from images.
 *		At present, images must be Z-Pixmap and only rasterfiles of depth
 *		less than that of the screen can be read in.
 */
#include <math.h>
#include <stdio.h>
#include <X11/Xos.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <malloc.h>
#include <etrealloc.h>
#include "rasterio.h"

static int run_length ;
static int num_chars ;
static unsigned char run_char ;

long bytes_to_long() ;

#define RAS_MAGIC 0x59a66a95
#define RT_OLD          0
#define RT_STANDARD     1
#define RT_BYTE_ENCODED 2
#define RMT_NONE        0
#define RMT_EQUAL_RGB   1

#define CONVERT_B(field) rasterfile.field = bytes_to_long(rasterfile_b.field)
#define B_CONVERT(field) long_to_bytes((long)rasterfile.field,rasterfile_b.field)

struct rasterfile_b
{
	unsigned char ras_magic[4] ;
	unsigned char ras_width[4] ;
	unsigned char ras_height[4] ;
	unsigned char ras_depth[4] ;
	unsigned char ras_length[4] ;
	unsigned char ras_type[4] ;
	unsigned char ras_maptype[4] ;
	unsigned char ras_maplength[4] ;
} ;

struct rasterfile
{
	int ras_magic ;
	int ras_width ;
	int ras_height ;
	int ras_depth ;
	int ras_length ;
	int ras_type ;
	int ras_maptype ;
	int ras_maplength ;
} ;

static
XImage *
x_create_image(dpy,visual,depth,width,height)
	Display *dpy ;
	Visual *visual ;
{
	int format = (depth == 1) ? XYBitmap : ZPixmap ;
	int bits_per_pixel = (depth > 1 && depth < 8) ? 8 : depth ;
	int size = ((width * bits_per_pixel + 7) / 8) * height ;
	char *calloc() ;
	char *data = calloc((unsigned)size,(unsigned)1) ;
	XImage *image = XCreateImage(dpy,visual,(unsigned)depth,format,0,
		data,(unsigned)width,(unsigned)height,8,0) ;
	image->bits_per_pixel = bits_per_pixel ;
	return image ;
}

static int l_image = 0 ;
static unsigned char *image_out = NULL ;

static
add_buf(buf,l)
	unsigned char *buf ;
{
	if (l)
	{
		int i ;
		image_out = (unsigned char *)etrealloc((char *)image_out,l_image + l) ;
		while(l--)
		{
			image_out[l_image++] = *buf++ ;
		}
	}
}

/*
 *		Flushes out current run buffer.
 */
static
flush_chrs()
{
	static unsigned char rec[3] ;
	int l = 0 ;
	if (run_length == 1)
	{
		if (run_char == 128)
		{
			rec[l++] = 128 ;
			rec[l++] = 0 ;
		}
		else
		{
			rec[l++] = run_char ;
		}
	}
	else if (run_length > 1)
	{
		rec[l++] = 128 ;
		rec[l++] = run_length - 1 ;
		rec[l++] = run_char ;
	}
	add_buf(rec,l) ;
	num_chars += l ;
	run_length = 0 ;
}

static
unsigned char
bit_convert(b,order)
	register unsigned char b ;
{
	if (order == LSBFirst)
	{
		register unsigned char c = 0 ;
		register int n = 8 ;
		while(n--)
		{
			c = (c << 1) | (b & 1) ;
			b >>= 1 ;
		}
		return c ;
	}
	return b ;
}

/*
 *		Writes out a character.
 */
static
put_chr(b)
	unsigned char b ;
{
	if (run_length == 0)
	{
		run_char = b ;
		run_length = 1 ;
	}
	else if (b != run_char || run_length > 255)
	{
		flush_chrs() ;
		run_char = b ;
		run_length = 1 ;
	}
	else
	{
		run_length++ ;
	}
}

WriteImageRast(dpy,image,filename,cmap)
	Display *dpy ;
	XImage *image ;
	char *filename ;
	Colormap cmap ;
{
	FILE *file = fopen(filename,"w") ;
	if (file != NULL)
	{
		WriteImageRastfp(dpy,image,file,cmap) ;
		fclose(file) ;
	}
}

/*
 *		Writes out an image as a raster file.
 */
WriteImageRastfp(dpy,image,file,cmap)
	Display *dpy ;
	XImage *image ;
	FILE *file ;
	Colormap cmap ;
{
	int screen = DefaultScreen(dpy) ;
	struct rasterfile rasterfile ;
	struct rasterfile_b rasterfile_b ;
	Visual *visual = DefaultVisual(dpy,screen) ;
	int bytes_per_line = image->bytes_per_line ;
	int bit_line_len = image->width * image->bits_per_pixel ;
	int short_line_len = (bit_line_len + 15) / 16 ;
	int line_len = short_line_len * 2 ;
	int real_len = (bit_line_len + 7) / 8 ;
	unsigned char *data = (unsigned char *)image->data ;

	l_image = 0 ;
	add_buf((unsigned char *)&rasterfile,sizeof rasterfile) ;
	if (image->format != XYBitmap)
	{
		int i ;
		static unsigned char c_red[256],c_green[256],c_blue[256] ;
		int num_entries = visual->map_entries ;
		for(i = 0; i < num_entries; i++)
		{
			XColor def ;
			def.pixel = i ;
			XQueryColor(dpy,cmap,&def) ;
			c_red[i] = def.red / 256 ;
			c_green[i] = def.green / 256 ;
			c_blue[i] = def.blue / 256 ;
		}
		add_buf((unsigned char *)c_red,num_entries) ;
		add_buf((unsigned char *)c_green,num_entries) ;
		add_buf((unsigned char *)c_blue,num_entries) ;
	}
	num_chars = 0 ;
	run_length = 0 ;

	/* Normal image, just squirt it out */
	{
		int i ;
		int bit_order = (image->depth == 1) ? image->bitmap_bit_order : MSBFirst ;

		/* For each line */
		for(i = 0; i < image->height; i++,data += bytes_per_line)
		{
			int j ;

			/* Convert and squirt */
			for(j = 0; j < real_len; j++)
			{
				unsigned char c = bit_convert(data[j],bit_order) ;
				put_chr(c) ;
			}

			/* Pad out to 16 bit boundary */
			while(j < line_len)
			{
				put_chr(0) ;
				j++ ;
			}
		}
		flush_chrs() ;
	}

	rasterfile.ras_magic = RAS_MAGIC ;
	rasterfile.ras_width = image->width ;
	rasterfile.ras_height = image->height ;
	rasterfile.ras_depth = image->depth ;
	rasterfile.ras_length = num_chars ;
	rasterfile.ras_type = RT_BYTE_ENCODED ;

	if (image->format != XYBitmap)
	{
		int num_entries = visual->map_entries ;
		rasterfile.ras_maptype = RMT_EQUAL_RGB ;
		rasterfile.ras_maplength = num_entries * 3 ;
	}
	else
	{
		rasterfile.ras_maptype = RMT_NONE ;
		rasterfile.ras_maplength = 0 ;
	}

	B_CONVERT(ras_magic) ;
	B_CONVERT(ras_width) ;
	B_CONVERT(ras_height) ;
	B_CONVERT(ras_depth) ;
	B_CONVERT(ras_length) ;
	B_CONVERT(ras_type) ;
	B_CONVERT(ras_maptype) ;
	B_CONVERT(ras_maplength) ;
	bcopy((unsigned char *)&rasterfile_b,image_out,sizeof rasterfile_b) ;
	(void)fwrite((char *)image_out,l_image,1,file) ;
}

XImage *
ReadImageRastfp(dpy,fp,ret_cmap)
	Display *dpy ;
	FILE *fp ;
	Colormap *ret_cmap ;
{
	int screen = DefaultScreen(dpy) ;
	Visual *visual = DefaultVisual(dpy,screen) ;
	unsigned char *file_data ;
	int size ;
	int bit_line_len ;
	int short_line_len ;
	int line_len ;
	int color_map_length = 0 ;
	int reverse_pix = 0 ;
	XColor *x_color_map = NULL ;
	unsigned char *gray_color_map = NULL ;
	int depth ;
	XImage *image1 ;
	int bits_per_pixel ;
	struct rasterfile rasterfile ;
	struct rasterfile_b rasterfile_b ;
	Colormap cmap = DefaultColormap(dpy,screen) ;
	int pseudo_cmap = 0 ;

	/* Read in the header */
	(void)fread((char *)&rasterfile_b,sizeof rasterfile_b,1,fp) ;
	CONVERT_B(ras_magic) ;
	CONVERT_B(ras_width) ;
	CONVERT_B(ras_height) ;
	CONVERT_B(ras_depth) ;
	CONVERT_B(ras_length) ;
	CONVERT_B(ras_type) ;
	CONVERT_B(ras_maptype) ;
	CONVERT_B(ras_maplength) ;

	/* If not rasterfile then forget it */
	if (rasterfile.ras_magic != RAS_MAGIC)
	{
		fprintf(stderr,"Non-valid rasterfile format\n") ;
		return NULL ;
	}

	/* If some stupid encoding then forget it */
	if (rasterfile.ras_type != RT_STANDARD &&
		rasterfile.ras_type != RT_BYTE_ENCODED)
	{
		fprintf(stderr,"Non-standard rasterfile format\n") ;
		return NULL ;
	}

	depth = rasterfile.ras_depth ;

	if (depth > 1 && depth < 8)
	{
		bits_per_pixel = 8 ;
	}
	else
	{
		bits_per_pixel = depth ;
	}

	bit_line_len = rasterfile.ras_width * bits_per_pixel ;
	short_line_len = (bit_line_len + 15) / 16 ;
	line_len = short_line_len * 2 ;
	size = line_len * rasterfile.ras_height ;

	/* If a color map is present */
	if (rasterfile.ras_maplength > 0)
	{
		unsigned char *color_map = (unsigned char *)etmalloc((unsigned)rasterfile.ras_maplength) ;
		fread((char *)color_map,rasterfile.ras_maplength,1,fp) ;

		/* If it is an rgb color map then decode it */
		if (rasterfile.ras_maptype == RMT_EQUAL_RGB)
		{
			unsigned char *rmap ;
			unsigned char *gmap ;
			unsigned char *bmap ;
			color_map_length = rasterfile.ras_maplength / 3 ;
			rmap = color_map ;
			gmap = rmap + color_map_length ;
			bmap = gmap + color_map_length ;

			/* If we must dither then get gray scale map */
			if (DefaultDepth(dpy,screen) < depth &&
			    DefaultDepth(dpy,screen) <= 4)
			{
				int i ;
				gray_color_map = (unsigned char *)etmalloc((unsigned)(color_map_length * sizeof(unsigned char))) ;
				for(i = 0; i < color_map_length; i++)
				{
					unsigned long red = *rmap++ ;
					unsigned long green = *gmap++ ;
					unsigned long blue = *bmap++ ;
					int val = (30 * red + 59 * green + 11 * blue) / 100 ;
					if (val > 255)
					{
						val = 255 ;
					}
					else if (val < 0)
					{
						val = 0 ;
					}
					gray_color_map[i] = val ;
				}
			}

			else if (depth == 1)
			{
				XColor first ;
				unsigned long red = *rmap++ ;
				unsigned long green = *gmap++ ;
				unsigned long blue = *bmap++ ;
				first.red = red * 256 ;
				first.green = green * 256 ;
				first.blue = blue * 256 ;
				XAllocColor(dpy,cmap,&first) ;
				if (first.pixel == BlackPixel(dpy,screen))
				{
					reverse_pix = 1 ;
				}
			}

			else
			{
				int i ;
				int same_cmap = 1 ;
				double pow() ;
				unsigned long n = pow((double)visual->map_entries,1 / 3.0) ;
				int nent = 256 / (n - 2) ;

				if (ret_cmap != NULL)
				{
					if (visual->class == PseudoColor &&
					    color_map_length <= visual->map_entries)
					{
						cmap = XCreateColormap(dpy,RootWindow(dpy,screen),
							visual,AllocAll) ;
						pseudo_cmap = 1 ;
					}
					else
					{
						cmap = XCreateColormap(dpy,RootWindow(dpy,screen),
							visual,AllocNone) ;
					}
				}

				x_color_map = (XColor *)etmalloc((unsigned)(color_map_length * sizeof(XColor))) ;
				for(i = 0; i < color_map_length; i++)
				{
					unsigned long red = *rmap++ ;
					unsigned long green = *gmap++ ;
					unsigned long blue = *bmap++ ;
					if (cmap == DefaultColormap(dpy,DefaultScreen(dpy)))
					{
						if (red > 0 && red < 255)
						{
							red = nent / 2 + (red / nent) * nent ;
						}
						if (green > 0 && green < 255)
						{
							green = nent / 2 + (green / nent) * nent ;
						}
						if (blue > 0 && blue < 255)
						{
							blue = nent / 2 + (blue / nent) * nent ;
						}
					}
					x_color_map[i].red = red * 256 ;
					x_color_map[i].green = green * 256 ;
					x_color_map[i].blue = blue * 256 ;
					x_color_map[i].flags = DoRed | DoBlue | DoGreen ;
					x_color_map[i].pixel = i ;
				}
				if (pseudo_cmap)
				{
					XStoreColors(dpy,cmap,x_color_map,color_map_length) ;
					etfree((char *)x_color_map) ;
					x_color_map = NULL ;
				}
				else
				{
					int i ;
					for(i = 0; i < color_map_length; i++)
					{
						XAllocColor(dpy,cmap,x_color_map + i) ;
						if (i != x_color_map[i].pixel)
						{
							same_cmap = 0 ;
						}
					}
					if (same_cmap)
					{
						etfree((char *)x_color_map) ;
						x_color_map = NULL ;
					}
				}
			}
		}
		etfree((char *)color_map) ;
	}

	/* Read in image data */
	file_data = (unsigned char *)malloc((unsigned)rasterfile.ras_length) ;
	fread((char *)file_data,rasterfile.ras_length,1,fp) ;

	/* If run length encoded then decode image data */
	if (rasterfile.ras_type == RT_BYTE_ENCODED)
	{
		unsigned char *data = (unsigned char *)malloc((unsigned)size) ;
		register unsigned char *p1 = data ;
		register unsigned char *p2 = file_data ;
		register unsigned char *limit = p2 + rasterfile.ras_length ;
		while(p2 < limit)
		{
			{
				unsigned char c = *p2++ ;
				if (c != 128)
				{
					*p1++ = c ;
					continue ;
				}
			}
			{
				register unsigned char count = *p2++ ;
				if (!count)
				{
					*p1++ = 128 ;
				}
				else
				{
					register unsigned char run_char = *p2++ ;
					do
					{
						*p1++ = run_char ;
					}
					while(count--) ;
				}
			}
		}
		free((char *)file_data) ;
		if (p1 - data != size)
		{
			fprintf(stderr,"run-length decoding of rasterfile did not work\n") ;
			return NULL ;
		}
		file_data = data ;
	}

	/* Else Create the image directly */
	{
		int format = (depth == 1) ? XYBitmap : ZPixmap ;
		image1 = XCreateImage(dpy,visual,(unsigned)depth,format,0,
			(char *)file_data,(unsigned)rasterfile.ras_width,(unsigned)rasterfile.ras_height,16,0) ;
		image1->bitmap_bit_order = image1->byte_order = MSBFirst ;
		image1->bits_per_pixel = bits_per_pixel ;
	}

	if (pseudo_cmap)
	{
		*ret_cmap = cmap ;
		return image1 ;
	}

	else if (depth == 1)
	{
		if (reverse_pix)
		{
			int i ;
			unsigned char *data = (unsigned char *)image1->data ;
			for(i = 0; i < size; i++)
			{
				data[i] = ~data[i] ;
			}
		}
		if (ret_cmap != NULL)
		{
			*ret_cmap = cmap ;
		}
		return image1 ;
	}

	/* If there is a color map then do dithering */
	else if (gray_color_map != NULL)
	{
		int i ;
		int width = image1->width ;
		int height = image1->height ;
		int new_width = (width / 2) * 2 ;
		int new_height = (height / 2) * 2 ;
		XImage *image2 = x_create_image(dpy,visual,1,new_width,new_height) ;
		unsigned char *data = (unsigned char *)image2->data ;
		int bpl = image2->bytes_per_line ;
		image2->byte_order = MSBFirst ;
		image2->bitmap_bit_order = MSBFirst ;
		for(i = 0; i < new_height; i+= 2,data += 2 * bpl)
		{
			int j ;
			register unsigned char *p1 = data ;
			register unsigned char *p2 = p1 + bpl ;
			int bit_pos = 0 ;
			for(j = 0; j < new_width; j += 2)
			{
				int val =
					((int)gray_color_map[XGetPixel(image1,j + 0,i + 0)] +
					(int)gray_color_map[XGetPixel(image1,j + 1,i + 0)] +
					(int)gray_color_map[XGetPixel(image1,j + 0,i + 1)] +
					(int)gray_color_map[XGetPixel(image1,j + 1,i + 1)]) / 4 ;
				unsigned long pix =
					val < 20 ?
						0xf :
					val < 90 ?
						0xd :
					val < 160 ?
						0x9 :
					val < 230 ?
						0x8 :
						0 ;
				unsigned long r1 = pix & 0x3 ;
				unsigned long r2 = pix >> 2 ;
				if (bit_pos == 0)
				{
					*p1 = r1 << 6 ;
					*p2 = r2 << 6 ;
					bit_pos += 2 ;
				}
				else if (bit_pos < 6)
				{
					*p1 |= r1 << (6 - bit_pos) ;
					*p2 |= r2 << (6 - bit_pos) ;
					bit_pos += 2 ;
				}
				else
				{
					*p1++ |= r1 ;
					*p2++ |= r2 ;
					bit_pos = 0 ;
				}
			}
		}
		etfree((char *)gray_color_map) ;
		XDestroyImage(image1) ;
		if (ret_cmap != NULL)
		{
			*ret_cmap = cmap ;
		}
		return image2 ;
	}

	/* Else substitute in the colormap */
	else if (x_color_map != NULL)
	{
		/* If depths are different thencreate new image */
		if (DefaultDepth(dpy,screen) != depth)
		{
			XImage *image2 = x_create_image(dpy,visual,DefaultDepth(dpy,screen),
				image1->width,image1->height) ;
			int i ;
			for(i = 0; i < image1->height; i++)
			{
				int j ;
				for(j = 0; j < image1->width; j++)
				{
					unsigned long pix = XGetPixel(image1,j,i) ;
					XPutPixel(image2,j,i,x_color_map[pix].pixel) ;
				}
			}
			etfree((char *)x_color_map) ;
			XDestroyImage(image1) ;
			if (ret_cmap != NULL)
			{
				*ret_cmap = cmap ;
			}
			return image2 ;
		}

		/* If 8 bits per pixel then do something special */
		else if (image1->bits_per_pixel == 8)
		{
			unsigned char *data = (unsigned char *)image1->data ;
			int width = image1->width ;
			int bpl = image1->bytes_per_line ;
			int n2 = image1->height ;
			while(n2--)
			{
				register unsigned char *p = data ;
				register int n = width ;
				while(n--)
				{
					*p = x_color_map[*p].pixel ;
					p++ ;
				}
				data += bpl ;
			}
			etfree((char *)x_color_map) ;
			if (ret_cmap != NULL)
			{
				*ret_cmap = cmap ;
			}
			return image1 ;
		}

		/* Else substitute in place */
		else
		{
			int i ;
			for(i = 0; i < image1->height; i++)
			{
				int j ;
				for(j = 0; j < image1->width; j++)
				{
					unsigned long pix = XGetPixel(image1,j,i) ;
					XPutPixel(image1,j,i,x_color_map[pix].pixel) ;
				}
			}
			etfree((char *)x_color_map) ;
			if (ret_cmap != NULL)
			{
				*ret_cmap = cmap ;
			}
			return image1 ;
		}
	}

	/* If depth bad then belch */
	else if (DefaultDepth(dpy,screen) < depth)
	{
		XDestroyImage(image1) ;
		fprintf(stderr,"Can not do image of depth %d without a colormap\n",depth) ;
		return NULL ;
	}

	else if (DefaultDepth(dpy,screen) > depth)
	{
		XImage *image2 = x_create_image(dpy,visual,DefaultDepth(dpy,screen),
			image1->width,image1->height) ;
		int i ;
		for(i = 0; i < image1->height; i++)
		{
			int j ;
			for(j = 0; j < image1->width; j++)
			{
				unsigned long pix = XGetPixel(image1,j,i) ;
				XPutPixel(image2,j,i,pix) ;
			}
		}
		XDestroyImage(image1) ;
		if (ret_cmap != NULL)
		{
			*ret_cmap = cmap ;
		}
		return image2 ;
	}
	if (ret_cmap != NULL)
	{
		*ret_cmap = cmap ;
	}
	return image1 ;
}

XImage *
ReadImageRastCmap(dpy,filename,cmap)
	Display *dpy ;
	char *filename ;
	Colormap *cmap ;
{
	FILE *fp = fopen(filename,"r") ;
	XImage *image ;

	if (fp == NULL)
	{
		fprintf(stderr,"Could not open image file '%s'\n",filename) ;
		return NULL ;
	}

	image = ReadImageRastfp(dpy,fp,cmap) ;

	fclose(fp) ;

	return image ;
}

XImage *
ReadImageRast(dpy,filename)
	Display *dpy ;
	char *filename ;
{
	return ReadImageRastCmap(dpy,filename,(Colormap *)NULL) ;
}
