/* +-------------------------------------------------------------------+ */
/* | Copyright 1993, David Koblas (koblas@netcom.com)		       | */
/* | Copyright 1995, 1996 Torsten Martinsen (bullestock@dk-online.dk)  | */
/* |								       | */
/* | Permission to use, copy, modify, and to distribute this software  | */
/* | and its documentation for any purpose is hereby granted without   | */
/* | fee, provided that the above copyright notice appear in all       | */
/* | copies and that both that copyright notice and this permission    | */
/* | notice appear in supporting documentation.	 There is no	       | */
/* | representations about the suitability of this software for	       | */
/* | any purpose.  this software is provided "as is" without express   | */
/* | or implied warranty.					       | */
/* |								       | */
/* +-------------------------------------------------------------------+ */

/* $Id: iprocess.c,v 1.18 2005/03/20 20:15:32 demailly Exp $ */

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <math.h>
#include <dlfcn.h>

#include <X11/Intrinsic.h>
#include <X11/Shell.h>
#include <X11/StringDefs.h>
#include <X11/Xatom.h>

#include "xaw_incdir/AsciiText.h"
#include "xaw_incdir/TextP.h"
#include "xaw_incdir/Command.h"
#include "xaw_incdir/Form.h"
#include "xaw_incdir/Paned.h"

#include "Paint.h"
#include "PaintP.h"
#include "xpaint.h"
#include "image.h"
#include "menu.h"
#include "messages.h"
#include "misc.h"
#include "protocol.h"
#include "graphic.h"
#include "region.h"
#include "text.h"

extern Image *ReadPS_(char *file);
extern char *Basename(char *file);

#ifdef XAW3D
#define BORDERWIDTH 1
#else
#define BORDERWIDTH 0
#endif

typedef struct {
    unsigned char color[3];
    int ratio;
} BaseTransform;

#define BUFSIZE 65536

char *filter_so_file = NULL;

/*
**  Some real image processing
 */

extern int file_isSpecialImage;
extern int file_transparent;
extern double file_vxp_rescaling;

extern imageprocessinfo ImgProcessInfo;
extern FILE * openTempFile(char **np);
extern void removeTempFile(void);

#define CLAMP(low, value, high) \
		if (value < low) value = low; else if (value > high) value = high
#define DEG2RAD	(M_PI/180.0)

#define ConvMatrixSize	3
typedef double ConvMatrix[ConvMatrixSize * ConvMatrixSize];

static char *psviewer;

static int *histogram(Image * input);

static PaintMenuItem fileMenu[] =
{
#define FILE_LOAD 0
    MI_SIMPLE("load"),
#define FILE_SAVE 1
    MI_SIMPLE("save"),
#define FILE_SAVEAS 2
    MI_SIMPLE("saveas"),
#define FILE_EDIT 3
    MI_SIMPLE("editor"),
#define FILE_CLOSE 4
    MI_SIMPLE("close"),
};

static PaintMenuItem scriptMenu[] =
{
#define SCRIPT_FILTERS 0
    MI_SIMPLE("filters"),
#define SCRIPT_IMAGES 1
    MI_SIMPLE("images"),
#define SCRIPT_3D_CURVES 2
    MI_SIMPLE("3d_curves"),
#define SCRIPT_3D_SURFACES 3
    MI_SIMPLE("3d_surfaces"),
#define SCRIPT_LAYERS 4
    MI_SIMPLE("layers"),
#define SCRIPT_PROCEDURES 5
    MI_SIMPLE("procedures"),
#define SCRIPT_BATCH 6
    MI_SIMPLE("batch"),
#define SCRIPT_TEMPLATES 7
    MI_SIMPLE("templates"),    
#define SCRIPT_SEPARATOR 8
    MI_SEPARATOR(),
#define SCRIPT_HELP 9
    MI_SIMPLECB("help", HelpDialog, "canvas.selectorMenu.script_editor"),
};

static PaintMenuItem textMenu[] =
{
#define TEXT_ERASE 0
    MI_SIMPLE("erase"),
    MI_SEPARATOR(),  /* 1 */
#define TEXT_FONT 2
    MI_SIMPLE("\\* font: ?*\\"),
#define TEXT_COLOR 3
    MI_SIMPLE("\\* color: ?*\\"),
#define TEXT_ROTATION 4
    MI_SIMPLE("\\* rotation: ?*\\"),
#define TEXT_PLUS_ROTATION 5
    MI_SIMPLE("\\* +rotation: ?*\\"),
#define TEXT_INCLINATION 6
    MI_SIMPLE("\\* inclination: ?*\\"),
#define TEXT_DILATION 7
    MI_SIMPLE("\\* dilation: ?*\\"),
#define TEXT_LINESPACING 8
    MI_SIMPLE("\\* linespacing: ?*\\"),
#define TEXT_X 9
    MI_SIMPLE("\\* x: ?*\\"),
#define TEXT_Y 10
    MI_SIMPLE("\\* y: ?*\\"),
#define TEXT_PLUS_X 11
    MI_SIMPLE("\\* +x: ?*\\"),
#define TEXT_PLUS_Y 12
    MI_SIMPLE("\\* +y: ?*\\"),
#define TEXT_SX 13
    MI_SIMPLE("\\* sx: ?*\\"),
#define TEXT_SY 14
    MI_SIMPLE("\\* sy: ?*\\"),
#define TEXT_PLUS_SX 15
    MI_SIMPLE("\\* +sx: ?*\\"),
#define TEXT_PLUS_SY 16
    MI_SIMPLE("\\* +sy: ?*\\"),
#define TEXT_NOOP 17
};

static PaintMenuItem vxpMenu[] =
{
#define VXPDATA_RECORD 0
    MI_FLAG("record", MF_CHECK), 
#define VXPDATA_RESCALING 1
    MI_SIMPLE("rescaling"),
#define VXPDATA_RASTERIZE 2
    MI_SIMPLE("rasterize"),
#define VXPDATA_CLEAR 3
    MI_SIMPLE("clear"),
#define VXPDATA_SEPARATOR4 4
    MI_SEPARATOR(),
#define VXPDATA_VXP2PS 5
    MI_SIMPLE("vxp2ps"),
#define VXPDATA_VXP2TEX 6
    MI_SIMPLE("vxp2tex"),
#define VXPDATA_VXP2DKW 7
    MI_SIMPLE("vxp2dkw"),    
#define VXPDATA_SEPARATOR8 8
    MI_SEPARATOR(), 
#define VXPDATA_HELP 9
    MI_SIMPLECB("help", HelpDialog, "canvas.selectorMenu.vxp_editor"),
};

static PaintMenuItem texMenu[] =
{
#define TEX_CLEAR 0
    MI_SIMPLE("clear"),
#define TEX_SIZE_OPACITY 1
    MI_SIMPLE("size-opacity"),    
};

static PaintMenuItem ocrMenu[] =
{
#define OCR_CLEAR 0
    MI_SIMPLE("clear"),
#define OCR_LANGUAGE 1
    MI_SIMPLE("language"),    
};

static PaintMenuBar scriptMenuBar[] =
{
    {None, "files", XtNumber(fileMenu), fileMenu},
    {None, "predef", XtNumber(scriptMenu), scriptMenu},
};

static PaintMenuBar textMenuBar[] =
{
    {None, "files", XtNumber(fileMenu), fileMenu},
    {None, "text", XtNumber(textMenu), textMenu},
};

static PaintMenuBar texMenuBar[] =
{
    {None, "files", XtNumber(fileMenu), fileMenu},
    {None, "edit", XtNumber(texMenu), texMenu},
};

static PaintMenuBar vxpMenuBar[] =
{
    {None, "files", XtNumber(fileMenu), fileMenu},
    {None, "edit", XtNumber(vxpMenu), vxpMenu},
};

static PaintMenuBar ocrMenuBar[] =
{
    {None, "files", XtNumber(fileMenu), fileMenu},
    {None, "edit", XtNumber(ocrMenu), ocrMenu},
};

static Image *
convolve(Image * input, double *mat, int n,
	 BaseTransform *base, Boolean absFlag)
{
    int x, y, xx, yy, xv, yv;
    double sum;
    unsigned char *p;
    unsigned char *op;
    double r, g, b;
    int ir, ig, ib;
    Image *output;

    const int xy = -n/2;
    const int ye = input->height + xy;
    const int xe = input->width + xy;
    const int hh = input->height + input->height;
    const int ww = input->width + input->width;

    output = ImageNew(input->width, input->height);
    op = output->data;

    sum = 0;
    for (yy = 0; yy < n; yy++) {
	for (xx = 0; xx < n; xx++) {
	    sum += mat[xx + n * yy];
	}
    }

    if (sum <= 0) sum = 1;

    for (y = xy; y < ye; y++) {
	for (x = xy; x < xe; x++) {
	    r = g = b = 0;
	    for (yy = 0; yy < n; yy++) {
		for (xx = 0; xx < n; xx++) {
		    xv = x + xx;
		    yv = y + yy;
		    if (xv < 0)
			xv = -xv;
		    if (yv < 0) 
			yv = -yv;
		    if (xv >= input->width) {
			xv = xv % ww;
			if (xv >= input->width)
			    xv = ww - xv - 1;
		    }
		    if (yv >= input->height) {
			yv = yv % hh;
			if (yv >= input->height)
			    yv = hh - yv - 1;
		    }
		    p = ImagePixel(input, xv, yv);
		    r += (double) *p++ * mat[xx + n * yy];
		    g += (double) *p++ * mat[xx + n * yy];
		    b += (double) *p * mat[xx + n * yy];
		}
	    }
	    if (absFlag) {
		if (r < 0)
		    r = -r;
		if (g < 0)
		    g = -g;
		if (b < 0)
		    b = -b;
	    }
	    ir = r / sum;
	    ig = g / sum;
	    ib = b / sum;
	    if (base) {
	        /* linear rescaling */
                double f = (double) base->ratio / 100.0;
	        ir = f * ir + (int)base->color[0] + 0.5;
	        ig = f * ig + (int)base->color[1] + 0.5;
	        ib = f * ib + (int)base->color[2] + 0.5;
	    }
	    CLAMP(0, ir, 255);
	    CLAMP(0, ig, 255);
	    CLAMP(0, ib, 255);

	    *op++ = ir;
	    *op++ = ig;
	    *op++ = ib;
	}

	if (y % 16 == 0)
	    StateTimeStep();
    }

    return output;
}

/*
**  rescale values from 0..255
 */
static void 
normalize(Image * image)
{
    int i, count;
    unsigned char *sp;
    unsigned char *ip;
    unsigned int maxval = 0;

    if (image->cmapSize != 0) {
	sp = image->cmapData;
	count = image->cmapSize * 3;
    } else {
	sp = image->data;
	count = image->width * image->height * 3;
    }

    for (ip = sp, i = 0; i < count; i++, ip++)
	if (*ip > maxval)
	    maxval = *ip;
    if (maxval == 0)
	return;
    for (ip = sp, i = 0; i < count; i++, ip++)
        *ip = (((unsigned int)(*ip)) * 255) / maxval;
}

/*
**  Convert image into a monochrome image
 */

unsigned char 
GreyScale(unsigned char r, unsigned char g, unsigned char b)
{
    return (30*r + 59*g + 11*b)/100;
}

static void 
monochrome(Image * image)
{
    int i, count;
    unsigned char *sp;
    unsigned char *ip;
    int v;

    if (image->cmapSize != 0) {
	sp = image->cmapData;
	count = image->cmapSize;
    } else {
	sp = image->data;
	count = image->width * image->height;
    }

    for (ip = sp, i = 0; i < count; i++) {
        v = GreyScale(ip[0], ip[1], ip[2]);
	*ip++ = v;
	*ip++ = v;
	*ip++ = v;
    }
}


Image *
ImageSmooth(Image * input)
{
    double *mat;
    int n, i;
    Image *output;

    /*
     * Build n x n convolution matrix (all 1's)
     */
    n = ImgProcessInfo.smoothMaskSize;
    mat = xmalloc(n * n * sizeof(double));
    for (i = 0; i < n * n; ++i)
	mat[i] = 1.0;

    output = convolve(input, mat, n, NULL, False);
    free(mat);
    return output;
}

Image *
ImageSharpen(Image * input)
{
    static ConvMatrix mat =
    {
	-1, -2, -1,
	-2, 20, -2,
	-1, -2, -1
    };
    return convolve(input, mat, ConvMatrixSize, NULL, False);
}

Image *
ImageMerge(Image * input)
{
    Image *output, *canvas;
    int width = input->width, height = input->height;
    int x, y, x1, x2, y1, y2, alpha = 0;
    unsigned char *ip, *cp, *op, *ia, *ca, *oa;
    unsigned int p=0, q=0;
    unsigned long omi=0;
    double d, coef=0;

    canvas = ImgProcessInfo.canvas;

    if (ImgProcessInfo.mergeType == 0) {
        d = (double)ImgProcessInfo.mergePercent / 10001.0;
        d = 160000.0 * d/(1.0-d);
        coef = (double) ((width-1) * (height-1));
        coef = d/(coef * coef);
    }
    if (ImgProcessInfo.mergeType == 1) {
        p = ImgProcessInfo.mergePercent;
        q = 10000 - q;
    }

    output = ImageNew(width, height);
    ImageCopyData(input, output);
    if (input->alpha && canvas->alpha) {
        alpha = width*height;
        output->alpha = (unsigned char*) xmalloc(alpha);
        memcpy(output->alpha, input->alpha, alpha);
        omi = (unsigned long)output->alpha - (unsigned long)input->alpha;
    }

    x1 = MAX(0, -ImgProcessInfo.shiftX);
    x2 = MIN(input->width, canvas->width-ImgProcessInfo.shiftX);
    y1 = MAX(0, -ImgProcessInfo.shiftY);
    y2 = MIN(input->height, canvas->height-ImgProcessInfo.shiftY);

    for (y = y1; y < y2; y++) {
        for (x = x1; x < x2; x++) {
	    ip = ImagePixel(input, x, y);
	    op = ImagePixel(output, x, y);            
	    cp = ImagePixel(canvas, x+ImgProcessInfo.shiftX, y+ImgProcessInfo.shiftY);
            if (ImgProcessInfo.mergeType <= 1) {
	        if (ImgProcessInfo.mergeType == 0) {
		    p =   (int) ( (double) (x * (width-1-x)) * 
			          (double) (y * (height-1-y)) * coef );
                    if (p > 10000) p = 10000;
		    q = 10000 - p;
		}
                op[0] = (p * ip[0] + q * cp[0])/10000;
                op[1] = (p * ip[1] + q * cp[1])/10000;
                op[2] = (p * ip[2] + q * cp[2])/10000;
	    } else {
	        if (ImgProcessInfo.mergeType == 2) {
		    op[0] = MAX(ip[0], cp[0]);
		    op[1] = MAX(ip[1], cp[1]);
		    op[2] = MAX(ip[2], cp[2]);
	        } else {
		    op[0] = MIN(ip[0], cp[0]);
		    op[1] = MIN(ip[1], cp[1]);
		    op[2] = MIN(ip[2], cp[2]);
		}
	    }
            if (alpha) {
	        ia = input->alpha + x + y * input->width;
	        oa = input->alpha + omi;
                ca = canvas->alpha + x + ImgProcessInfo.shiftX +
		     (y+ImgProcessInfo.shiftY) * canvas->width;
                if (ImgProcessInfo.mergeType <= 1)
                    *oa = (p * *ia + q * *ca)/10000;
                else
                if (ImgProcessInfo.mergeType == 2)
		    *oa = MAX(*ia, *ca);
                else
		    *oa = MIN(*ia, *ca);
	    }
        }
    }

    return output;
}

Image *
ImageEdge(Image * input)
{
    static ConvMatrix mat =
    {
	-1, -2,  0,
	-2,  0,  2,
	 0,  2,  1
    };
    Image *image = convolve(input, mat, ConvMatrixSize, NULL, True);

    normalize(image);

    return image;
}

Image *
ImageEmboss(Image * input)
{
    static ConvMatrix mat =
    {
	-1, -2,  0,
	-2,  0,  2,
	 0,  2,  1
    };
    static BaseTransform base;

    Image *image;

    memset(base.color, ImgProcessInfo.embossBG, 3);
    base.ratio = ImgProcessInfo.embossRatio;
    image = convolve(input, mat, ConvMatrixSize, &base, False);

    monochrome(image);

    return image;
}

Image *
ImageInvert(Image * input)
{
    Image *output;
    unsigned char *op;

#define MEMORY_SAVINGS 1
#if MEMORY_SAVINGS
    unsigned char *ip;
    int i, count;

    /*
    **	 If the input has a colormap, just invert that.
     */
    if (input->cmapSize != 0) {
	output = ImageNewCmap(input->width, input->height, input->cmapSize);
	ip = input->cmapData;
	op = output->cmapData;
        output->isGrey = input->isGrey;
        output->isBW = input->isBW;
        count = input->cmapSize;
        
	memcpy(output->data, input->data,
	     sizeof(char) * input->scale * input->width * input->height);

    } else {
	output = ImageNew(input->width, input->height);
	ip = input->data;
	op = output->data;
	count = input->width * input->height;
    }

    for (i = 0; i < count; i++) {
         *op++ = 255 - *ip++;
	 *op++ = 255 - *ip++;
	 *op++ = 255 - *ip++;
    }

#else
    int x, y;
    unsigned char *p;

    output = ImageNew(input->width, input->height);
    op = output->data;

    for (y = 0; y < input->height; y++)
    {
	for (x = 0; x < input->width; x++)
	{
	    p = ImagePixel(input, x, y);
	    *op++ = 255 - *p++;
	    *op++ = 255 - *p++;
	    *op++ = 255 - *p;
	}
    }
#endif

    return output;
}

Image *
ImageOilPaint(Image * input)
{
    Image *output = ImageNew(input->width, input->height);
    unsigned char *op = output->data;
    int x, y, xx, yy, i;
    int rVal, gVal, bVal;
    int rCnt, gCnt, bCnt;
    int rHist[256], gHist[256], bHist[256];
    int oilArea_2;


    oilArea_2 = ImgProcessInfo.oilArea / 2;

    for (y = 0; y < input->height; y++) {
	for (x = 0; x < input->width; x++) {
	    /*
	    **	compute histogram of (on-screen hunk of) n*n 
	    **	  region centered plane
	     */

	    rCnt = gCnt = bCnt = 0;
	    rVal = gVal = bVal = 0;
	    for (i = 0; i < XtNumber(rHist); i++)
		rHist[i] = gHist[i] = bHist[i] = 0;

	    for (yy = y - oilArea_2; yy < y + oilArea_2; yy++) {
		if (yy < 0 || yy >= input->height)
		    continue;
		for (xx = x - oilArea_2; xx < x + oilArea_2; xx++) {
		    int c, p;
		    unsigned char *rgb;

		    if (xx < 0 || xx >= input->width)
			continue;

		    rgb = ImagePixel(input, xx, yy);

		    if ((c = ++rHist[(p = rgb[0]) / 4]) > rCnt) {
			rVal = p;
			rCnt = c;
		    }
		    if ((c = ++gHist[(p = rgb[1]) / 4]) > gCnt) {
			gVal = p;
			gCnt = c;
		    }
		    if ((c = ++bHist[(p = rgb[2]) / 4]) > bCnt) {
			bVal = p;
			bCnt = c;
		    }
		}
	    }

	    *op++ = rVal;
	    *op++ = gVal;
	    *op++ = bVal;
	}

	if (y % 16 == 0)
	    StateTimeStep();
    }

    return output;
}


/*
 * Add random noise to an image.
 */
Image *
ImageAddNoise(Image * input)
{
    unsigned char *p;
    int x, y, r, g, b, ddelta;
    Image *output = ImageNew(input->width, input->height);
    unsigned char *op = output->data;


    ddelta = ImgProcessInfo.noiseDelta * 2;

    for (y = 0; y < input->height; y++) {
	for (x = 0; x < input->width; x++) {
	    p = ImagePixel(input, x, y);
	    r = p[0] + ddelta * gauss();
	    CLAMP(0, r, 255);
	    g = p[1] + ddelta * gauss();
	    CLAMP(0, g, 255);
	    b = p[2] + ddelta * gauss();
	    CLAMP(0, b, 255);
	    *op++ = r;
	    *op++ = g;
	    *op++ = b;
	}
    }
    return output;
}

/*
 * This function works in-place.
 * Because it swaps pixels in the input image, which may be color mapped
 * or not, is has knowledge about the Image format that should probably
 * have stayed in image.h. Too bad.
 */
Image *
ImageSpread(Image * input)
{
    int w = input->width, h = input->height;
    unsigned char *p, *np;
    int x, y, minx, miny, maxx, maxy, xn, yn, dist;

    dist = ImgProcessInfo.spreadDistance;
    for (y = 0; y < h; y++) {
	for (x = 0; x < w; x++) {
	    p = ImagePixel(input, x, y);
	    /* find random neighbour pixel within +- dist */
	    minx = x - dist;
	    if (minx < 0)
		minx = 0;
	    maxx = x + dist;
	    if (maxx >= w)
		maxx = w - 1;
	    xn = RANDOMI2(minx, maxx);

	    miny = y - dist;
	    if (miny < 0)
		miny = 0;
	    maxy = y + dist;
	    if (maxy >= h)
		maxy = h - 1;
	    yn = RANDOMI2(miny, maxy);
	    /* swap pixel with neighbour */
	    np = ImagePixel(input, xn, yn);
	    if (input->cmapSize == 0) {
		unsigned char rn, gn, bn;

		rn = np[0];
		gn = np[1];
		bn = np[2];
		np[0] = p[0];
		np[1] = p[1];
		np[2] = p[2];
		p[0] = rn;
		p[1] = gn;
		p[2] = bn;
	    } else if (input->cmapSize > 256) {
		unsigned short i, *ps, *nps;

		ps = &((unsigned short *) input->data)[y * w + x];
		nps = &((unsigned short *) input->data)[yn * w + xn];
		i = *nps;
		*nps = *ps;
		*ps = i;
	    } else {		/* colour map of 256 or less */
		unsigned char i;

		p = &input->data[y * w + x];
		np = &input->data[yn * w + xn];
		i = *np;
		*np = *p;
		*p = i;
	    }
	}
    }

    return input;
}

Image *
ImageBlend(Image * input)
{
    int w = input->width, h = input->height;
    Image *output = ImageNew(w, h);
    unsigned char *op = output->data;
    unsigned char *rgb;
    int x, y, n, ox, oy, px, py, dx, dy;
    int rSum, gSum, bSum, r, g, b;
    double diagonal, gradient, ap;


    ox = w / 2;
    oy = h / 2;
    diagonal = ((double) h) / w;

    /*
     * Compute average of pixels on edge of region. While we are
     * at it, we copy the edge to the output as well.
     */
    rSum = gSum = bSum = 0;
    for (x = 0; x < w; ++x) {
	rgb = ImagePixel(input, x, 0);
	r = rgb[0];
	g = rgb[1];
	b = rgb[2];
	rSum += r;
	gSum += g;
	bSum += b;
	*op++ = r;
	*op++ = g;
	*op++ = b;
    }
    op = ImagePixel(output, 0, h - 1);
    for (x = 0; x < w; ++x) {
	rgb = ImagePixel(input, x, h - 1);
	r = rgb[0];
	g = rgb[1];
	b = rgb[2];
	rSum += r;
	gSum += g;
	bSum += b;
	*op++ = r;
	*op++ = g;
	*op++ = b;
    }
    for (y = 1; y < h - 1; ++y) {
	rgb = ImagePixel(input, 0, y);
	r = rgb[0];
	g = rgb[1];
	b = rgb[2];
	rSum += r;
	gSum += g;
	bSum += b;
	op = ImagePixel(output, 0, y);
	*op++ = r;
	*op++ = g;
	*op++ = b;

	rgb = ImagePixel(input, w - 1, y);
	r = rgb[0];
	g = rgb[1];
	b = rgb[2];
	rSum += r;
	gSum += g;
	bSum += b;
	op = ImagePixel(output, w - 1, y);
	*op++ = r;
	*op++ = g;
	*op++ = b;
    }

    n = 2 * (w + h - 2);	/* total # of pixels on edge */
    rSum /= n;
    gSum /= n;
    bSum /= n;
    op = ImagePixel(output, 1, 1);

    /* compute colour of each point in the interior */
    for (y = 1; y < h - 1; y++) {
	for (x = 1; x < w - 1; x++) {
	    dx = x - ox;
	    dy = y - oy;
	    if (dx == 0 && dy == 0) {	/* this is the centre point */
		r = rSum;
		g = gSum;
		b = bSum;
	    } else {
		if (dx == 0) {	/* special case 1 */
		    px = ox;
		    py = (dy > 0) ? h - 1 : 0;
		} else if (dy == 0) {	/* special case 2 */
		    py = oy;
		    px = (dx > 0) ? w - 1 : 0;
		} else {	/* general case */
		    gradient = ((double) dy) / dx;
		    if (fabs(gradient) < fabs(diagonal)) {
			px = (dx > 0) ? w - 1 : 0;
			py = oy + ((px - ox) * dy) / dx;
		    } else {
			py = (dy > 0) ? h - 1 : 0;
			px = ox + ((py - oy) * dx) / dy;
		    }
		}

		/*
		 * given O(ox,oy), P(px,py), and A(x,y), compute
		 *   |AO|/|PO|
		 */
		ap = sqrt((double) (dx * dx) + (double) (dy * dy)) /
		    sqrt((double) ((px - ox) * (px - ox)) +
			 (double) ((py - oy) * (py - oy)));

		rgb = ImagePixel(input, px, py);
		r = (1 - ap) * rSum + ap * rgb[0];
		g = (1 - ap) * gSum + ap * rgb[1];
		b = (1 - ap) * bSum + ap * rgb[2];
	    }
	    *op++ = r;
	    *op++ = g;
	    *op++ = b;
	}
	op += 6;		/* skip last on this line and first on next */
    }

    return output;
}

Image *
ImagePixelize(Image * input)
{
    int width = input->width, height = input->height;
    Image *output = ImageNew(width, height);
    unsigned char *op, *rgb;
    int x, y, xx, yy, n;
    int rSum, gSum, bSum;
    int xsize, ysize;


    xsize = ImgProcessInfo.pixelizeXSize;
    ysize = ImgProcessInfo.pixelizeYSize;

    if (xsize > width)
	xsize = width;
    if (ysize > height)
	ysize = height;
    n = xsize * ysize;

    for (y = 0; y < height; y += ysize) {
	for (x = 0; x < width; x += xsize) {
	    /*
	     *	compute average of pixels inside megapixel
	     */
	    rSum = gSum = bSum = 0;
	    for (yy = y; yy < y + ysize; yy++) {
		if (yy >= height)
		    continue;
		for (xx = x; xx < x + xsize; xx++) {
		    if (xx >= width)
			continue;
		    rgb = ImagePixel(input, xx, yy);
		    rSum += rgb[0];
		    gSum += rgb[1];
		    bSum += rgb[2];
		}
	    }
	    rSum /= n;
	    gSum /= n;
	    bSum /= n;
	    /*
	     * replace each pixel in megapixel with average
	     */
	    for (yy = y; yy < y + ysize; yy++) {
		if (yy >= height)
		    continue;
		for (xx = x; xx < x + xsize; xx++) {
		    if (xx >= width)
			continue;
		    op = ImagePixel(output, xx, yy);
		    *op++ = rSum;
		    *op++ = gSum;
		    *op = bSum;
		}
	    }
	}

	if (y % 16 == 0)
	    StateTimeStep();
    }

    /* if any incomplete megapixels are left, copy them to the output */
    for (x = (width / xsize) * xsize; x < width; ++x)
	for (y = 0; y < height; ++y) {
	    rgb = ImagePixel(input, x, y);
	    op = ImagePixel(output, x, y);
	    *op++ = *rgb++;
	    *op++ = *rgb++;
	    *op = *rgb;
	}
    for (y = (height / ysize) * ysize; y < height; ++y)
	for (x = 0; x < width; ++x) {
	    rgb = ImagePixel(input, x, y);
	    op = ImagePixel(output, x, y);
	    *op++ = *rgb++;
	    *op++ = *rgb++;
	    *op = *rgb;
	}

    return output;
}


#define SWAP(a, b)	{ int t = (a); (a) = (b); (b) = t; }

Image *
ImageDespeckle(Image * input)
{
    int width = input->width, height = input->height;
    Image *output = ImageNew(width, height);
    unsigned char *op, *rgb;
    int x, y, xx, yy, mask, mask2, i, j, k, l;
    int *ra, *ga, *ba;

    mask = ImgProcessInfo.despeckleMask;
    if (mask > width)
	mask = width;
    if (mask > height)
	mask = height;
    mask2 = mask / 2;

    /* arrays for storing pixels inside mask */
    ra = xmalloc(mask * mask * sizeof(int));
    ga = xmalloc(mask * mask * sizeof(int));
    ba = xmalloc(mask * mask * sizeof(int));

    op = output->data;
    for (y = 0; y < height; ++y) {
	for (x = 0; x < width; ++x) {
	    i = 0;
	    for (yy = MAX(0, y - mask2); yy < MIN(height, y + mask2); ++yy)
		for (xx = MAX(0, x - mask2); xx < MIN(width, x + mask2); ++xx) {
		    rgb = ImagePixel(input, xx, yy);
		    ra[i] = *rgb++;
		    ga[i] = *rgb++;
		    ba[i] = *rgb;
		    ++i;
		}
	    /*
	     * now find median by (shell-)sorting the arrays and
	     * picking the center value
	     */
	    for (j = i / 2; j > 0; j = j / 2)
		for (k = j; k < i; k++) {
		    for (l = k - j; l >= 0 && ra[l] > ra[l + j]; l -= j)
			SWAP(ra[l], ra[l + j]);
		    for (l = k - j; l >= 0 && ga[l] > ga[l + j]; l -= j)
			SWAP(ga[l], ga[l + j]);
		    for (l = k - j; l >= 0 && ba[l] > ba[l + j]; l -= j)
			SWAP(ba[l], ba[l + j]);
		}
	    if (i & 1) {	/* uneven number of data points */
		*op++ = ra[i / 2];
		*op++ = ga[i / 2];
		*op++ = ba[i / 2];
	    } else {		/* even, take average */
		*op++ = (ra[i / 2 - 1] + ra[i / 2]) / 2;
		*op++ = (ga[i / 2 - 1] + ga[i / 2]) / 2;
		*op++ = (ba[i / 2 - 1] + ba[i / 2]) / 2;
	    }
	}
	if (y % 16 == 0)
	    StateTimeStep();
    }

    free(ra);
    free(ga);
    free(ba);

    return output;
}


/*
 * Normalize the contrast of an image.
 */
Image *
ImageNormalizeContrast(Image * input)
{
    unsigned char *p;
    int *hist;
    int x, y, w, h, i, max = 0, cumsum, limit;
    Image *output = ImageNew(input->width, input->height);
    unsigned char *op = output->data;
    int blackpcnt, blackval, whitepcnt, whiteval;


    blackpcnt = ImgProcessInfo.contrastB;
    whitepcnt = 100 - ImgProcessInfo.contrastW;

    hist = histogram(input);

    w = input->width;
    h = input->height;

    /* Find lower knee point in histogram to determine 'black' threshold. */
    cumsum = 0;
    limit = w * h * blackpcnt / 100;
    for (i = 0; i < 256; ++i)
	if ((cumsum += hist[i]) > limit)
	    break;
    blackval = i;

    /* Likewise for 'white' threshold. */
    cumsum = 0;
    limit = w * h * whitepcnt / 100;
    for (i = 255; i > 0; --i) {
	if ((max == 0) && hist[i])
	    max = i;	/* max is index of highest non-zero entry */
	if ((cumsum += hist[i]) > limit)
	    break;
    }
    whiteval = i;

    if (whiteval == blackval) {
        for (y = 0; y < h; y++) {
	    for (x = 0; x < w; x++) {
	        p = ImagePixel(input, x, y);
	        *op++ = *p++;
	        *op++ = *p++;
	        *op++ = *p;
	    }
	}
        free(hist);
        return output;
    }

    /*
     * Now create a histogram with a linear ramp
     * from (blackval, 0) to (whiteval, max)
     */
    for (i = 0; i < blackval; ++i)
	hist[i] = 0;
    for (; i <= whiteval; ++i)
	hist[i] = max * (i - blackval) / (whiteval - blackval);
    for (; i < 256; ++i)
	hist[i] = max;

    for (y = 0; y < h; y++) {
	for (x = 0; x < w; x++) {
	    p = ImagePixel(input, x, y);
	    *op++ = hist[*p++];
	    *op++ = hist[*p++];
	    *op++ = hist[*p];
	}
    }
    free(hist);

    return output;
}

/*
 * Build histogram data for the image.
 */
static int *
histogram(Image * input)
{
    unsigned char *p;
    int x, y, w, h, i, r, g, b, *hist;

    w = input->width;
    h = input->height;

    /* Make a table of 256 zeros plus one sentinel */
    hist = xmalloc(257 * sizeof(int));
    for (i = 0; i <= 256; ++i)
	hist[i] = 0;

    /*
     * Build histogram of intensity values.
     */
    for (y = 0; y < h; y++) {
	for (x = 0; x < w; x++) {
	    p = ImagePixel(input, x, y);
	    r = *p++;
	    g = *p++;
	    b = *p;
	    i = GreyScale(r, g, b);
	    ++hist[i];
	}
    }
    return hist;
}

#if 0

#define HIST_H	  120		/* Height of histogram */
#define HIST_W	  256		/* Width of histogram */
#define HIST_B	    2		/* Horizontal border width for histogram */
#define HIST_R	   12		/* Height of ruler below histogram */
#define HIST_T	    9		/* Height of tick marks on ruler */
#define LIGHTGREY 200		/* Greyscale level used for bars */
#define MIDGREY	  255		/* Greyscale level used for ruler */
#define DARKGREY  120		/* Greyscale level used for background */

/*
 * Create an image (width 256, height HIST_H) depicting the histogram data.
 */
Image *
HistogramImage(char *hist)
{
    unsigned char *p;
    int x, y, i;
    int max, e:
    unsigned char *ihist;
    Image *output = ImageNewGrey(HIST_W + 2 * HIST_B, HIST_H + HIST_R);;

    max = 0;
    ihist = xmalloc(128 * sizeof(int));
    for (i = 0; i < 128; ++i) {
	e = ihist[i] = hist[i * 2] + hist[i * 2 + 1];
	if (e > max)
	    max = e;
    }
    /*
     * Now create histogram image.
     * Only display 128 bins to make the histogram easier to read.
     * Leave a border of a few pixels in each side.
     */

    memset(output->data, DARKGREY, (HIST_W + 2 * HIST_B) * (HIST_H + HIST_R));
    for (x = 0; x < 128; ++x) {
	i = ihist[x] * (HIST_H - 1) / max;
	for (y = HIST_H - 1 - i; y < HIST_H - 1; y++) {
	    p = output->data + y * (HIST_W + 2 * HIST_B) + x * 2 + HIST_B;
	    *p++ = LIGHTGREY;
	    *p = LIGHTGREY;
	}
    }
    /* Ruler */
    memset(output->data + HIST_H * (HIST_W + 2 * HIST_B) + HIST_B,
	   MIDGREY, HIST_W);
    for (x = 0; x < 5; ++x) {
	int tick[] =
	{0, HIST_W / 4, HIST_W / 2, HIST_W * 3 / 4, HIST_W - 1};

	for (y = 0; y < HIST_T; y++) {
	    p = output->data + (y + HIST_H + 1) * (HIST_W + 2 * HIST_B)
		+ tick[x] + HIST_B;
	    *p = MIDGREY;
	}
    }
    free(ihist);

    return output;
}
#endif

/*
 * Tilt an image.
 */

/*
 * Compute the interpolated RGB values of a 1 x 1 pixel centered
 * at (x,y) and store these values in op[0] trough op[2].
 * The interpolation is based on weighting the RGB values proportionally
 * to the overlapping areas.
 */
void 
PixelInterpolate(unsigned char *op, Image *input, double x, double y)
{
    int xf, xc, yf, yc, out;
    double a, b, c, d, A, B, C, D;
    unsigned char *pff, *pfc, *pcf, *pcc;

    xf = floor(x);
    xc = ceil(x);
    yf = floor(y);
    yc = ceil(y);

    out = 0;
    if (xc < 0) out = 1;
    if (yc < 0) out = 1;
    if (xf >= input->width) out = 1;
    if (yf >= input->height) out = 1;
    if (out) {
        out = -1;
        memcpy(op, &out, 3);
	return;
    }
    if (xf < 0) xf = xc = 0;
    if (xc >= input->width) xf = xc = input->width - 1;
    if (yf < 0) yf = yc = 0;
    if (yc >= input->height) yf = yc = input->height - 1;
    
    pff = ImagePixel(input, xf, yf);

    if (xc == xf) {
	if (yc == yf) {
	    /* output pixel coincides with single input pixel */
	    *op++ = *pff++;
	    *op++ = *pff++;
	    *op++ = *pff++;
	} else {
	    /* pixel interpolates two pixels on top of each other */
	    A = yc - y;
	    B = y - yf;
            pfc = ImagePixel(input, xf, yc);
	    *op++ = A * *pff++ + B * *pfc++;
	    *op++ = A * *pff++ + B * *pfc++;
	    *op++ = A * *pff++ + B * *pfc++;
	}
    } else if (yc == yf) {
	/* pixel interpolates two side-by-side pixels */
	A = xc - x;
	B = x - xf;        
	pcf = ImagePixel(input, xc, yf);
	*op++ = A * *pff++ + B * *pcf++;
	*op++ = A * *pff++ + B * *pcf++;
	*op++ = A * *pff++ + B * *pcf++;
    } else {
	/* general case: pixel interpolates all four neighbour pixels */
	a = xc - x;
	b = x - xf;
	c = yc - y;
	d = y - yf;
	A = a * c;
	B = a * d;
	C = b * c;
	D = b * d;
	pfc = ImagePixel(input, xf, yc);
	pcf = ImagePixel(input, xc, yf);
	pcc = ImagePixel(input, xc, yc);
	*op++ = A * *pff++ + B * *pfc++ + C * *pcf++ + D * *pcc++;
	*op++ = A * *pff++ + B * *pfc++ + C * *pcf++ + D * *pcc++;
	*op++ = A * *pff++ + B * *pfc++ + C * *pcf++ + D * *pcc++;
    }
}

Image *
ImageDistort(Image * input)
{
    int x, y, xi, yi;
    int width = input->width, height = input->height;
    Image *output;
    unsigned char *p, *op;
    double a, b, sx, sy, sx2, sy2, sp, w, h;

    a = ImgProcessInfo.distortX;
    b = ImgProcessInfo.distortY;
    w = width - 1.0;
    h = height - 1.0;

    if (width<=1 || height<=1) return input;

    output = ImageNew(width, height);

    for (y = 0; y < height; y++) {
        sy = (double)y / h;
        sy2 = 1.0 - sy;
        sy2 = sy*sy*sy2*sy2;
	for (x = 0; x < width; x++) {
	    sx = (double)x / w;
            sx2 = 1.0 - sx;
            sx2 = sx*sx*sx2*sx2;
            sp = sx2 * sy2;
            xi = (int) (w * (sx + a * sp) + 0.5);
            CLAMP(0, xi, width-1);
	    yi = (int) (h * (sy + b * sp) + 0.5);
            CLAMP(0, yi, height-1);
            p = ImagePixel(input, xi, yi);
	    op = ImagePixel(output, x, y);
	    *op++ = *p++;
	    *op++ = *p++;
	    *op = *p;
	}
    }

    return output;
}

int
ReadProjData(char *str, int *i)
{
    int n, j;
    double a, b;

    if (strncasecmp(str, "rect", 4)) {
        n = sscanf(str, "%d,%d %d,%d %d,%d %d,%d",
	       &i[0], &i[1], &i[2], &i[3], &i[4], &i[5], &i[6], &i[7]);
	return -(n < 8);
    } else {
        str = str+4;
	if (!strncasecmp(str, "xy", 2)) {
	    n = sscanf(str+2, "%d,%d %d,%d", &i[0], &i[1], &i[4], &i[5]);
	} else
	if (!strncasecmp(str, "wh", 2)) {  
            n = sscanf(str+2, "%d,%d %d,%d", &i[0], &i[1], &i[4], &i[5]);
	    i[4] = i[0] + i[4];
	    i[5] = i[1] + i[5];
	} else {
	    /* trying to guess a rectangle, return 1 */
	    i[0] = i[1] = 0;
 	    a = b = 1.0;
            sscanf(str, "%d,%d %lf,%lf", &i[0], &i[1], &a, &b);
	    i[4] = 100000 * a + 0.5;
	    i[5] = 100000 * b + 0.5;
	    return 1;
	}
	/* insufficient number of arguments */
	if (n < 4) return -1;
	i[2] = i[4];
	i[3] = i[1];
	i[6] = i[0];
	i[7] = i[5];
        return 0;
    }
}

/* Projective transformation
 * Maps any 4-tuple of points to any other 4-tuple of points
 * Useful for correction of perspective
 */

Image *
ImageProjTransform(Image * input)
{
    Image * output;
    unsigned char *ip, *op;
    int i, j, k, l, x, y;
    int u[8], v[8];
    double a, t[3], p[3][3], q[3][3], r[3][3];

    /* read projective transform data */
    if (atoi(ImgProcessInfo.proj2)) {    
        if (ReadProjData(ImgProcessInfo.proj1, u)) return input;
        if ((i=ReadProjData(ImgProcessInfo.proj0, v)) < 0) return input;
    } else {
        if (ReadProjData(ImgProcessInfo.proj0, u)) return input;
        if ((i=ReadProjData(ImgProcessInfo.proj1, v)) < 0) return input;
    }

    if (i == 1) {
        v[2] = (u[0]+u[2]+u[4]+u[6])/4 + v[0];
        v[3] = (u[1]+u[3]+u[5]+u[7])/4 + v[1];
	t[0] = u[2] - u[0];
	t[1] = u[3] - u[1];
	t[2] = t[0] * t[0] + t[1] * t[1];
	t[0] = u[6] - u[4];
	t[1] = u[7] - u[5];
	t[2] += t[0] * t[0] + t[1] * t[1];
	v[6] = (double) v[4] * sqrt(t[2]/2) / 100000.0;
	t[0] = u[4] - u[2];
	t[1] = u[5] - u[3];
	t[2] = t[0] * t[0] + t[1] * t[1];
	t[0] = u[6] - u[0];
	t[1] = u[7] - u[1];
	t[2] += t[0] * t[0] + t[1] * t[1];
	v[7] = (double) v[5] * sqrt(t[2]/2) / 100000.0;
	v[0] = v[2] - v[6]/2;
	v[1] = v[3] - v[7]/2;
	v[2] = v[0] + v[6];
	v[3] = v[1];
	v[4] = v[2];
	v[5] = v[1] + v[7];
	v[6] = v[0];
	v[7] = v[5];
    }
    
    output = ImageNew(input->width, input->height);
    /* first matrix p */
    p[0][0] = 1.0;
    p[1][0] = u[0];
    p[2][0] = u[1];
    k = (u[4]-u[2])*(u[5]-u[7])-(u[5]-u[3])*(u[4]-u[6]);
    x = (u[4]-u[0])*(u[5]-u[7])-(u[5]-u[1])*(u[4]-u[6]);
    y = (u[4]-u[2])*(u[5]-u[1])-(u[5]-u[3])*(u[4]-u[0]);
    a = (double)x/(double)k;
    p[0][1] = a - 1.0;
    p[1][1] = a * u[2] - u[0];
    p[2][1] = a * u[3] - u[1];
    a = (double)y/(double)k;    
    p[0][2] = a - 1.0;
    p[1][2] = a * u[6] - u[0];
    p[2][2] = a * u[7] - u[1];
    /* second matrix q */
    q[0][0] = 1.0;
    q[1][0] = v[0];
    q[2][0] = v[1];
    k = (v[4]-v[2])*(v[5]-v[7])-(v[5]-v[3])*(v[4]-v[6]);
    x = (v[4]-v[0])*(v[5]-v[7])-(v[5]-v[1])*(v[4]-v[6]);
    y = (v[4]-v[2])*(v[5]-v[1])-(v[5]-v[3])*(v[4]-v[0]);
    a = (double)x/(double)k;
    q[0][1] = a - 1.0;
    q[1][1] = a * v[2] - v[0];
    q[2][1] = a * v[3] - v[1];
    a = (double)y/(double)k;
    q[0][2] = a - 1.0;
    q[1][2] = a * v[6] - v[0];
    q[2][2] = a * v[7] - v[1];
    /* co-matrix of q */
    for (j=0; j<=2; j++)    
      for (i=0; i<=2; i++) {
	k = (i+1)%3;
	l = (i+2)%3;
	x = (j+1)%3;
	y = (j+2)%3;	
        r[j][i] = q[k][x]*q[l][y] - q[k][y]*q[l][x];
      }
    /* 3 x 3 determinant */
    a = q[0][0]*r[0][0]  + q[0][1]*r[1][0]  + q[0][2]*r[2][0];
    /* inverse of second matrix */
    for (j=0; j<=2; j++)    
      for (i=0; i<=2; i++)
	r[i][j] = r[i][j] / a;
    /* product of matrices q = (p * r) */
    for (j=0; j<=2; j++)    
      for (i=0; i<=2; i++)
	q[i][j] = p[i][0]*r[0][j] + p[i][1]*r[1][j] + p[i][2]*r[2][j];

    j = atoi(ImgProcessInfo.proj3);
    
    /* compute resulting image */
    for (y = 0; y < output->height; y++)
      for (x = 0; x < output->width; x++) {
        for (i=0; i<=2; i++)
	  t[i] = q[i][0] + q[i][1] * (double)x + q[i][2] * (double)y;
        op = ImagePixel(output, x, y);
	if (j)
	  PixelInterpolate(op, input, t[1]/t[0], t[2]/t[0]);
        else {
	  i = 0;
          k = t[1]/t[0] + 0.5;
          l = t[2]/t[0] + 0.5;	
          if (k < 0) i = 1;
          if (k >= input->width) i = 1;
          if (l < 0) i = 1;
          if (l >= input->height) i = 1;
          if (i) {
	      i = -1;
	      memcpy(op, &i, 3);
	  } else {
              ip = ImagePixel(input, k, l);
	      memcpy(op, ip, 3);
	  }
	}
      }
    return output;
}

/*
 * Produce the 'solarization' effect seen when exposing a 
 * photographic film to light during the development process.
 * Done here by inverting all pixels above threshold level.
 */
Image *
ImageSolarize(Image * input)
{
    Image *output = ImageNewCmap(input->width, input->height, input->cmapSize);
    int i;
    unsigned char *ip, *op;
    int count, limit;

    limit = ImgProcessInfo.solarizeThreshold * 255 / 100;

    /*
    **	 If the input has a colormap, just tweak that.
     */
    if (input->cmapSize != 0) {
	ip = input->cmapData;
	op = output->cmapData;
	count = input->cmapSize;

	memcpy(output->data, input->data,
	     sizeof(char) * input->scale * input->width * input->height);
    } else {
	ip = input->data;
	op = output->data;
	count = input->width * input->height;
    }

    for (i = 0; i < count; i++) {
	*op++ = (*ip > limit) ? 255 - *ip++ : *ip++;
	*op++ = (*ip > limit) ? 255 - *ip++ : *ip++;
	*op++ = (*ip > limit) ? 255 - *ip++ : *ip++;
    }

    return output;
}

/*
 * Colourmap quantization. Hacked from ppmqvga.c, which is part of Netpbm and
 * was originally written by Lyle Rains (lrains@netcom.com) and Bill Davidsen
 * (davidsen@crd.ge.com).
 * You are probably not supposed to understand this.
 */

#define RED_BITS   5
#define GREEN_BITS 6
#define BLUE_BITS  5

#define MAX_RED	   (1 << RED_BITS)
#define MAX_GREEN  (1 << GREEN_BITS)
#define MAX_BLUE   (1 << BLUE_BITS)

#define MAXWEIGHT  128
#define STDWEIGHT_DIV  (2 << 8)
#define STDWEIGHT_MUL  (2 << 10)
#define GAIN	   4

static int r, g, b, clutx, rep_threshold, rep_weight, dr, dg, db;
static int *color_cube, ncolors;
static unsigned char *clut;

#define CUBEINDEX(r,g,b)  (r)*(MAX_GREEN*MAX_BLUE) + (g)*MAX_BLUE + (b)

static void 
diffuse(void)
{
    int _7_32nds, _3_32nds, _1_16th;

    if (clutx < ncolors) {
	if (color_cube[CUBEINDEX(r, g, b)] > rep_threshold) {
	    clut[clutx * 4 + 0] = ((2 * r + 1) * 256) / (2 * MAX_RED);
	    clut[clutx * 4 + 1] = ((2 * g + 1) * 256) / (2 * MAX_GREEN);
	    clut[clutx * 4 + 2] = ((2 * b + 1) * 256) / (2 * MAX_BLUE);
	    ++clutx;
	    color_cube[CUBEINDEX(r, g, b)] -= rep_weight;
	}
	_7_32nds = (7 * color_cube[CUBEINDEX(r, g, b)]) / 32;
	_3_32nds = (3 * color_cube[CUBEINDEX(r, g, b)]) / 32;
	_1_16th = color_cube[CUBEINDEX(r, g, b)] - 3 * (_7_32nds + _3_32nds);
	color_cube[CUBEINDEX(r, g, b)] = 0;
	/* spread error evenly in color space. */
	color_cube[CUBEINDEX(r, g, b + db)] += _7_32nds;
	color_cube[CUBEINDEX(r, g + dg, b)] += _7_32nds;
	color_cube[CUBEINDEX(r + dr, g, b)] += _7_32nds;
	color_cube[CUBEINDEX(r, g + dg, b + db)] += _3_32nds;
	color_cube[CUBEINDEX(r + dr, g, b + db)] += _3_32nds;
	color_cube[CUBEINDEX(r + dr, g + dg, b)] += _3_32nds;
	color_cube[CUBEINDEX(r + dr, g + dg, b + db)] += _1_16th;

	/*
	 * Conserve the error at edges if possible
	 * (which it is, except the last pixel)
	 */
	if (color_cube[CUBEINDEX(r, g, b)] != 0) {
	    if (dg != 0)
		color_cube[CUBEINDEX(r, g + dg, b)] +=
		    color_cube[CUBEINDEX(r, g, b)];
	    else if (dr != 0)
		color_cube[CUBEINDEX(r + dr, g, b)] +=
		    color_cube[CUBEINDEX(r, g, b)];
	    else if (db != 0)
		color_cube[CUBEINDEX(r, g, b) + db] +=
		    color_cube[CUBEINDEX(r, g, b)];
	    else
		fprintf(stderr, "%s\n", msgText[LOST_ERROR_TERM]);
	}
    }
    color_cube[CUBEINDEX(r, g, b)] = -1;
}

/*
** Find representative color nearest to requested color.  Check color cube
** for a cached color index.  If not cached, compute nearest and cache result.
 */
static int 
nearest_color(unsigned char *p)
{
    register unsigned char *test;
    register unsigned i;
    unsigned long min_dist_sqd, dist_sqd;
    int nearest = 0;
    int *cache;
    int r, g, b;

    r = *p++;
    g = *p++;
    b = *p;
    cache = &(color_cube[CUBEINDEX((r << RED_BITS) / 256,
				   (g << GREEN_BITS) / 256,
				   (b << BLUE_BITS) / 256)]);
    if (*cache >= 0)
	return *cache;
    min_dist_sqd = ~0;
    for (i = 0; i < ncolors; ++i) {
	test = &clut[i * 4];
	dist_sqd =
	    3 * (r - test[0]) * (r - test[0]) +
	    4 * (g - test[1]) * (g - test[1]) +
	    2 * (b - test[2]) * (b - test[2]);
	if (dist_sqd < min_dist_sqd) {
	    nearest = i;
	    min_dist_sqd = dist_sqd;
	}
    }
    return (*cache = nearest);
}

void 
GammaScale(unsigned char * scale, double f)
{
    int c;
    scale[0] = 0;
    scale[255] = 255;
    if (f > 12) {
        for (c = 1; c <= 254; c++) scale[c] = 0;
        return;
    }
    if (f < -12) {
        for (c = 1; c <= 254; c++) scale[c] = 255;
        return;
    }
    /* logarithmic gamma scale */
    f = pow(2.0, -f);
    for (c = 1; c <= 254; c++)
        scale[c] = (int)(255.5 * pow(c/255.0, f));
}

Image *
ImageGammaCorrection(Image * input)
{
    int w = input->width, h = input->height;
    Image *output;
    unsigned char scale[768];
    unsigned char *ip, *op;
    int c, nc, x, y, i, j, corr, l, m;
    double a;
    double *coef = (double *)ImgProcessInfo.rgb_mat;
    char *ptr, *ptrp;

    l = strlen(ImgProcessInfo.RGB_correction);
    m = 0;
    
 iter:
    corr = ImgProcessInfo.RGB_correction[m] - '0';

    /* find out whether output will be grey image */    
    nc = 0;
    if (corr >= 4 ||
            (corr == 0 && input->isGrey) ||
            (corr == 1 && input->isGrey) ||
            (corr == 2 && input->isGrey &&
            ImgProcessInfo.r_gamma == ImgProcessInfo.g_gamma &&
            ImgProcessInfo.g_gamma == ImgProcessInfo.b_gamma)) {
	if (nc == 0) nc = 1;
    } else
        nc = 3;

    if (nc == 1) 
        output = ImageNewGrey(w, h);
    else
        output = ImageNew(w, h);
    
    if (corr == 0) {
        /* threshold */
        a = 255.0 / (double)(ImgProcessInfo.RGB_max-ImgProcessInfo.RGB_min);
        for (y = 0; y < h; y++) {
            op = output->data + nc*w*y;
	    for (x = 0; x < w; x++) {
                ip = ImagePixel(input, x, y);
                for (i=0; i<nc; i++) {
		    c = (int) (((int)(*ip++)-ImgProcessInfo.RGB_min) * a + 0.5);
                    if (c < 0) c = 0;
                    if (c > 255) c = 255;
                    *op++ = c;
		}
	    }
	}
    }

    if (corr == 1 ||
        corr == 2) {
        /* gamma correction */
        GammaScale(scale, ImgProcessInfo.r_gamma);
        if (nc == 3) {
            if (corr == 1) {
	        memcpy(scale+256, scale, 256);
	        memcpy(scale+512, scale, 256);
	    } else {
                GammaScale(scale+256, ImgProcessInfo.g_gamma);
                GammaScale(scale+512, ImgProcessInfo.b_gamma);
	    }
        }
        for (y = 0; y < h; y++) {
            op = output->data + nc*w*y;
            if (nc == 1)
	        for (x = 0; x < w; x++) {
                    ip = ImagePixel(input, x, y);
	            *op++ = scale[*ip++];
	        }
            else
  	        for (x = 0; x < w; x++) {
                    ip = ImagePixel(input, x, y);
 	            for (c=0; c<nc; c++) 
	                *op++ = scale[(c<<8) + *ip++];
	        }
        }
    }

    if (corr == 3) {
        /* linear correction */
        for (y = 0; y < h; y++) {
	    /* This is a color image then */
            op = output->data + 3*w*y;
	    for (x = 0; x < w; x++) {
                ip = ImagePixel(input, x, y);
                for (i=0; i<=2; i++) {
		    j = i<<2;
		    a = (ip[0]*coef[j]+ip[1]*coef[j+1]+ip[2]*coef[j+2])/100.0
                        + coef[j+3] + 0.5; 
                    c = (int) a;
                    if (c < 0) c = 0;
                    if (c > 255) c = 255;
                    *op++ = c;
		}
	    }
	}
    }

    if (corr >= 4 &&
        corr <= 6) {
        /* R,G,B transform */
        c = corr - 4;
        for (y = 0; y < h; y++) {
            op = output->data + w*y;
	    for (x = 0; x < w; x++) {
                ip = ImagePixel(input, x, y);
	        *op++ = ip[c];
	    }
	}
        output->isGrey = False;
        memset(output->cmapData, 0, 3*256);
        for (x = 0; x < 256; x++)
	    output->cmapData[3*x+c] = x;
    }

    if (corr == 7) {
        /* maximum intensity */
        for (y = 0; y < h; y++) {
            op = output->data + w*y;
	    for (x = 0; x < w; x++) {
                ip = ImagePixel(input, x, y);
	        *op++ = MAX(MAX(ip[0], ip[1]), ip[2]);
	    }
	}
    }

    if (corr == 8) {
        /* minimum intensity */
        for (y = 0; y < h; y++) {
            op = output->data + w*y;
	    for (x = 0; x < w; x++) {
                ip = ImagePixel(input, x, y);
	        *op++ = MIN(MIN(ip[0], ip[1]), ip[2]);
	    }
	}
    }

    if (corr == 9) {
        /* find mask with respect to background */
        for (y = 0; y < h; y++) {
            op = output->data + w*y;
	    for (x = 0; x < w; x++) {
                ip = ImagePixel(input, x, y);
                if (memcmp(ip, Global.bg, 3))
                    *op++ = 0xff; 
                else 
                    *op++ = 0;
	    }
	}
    }

    if (m == l-1) return output;

    if (m > 0) ImageDelete(input);
    input = output;
    m++;
    goto iter;
}

Image *
ImageFloydSteinberg(Image * input)
{
    int w = input->width, h = input->height;
    Image *output;
    int d, wp, c, nc, nd, p, x, y, error = 0;
    unsigned char *op;
    unsigned char subst[256];
    unsigned char scale[256];

    if (input->isGrey) {
        nc = 1;
        output = ImageNewGrey(w, h);
    } else {
        nc = 3;
        output = ImageNew(w, h);
    }
    wp = nc * w;

    ImageCopyData(input, output);

    /* calculate substitution values */
    x = ImgProcessInfo.FSsteps - 1;
    p = ImgProcessInfo.FSmidrange - 1;
    GammaScale(scale, ImgProcessInfo.FSgamma);

    for (c = 0; c <= 255; c++) {
        y = (c * x + p) / 255;
        subst[c] = (unsigned char)((255 * y  + x/2) / x);
    }

    for (c = 0; c < nc; c++)
    for (y = 0; y < h; y++) {
        d = 1 - ((y&1)<<1);
        nd = d*nc;
        op = output->data + wp*y + c;
        if (d == -1)
            op += wp - nc;
        for (x = 0; x < w; x++) {
	    p = (int)(scale[*op]);
            *op = subst[p];
            error = p - (int)(*op);
            if (!error) goto fast_done;
            
	    /*  error diffusion */
            if (x < w-1) {
	        /* Right pixel */
	        p = (int) op[nd] + (int) (error * 7/16);
	        if (p < 0) p = 0;
	        if (p > 255) p = 255;
                op[nd] = p;
	    }
            if (y < h-1) {
	        if (x > 0) {
		    /* Bottom left pixel */
		    p = (int) op[wp-nd] + (int) (error * 3/16);
	            if (p < 0) p = 0;
	            if (p > 255) p = 255;
		    op[wp-nd] = p;
		}
	        /* Bottom pixel */
	        p = (int) op[wp] + (int) (error * 5/16);
	        if (p < 0) p = 0;
	        if (p > 255) p = 255;
	        op[wp] = p;
	        if (x < w-1) {
	            /* Bottom right pixel */
	            p = (int) op[wp+nd] + (int) (error / 16);
	            if (p < 0) p = 0;
	            if (p > 255) p = 255;
	            op[wp+nd] = p;
		}
	    }
	  fast_done:
            op += nd;
	}
    }

    return output;
}

Image *
ImageQuantize(Image * input)
{
    int w = input->width, h = input->height;
    Image *output = ImageNew(w, h);
    unsigned char *op = output->data;
    int *weight_convert;
    int k, x, y, i, j, nearest;
    int total_weight, cum_weight[MAX_GREEN];
    int *erropt;
    int *errP;
    unsigned char *p, *clutP;


    ncolors = ImgProcessInfo.quantizeColors;

    /* Make a color cube, initialized to zeros */
    color_cube = calloc(MAX_RED * MAX_GREEN * MAX_BLUE, sizeof(int));
    clut = calloc(ncolors * 4, sizeof(int));
    erropt = calloc(ncolors * 4, sizeof(int));

    clutx = 0;

    /* Count all occurrences of each color */
    for (y = 0; y < h; ++y)
	for (x = 0; x < w; ++x) {
	    p = ImagePixel(input, x, y);
	    r = p[0] / (256 / MAX_RED);
	    g = p[1] / (256 / MAX_GREEN);
	    b = p[2] / (256 / MAX_BLUE);
	    ++color_cube[CUBEINDEX(r, g, b)];
	}

    /* Initialize logarithmic weighting table */
    weight_convert = xmalloc(MAXWEIGHT * sizeof(int));
    weight_convert[0] = 0;
    for (i = 1; i < MAXWEIGHT; ++i) {
	weight_convert[i] = (int) (100.0 * log((double) i));
    }

    k = w * h;
    if ((k /= STDWEIGHT_DIV) == 0)
	k = 1;
    total_weight = i = 0;
    for (g = 0; g < MAX_GREEN; ++g) {
	for (r = 0; r < MAX_RED; ++r) {
	    for (b = 0; b < MAX_BLUE; ++b) {
		register int weight;
		/* Normalize the weights, independent of picture size. */
		weight = color_cube[CUBEINDEX(r, g, b)] * STDWEIGHT_MUL;
		weight /= k;
		if (weight)
		    ++i;
		if (weight >= MAXWEIGHT)
		    weight = MAXWEIGHT - 1;
		total_weight += (color_cube[CUBEINDEX(r, g, b)]
				 = weight_convert[weight]);
	    }
	}
	cum_weight[g] = total_weight;
    }
    rep_weight = total_weight / ncolors;

    /* Magic foo-foo dust here.	 What IS the correct way to select threshold? */
    rep_threshold = total_weight * (28 + 110000 / i) / 95000;

    /*
     * Do a 3-D error diffusion dither on the data in the color cube
     * to select the representative colors.  Do the dither back and forth in
     * such a manner that all the error is conserved (none lost at the edges).
     */

    dg = 1;
    for (g = 0; g < MAX_GREEN; ++g) {
	dr = 1;
	for (r = 0; r < MAX_RED; ++r) {
	    db = 1;
	    for (b = 0; b < MAX_BLUE - 1; ++b)
		diffuse();
	    db = 0;
	    diffuse();
	    ++b;
	    if (++r == MAX_RED - 1)
		dr = 0;
	    db = -1;
	    while (--b > 0)
		diffuse();
	    db = 0;
	    diffuse();
	}
	/* Modify threshold to keep rep points proportionally distributed */
	if ((j = clutx - (ncolors * cum_weight[g]) / total_weight) != 0)
	    rep_threshold += j * GAIN;

	if (++g == MAX_GREEN - 1)
	    dg = 0;
	dr = -1;
	while (r-- > 0) {
	    db = 1;
	    for (b = 0; b < MAX_BLUE - 1; ++b)
		diffuse();
	    db = 0;
	    diffuse();
	    ++b;
	    if (--r == 0)
		dr = 0;
	    db = -1;
	    while (--b > 0)
		diffuse();
	    db = 0;
	    diffuse();
	}
	/* Modify threshold to keep rep points proportionally distributed */
	if ((j = clutx - (ncolors * cum_weight[g]) / total_weight) != 0)
	    rep_threshold += j * GAIN;
    }

    /*
     * Check the error associated with the use of each color, and
     * change the value of the color to minimize the error.
     */
    for (y = 0; y < h; ++y) {
	for (x = 0; x < w; ++x) {
	    p = ImagePixel(input, x, y);
	    nearest = nearest_color(p);
	    errP = &erropt[nearest * 4];
	    clutP = &clut[nearest * 4];
	    errP[0] += *p++ - clutP[0];
	    errP[1] += *p++ - clutP[1];
	    errP[2] += *p - clutP[2];
	    ++errP[3];
	}
    }

    for (i = 0; i < ncolors; ++i) {
	clutP = &clut[i * 4];
	errP = &erropt[i * 4];
	j = errP[3];
	if (j > 0)
	    j *= 4;
	else if (j == 0)
	    j = 1;
	clutP[0] += (errP[0] / j) * 4;
	clutP[1] += (errP[1] / j) * 4;
	clutP[2] += (errP[2] / j) * 4;
    }

    /* Reset the color cache. */
    for (i = 0; i < MAX_RED * MAX_GREEN * MAX_BLUE; ++i)
	color_cube[i] = -1;

    /*
     * Map the colors in the image to their closest match in the new colormap.
     */
    for (y = 0; y < h; ++y) {
	for (x = 0; x < w; ++x) {
	    p = ImagePixel(input, x, y);
	    nearest = nearest_color(p);
	    clutP = &clut[nearest * 4];
	    *op++ = *clutP++;
	    *op++ = *clutP++;
	    *op++ = *clutP;
	}
    }

    free(clut);
    free(erropt);
    free(weight_convert);

    return output;
}

/*
 * Convert an image to grey scale.
 */

Image *
ImageGrey(Image * input)
{
    Image *output = ImageNewGrey(input->width, input->height);
    unsigned char *p, *op = output->data;
    int x, y, v;

    for (y = 0; y < input->height; y++) {
	for (x = 0; x < input->width; x++) {
	    p = ImagePixel(input, x, y);
	    v = GreyScale(p[0], p[1], p[2]);
	    *op++ = v;
	}
    }
    return output;
}

Image *
ImageBWMask(Image * input)
{
    Image *output = ImageNewGrey(input->width, input->height);
    unsigned char *p, *op = output->data;
    int x, y, v;

    for (y = 0; y < input->height; y++) {
	for (x = 0; x < input->width; x++) {
	    p = ImagePixel(input, x, y);
            if (memcmp(p, Global.bg, 3)) v = 0xff; else v = 0;
	    *op++ = v;
	}
    }
    return output;
}

/*
 *  Text input procedures
 */

static void 
insertTextCallback(Widget w, XtPointer argArg, XtPointer ptr)
{
    ScriptInfo *arg = (ScriptInfo *) argArg;
    char buf[256];
    int j;
    XawTextPosition p;
    XawTextBlock b;

    for (j=TEXT_FONT; j<=TEXT_NOOP; j++)
        if (textMenu[j].widget == w) break;
    
    switch(j) {
        case TEXT_FONT:
	  sprintf(buf, "\\*font:Liberation-18*\\");
             break;
        case TEXT_COLOR:
             strcpy(buf, "\\*color:#2367ab*\\");
             break;
        case TEXT_ROTATION:
	     sprintf(buf, "\\*rotation:30*\\");
             break;
        case TEXT_PLUS_ROTATION:
             sprintf(buf, "\\*+rotation:20*\\");
             break;
        case TEXT_INCLINATION:
             sprintf(buf, "\\*inclination:0.15*\\");
             break;
        case TEXT_DILATION:
             sprintf(buf, "\\*dilation:1.4*\\");
             break;
        case TEXT_LINESPACING:
             sprintf(buf, "\\*linespacing:0.7*\\");
             break;
        case TEXT_X:
             strcpy(buf, "\\*x:50*\\");
             break;
        case TEXT_Y:
             strcpy(buf, "\\*y:80*\\");
             break;
        case TEXT_PLUS_X:
             strcpy(buf, "\\*+x:50*\\");
             break;
        case TEXT_PLUS_Y:
             strcpy(buf, "\\*+y:80*\\");
             break;
        case TEXT_SX:
             strcpy(buf, "\\*sx:50*\\");
             break;
        case TEXT_SY:
             strcpy(buf, "\\*sy:80*\\");
             break;
        case TEXT_PLUS_SX:
             strcpy(buf, "\\*+sx:50*\\");
             break;
        case TEXT_PLUS_SY:
             strcpy(buf, "\\*+sy:80*\\");
             break;
        default:
	     return;
    }
    p = XawTextGetInsertionPoint(arg->program);
    b.firstPos = 0;
    b.length = strlen(buf);
    b.ptr = buf;
    b.format = 0;
    XawTextReplace(arg->program, p, p, &b);
}

/*
 *  TeX input procedures
 */

static void 
chooseTeXParametersOkCallback(Widget w, XtPointer wlArg, XtPointer infoArg)
{
    TextPromptInfo *info = (TextPromptInfo *) infoArg;
    ScriptInfo *l = (ScriptInfo *)wlArg;
    int dpi, opacity;
    
    dpi = (int) (16 * atof(info->prompts[0].rstr));
    if (dpi < 30 || dpi > 3840) {
      Notice(Global.toplevel, msgText[TEX_INCORRECT_SIZE]);
      return;
    }
    opacity = atoi(info->prompts[1].rstr);
    if (opacity < 0 || opacity > 255) {
      Notice(Global.toplevel, msgText[TEX_INCORRECT_VALUE]);
      return;
    }
    l->dpi = dpi;
    l->opacity = opacity;    
}

void
chooseTeXParametersCallback(Widget w, XtPointer infoArg, XtPointer junk)
{
    ScriptInfo *l = (ScriptInfo *) infoArg;
    static TextPromptInfo info;
    static struct textPromptInfo value[2];
    static char sizeStr[16];
    static char opacityStr[16];

    sprintf(sizeStr, "%g", l->dpi / 16.0);
    sprintf(opacityStr, "%d", l->opacity);
	
    info.title = msgText[TEX_SIZE_OF_TEXT_AND_OPACITY];
    value[0].prompt = msgText[TEX_SIZE];
    value[0].str = "12";
    value[0].len = 8;
    value[1].prompt = msgText[TEX_OPACITY];
    value[1].str = "255";
    value[1].len = 8;    
    info.prompts = value;
    info.nprompt = 2;

    TextPrompt(Global.toplevel, info.title, &info,
	       chooseTeXParametersOkCallback, NULL, (XtPointer) l);
}

/*
 * User defined script procedures
 */

static void
closeCallback(Widget w, XtPointer wlArg, XtPointer junk)
{
   ScriptInfo *info = (ScriptInfo *) wlArg;
   PopdownMenusGlobal();
   XtDestroyWidget(info->shell);
   if (info == Global.ocrinfo) Global.ocrinfo = NULL;
   XtFree((XtPointer)info);
}

static void
collect_output(Widget wid, FILE *out, char *str)
{
   char *ptr, *text;
   char c;
   int l;
   if (!out) return;
   XFlush(XtDisplay(wid));
   XtVaGetValues(wid, XtNstring, &ptr, NULL);
   if (!ptr) ptr = "";
   text = (char *)malloc(strlen(ptr) + strlen(str) + 3);
   if (*ptr)
      sprintf(text, "%s\n%s", ptr, str);
   else
      strcpy(text, str);
   l = strlen(text);
   while(!feof(out)) {
      c = fgetc(out);
      text = (char *)realloc(text, l+3);
      text[l++] = c;
   }
   pclose(out);
   text[l] = '\0';
   XtVaSetValues(wid, XtNstring, text, NULL);
   free(text);
}

static void
TeXCompileCallback(Widget w, XtPointer wlArg, XtPointer junk)
{
   struct stat buf;
   ScriptInfo *info = (ScriptInfo *) wlArg;
   FILE *fd, *out;
   char *text;
   char tmpdir[256], texfile[256], cmd[512];
   int n;

   XtVaGetValues(info->program, XtNstring, &text, NULL);
   if (!text || !*text) {
      Notice(Global.toplevel, msgText[TEX_INPUT_DOES_NOT_CONTAIN_ANY_DATA]);
      return;
   }

   /* create temporary name */
   sprintf(tmpdir, "%s%s",
	  (getenv("HOME")?getenv("HOME"):""),
	  (getenv("HOME")?"/.xpaint/tmp":"/tmp"));

   /* copy buffer to temporary TeX file */
   n = 0;
 retry:
   sprintf(texfile, "%s/text_input_%04d.tex", tmpdir, n);
   if (stat(texfile, &buf) != -1) { n++; goto retry; }
   if (junk) *((int *)junk) = n; 

   sprintf(texfile, "%s/text_input_%04d", tmpdir, n);   
   sprintf(cmd, "%s.tex", texfile);
   fd = fopen(cmd, "w");
   if (!fd) return;

   fprintf(fd, "\\input /usr/share/dvipgm/formulas.tex\n");
   fprintf(fd, "\\long\\def\\textpar#1#2{$\\smash{\\rlap{\\raise-793.1pt\\hbox{%%\n");
   fprintf(fd, "\\vbox to 800pt{\\hsize=#1\\noindent #2\\vfill}}}}$}\n");
   fprintf(fd, "%s\n", text);
   fprintf(fd, "\\end\n");
   fclose(fd);

   sprintf(cmd, "tex -interaction=nonstopmode "
	   "-output-directory=\"%s\" \"%s.tex\"", tmpdir, texfile);
   fprintf(stderr, "%s\n", cmd);
   out = popen(cmd, "r");
   collect_output(info->cclog, out, cmd);
}

static void
TeXBuildCallback(Widget w, XtPointer wlArg, XtPointer junk)
{
    ScriptInfo *info = (ScriptInfo *)wlArg;
    Display *dpy;
    Pixmap pix, mask;
    Colormap cmap;
    XColor xc;
    GC gc;
    XRectangle rect;
    Pixel black, white, fg;
    unsigned char *formula = NULL;
    int dpi, opacity, u, v, x, y, ascent;
    unsigned int color;
    Image *output;
    unsigned char *maskdata;
    Widget paint;
    PaintWidget pw;
    char dvifile[256];
    char *text, *ptr;

    paint = info->paint;    
    if (!CheckPaintWidget(paint)) return;
	
    dpi = info->dpi;
    opacity = info->opacity;
    
    TeXCompileCallback(w, wlArg, &u);
    sprintf(dvifile, "%s%s/text_input_%04d.dvi",
	    (getenv("HOME")?getenv("HOME"):""),
	    (getenv("HOME")?"/.xpaint/tmp":"/tmp"), u);
    fprintf(stderr, msgText[DVI_OPENING], dvifile);
    fprintf(stderr, "\n");
    
    dpy = XtDisplay(paint);
    cmap = DefaultColormapOfScreen(XtScreen(paint));
    XtVaGetValues(paint, XtNforeground, &fg, NULL);
    
    xc.pixel = fg;
    XQueryColor(dpy, cmap, &xc);
    PwRegionMergeColorSum(765 - (xc.red>>8) - (xc.green>>8) - (xc.blue>>8));
    PwRegionMergeOpacity(opacity);
    color = (opacity<<24)|((xc.red>>8)<<16)|((xc.green>>8)<<8)|(xc.blue>>8);

    output = NULL;
    DVIToImage(dvifile, dpi, color, &output, &maskdata, &ascent);    
    if (output) {
      rect.x = 0;
      rect.y = 0;
      rect.width = u = output->width;
      rect.height = v = output->height;
      mask = XCreatePixmap(dpy, XtWindow(paint),
		           output->width, output->height, 1);
      gc = XCreateGC(dpy, mask, 0, 0);
      white = WhitePixelOfScreen(XtScreen(paint));
      black = BlackPixelOfScreen(XtScreen(paint));      
      for (y=0; y<v; y++) for (x=0; x<u; x++) {
	  XSetForeground(dpy, gc, ((maskdata[x+u*y])? white:black));
	  XDrawPoint(dpy, mask, gc, x, y);
      }
      pw = (PaintWidget)paint;
      if (getIndexOp() == 10) {
         WriteTextGetPosition(paint, &x, &y);
         y = (y > ascent)? y - ascent : 0;
      } else
      if (pw->paint.region.source) {
         x = pw->paint.region.rect.x;
         y = pw->paint.region.rect.y;	  
      } else {
  	 x = y = 0;
      }
      PwRegionClear(paint);      
      ImageToPixmap(output, paint, &pix, &cmap);
      PwRegionSet(paint, &rect, pix, mask);
      pw->paint.region.rect.x = 0;
      pw->paint.region.rect.y = 0;      
      RegionMove(pw, x, y);
      
      if (DoVxp(paint)) {
        sprintf(Global.vxpinput, "\n*texinput %d %d %d %06x\n",
  	      dpi, ascent, opacity, color & 0xffffff);
        RecordVxp(paint);
        text = NULL;
        XtVaGetValues(info->program, XtNstring, &text, NULL);
        if (text) {
  	  ptr = text;
  	  Global.vxpinput[0] = '=';	   
  	  Global.vxpinput[1] = '\0';
  	  RecordVxp(paint);	   
  	  while (*ptr) {
  	    Global.vxpinput[0] = *ptr;
  	    RecordVxp(paint);
  	    if (*ptr == '\n') {
  	      Global.vxpinput[0] = '=';
  	      RecordVxp(paint);
  	    }
  	    ++ptr;
  	  }
        }
      }
  }
}
static void
compileCallback(Widget w, XtPointer wlArg, XtPointer junk)
{
   ScriptInfo *info = (ScriptInfo *) wlArg;
   FILE *fd;
   FILE *out;
   struct stat statbuf;
   void (* proc)(Widget wid);
   Image * (* iproc)();
   Image * image;
   Screen *screen;
   Visual *visual;
   Pixmap pix;
   Colormap cmap;
   Widget paint;
   char *tmp, *ptr, *text;
   char cmd[512];
   char header[30];
   char *error;
   char *so_file;
   void *dl_handle;   
   unsigned char *alpha = NULL;
   int i;

   /* create temporary name */
   fd = openTempFile(&tmp);
   fclose(fd);

   /* copy buffer to temporary C file */
   XtVaGetValues(info->program, XtNstring, &text, NULL);
   if (!text || !*text) {
      Notice(Global.toplevel, msgText[C_SCRIPT_DOES_NOT_CONTAIN_ANY_DATA]);
      return;
   }
   sprintf(cmd, "%s.c", tmp);
   fd = fopen(cmd, "w");
   if (!fd) return;

   ptr = text;
   i = 0;
   while (*ptr) {
      fputc(*ptr, fd);
      if (i <= 25) {
         header[i] = *ptr;
         if (*ptr == '\n') header[i] = '\0';
	 i++;
      }
      ++ptr;
   }
   fclose(fd);
   header[i] = '\0';

   info->mode = -1;
   
   if (strncasecmp(header, "/* Xpaint-image */", 18) == 0)
      info->mode = SCRIPT_IMAGES;
   else
   if (strncasecmp(header, "/* Xpaint-filter */", 19) == 0) {
      info->mode = SCRIPT_FILTERS;
      if (filter_so_file) {
	unlink(filter_so_file);
        free(filter_so_file);
      }
      filter_so_file = NULL;
   }
   else
   if (strncasecmp(header, "/* Xpaint-procedure */", 22) == 0)
      info->mode = SCRIPT_PROCEDURES;

   /* compile C script */
   sprintf(cmd,
	   "gcc -fPIC -I%s/include -I/usr/include/X11 -c %s.c -o %s.o 2>&1\n"
	   "gcc -fPIC -shared -Wl,-soname,%s.so %s.o -o %s.so 2>&1",
	   GetShareDir(), tmp, tmp, strstr(tmp, "XPaint"), tmp, tmp);
   out = popen(cmd, "r");
   collect_output(info->cclog, out, cmd);
   
   /* clean *.c and *.o file */
   sprintf(cmd, "%s.c", tmp);
   unlink(cmd);
   sprintf(cmd, "%s.o", tmp);
   unlink(cmd);

   /* load dll library */
   sprintf(cmd, "%s.so", tmp);

   if (info->mode >= 0 && stat(cmd, &statbuf) == 0)
      dl_handle = dlopen(cmd, RTLD_LAZY);
   else
      dl_handle = NULL;

   /* clean tmp file */
   removeTempFile();

   if (dl_handle) {
      so_file = strdup(cmd);
      strcpy(cmd, msgText[C_SCRIPT_SUCCESSFULLY_COMPILED_AND_LINKED]);
   } else {
      ptr = dlerror();
      sprintf(cmd, "%s %s",
              msgText[C_SCRIPT_COULD_NOT_BE_COMPILED_OR_LINKED],
	      (ptr? ptr : "check gcc output"));
   }

   XFlush(XtDisplay(info->cclog));
   XtVaGetValues(info->cclog, XtNstring, &ptr, NULL);
   text = (char *)malloc(strlen(cmd)+strlen(ptr)+4);
   sprintf(text, "%s\n%s", ptr, cmd);
   XtVaSetValues(info->cclog, XtNstring, text, NULL);
   free(text);
   
   if (!dl_handle) return;

   if (info->mode == SCRIPT_FILTERS) {
     filter_so_file = so_file;
     dlclose(dl_handle);
     return;
   }

   if (info->mode == SCRIPT_IMAGES) {
      iproc = dlsym(dl_handle, "ImageCreate");
      if ((error = dlerror()) != NULL || !iproc) {
	 if (!error) error = msgText[UNABLE_TO_CREATE_IMAGE];
         Notice(Global.toplevel, error);
	 dlclose(dl_handle);
	 unlink(so_file);
	 free(so_file);
         return;
      }
      pix = None;
      image = iproc();
      if (image) {
	 screen = XtScreen(Global.toplevel);
	 visual = DefaultVisualOfScreen(screen);
	 cmap = XCreateColormap(XtDisplay(Global.toplevel), 
		    RootWindowOfScreen(screen), visual, AllocNone);
         alpha = image->alpha;
         image->alpha =  NULL;
	 ImageToPixmap(image, Global.toplevel, &pix, &cmap);
      }
      if (!pix) {
         Notice(Global.toplevel, msgText[UNABLE_TO_CREATE_IMAGE]);
	 goto closehandle;
      }
      if (alpha) {
         file_isSpecialImage = 1;
         file_transparent = 1;
      }
      paint = graphicCreate(makeGraphicShell(Global.toplevel), 0, 0, -1, 
                            pix, cmap, alpha);
      SetAlphaMode((Widget)paint, -1);
      goto closehandle;
   }

   if (info->mode == SCRIPT_PROCEDURES) {
      proc = dlsym(dl_handle, "PaintProcedure");
      if ((error = dlerror()) != NULL) {
         Notice(Global.toplevel, error);
         goto closehandle;
      }
      if (proc) proc(Global.toplevel);
   }
   
 closehandle:
   dlclose(dl_handle);
   unlink(so_file);
   free(so_file);
   so_file = NULL;
}

static void
externCallback(Widget w, XtPointer wlArg, XtPointer junk)
{
   FILE *fd;
   ScriptInfo *info = (ScriptInfo *) wlArg;
   int l;
   char *tmp, *ptr, *text;
   char name[256];
   char buf[2048];

   /* write buffer to file */
   PopdownMenusGlobal();
   fd = openTempFile(&tmp);
   fclose(fd);
   sprintf(name, "%s.c", tmp);
   removeTempFile();
   fd = fopen(name, "w");
   if (!fd) {
      Notice(w, msgText[UNABLE_TO_OPEN_FILE], name);
      return;
   }
   XtVaGetValues(info->program, XtNstring, &text, NULL);
   if (text && *text) {
      ptr = text;
      while (*ptr) {
         fputc(*ptr, fd);
         ++ptr;
      }
   }
   fclose(fd);

   /* call external editor */
   sprintf(buf, "%s %s", EDITOR, name);
   if(system(buf));

   /* Now copy edited file into buffer */
   fd = fopen(name, "r");
   if (!fd) {
      Notice(w, msgText[UNABLE_TO_OPEN_FILE], name);
      return;
   }

   text = xmalloc(2);
   *text = '\0';
   l = 0;
   while (fgets(buf, 2040, fd)) {
      l += strlen(buf);
      text = realloc(text, l+2);
      strcat(text, buf);
   }
   fclose(fd);
   unlink(name);
   XtVaSetValues(info->program, XtNstring, text, NULL);
}

static void 
loadFileOkCallback(Widget w, XtPointer wlArg, char *file)
{
   FILE *fd;
   ScriptInfo *info = (ScriptInfo *) wlArg;
   static char *text;
   char buf[2048];
   int l;

   if (!file || !*file) return;
   fd = fopen(file, "r");
   if (!fd) {
      Notice(w, msgText[UNABLE_TO_OPEN_FILE], file);
      return;
   }
   free(info->scriptfile);
   info->scriptfile = strdup(file);
   SetScriptDir(info->scriptfile);   
   XtVaSetValues(info->name, XtNlabel, file, NULL);
   text = xmalloc(2);
   *text = '\0';
   l = 0;
   while (fgets(buf, 2040, fd)) {
      l += strlen(buf);
      text = realloc(text, l+2);
      strcat(text, buf);
   }
   fclose(fd);
   XtVaSetValues(info->program, XtNstring, text, NULL);
}

static void
loadFileCallback(Widget w, XtPointer wlArg, XtPointer junk)
{
   char buf[256];
   *buf = '\0';
   if (getcwd(buf, 256)) strcat(buf, "/");
   Global.explore = True;
   GetFileName(GetShell(w), BROWSER_SIMPLEREAD, 
               buf, (XtCallbackProc) loadFileOkCallback, wlArg);
   Global.explore = False;
}

static void
saveProceedCallback(Widget w, XtPointer wlArg, XtPointer junk)
{
   FILE *fd;
   void **str = wlArg;
   ScriptInfo *info;
   Image *image = NULL;
   char *file, *ptr, *text, *script, *out;

   file = (char *)str[0];
   info = (ScriptInfo *)str[1];
   free(info->scriptfile);   
   info->scriptfile = strdup(file);
   SetScriptDir(info->scriptfile);
   
   if (info->mode >= 128) {
      out = strdup(file);
      ptr = out + strlen(out) - 1;
      while (ptr > out && *ptr != '.' && *ptr != '/') --ptr;
      if (*ptr == '.') *ptr = '\0';
      script = malloc(strlen(out) + 6);
      strcpy(script, out);
      strcat(script, ".vxp");
   } else
      script = file;
   
   fd = fopen(script, "w");
   if (!fd) {
      Notice(w, msgText[UNABLE_TO_OPEN_FILE], script);
      return;
   }
   
   XtVaSetValues(info->name, XtNlabel, script, NULL);
   XtVaGetValues(info->program, XtNstring, &text, NULL);
   if (!text || !*text) {
      fclose(fd);
      return;
   }
   ptr = text;
   while (*ptr) {
      fputc(*ptr, fd);
      ++ptr;
   }
   if (info->mode >= 128) {
     /* This is a VXP file to be converted, not a C script */
      fprintf(fd, "\n*end\n");
      fclose(fd);      
      text = malloc(strlen(file) + 128);
      if (info->mode == 129) {
        sprintf(text, "vxp2ps %s", script);
	if (system(text));
        sprintf(text, "%s %s.ps &", psviewer, out);
	if (system(text));	
      } else
      if (info->mode == 130) {
        sprintf(text, "vxp2tex %s", script);
	if (system(text));
        sprintf(text, "%s %s+formulas.tex &", EDITOR, out);
	if (system(text));
      } else
      if (info->mode == 131) {
        sprintf(text, "vxp2dkw %s", script);
	if (system(text));
        sprintf(text, "deskwrite %s.dkw &", out);
	if (system(text));
      } else
      if (info->mode == 132) {
	image = ReadPS_(script);
	if (image) GraphicOpenFile(Global.toplevel, script, image);
      }
      free(out);
      free(script);
      free(text);
      info->mode = 0;
   } else
      fclose(fd);
}

static void
noOverwriteCallback(Widget w, XtPointer data, XtPointer junk)
{
}

static void 
saveFileOkCallback(Widget w, XtPointer wlArg, char *file)
{
   char msg[512];
   static void *str[2];
   struct stat buf;

   if (!file || !*file) return;

   str[0] = file;
   str[1] = (void *)wlArg;
   
   if (!stat(file, &buf)) {
        sprintf(msg, msgText[OVERWRITE_FILE], file);
        AlertBox(GetShell(w), msg, saveProceedCallback, 
                 noOverwriteCallback, (XtPointer)str);
	return;
   }

   saveProceedCallback(w, str, NULL);
}

static void
saveFileCallback(Widget w, XtPointer wlArg, XtPointer junk)
{
   ScriptInfo *info = (ScriptInfo *) wlArg;
   char buf[512];
   *buf = '\0';
   if (getcwd(buf, 256)) strcat(buf, "/");
   Global.explore = True;
   if (!info->scriptfile || !info->scriptfile[0]) {
      GetFileName(GetShell(w), BROWSER_SIMPLESAVE, 
                  buf, (XtCallbackProc) saveFileOkCallback, wlArg);
   } else
      saveFileOkCallback(w, wlArg, info->scriptfile);
   Global.explore = False;
}

static void
saveasFileCallback(Widget w, XtPointer wlArg, XtPointer junk)
{
   ScriptInfo *info = (ScriptInfo *) wlArg;  
   char buf[512];
   *buf = '\0';
   if (junk)
       info->mode = 128 + (int)(long int)junk;
   if (info->scriptfile && info->scriptfile[0])
       strcpy(buf, info->scriptfile);
   else
   if (getcwd(buf, 256)) strcat(buf, "/");
   Global.explore = True;
   GetFileName(GetShell(w), BROWSER_SIMPLESAVE, 
               buf, (XtCallbackProc) saveFileOkCallback, wlArg);
   Global.explore = False;
}

static void
loadScriptCallback(Widget w, XtPointer wlArg, XtPointer junk)
{
   ScriptInfo *info = (ScriptInfo *) wlArg;
   static char directory[256];
   int i;

   info->mode = SCRIPT_IMAGES;
   for (i=0; i<SCRIPT_SEPARATOR; i++)
      if (w == scriptMenu[i].widget) {
	 info->mode = i;
	 break;
      }

   Global.explore = True;
   sprintf(directory, "%s/c_scripts/%s/", GetShareDir(),
	              scriptMenu[info->mode].name);

   GetFileName(GetShell(w), BROWSER_SIMPLEREAD, directory, 
      (XtCallbackProc) loadFileOkCallback, wlArg);
   Global.explore = False;
}

static void 
buildLXPOkCallback(Widget w, XtPointer wlArg, XtPointer junk)
{
    ScriptInfo *info = (ScriptInfo *) wlArg;
    struct stat buf;
    FILE *fo;
    char cmd[8192];
    char tmpdir[256];
    char *home, *file, *text;
    char *ptr = NULL, *ptre = NULL, *ptrp, *ptrq, **list = NULL;
    int m = 0, n = 0, k;

    home = getenv("HOME");
    if (!home) return;

    file = strdup((char *)junk);
    k = strlen(file) - 4;
    if (k >= 0 && !strcmp(file + k, ".lxp")) file[k] = '\0';
    if (file[0] == '\0') return;
    
    XtVaGetValues(info->program, XtNstring, &text, NULL);
    
    /* printf("%s %s\n", info->scriptfile, file); */
    ptr = strstr(text, "LXP_files[");
    if (ptr) ptre = strstr(ptr, "NULL");
 cycle:
    if (ptr && ptre) ptrp = strchr(ptr+1, '\"');
    if (ptrp) ptrq = strchr(ptrp+1, '\"'); else ptrq = NULL;
    if (ptrq) {
        list = (char**) realloc(list, (m+1)*sizeof(char *));
        k = ptrq - ptrp - 1;
        list[m] = (char *) malloc((k+1)*sizeof(char));
	memcpy(list[m], ptrp+1, k);
	list[m][k] = 0;
	printf("%s\n", list[m]);
        ptr = ptrq + 1;
	m++;
        if (ptr && (strchr(ptr, ',')  < ptre)) goto cycle;	
    }

 retry:
    sprintf(tmpdir, "%s/.xpaint/tmp/%s_dir_%04d", home, Basename(file), n);
    if (stat(tmpdir, &buf) != -1) { n++; goto retry; }

    mkdir(tmpdir, 0744);
    printf("scriptfile: %s\nnewfilename: %s\ntmpdir: %s\n  ", info->scriptfile, file, tmpdir);

    for (k = 0;  k < m; k++) {
      sprintf(cmd, "cp -p -f \"%s\" \"%s/%s\"",
	      ArchiveFile(list[k]), tmpdir, Basename(list[k]));
      if (system(cmd));
    }

    sprintf(cmd, "%s/script.c", tmpdir);
    fo = fopen(cmd, "w");
    if (fo) {
      fprintf(fo, "%s", text);
      fclose(fo);
    }
    sprintf(cmd, "( cd \"%s\" ; tar cvfz ../\"%s\".lxp . ) ; "
	    "mv -f \"%s/../%s\".lxp \"%s\".lxp ; rm -rf \"%s\"", 
            tmpdir, Basename(file), tmpdir, Basename(file), file, tmpdir);
    printf("%s\n", cmd);
    if (system(cmd));
    Notice(Global.toplevel, msgText[LXP_FILE_CREATED], file);    
}

static void 
buildLXPCallback(Widget w, XtPointer wlArg, XtPointer junk)
{
    ScriptInfo *info = (ScriptInfo *) wlArg;
    char *text, *name;
    
    /* Extract text */
    XtVaGetValues(info->program, XtNstring, &text, NULL);
    if (!text || !*text) {
         Notice(Global.toplevel, msgText[C_SCRIPT_DOES_NOT_CONTAIN_ANY_DATA]);
	 return;
    }
    
    GetFileName(w, BROWSER_SIMPLESAVE, NULL, buildLXPOkCallback, wlArg);
}

void
editorResized(Widget w, ScriptInfo * l, XConfigureEvent * event, Boolean * flg)
{
    Dimension width, height;
    Dimension width1, height1, height2;

    XtVaGetValues(l->shell, XtNwidth, &width, XtNheight, &height, NULL);
    XtResizeWidget(XtParent(l->name), width, Global.btn_height + 16, BORDERWIDTH);
    XtResizeWidget(l->pane, width, height, BORDERWIDTH);
    XtVaSetValues(l->pane, XtNwidth, width, XtNheight, height, NULL);
    XtVaGetValues(XtParent(l->program), XtNheight, &height1, NULL);

#ifdef XAW3DXFT
    width1 = (width>13)? width-11 : 2;
    height1 = (height1>10)? height1-8 : 2;
    height2 = (height>height1+63)? 
               height - height1 - Global.btn_height - 32 : 10;
#else
#ifdef XAW3DG
    width1 = (width>12)? width-10 : 2;
    height1 = (height1>10)? height1-8 : 2;
    height2 = (height>height1+63)? height - height1 - 53 : 10;
#else
    width1 = (width>10)? width-8 : 2;
    height1 = (height1>10)? height1-8 : 2;
    height2 = (height>height1+60)? height - height1 - 50 : 10;
#endif
#endif
    XtResizeWidget(XtParent(l->program), width1+11, height1+8, BORDERWIDTH);
    XtResizeWidget(l->program, width1, height1, BORDERWIDTH);
    XtVaSetValues(XtParent(l->program), 
       XtNwidth, width1+11, XtNheight, height1+8, NULL);
    XtVaSetValues(l->program, 
       XtNwidth, width1, XtNheight, height1, NULL);
    XtMoveWidget(XtParent(l->program), 0, Global.btn_height + 16);
    XtMoveWidget(l->program, 4, 0);

    if (!l->cclog) return;
    XtResizeWidget(XtParent(l->cclog), width1+11, height2+8, BORDERWIDTH);
    XtResizeWidget(l->cclog, width1, height2, BORDERWIDTH);
    XtVaSetValues(XtParent(l->cclog), 
       XtNwidth, width1+11, XtNheight, height2+8, NULL);
    XtVaSetValues(l->cclog, 
       XtNwidth, width1, XtNheight, height2, NULL);
    XtMoveWidget(XtParent(l->cclog), 0, Global.btn_height + 25 + height1);
    XtMoveWidget(l->cclog, 4, 0);
}

static void
popupHandler(Widget w, ScriptInfo * info, XEvent * event, Boolean * flag)
{
    if (Global.popped_up) {
        if (event->type == ButtonRelease)
            PopdownMenusGlobal();
        event->type = None;       
    }
}

static void
recordCallback(Widget w, XtPointer wlArg, XtPointer junk)
{

   ScriptInfo *info = (ScriptInfo *) wlArg;  
   PaintWidget pw = (PaintWidget) info->paint;
   XColor col;
   
   pw->paint.dovxp = !pw->paint.dovxp;
   MenuCheckItem(w, pw->paint.dovxp);
   XAllocNamedColor(XtDisplay(info->compile),
		    DefaultColormapOfScreen(XtScreen(info->compile)),
		    (pw->paint.dovxp? "red" : "black"), &col, &col);
   XtVaSetValues(info->compile, XtNforeground, col.pixel, NULL);
}

static void
vxp2psCallback(Widget w, XtPointer wlArg, XtPointer junk)
{
   saveasFileCallback(w, wlArg, (XtPointer)1);
}

static void
vxp2texCallback(Widget w, XtPointer wlArg, XtPointer junk)
{
   saveasFileCallback(w, wlArg, (XtPointer)2);
}

static void
vxp2dkwCallback(Widget w, XtPointer wlArg, XtPointer junk)
{
   saveasFileCallback(w, wlArg, (XtPointer)3);
}

static void
rasterizeCallback(Widget w, XtPointer wlArg, XtPointer junk)
{
   saveasFileCallback(w, wlArg, (XtPointer)4);
}

static void
rescalingOkCallback(Widget w, XtPointer wlArg, XtPointer infoArg)
{
   TextPromptInfo *info = (TextPromptInfo *) infoArg;
   double f;
   
   f = atof(info->prompts[0].rstr);
   if (f <= 0) return;
   file_vxp_rescaling = f;
}

static void
rescalingCallback(Widget w, XtPointer wlArg, XtPointer junk)
{
   static TextPromptInfo info;
   static struct textPromptInfo value[1];
   char buffer[32];

   info.prompts = value;
   info.title = msgText[VXP_RESCALING_FACTOR];
   info.nprompt = 1;
   value[0].prompt = msgText[RESCALING_FACTOR];
   sprintf(buffer, "%g", file_vxp_rescaling);
   value[0].str = buffer;   

   TextPrompt(w, info.title,
              &info, rescalingOkCallback, NULL, NULL);
}

static void
clearCallback(Widget w, XtPointer wlArg, XtPointer junk)
{
   ScriptInfo *info = (ScriptInfo *) wlArg;
   char *str;
   if (w == vxpMenu[VXPDATA_CLEAR].widget) str = "#VXP"; else str = "";
   XtVaSetValues(info->program, XtNstring, str, NULL);
   XawTextSetInsertionPoint(info->program, strlen(str));
}

static void
writeCallback(Widget w, XtPointer wlArg, XtPointer junk)
{
   Display *dpy;
   PaintWidget pw;
   ScriptInfo *info = (ScriptInfo *) wlArg;
   char *str, *ptr;
   void *l = NULL;
   int u, v, x, y, x1, y1, x2, y2;
   Pixmap bigpix, pix, mask;
   Pixel white, fg;
   Colormap cmap;
   XColor xc;
   XImage *xim;
   GC gc;
   XRectangle rect;
   XtVaGetValues(info->paint, XtNdrawWidth, &u, XtNdrawHeight, &v,
		 XtNforeground, &fg, NULL);
   if (u <= 0 || v <= 0) return;
   pw = (PaintWidget) info->paint;
   str = malloc(32);
   sprintf(str, "%d %d", u, v);
   initFontPixel(fg);
   WriteText(info->paint, &l, str);
   free(str);
   XtVaGetValues(info->program, XtNstring, &str, NULL);
   if (!str || !*str) return;
   WriteText(info->paint, &l, str);
   XtVaGetValues(w, XtNcolormap, &cmap, NULL);
   bigpix = GetLocalInfoDrawable(l);
   dpy = XtDisplay(w);
   xim = XGetImage(dpy, bigpix, 0, 0, u, v, AllPlanes, ZPixmap);
   WriteText(info->paint, &l, NULL);
   white = WhitePixelOfScreen(XtScreen(w));
   for (y = 0; y < v; y++) {
       for (x = 0; x < u; x++) {
	   if (XGetPixel(xim, x, y) != white) break;
       }
       if (x < u) break;
   }
   y1 = y;
   for (x = 0; x < u; x++) {
       for (y = 0; y < v; y++) {
	   if (XGetPixel(xim, x, y) != white) break;
       }
       if (y < v) break;
   }
   x1 = x;
   for (y = v - 1; y >= 0; y--) {
       for (x = 0; x < u; x++) {
	   if (XGetPixel(xim, x, y) != white) break;
       }
       if (x < u) break;
   }
   y2 = y;
   for (x = u - 1; x >= 0; x--) {
       for (y = 0; y < v; y++) {
	   if (XGetPixel(xim, x, y) != white) break;
       }
       if (y < v) break;
   }
   x2 = x;
   if (x2 <= x1 || y2 <= y1) {
	XFreePixmap(dpy, bigpix);
	XDestroyImage(xim);
	return;
   }
   x2 = x2 - x1 + 1;
   y2 = y2 - y1 + 1;
   rect.width = x2;
   rect.height = y2;
   if (getIndexOp() == 10) {
       WriteTextGetPosition((Widget)pw, &x, &y);
       rect.x = x;
       rect.y = y;       
   } else
   if (pw->paint.region.source) {
       rect.x = pw->paint.region.rect.x;
       rect.y = pw->paint.region.rect.y;
   } else {
       rect.x = 0;
       rect.y = 0;
   }
   
   PwRegionClear(info->paint);
   pix = XCreatePixmap(dpy, DefaultRootWindow(dpy),
			   x2, y2, pw->core.depth);
   mask = XCreatePixmap(dpy, pix, x2, y2, 1);
   gc = XtGetGC(info->paint, 0, 0);
   XCopyArea(dpy, bigpix, pix, gc, x1, y1, x2, y2, 0, 0);
   gc = XCreateGC(dpy, mask, 0, 0);
   v = 765;
   if (DefaultDepth(dpy, DefaultScreen(dpy)) < 24)
       junk = NULL;
   else
       junk = (XtPointer) 1;
   for (y = 0; y < y2; y++) {
       for (x = 0; x < x2; x++) {
	   xc.pixel = XGetPixel(xim, x1 + x, y1 + y);
	   XSetForeground(dpy, gc, (xc.pixel != white)? 1:0);
	   XDrawPoint(dpy, mask, gc, x, y);
           if (junk) {
	       u = (xc.pixel&255) + ((xc.pixel>>8)&255) + ((xc.pixel>>16)&255);
           } else {
	       XQueryColor(dpy, cmap, &xc);
	       u = (xc.red >> 8) + (xc.green >> 8) + (xc.blue >> 8);
	   }
           if (u < v) v = u;
       }
   }
   XFreePixmap(dpy, bigpix);
   XFreeGC(dpy, gc);
   PwRegionSet(info->paint, &rect, pix, mask);
   pw->paint.region.rect.x = 0;
   pw->paint.region.rect.y = 0;   
   RegionMove(pw, rect.x, rect.y);
   PwRegionMergeColorSum(765 - v);
   if (DoVxp(info->paint)) {
       sprintf(Global.vxpinput, "\n*textdata %06x\n", fg);
       RecordVxp(info->paint);
   }
   ptr = str;
   Global.vxpinput[0] = ':'; 
   Global.vxpinput[1] = '\0';
   RecordVxp(info->paint);	   
   while (*ptr) {
       Global.vxpinput[0] = *ptr;
       RecordVxp(info->paint);
       if (*ptr == '\n') {
	 Global.vxpinput[0] = ':';
  	   RecordVxp(info->paint);
       }
       ++ptr;
   }   
}

static void 
languageOkCallback(Widget w, XtPointer paintArg, XtPointer infoArg)
{
    PaintWidget pw = (PaintWidget) w;
    TextPromptInfo *info = (TextPromptInfo *) infoArg;

    free(Global.ocrlang);
    Global.ocrlang = strdup(info->prompts[0].rstr);
}

static void
languageCallback(Widget w, XtPointer wlArg, XtPointer junk)
{
    static TextPromptInfo info;
    static struct textPromptInfo value[1];

    info.prompts = value;
    info.title = msgText[OCR_RECOGNITION_LANGUAGE];
    info.nprompt = 1;
    value[0].prompt = msgText[OCR_LANGUAGE_ABBREVIATED];
    value[0].str = Global.ocrlang;

    TextPrompt(w, msgText[OCR_RECOGNITION_LANGUAGE],
               &info, languageOkCallback, NULL, NULL);
}

static void
updateCallback(Widget w, XtPointer wlArg, XtPointer junk)
{
   ScriptInfo *info = (ScriptInfo *) wlArg;  
   PaintWidget pw = (PaintWidget) GET_PW(info->paint);
   XawTextBlock tb;
   XawTextPosition p1, p2;
   char *str;
   tb.firstPos = 0;
   tb.length = pw->paint.vxpl - info->textpos;
   tb.ptr = &pw->paint.vxp[info->textpos];
   tb.format = FMT8BIT;
   p1 = ((TextWidget)info->program)->text.lastPos;
   p2 = p1 + tb.length;
   XawTextReplace(info->program, p1, p2, &tb);
   XawTextSetInsertionPoint(info->program, p2);
   XawTextInvalidate(info->program, p1, p2);
   info->textpos = pw->paint.vxpl;
}

void *
ScriptEditor(Widget w, XtPointer wid, XtPointer data)
{
    Display *dpy;
    Window root;
    Widget topform, midform, botform;
    Widget bar, cancel, compile, build, last;
    int i, scripttype = 0, x, y;
    ScriptInfo *info;
    char *title[] = {
        "c_script_editor",
	"tex_editor",
	"text_editor",	
	"vxp_editor",
	"ocr_editor"
    };
    char *wintitle;

    PopdownMenusGlobal();
    
    if (data) scripttype = (int)(long int)data;
    if (wid && !data) scripttype = 3;  

    info = XtNew(ScriptInfo);
    info->dpi = 192;
    info->opacity = 255;
    if (scripttype == 1 || scripttype == 4)
      Global.ocrinfo = (void *)info;
    else
      Global.ocrinfo = NULL;

    info->mode = 0;
    info->scriptfile = NULL;
    SetScriptDir(NULL);   
    info->paint = (Widget)wid;
    info->textpos = 0;

    if (getIndexOp() == 10) {
        WriteTextGetPosition((Widget)wid, &x, &y);
        FakeButtonClick((Widget)wid, x, y, Button1);
    }

    dpy = XtDisplay(GetShell(w));
    root = RootWindowOfScreen(XtScreen(GetShell(w)));

    info->shell = XtVisCreatePopupShell(title[scripttype],
                                  topLevelShellWidgetClass,
				  Global.toplevel, NULL, 0);
    
    info->pane = XtVaCreateManagedWidget("pane", panedWidgetClass, info->shell,
                                         NULL);
    topform = XtVaCreateManagedWidget("scriptform",
				      formWidgetClass, info->pane, NULL);

    if (scripttype == 0)
        bar = MenuBarCreate(topform, XtNumber(scriptMenuBar), scriptMenuBar);
    else
    if (scripttype == 1)
        bar = MenuBarCreate(topform, XtNumber(texMenuBar), texMenuBar);
    else
    if (scripttype == 2)
        bar = MenuBarCreate(topform, XtNumber(textMenuBar), textMenuBar);
    else
    if (scripttype == 3)
        bar = MenuBarCreate(topform, XtNumber(vxpMenuBar), vxpMenuBar);
    else
    if (scripttype == 4)
        bar = MenuBarCreate(topform, XtNumber(ocrMenuBar), ocrMenuBar);
      
    XtVaSetValues(bar, XtNvertDistance, 2, NULL);

    last = bar;
    
    if (scripttype <= 3) {
        compile = XtVaCreateManagedWidget("compile",
		 		    commandWidgetClass, topform,
				    XtNfromHoriz, bar,
				    XtNhorizDistance, 32,
				    XtNvertDistance, 5,
				    XtNheight, Global.btn_height,	      
				    NULL);
 	if (scripttype <= 1) {
            build = XtVaCreateManagedWidget("build",
		 		    commandWidgetClass, topform,
				    XtNfromHoriz, compile,
				    XtNhorizDistance, 4,
				    XtNvertDistance, 5,
				    XtNheight, Global.btn_height,	      
				    NULL);
	    last = build;
	} else {
	    build = None;
	    last = compile;
	}
    } else
        compile = None;
    
    info->compile = compile;
    info->build = build;

    cancel = XtVaCreateManagedWidget("close",
		 		    commandWidgetClass, topform,
				    XtNfromHoriz, last,
				    XtNhorizDistance, 32, 
				    XtNvertDistance, 5,
				    XtNheight, Global.btn_height,	     
				    NULL);

    info->name = XtVaCreateManagedWidget("name",
				    labelWidgetClass, topform,
				    XtNlabel, "",
				    XtNwidth, 400,
				    XtNfromHoriz, cancel,
                                    XtNhorizDistance, 24,
                                    XtNvertDistance, 6,
				    XtNborderWidth, 0,
                                    NULL);

    midform = XtVaCreateManagedWidget("midform", 
                                    formWidgetClass, info->pane, NULL);
    if (scripttype <= 1)
    botform = XtVaCreateManagedWidget("botform", 
                                    formWidgetClass, info->pane, NULL);

    info->program = 
          XtVaCreateManagedWidget("program", asciiTextWidgetClass, midform,
                                  XtNeditType, XawtextEdit,
                                  XtNresizable, True,
                                  XtNwidth, Global.btn_height * 25,
                                  XtNheight, Global.btn_height * 16,
				  //				  XtNstring, "",
				  XtNscrollHorizontal, XawtextScrollWhenNeeded,
				  XtNscrollVertical, XawtextScrollWhenNeeded,
				  NULL);
    XtInsertRawEventHandler(info->program, 
			    ButtonPressMask | ButtonReleaseMask,
                            False, (XtEventHandler) popupHandler, info, 
                            XtListHead);

    if (scripttype <= 1) {
        info->cclog = 
            XtVaCreateManagedWidget("cclog", asciiTextWidgetClass, botform,
                                  XtNeditType, XawtextRead,
                                  XtNresizable, True,
                                  XtNwidth, Global.btn_height * 25,
				  XtNheight, Global.btn_height * 15,
				  XtNstring, NULL,
				  XtNscrollHorizontal, XawtextScrollWhenNeeded,
				  XtNscrollVertical, XawtextScrollWhenNeeded,
				  NULL);
        XtInsertRawEventHandler(info->cclog, 
			    ButtonPressMask | ButtonReleaseMask,
                            False, (XtEventHandler) popupHandler, info, 
                            XtListHead);
    }

    AddDestroyCallback(info->shell,
                       (DestroyCallbackFunc) closeCallback, (XtPointer) info);

    XtAddCallback(fileMenu[FILE_LOAD].widget, XtNcallback, 
		     (XtCallbackProc) loadFileCallback, (XtPointer) info);
    XtAddCallback(fileMenu[FILE_SAVE].widget, XtNcallback, 
		     (XtCallbackProc) saveFileCallback, (XtPointer) info);
    XtAddCallback(fileMenu[FILE_SAVEAS].widget, XtNcallback, 
		     (XtCallbackProc) saveasFileCallback, (XtPointer) info);
    XtAddCallback(fileMenu[FILE_EDIT].widget, XtNcallback, 
		     (XtCallbackProc) externCallback, (XtPointer) info);
    XtAddCallback(fileMenu[FILE_CLOSE].widget, XtNcallback, 
		     (XtCallbackProc) closeCallback, (XtPointer) info);
    XtAddCallback(cancel, XtNcallback,
                  (XtCallbackProc)closeCallback, (XtPointer) info);

    if (scripttype == 0) {
        wintitle = msgText[C_SCRIPT_EDITOR];
	XtAddCallback(compile, XtNcallback,
                     (XtCallbackProc)compileCallback, (XtPointer) info);
	XtAddCallback(build, XtNcallback,
                     (XtCallbackProc)buildLXPCallback, (XtPointer) info);
	
       for (i=0; i<SCRIPT_SEPARATOR; i++)
           XtAddCallback(scriptMenu[i].widget, XtNcallback, 
		     (XtCallbackProc) loadScriptCallback, (XtPointer) info);
    } else
    if (scripttype == 1) {
        wintitle = msgText[TEX_EDITOR];
	XtAddCallback(compile, XtNcallback,
                     (XtCallbackProc)TeXCompileCallback, (XtPointer) info);
	XtAddCallback(build, XtNcallback,
                     (XtCallbackProc)TeXBuildCallback, (XtPointer) info);
        XtAddCallback(texMenu[TEX_CLEAR].widget, XtNcallback,
		     (XtCallbackProc) clearCallback, (XtPointer) info);
        XtAddCallback(texMenu[TEX_SIZE_OPACITY].widget, XtNcallback,
		     (XtCallbackProc) chooseTeXParametersCallback,
		     (XtPointer) info);
    } else
    if (scripttype == 2) {
        wintitle = msgText[TEXT_EDITOR];
       info->cclog = None;
       XtAddCallback(textMenu[0].widget, XtNcallback, 
		     (XtCallbackProc) clearCallback, (XtPointer) info);       
       for (i=TEXT_FONT; i<TEXT_NOOP; i++)
           XtAddCallback(textMenu[i].widget, XtNcallback, 
		     (XtCallbackProc) insertTextCallback, (XtPointer) info);
       XtAddCallback(compile, XtNcallback, 
		     (XtCallbackProc) writeCallback, (XtPointer) info);       
    } else
    if (scripttype == 3) {
        wintitle = msgText[VXP_EDITOR];
       info->cclog = None;
       XtAddCallback(vxpMenu[VXPDATA_RECORD].widget, XtNcallback,
		     (XtCallbackProc) recordCallback, (XtPointer) info);       
       XtAddCallback(vxpMenu[VXPDATA_RESCALING].widget, XtNcallback,
		     (XtCallbackProc) rescalingCallback, (XtPointer) info);
       XtAddCallback(vxpMenu[VXPDATA_RASTERIZE].widget, XtNcallback,
		     (XtCallbackProc) rasterizeCallback, (XtPointer) info);
       XtAddCallback(vxpMenu[VXPDATA_CLEAR].widget, XtNcallback,
		     (XtCallbackProc) clearCallback, (XtPointer) info);       
       XtAddCallback(vxpMenu[VXPDATA_VXP2PS].widget, XtNcallback,
		     (XtCallbackProc) vxp2psCallback, (XtPointer) info);       
       XtAddCallback(vxpMenu[VXPDATA_VXP2TEX].widget, XtNcallback,
		     (XtCallbackProc) vxp2texCallback, (XtPointer) info);       
       XtAddCallback(vxpMenu[VXPDATA_VXP2DKW].widget, XtNcallback,
		     (XtCallbackProc) vxp2dkwCallback, (XtPointer) info);       
       XtAddCallback(compile, XtNcallback,
		     (XtCallbackProc) updateCallback, (XtPointer) info);
       if (DoVxp(wid)) ((PaintWidget)wid)->paint.dovxp = True;
       recordCallback(vxpMenu[VXPDATA_RECORD].widget, info, NULL);       
    } else
    if (scripttype == 4) {
       wintitle = msgText[OCR_EDITOR];
       info->cclog = None;
       XtAddCallback(ocrMenu[OCR_CLEAR].widget, XtNcallback,
		     (XtCallbackProc) clearCallback, (XtPointer) info);
       XtAddCallback(ocrMenu[OCR_LANGUAGE].widget, XtNcallback,
		     (XtCallbackProc) languageCallback, (XtPointer) info);
    }

    
    /* XtRealizeWidget(info->shell); */
    XtPopup(info->shell, XtGrabNone);
    XtVaGetValues(info->program, XtNstring, &psviewer, NULL);
    psviewer = strdup(psviewer);
    XtVaSetValues(info->program, XtNstring, "", NULL);    
    SetWMInputHint(dpy, XtWindow(info->shell));
    StoreName(info->shell, wintitle);
    
    XtVaSetValues(topform, XtNshowGrip, False, NULL);
    XtVaSetValues(midform, XtNshowGrip, True, NULL);
    if (scripttype <= 1) XtVaSetValues(botform, XtNshowGrip, True, NULL);

    XtUnmanageChild(info->name);
    XMapWindow(dpy, XtWindow(info->name));
    XtUnmanageChild(cancel);
    XMapWindow(dpy, XtWindow(cancel));
    if (compile) {
        XtUnmanageChild(compile);
        XMapWindow(dpy, XtWindow(compile));
    }
    if (build) {
        XtUnmanageChild(build);
        XMapWindow(dpy, XtWindow(build));
    }
    XtUnmanageChild(info->program);
    XMapWindow(dpy, XtWindow(info->program));

    if (scripttype <= 1) {
        XtUnmanageChild(info->cclog);
        XMapWindow(dpy, XtWindow(info->cclog));
        XtAddEventHandler(info->cclog, ButtonPressMask, False,
		          (XtEventHandler) mousewheelScroll, (XtPointer) NULL);
    } else
    if (scripttype == 3) {
        if (data) updateCallback(compile, info, NULL);
    }
   
    XtAddEventHandler(midform, StructureNotifyMask, False,
		      (XtEventHandler) editorResized, (XtPointer) info);
    XtAddEventHandler(info->shell, StructureNotifyMask, False,
		      (XtEventHandler) editorResized, (XtPointer) info);
    XtAddEventHandler(info->program, ButtonPressMask, False,
		      (XtEventHandler) mousewheelScroll, (XtPointer) NULL);
    XtSetMinSizeHints(info->shell, 300, 160);
    return info;
}

void AppendTextFile(int n)
{
    ScriptInfo *info = (ScriptInfo *)Global.ocrinfo;
    XawTextBlock tb;
    XawTextPosition p1, p2;
    char tmpfile[512];
    char *ptr;
    char c;
    FILE *fd;
    long int i, l;
    if (!info) return;
    sprintf(tmpfile, "%s%s/%s",
	    (getenv("HOME")?getenv("HOME"):""),
	    (getenv("HOME")?"/.xpaint/tmp":"/tmp"),
	    "xpaint_ocr_output.txt");
    fd = fopen(tmpfile, "r");
    if (!fd) return;
    fseek(fd, 0, SEEK_END);
    l = ftell(fd);
    ptr = malloc(l + 8);
    fseek(fd, 0, SEEK_SET);
    i = 0;
    while (i < l) {
      c = fgetc(fd);
      if (c == 0xc) c = 0xa;
      ptr[i++] = c;
    }
    ptr[i] = '\0';
    fclose(fd);
    tb.firstPos = 0;
    tb.length = l;
    tb.ptr = ptr;
    tb.format = FMT8BIT;
    p1 = ((TextWidget)info->program)->text.lastPos;
    p2 = p1 + l;
    XawTextReplace(info->program, p1, p2, &tb);
    XawTextSetInsertionPoint(info->program, p2);
    XawTextInvalidate(info->program, p1, p2);
    free(ptr);
    unlink(tmpfile);
}

/* convenience routines */
int
RegionX()
{
    PaintWidget pw = (PaintWidget)Global.curpaint;
    return pw->paint.region.rect.x;
}
int
RegionY()
{
    PaintWidget pw = (PaintWidget)Global.curpaint;
    return pw->paint.region.rect.y;
}
int
RegionWidth()
{
    PaintWidget pw = (PaintWidget)Global.curpaint;
    return pw->paint.region.rect.width;
}
int
RegionHeight()
{
    PaintWidget pw = (PaintWidget)Global.curpaint;
    return pw->paint.region.rect.height;
}

void ImageToRegion(Image * input, XRectangle rect, Pixmap mask)
{
    Pixmap pix;
    Colormap cmap;
    ImageToPixmap(input, Global.curpaint, &pix, &cmap);
    PwRegionSet(Global.curpaint, &rect, pix, mask);
}

void ImageToCanvas(Image * input, Pixmap mask)
{
    Pixmap pix;
    Colormap cmap;
    XRectangle rect;
    if (!input || !Global.curpaint) return;
    rect.width = input->width;
    rect.height = input->height;
    rect.x = 0;
    rect.y = 0;
    ImageToPixmap(input, Global.curpaint, &pix, &cmap);
    PwRegionSet(Global.curpaint, &rect, pix, mask);
    PwRegionSet(Global.curpaint, None, None, None);
}

Image *
CanvasToImage()
{
    PaintWidget pw = (PaintWidget)Global.curpaint;
    Image * image;

    image = PixmapToImage((Widget)pw, GET_PIXMAP(pw), Global.clipboard.cmap);
    return image;
}

Boolean
isFilterDefined()
{
    return (filter_so_file)? True : False;
}

Image *
ImageUserDefined(Image * input)
{
    Image *output;
    char * error;
    void (* proc)(Image *, Image *);
    void * dl_filter;
    
    if (!filter_so_file) return input;

    dl_filter = dlopen(filter_so_file, RTLD_LAZY);
    if (!dl_filter) return input;

    proc = dlsym(dl_filter, "FilterProcess");
    if ((error = dlerror()) != NULL) {
       Notice(Global.toplevel, error);
       return input;
    }

    output = ImageNew(input->width, input->height);

    proc(input, output);

    return output;
}


#define GRAY(p)		(((p)[0]*169 + (p)[1]*256 + (p)[2]*87)/512)
#define SQ(x)		((x)*(x))

/*
 * Directional filter, according to the algorithm described on p. 60 of:
 * _Algorithms_for_Graphics_and_Image_Processing_. Theo Pavlidis.
 * Computer Science Press, 1982.
 * For each pixel, detect the most prominent edge and apply a filter that
 * does not degrade that edge.
 */
Image *
ImageDirectionalFilter(Image * input)
{
    unsigned char *p00, *p10, *p_0, *p11, *p__, *p01, *p0_, *p_1, *p1_;
    int g00, g10, g_0, g11, g__, g01, g0_, g_1, g1_;
    int v0, v45, v90, v135, vmin, theta;
    int x, y;
    Image *output = ImageNew(input->width, input->height);
    unsigned char *op = output->data;

    /*
     * We don't process the border of the image tto avoid the hassle
     * of having do deal with boundary conditions. Hopefully no one
     * will notice.
     */
    /* copy first row unchanged */
    for (x = 0; x < input->width; x++) {
	p00 = ImagePixel(input, x, 0);
	*op++ = *p00++;
	*op++ = *p00++;
	*op++ = *p00++;
    }

    for (y = 1; y < input->height - 1; y++) {
	/* copy first column unchanged */
	p00 = ImagePixel(input, 0, y);
	*op++ = *p00++;
	*op++ = *p00++;
	*op++ = *p00++;
	for (x = 1; x < input->width - 1; x++) {
	    /* find values of pixel and all neighbours */
	    p00 = ImagePixel(input, x, y);
	    p10 = ImagePixel(input, x + 1, y);
	    p_0 = ImagePixel(input, x - 1, y);
	    p11 = ImagePixel(input, x + 1, y + 1);
	    p__ = ImagePixel(input, x - 1, y - 1);
	    p01 = ImagePixel(input, x, y + 1);
	    p0_ = ImagePixel(input, x, y - 1);
	    p_1 = ImagePixel(input, x - 1, y + 1);
	    p1_ = ImagePixel(input, x + 1, y - 1);

	    /* get grayscale values */
	    g00 = GRAY(p00);
	    g01 = GRAY(p01);
	    g10 = GRAY(p10);
	    g11 = GRAY(p11);
	    g0_ = GRAY(p0_);
	    g_0 = GRAY(p_0);
	    g__ = GRAY(p__);
	    g_1 = GRAY(p_1);
	    g1_ = GRAY(p1_);

	    /* estimate direction of edge, if any */
	    v0 = SQ(g00 - g10) + SQ(g00 - g_0);
	    v45 = SQ(g00 - g11) + SQ(g00 - g__);
	    v90 = SQ(g00 - g01) + SQ(g00 - g0_);
	    v135 = SQ(g00 - g_1) + SQ(g00 - g1_);

	    vmin = MIN(MIN(v0, v45), MIN(v90, v135));
	    theta = 0;
	    if (vmin == v45)
		theta = 1;
	    else if (vmin == v90)
		theta = 2;
	    else if (vmin == v135)
		theta = 3;

	    /* apply filtering according to direction of edge */
	    switch (theta) {
	    case 0:		/* 0 degrees */
		*op++ = (*p_0++ + *p00++ + *p10++) / 3;
		*op++ = (*p_0++ + *p00++ + *p10++) / 3;
		*op++ = (*p_0++ + *p00++ + *p10++) / 3;
		break;
	    case 1:		/* 45 degrees */
		*op++ = (*p__++ + *p00++ + *p11++) / 3;
		*op++ = (*p__++ + *p00++ + *p11++) / 3;
		*op++ = (*p__++ + *p00++ + *p11++) / 3;
		break;
	    case 2:		/* 90 degrees */
		*op++ = (*p0_++ + *p00++ + *p01++) / 3;
		*op++ = (*p0_++ + *p00++ + *p01++) / 3;
		*op++ = (*p0_++ + *p00++ + *p01++) / 3;
		break;
	    case 3:		/* 135 degrees */
		*op++ = (*p1_++ + *p00++ + *p_1++) / 3;
		*op++ = (*p1_++ + *p00++ + *p_1++) / 3;
		*op++ = (*p1_++ + *p00++ + *p_1++) / 3;
		break;
	    }
	}
	/* copy last column unchanged */
	p00 = ImagePixel(input, x, y);
	*op++ = *p00++;
	*op++ = *p00++;
	*op++ = *p00++;
    }

    /* copy last row unchanged */
    for (x = 0; x < input->width; x++) {
	p00 = ImagePixel(input, x, y);
	*op++ = *p00++;
	*op++ = *p00++;
	*op++ = *p00++;
    }
    return output;
}

#if 0
#define ISFOREGND(p) ((p) ? \
		      (deltaR-deltaRV <= p[0]) && (p[0] <= deltaR+deltaRV) && \
		      (deltaG-deltaGV <= p[1]) && (p[1] <= deltaG+deltaGV) && \
		      (deltaB-deltaBV <= p[2]) && (p[2] <= deltaB+deltaBV) : 1)

/*
 * Thicken an image.
 */
Image *
ImageThicken(Image * input)
{
    unsigned char *p00, *p10, *p_0, *p01, *p0_;
    int x, y, width, height, br, bg, bb;
    int deltaR, deltaRV, deltaG, deltaGV, deltaB, deltaBV;
    Image *output;
    unsigned char *op;

    width = input->width;
    height = input->height;
    output = ImageNew(width, height);
    op = output->data;

    /* Get RGB values of background colour */
    br = ImgProcessInfo.background->red / 256;
    bg = ImgProcessInfo.background->green / 256;
    bb = ImgProcessInfo.background->blue / 256;

    for (y = 0; y < height; y++)
	for (x = 0; x < width; x++) {
	    /* find values of pixel and all d-neighbours */
	    p00 = ImagePixel(input, x, y);
	    p10 = x < width - 1 ? ImagePixel(input, x + 1, y) : NULL;
	    p_0 = x > 0 ? ImagePixel(input, x - 1, y) : NULL;
	    p01 = y < height - 1 ? ImagePixel(input, x, y + 1) : NULL;
	    p0_ = y > 0 ? ImagePixel(input, x, y - 1) : NULL;

	    if (ISFOREGND(p00)) {
		*op++ = *p00++;
		*op++ = *p00++;
		*op++ = *p00++;
	    } else {
		*op++ = br;
		*op++ = bg;
		*op++ = bb;
	    }
	}

    return output;
}
#endif
