/* convert.c - convert RImage to Pixmap
 * 
 *  Raster graphics library
 *
 *  Copyright (c) 1997 Alfredo K. Kojima 
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Library General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *  
 *  This library 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
 *  Library General Public License for more details.
 *  
 *  You should have received a copy of the GNU Library General Public
 *  License along with this library; if not, write to the Free
 *  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include "config.h"

/* AIX requires this to be the first thing in the file.  */
#ifdef __GNUC__
# define alloca __builtin_alloca
#else
# if HAVE_ALLOCA_H
#  include <alloca.h>
# else
#  ifdef _AIX
#   pragma alloca
#  else
#   ifndef alloca /* predefined by HP cc +Olibcalls */
char *alloca ();
#   endif
#  endif
# endif
#endif


#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "wraster.h"


static XImage*
image2TrueColor(RContext *context, RImage *image)
{
    XImage *ximg;
    register int x, y;
    unsigned char *r, *g, *b;
    unsigned char *data;
    unsigned long pixel;
    unsigned long rmask, gmask, bmask;

    ximg = XCreateImage(context->dpy, context->visual, context->depth,
			ZPixmap, 0, NULL, image->width, image->height, 8, 0);
    if (!ximg) {
	sprintf(RErrorString, "could not create XImage");
	return NULL;
    }
    ximg->data = malloc(ximg->bytes_per_line*image->height);
    if (!ximg->data) {
	sprintf(RErrorString, "out of memory");
	XDestroyImage(ximg);
	return NULL;
    }
    data = (unsigned char*)ximg->data;

    r = image->data[0];
    g = image->data[1];
    b = image->data[2];
    
    rmask = context->visual->red_mask;
    gmask = context->visual->green_mask;
    bmask = context->visual->blue_mask;
    switch (context->depth) {
     case 24:
	if (ximg->byte_order == LSBFirst) {
	    for (y=0; y < image->height; y++) {
		for (x=0; x < image->width; x++) {
		    pixel = (*(r++)<<16) | (*(g++)<<8) | *(b++);
		    XPutPixel(ximg, x, y, pixel);
		}
	    }
	} else {
	    for (y=0; y < image->height; y++) {
		for (x=0; x < image->width; x++) {
		    pixel = (*(b++)<<16) | (*(g++)<<8) | *(r++);
		    XPutPixel(ximg, x, y, pixel);
		}
	    }
	}
	break;
      case 16:
	if (context->attribs->render_mode==RM_MATCH) {
	    for (y=0; y < image->height; y++) {
		for (x=0; x < image->width; x++) {
		    pixel = (((*(r++)<<8)&rmask) | ((*(g++)<<3)&gmask)
			     | ((*(b++)>>3)&bmask));
		    XPutPixel(ximg, x, y, pixel);
		}
	    }
	} else {
	    short *rerr, *gerr, *berr;
	    short *nrerr, *ngerr, *nberr;
	    short *trerr, *tgerr, *tberr;
	    register int x, r, g, b;
	    int rer, ger, ber, ofs, y, i;

	    rerr = alloca(image->width*sizeof(short) + 1);
	    gerr = alloca(image->width*sizeof(short) + 1);
	    berr = alloca(image->width*sizeof(short) + 1);
	    nrerr = alloca(image->width*sizeof(short) + 1);
	    ngerr = alloca(image->width*sizeof(short) + 1);
	    nberr = alloca(image->width*sizeof(short) + 1);
	    if (!rerr || !gerr || !berr || !nrerr || !ngerr || !nberr) {
		sprintf(RErrorString, "out of memory");
		free(data);
		return NULL;
	    }
	    trerr = rerr;
	    tgerr = gerr;
	    tberr = berr;	    
	    for (i=0; i<image->width; i++) {
		*trerr = image->data[0][i];
		*tgerr = image->data[1][i];
		*tberr = image->data[2][i];
		trerr++; tgerr++; tberr++;
	    }
	    /* convert and dither the image to XImage */
	    ofs = 0;
	    for (y=0; y<image->height; y++) {
		if (y<image->height-1) {
		    trerr = nrerr;
		    tgerr = ngerr;
		    tberr = nberr;
		    for (i=ofs+image->width; i<ofs+image->width*2; i++) {      
			*trerr = image->data[0][i];
			*tgerr = image->data[1][i];
			*tberr = image->data[2][i];
			trerr++; tgerr++; tberr++;
		    }
		    /* last column */
		    *trerr = image->data[0][i-1];
		    *tgerr = image->data[1][i-1];
		    *tberr = image->data[2][i-1];
		}
		for (x=0; x<image->width; x++) {
		    /* reduce pixel */
		    
		    if (rerr[x]>255) rerr[x]=255;
		    if (gerr[x]>255) gerr[x]=255;
		    if (berr[x]>255) berr[x]=255;
		    r = rerr[x];
		    g = gerr[x];
		    b = berr[x];
		    r = (r<<8)&rmask;
		    g = (g<<3)&gmask;
		    b = (b>>3)&bmask;
		    XPutPixel(ximg, x, y, r|g|b);
		    /* calc error */
		    rer = rerr[x] - (r>>8);
		    ger = gerr[x] - (g>>3);
		    ber = berr[x] - (b<<3);
		    /* distribute error */
		    /* x+1, y */
		    rerr[x+1]+=(rer*3)/8;
		    gerr[x+1]+=(ger*3)/8;
		    berr[x+1]+=(ber*3)/8;
		    /* x, y+1 */
		    nrerr[x]+=(rer*3)/8;
		    ngerr[x]+=(ger*3)/8;
		    nberr[x]+=(ber*3)/8;
		    /* x+1, y+1 */
		    nrerr[x+1]+=(rer*2)/8;
		    ngerr[x+1]+=(ger*2)/8;
		    nberr[x+1]+=(ber*2)/8;
		}
		ofs+=image->width;
		/* skip to next line */
		trerr = rerr;
		tgerr = gerr;
		tberr = berr;

		rerr = nrerr;
		gerr = ngerr;
		berr = nberr;
		
		nrerr = trerr;
		ngerr = tgerr;
		nberr = tberr;
	    }
	}
	break;
      case 15:
        {
	    switch (context->attribs->render_mode) {
	     case RM_MATCH:
		{
		    for( y=0; y<image->height; y++ ) {
			for( x=0; x<image->width; x++ ) {
			    pixel = ( ((*(r++)<<7)&rmask) |
				     ((*(g++)<<2)&gmask) |
				     ((*(b++)>>3)&bmask) );
			    XPutPixel( ximg, x, y, pixel );
			}
		    }
		}
		break;
	     case RM_DITHER:
		{
		    short *rerr, *gerr, *berr;
		    short *nrerr, *ngerr, *nberr;
		    short *trerr, *tgerr, *tberr;
		    register int x, r, g, b;
		    int rer, ger, ber, ofs, y, i;
		    
		    rerr = alloca(image->width*sizeof(short) + 1);
		    gerr = alloca(image->width*sizeof(short) + 1);
		    berr = alloca(image->width*sizeof(short) + 1);
		    nrerr = alloca(image->width*sizeof(short) + 1);
		    ngerr = alloca(image->width*sizeof(short) + 1);
		    nberr = alloca(image->width*sizeof(short) + 1);
		    if (!rerr || !gerr || !berr || !nrerr || !ngerr || !nberr) {
			sprintf(RErrorString, "out of memory");
			free(data);
			return NULL;
		    }
		    trerr = rerr;
		    tgerr = gerr;
		    tberr = berr;           
		    for (i=0; i<image->width; i++) {
			*trerr = image->data[0][i];
			*tgerr = image->data[1][i];
			*tberr = image->data[2][i];
			trerr++; tgerr++; tberr++;
		    }
		    /* convert and dither the image to XImage */
		    ofs = 0;
		    for (y=0; y<image->height; y++) {
			if (y<image->height-1) {
			    trerr = nrerr;
			    tgerr = ngerr;
			    tberr = nberr;
			    for (i=ofs+image->width; i<ofs+image->width*2; i++) {      
				*trerr = image->data[0][i];
				*tgerr = image->data[1][i];
				*tberr = image->data[2][i];
				trerr++; tgerr++; tberr++;
			    }
			    /* last column */
			    *trerr = image->data[0][i-1];
			    *tgerr = image->data[1][i-1];
			    *tberr = image->data[2][i-1];
			}
			for (x=0; x<image->width; x++) {
			    /* reduce pixel */
			    
			    if (rerr[x]>255) rerr[x]=255;
			    if (gerr[x]>255) gerr[x]=255;
			    if (berr[x]>255) berr[x]=255;
			    r = rerr[x];
			    g = gerr[x];
			    b = berr[x];
			    r = (r<<7)&rmask;
			    g = (g<<2)&gmask;
			    b = (b>>3)&bmask;
			    XPutPixel(ximg, x, y, r|g|b);
			    /* calc error */
			    rer = rerr[x] - (r>>7);
			    ger = gerr[x] - (g>>2);
			    ber = berr[x] - (b<<3);
			    /* distribute error */
			    /* x+1, y */
			    rerr[x+1]+=(rer*3)/8;
			    gerr[x+1]+=(ger*3)/8;
			    berr[x+1]+=(ber*3)/8;
			    /* x, y+1 */
			    nrerr[x]+=(rer*3)/8;
			    ngerr[x]+=(ger*3)/8;
			    nberr[x]+=(ber*3)/8;
			    /* x+1, y+1 */
			    nrerr[x+1]+=(rer*2)/8;
			    ngerr[x+1]+=(ger*2)/8;
			    nberr[x+1]+=(ber*2)/8;
			}
			ofs+=image->width;
			/* skip to next line */
			trerr = rerr;
			tgerr = gerr;
			tberr = berr;
			
			rerr = nrerr;
			gerr = ngerr;
			berr = nberr;
			
			nrerr = trerr;
			ngerr = tgerr;
			nberr = tberr;
		    }
		}
		
		break;
	    }
	}
	break;
     default:
	sprintf(RErrorString, "depth not supported");
	return NULL;	
    }
    return ximg;
}


static XImage*
image2PseudoColor(RContext *ctx, RImage *image)
{
    XImage *ximg;
    int i, cpc=ctx->attribs->colors_per_channel;
    unsigned char *data=NULL;

    ximg = XCreateImage(ctx->dpy, ctx->visual, ctx->depth, ZPixmap, 0,
			NULL, image->width, image->height, 8, 0);
    if (!ximg) {
	sprintf(RErrorString, "could not create XImage");
	return NULL;
    }
    data = malloc(ximg->bytes_per_line*image->height);
    if (!data) {
	XDestroyImage(ximg);
	sprintf(RErrorString, "out of memory");
	return NULL;
    }

    switch (ctx->depth) {
     case 8:
	if (ctx->attribs->render_mode == RM_DITHER) {
	    short *rerr, *gerr, *berr;
	    short *nrerr, *ngerr, *nberr;
	    short *trerr, *tgerr, *tberr;
	    int x, r, g, b, cpcpc = cpc*cpc;
	    int rer, ger, ber, ofs, d, y;

	    rerr = alloca(image->width*sizeof(short) + 1);
	    gerr = alloca(image->width*sizeof(short) + 1);
	    berr = alloca(image->width*sizeof(short) + 1);
	    nrerr = alloca(image->width*sizeof(short) + 1);
	    ngerr = alloca(image->width*sizeof(short) + 1);
	    nberr = alloca(image->width*sizeof(short) + 1);
	    if (!rerr || !gerr || !berr || !nrerr || !ngerr || !nberr) {
		sprintf(RErrorString, "out of memory");
		free(data);
		return NULL;
	    }
	    trerr = rerr;
	    tgerr = gerr;
	    tberr = berr;	    
	    for (i=0; i<image->width; i++) {
		*trerr = image->data[0][i];
		*tgerr = image->data[1][i];
		*tberr = image->data[2][i];
		trerr++; tgerr++; tberr++;
	    }
	    /* convert and dither the image to WImage */
	    d = 0xff/(cpc-1);
	    ofs = 0;
	    for (y=0; y<image->height; y++) {
		if (y<image->height-1) {
		    trerr = nrerr;
		    tgerr = ngerr;
		    tberr = nberr;
		    for (i=ofs+image->width; i<ofs+image->width*2; i++) {      
			*trerr = image->data[0][i];
			*tgerr = image->data[1][i];
			*tberr = image->data[2][i];
			trerr++; tgerr++; tberr++;
		    }
		    /* last column */
		    *trerr = image->data[0][i-1];
		    *tgerr = image->data[1][i-1];
		    *tberr = image->data[2][i-1];
		}
		for (x=0; x<image->width; x++) {
		    /* reduce pixel */
		    r = (rerr[x] * cpc)/0xff;
		    g = (gerr[x] * cpc)/0xff;
		    b = (berr[x] * cpc)/0xff;
		    if (r<0) r=0; else if (r>=cpc) r=cpc-1;
		    if (g<0) g=0; else if (g>=cpc) g=cpc-1;
		    if (b<0) b=0; else if (b>=cpc) b=cpc-1;

		    data[ofs] = ctx->colors[r*cpcpc+g*cpc+b].pixel;
		    /* calc error */
		    rer = rerr[x] - r*d;
		    ger = gerr[x] - g*d;
		    ber = berr[x] - b*d;

		    /* distribute error */
		    /* x+1, y */
		    rerr[x+1]+=(rer*3)/8;
		    gerr[x+1]+=(ger*3)/8;
		    berr[x+1]+=(ber*3)/8;
		    /* x, y+1 */
		    nrerr[x]+=(rer*3)/8;
		    ngerr[x]+=(ger*3)/8;
		    nberr[x]+=(ber*3)/8;
		    /* x+1, y+1 */
		    nrerr[x+1]+=(rer*2)/8;
		    ngerr[x+1]+=(ger*2)/8;
		    nberr[x+1]+=(ber*2)/8;
		    ofs++;
		}
		/* skip to next line */
		trerr = rerr;
		tgerr = gerr;
		tberr = berr;

		rerr = nrerr;
		gerr = ngerr;
		berr = nberr;
		
		nrerr = trerr;
		ngerr = tgerr;
		nberr = tberr;
	    }
	    /* 8bpp dither */
	} else {
	    /* 8bpp fake match */
	    int x, r, g, b;
	    int ofs, y;

	    ofs = 0;
	    for (y=0; y<image->height; y++) {
		for (x=0; x<image->width; x++) {
		    /* reduce pixel */
		    r = (image->data[0][ofs] * cpc)/0xff;
		    g = (image->data[1][ofs] * cpc)/0xff;
		    b = (image->data[2][ofs] * cpc)/0xff;
		    if (r<0) r=0; else if (r>=cpc) r=cpc-1;
		    if (g<0) g=0; else if (g>=cpc) g=cpc-1;
		    if (b<0) b=0; else if (b>=cpc) b=cpc-1;

		    data[ofs] = ctx->colors[r*cpc*cpc+g*cpc+b].pixel;
		    ofs++;
		}
	    }
	}
	break;
     default:
	sprintf(RErrorString, "depth not supported");
	return NULL;
    }
    ximg->data = (char*)data;

    return ximg;
}


static XImage*
image2GrayScale(RContext *ctx, RImage *image)
{
    XImage *ximg;
    int i, cpc=ctx->attribs->colors_per_channel;
    unsigned char *data=NULL;

    ximg = XCreateImage(ctx->dpy, ctx->visual, ctx->depth, ZPixmap, 0,
			NULL, image->width, image->height, 8, 0);
    if (!ximg) {
	sprintf(RErrorString, "could not create XImage");
	return NULL;
    }
    data = malloc(ximg->bytes_per_line*image->height);
    if (!data) {
	XDestroyImage(ximg);
	sprintf(RErrorString, "out of memory");
	return NULL;
    }

    switch (ctx->depth) {
     case 8:
	if (ctx->attribs->render_mode == RM_DITHER) {
	    short *rerr, *gerr, *berr;
	    short *nrerr, *ngerr, *nberr;
	    short *trerr, *tgerr, *tberr;
	    int x, r, g, b, cpcpc = cpc*cpc;
	    int rer, ger, ber, ofs, d, y;

	    rerr = alloca(image->width*sizeof(short) + 1);
	    gerr = alloca(image->width*sizeof(short) + 1);
	    berr = alloca(image->width*sizeof(short) + 1);
	    nrerr = alloca(image->width*sizeof(short) + 1);
	    ngerr = alloca(image->width*sizeof(short) + 1);
	    nberr = alloca(image->width*sizeof(short) + 1);
	    if (!rerr || !gerr || !berr || !nrerr || !ngerr || !nberr) {
		sprintf(RErrorString, "out of memory");
		free(data);
		return NULL;
	    }
	    trerr = rerr;
	    tgerr = gerr;
	    tberr = berr;	    
	    for (i=0; i<image->width; i++) {
		*trerr = image->data[0][i];
		*tgerr = image->data[1][i];
		*tberr = image->data[2][i];
		trerr++; tgerr++; tberr++;
	    }
	    /* convert and dither the image to WImage */
	    d = 0xff/(cpc-1);
	    ofs = 0;
	    for (y=0; y<image->height; y++) {
		if (y<image->height-1) {
		    trerr = nrerr;
		    tgerr = ngerr;
		    tberr = nberr;
		    for (i=ofs+image->width; i<ofs+image->width*2; i++) {      
			*trerr = image->data[0][i];
			*tgerr = image->data[1][i];
			*tberr = image->data[2][i];
			trerr++; tgerr++; tberr++;
		    }
		    /* last column */
		    *trerr = image->data[0][i-1];
		    *tgerr = image->data[1][i-1];
		    *tberr = image->data[2][i-1];
		}
		for (x=0; x<image->width; x++) {
		    /* reduce pixel */
		    r = (rerr[x] * cpc)/0xff;
		    g = (gerr[x] * cpc)/0xff;
		    b = (berr[x] * cpc)/0xff;
		    if (r<0) r=0; else if (r>=cpc) r=cpc-1;
		    if (g<0) g=0; else if (g>=cpc) g=cpc-1;
		    if (b<0) b=0; else if (b>=cpc) b=cpc-1;

		    data[ofs] = ctx->colors[r*cpcpc+g*cpc+b].pixel;
		    /* calc error */
		    rer = rerr[x] - r*d;
		    ger = gerr[x] - g*d;
		    ber = berr[x] - b*d;

		    /* distribute error */
		    /* x+1, y */
		    rerr[x+1]+=(rer*3)/8;
		    gerr[x+1]+=(ger*3)/8;
		    berr[x+1]+=(ber*3)/8;
		    /* x, y+1 */
		    nrerr[x]+=(rer*3)/8;
		    ngerr[x]+=(ger*3)/8;
		    nberr[x]+=(ber*3)/8;
		    /* x+1, y+1 */
		    nrerr[x+1]+=(rer*2)/8;
		    ngerr[x+1]+=(ger*2)/8;
		    nberr[x+1]+=(ber*2)/8;
		    ofs++;
		}
		/* skip to next line */
		trerr = rerr;
		tgerr = gerr;
		tberr = berr;

		rerr = nrerr;
		gerr = ngerr;
		berr = nberr;
		
		nrerr = trerr;
		ngerr = tgerr;
		nberr = tberr;
	    }
	    /* 8bpp dither */
	} else {
	    /* 8bpp fake match */
	    int x, r, g, b;
	    int ofs, y;

	    ofs = 0;
	    for (y=0; y<image->height; y++) {
		for (x=0; x<image->width; x++) {
		    /* reduce pixel */
		    r = (image->data[0][ofs] * cpc)/0xff;
		    g = (image->data[1][ofs] * cpc)/0xff;
		    b = (image->data[2][ofs] * cpc)/0xff;
		    if (r<0) r=0; else if (r>=cpc) r=cpc-1;
		    if (g<0) g=0; else if (g>=cpc) g=cpc-1;
		    if (b<0) b=0; else if (b>=cpc) b=cpc-1;

		    data[ofs] = ctx->colors[(r+g+b)/3].pixel;
		    ofs++;
		}
	    }
	}
	break;
     default:
	sprintf(RErrorString, "depth not supported");
	return NULL;
    }
    ximg->data = (char*)data;

    return ximg;
}



int 
RConvertImage(RContext *context, RImage *image, Pixmap *pixmap)
{
    XImage *ximg=NULL;

    /* clear error message */
    RErrorString[0] = 0;
    
    if (context->vclass == TrueColor)
      ximg = image2TrueColor(context, image);
    else if (context->vclass == PseudoColor)
      ximg = image2PseudoColor(context, image);
    else if (context->vclass == GrayScale)
      ximg = image2GrayScale(context, image);
    
    if (!ximg) {
	strcat(RErrorString, ":could not convert RImage to XImage");
#ifdef C_ALLOCA
	alloca(0);
#endif
	return False;
    }
    *pixmap = XCreatePixmap(context->dpy, context->drawable, image->width,
			    image->height, context->depth);
    XPutImage(context->dpy, *pixmap, context->copy_gc, ximg, 0, 0, 0, 0,
	      image->width, image->height);
    XDestroyImage(ximg);

#ifdef C_ALLOCA
    alloca(0);
#endif
    return True;
}


int 
RConvertImageMask(RContext *context, RImage *image, Pixmap *pixmap, 
		  Pixmap *mask, int threshold)
{
#ifdef C_ALLOCA
    alloca(0);
#endif
    return False;
}

