/*
        mindlib/image.c

        This program is free software; you can redistribute it and/or modify
        it under the terms of the GNU General Public License as published by
        the Free Software Foundation; either version 2, or (at your option)
        any later version.

        This program is distributed in the hope that it will be useful,
        but WITHOUT ANY WARRANTY; without even the implied warranty of
        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
        GNU General Public License for more details.

        You should have received a copy of the GNU General Public License
        along with this program; if not, write to the Free Software
        Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <X11/Intrinsic.h>
#if NOEASY != 1 
#include "../include/Image.h"
#include "../include/hash.h"
#include "../include/palette.h"
#include "../include/misc.h"
#else
#include "../include/Image.h"
#include "../include/hash.h"
#include "../include/palette.h"
#include "../include/misc.h"
#endif
#define HASH_SIZE	128

Image *ImageNew(int width, int height)
{
	Image	*image = XtNew(Image);

	image->refCount = 1;
	image->isBW = False;
	image->isGrey = False;
	image->cmapPacked = False;
	image->cmapSize = 0;
	image->cmapData = NULL;
	image->width = width;
	image->height = height;
	image->sourceColormap = None;
	image->sourcePixmap = None;
	image->sourceMask = None;
	image->scale = 3;
	if (width == 0 || height == 0)
		image->data = NULL;
	else
		image->data = (unsigned char *)XtMalloc(width * height * sizeof(char) * 3);
	image->maskData = NULL;
	return image;
}

Image *ImageNewCmap(int width, int height, int size)
{
	Image	*image = ImageNew(0, 0);

	if (size == 0)
		image->scale = 3;
	else if (size <= 256)
		image->scale = 1;
	else
		image->scale = sizeof(short) / sizeof(char);

	image->width    = width;
	image->height   = height;
	image->data     = (unsigned char *)XtMalloc(width * height * sizeof(char) * image->scale);
	if (size != 0)
		image->cmapData = (unsigned char *)XtMalloc(size * sizeof(char) * 3);
	image->cmapSize = size;

	return image;
}

Image *ImageNewBW(int width, int height)
{
	Image	*image = ImageNewCmap(width, height, 2);

	image->cmapData[0] = 0;
	image->cmapData[1] = 0;
	image->cmapData[2] = 0;
	image->cmapData[3] = 255;
	image->cmapData[4] = 255;
	image->cmapData[5] = 255;

	return image;
}

Image *ImageNewGrey(int width, int height)
{
	int	i;
	Image	*image = ImageNewCmap(width, height, 256);

	image->isGrey = True;
	for (i = 0; i < image->cmapSize; i++) {
		image->cmapData[i * 3 + 0] = i;
		image->cmapData[i * 3 + 1] = i;
		image->cmapData[i * 3 + 2] = i;
	}

	return image;
}

void ImageMakeMask(Image *image)
{
	image->maskData = (unsigned char *)XtMalloc(image->width * image->height * sizeof(char));
}


void ImageDelete(Image *image)
{
	image->refCount--;
	if (image->refCount > 0)
		return;

	if (image->cmapSize > 0 && image->cmapData != NULL)
		XtFree((char *)image->cmapData);
	if (image->data != NULL)
		XtFree((char *)image->data);
	if (image->maskData != NULL)
		XtFree((char *)image->maskData);
	XtFree((char *)image);
}

/*
**  Create a nice image for writing routines.
*/

static int  writeCMP(XColor *a, XColor *b)
{
	return a->pixel - b->pixel;
}

Image *PixmapToImage(Widget w, Pixmap pix, Colormap cmap)
{
	XImage		*xim;
	Image		*image;
	Display		*dpy = XtDisplay(w);
        int             x, y;
        int    		width, height;
	unsigned char	*ptr;
	unsigned short	*sptr;
	int		format = 0;
	void		*htable = NULL;
	Palette		*map = PaletteFind(w, cmap);

	GetPixmapWHD(dpy, pix, &width, &height, NULL);
	xim = XGetImage(dpy, pix, 0, 0, width, height, AllPlanes, ZPixmap);

	if (map == NULL) {
		htable = HashCreate((int (*)(void *, void *))writeCMP, NULL, HASH_SIZE);
		image = ImageNew(width, height);
	} else {
		if (map->isMapped) {
			unsigned char	*cptr;

			image = ImageNewCmap(width, height, map->ncolors);
			cptr = image->cmapData;

			for (y = 0; y < map->ncolors; y++, cptr += 3) {
				XColor	*col = PaletteLookup(map, y);
				unsigned char	r = col->red >> 8;
				unsigned char	g = col->green >> 8;
				unsigned char	b = col->blue >> 8;

				cptr[0] = r;
				cptr[1] = g;
				cptr[2] = b;

				if (r != g || g != b)
					format = 2;
				else if (format == 0 && r != 0 && r != 255)
					format = 1;
			}
		} else {
			image = ImageNew(width, height);
		}
	}

	ptr   = image->data;
	sptr  = (unsigned short *)image->data;

	for (y = 0; y < height; y++) {
		for (x = 0; x < width; x++) {
			XColor		c, *col;
			Pixel		pixel = XGetPixel(xim, x, y);
			unsigned char	r, g, b;

		      if (map){
			if  (map->isMapped) {
				if (map->ncolors <= 256)
					*ptr++  = (unsigned char)pixel;
				else
					*sptr++ = (unsigned char)pixel;
			} else {
				c.pixel = pixel;
				if (map != NULL) {
					col = PaletteLookup(map, pixel);
				} else {
					if ((col = (XColor *)HashFind(htable, pixel % HASH_SIZE, &c)) == NULL) {
						col = XtNew(XColor);
						col->flags = DoRed|DoGreen|DoBlue;
						col->pixel = pixel;
						XQueryColor(dpy, cmap, col);
						HashAdd(htable, pixel % HASH_SIZE, col);
					}
				}

				*ptr++ = r = col->red >> 8;
				*ptr++ = g = col->green >> 8;
				*ptr++ = b = col->blue >> 8;

				if (r != g || g != b)
					format = 2;
				else if (format == 0 && r != 0 && r != 255)
					format = 1;
			}
		     } else {
				c.pixel = pixel;
				if (map != NULL) {
					col = PaletteLookup(map, pixel);
				} else {
					if ((col = (XColor *)HashFind(htable, pixel % HASH_SIZE, &c)) == NULL) {
						col = XtNew(XColor);
						col->flags = DoRed|DoGreen|DoBlue;
						col->pixel = pixel;
						XQueryColor(dpy, cmap, col);
						HashAdd(htable, pixel % HASH_SIZE, col);
					}
				}

				*ptr++ = r = col->red >> 8;
				*ptr++ = g = col->green >> 8;
				*ptr++ = b = col->blue >> 8;

				if (r != g || g != b)
					format = 2;
				else if (format == 0 && r != 0 && r != 255)
					format = 1;
			}
		}

	}

	/*
	**  Check to see if we just created a B&W or Grey scale image?
	*/
	if (format == 0 || format == 1) {
		int		newSize;
		unsigned char	*newMap;
		unsigned char	*ip;

		if (format == 0) {
			newSize = 2;
			newMap = (unsigned char *)XtCalloc(sizeof(char) * 3, 2);
			newMap[0+0] = 0;
			newMap[0+1] = 0;
			newMap[0+2] = 0;
			newMap[3+0] = 255;
			newMap[3+1] = 255;
			newMap[3+2] = 255;
		} else {
			newSize = 256;
			newMap = (unsigned char *)XtCalloc(sizeof(char) * 3, 256);
			for (y = 0; y < 256; y++) {
				newMap[y * 3 + 0] = y;
				newMap[y * 3 + 1] = y;
				newMap[y * 3 + 2] = y;
			}
		}

		ip = image->data;
		for (y = 0; y < height; y++) {
			for (x = 0; x < width; x++, ip++) {
				unsigned char	*rgb;

				ImagePixel(image, x, y, rgb);

				if (format == 0 && *rgb == 255) {
					*ip = 1;
				} else {
					*ip = *rgb;
				}
			}
		}

		if (image->cmapData != NULL)
			XtFree((char *)image->cmapData);

		image->cmapSize = newSize;
		image->cmapData = newMap;

		if (format == 0)
			image->isBW = True;
		else if (format == 1)
			image->isGrey = True;
	}

	image->sourceColormap = (unsigned long)cmap;
	image->sourcePixmap   = (unsigned long)pix;

	if (htable != NULL)
		HashDestroy(htable);
	XDestroyImage(xim);

	return image;
}

/*
**  Compress an image into a nice number of
**   colors for display purposes.
*/
static Image *quantizeColormap(Image *input, Palette *map, Boolean flag)
{
	Image		*output;
	unsigned char	*op;
	int		x, y;
	int		ncol;

	/*
	**  If the output is either B&W or grey do something
	**   fast an easy.
	*/
	if (!map->isGrey) 
		return ImageCompress(input, map->ncolors);
	
	ncol = map->ncolors > 256 ? 256 : map->ncolors;
	output = ImageNewCmap(input->width, input->height, ncol); 

	op = output->data;

	for (y = 0; y < ncol; y++) {
		unsigned char	v = (unsigned char) ((float)y / (float)(ncol-1)) * 255.0;
		ImageSetCmap(output, y, v, v, v);
	}

	for (y = 0; y < input->height; y++) {
		for (x = 0; x < input->width; x++, op++) {
			unsigned char	*dp, v;

			ImagePixel(input, x, y, dp);
			v = (dp[0]*11 + dp[1]*16 + dp[2]*5) >> 5;  /* pp=.33R+.5G+.17B */
			*op = (int)(((float)v / 256.0) * (float)ncol);
		}
	}

	output->maskData = input->maskData;
	input->maskData  = NULL;
	ImageDelete(input);
	return output;
}

static void compressColormap(Image *image)
{
	unsigned char	used[32768];
	int		size = image->width * image->height;
	int		i, count, newSize;
	unsigned char	*newMap;
	unsigned short	*isp;
	unsigned char	*icp;

	if (image->cmapSize <= 2 || image->cmapPacked)
		return;

	memset(used, False, sizeof(used));
	
	/*
	**  Find out usage information over the colormap 
	*/
	count = 0;
	if (image->cmapSize > 256) {
		isp = (unsigned short *)image->data;
		for (i = 0; i < size && count != image->cmapSize; i++, isp++) {
			if (!used[*isp]) {
				used[*isp] = True;
				count++;
			}
		}
	} else {
		icp = image->data;
		for (i = 0; i < size && count != image->cmapSize; i++, icp++) {
			if (!used[*icp]) {
				used[*icp] = True;
				count++;
			}
		}
	}

	/*
	**  Colormap is fully used.
	*/
	if (count == image->cmapSize) {
		image->cmapPacked = True;
		return;
	}

	/*
	**  Now build the remapping colormap, and
	**    set the index.
	*/
	newSize = count;
	newMap = (unsigned char *)XtCalloc(count, sizeof(unsigned char) * 3);
	for (count = i = 0; i < image->cmapSize; i++) {
		if (!used[i])
			continue;
		newMap[count * 3 + 0] = image->cmapData[i * 3 + 0];
		newMap[count * 3 + 1] = image->cmapData[i * 3 + 1];
		newMap[count * 3 + 2] = image->cmapData[i * 3 + 2];
		used[i] = count++;
	}

	if (image->cmapSize > 256 && newSize > 256) {
		isp = (unsigned short *)image->data;
		for (i = 0; i < size; i++, isp++)
			*isp = used[*isp];
	} else if (image->cmapSize > 256 && newSize <= 256) {
		/*
		**  Map a big colormap down to a small one.
		*/
		isp = (unsigned short *)image->data;
		icp = image->data;
		for (i = 0; i < size; i++, icp++, isp++)
			*icp = used[*isp];

		image->data = (unsigned char *)XtRealloc((char *)image->data, size * sizeof(unsigned char));
	} else {
		icp = image->data;
		for (i = 0; i < size; i++, icp++)
			*icp = used[*icp];
	}

	XtFree((char *)image->cmapData);
	image->cmapData = newMap;
	image->cmapSize = newSize;
	image->cmapPacked = True;
	image->isGrey = False;
}

/*
**  Convert an imput image into a nice pixmap 
**   so we can edit it.
**
**  Side effect  -- always destroy the input image
*/
Boolean ImageToPixmap(Image *image, Widget w, Pixmap *pix, Colormap *cmap)
{
	GC		gc;
	Display		*dpy = XtDisplay(w);
	Palette         *map;
	XImage		*xim;
	int		x, y;
	int		width = image->width, height = image->height;

	map   = PaletteCreate(w);
	*cmap = map->cmap;

        if ((*pix = XCreatePixmap(dpy, DefaultRootWindow(dpy), image->width, image->height, map->depth)) == None)
		return False;

	if ((xim = NewXImage(dpy, NULL, map->depth, image->width, image->height)) == NULL) {
		XFreePixmap(dpy, *pix);
		return False;
	}

	if ((image->cmapSize > map->ncolors) ||
	    (image->cmapSize == 0 && map->isMapped))
		image = quantizeColormap(image, map, False);

	if (image->cmapSize > 0)
		compressColormap(image);

	if (image->cmapSize > 0) {
		unsigned short 	*sdp = (unsigned short *)image->data;
		unsigned char 	*cdp = (unsigned char  *)image->data;
		Pixel		*list = (Pixel*)XtCalloc(sizeof(Pixel), image->cmapSize);
		XColor		*xcol = (XColor*)XtCalloc(sizeof(XColor), image->cmapSize);

		for (y = 0; y < image->cmapSize; y++) {
			xcol[y].red   = image->cmapData[y * 3 + 0] << 8;
			xcol[y].green = image->cmapData[y * 3 + 1] << 8;
			xcol[y].blue  = image->cmapData[y * 3 + 2] << 8;
		}

		PaletteAllocN(map, xcol, image->cmapSize, list);

		for (y = 0; y < height; y++) { 
			for (x = 0; x < width; x++, sdp++, cdp++) 
				XPutPixel(xim, x, y, list[image->cmapSize > 256 ? *sdp : *cdp]);
		}
		
		XtFree((char *)list);
		XtFree((char *)xcol);
	} else {
		for (y = 0; y < height; y++) {
			for (x = 0; x < width; x++) {
				unsigned char	*cp;
				XColor		c;

				ImagePixel(image, x, y, cp);

				c.red   = cp[0] << 8;
				c.green = cp[1] << 8;
				c.blue  = cp[2] << 8;

				XPutPixel(xim, x, y, PaletteAlloc(map, &c));
			}

		}
	}

	gc = XCreateGC(XtDisplay(w), *pix, 0, 0);
	XPutImage(dpy, *pix, gc, xim, 0, 0, 0, 0, width, height);
	XFreeGC(XtDisplay(w), gc);

	XDestroyImage(xim);

	ImageDelete(image);

	return True;
}

