/* Xpaint-filter */

#include <Xpaint.h>

/* 
 * The key-word "FilterProcess" is reserved for user-defined filter routines;
 * Such a filter processes an "input" image and renders an "output" image.
 * 
 * Pixels are unsigned char arrays p[0]=red, p[1]=green, p[2]=blue
 * (thus each value should be in the range 0..255)
 *
 * In the example below, cp = canvas pixel, ip = input pixel, op = output pixel
 * the procedure merges the input (region) with the underlying canvas image
 *
 * This is a sophisticated example showing the use of a popup menu by which
 * the user can enter further parameters to adjust the given filter routine.
 *
 */

#define NUM 3   /* number of user defined parameters */
static struct textPromptInfo prompts[NUM];
static int u[8], v[8];

/* 
 * An example of user defined function which performs a projective
 * transform depending on the values of f[i], i=0..NUM-1.
 */
Image * function(Image * input)
{
    Image * output;
    unsigned char *ip, *op;
    int i, j, k, l, x, y;
    double a, t[3], p[3][3], q[3][3], r[3][3];

    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];

    /* debugging
    printf("\n(source) ");
    for (i=0; i<=7; i++)
      printf("%d ", u[i]);
    printf("\n(image) ");    
    for (i=0; i<=7; i++)
      printf("%d ", v[i]);
    printf("\n\n");    
    for (i=0; i<=2; i++)
      printf("%g %g %g\n", p[i][0], p[i][1], p[i][2]);
    printf("\n");
    for (i=0; i<=2; i++)
      printf("%g %g %g\n", r[i][0], r[i][1], r[i][2]);
    printf("\n");    
    for (i=0; i<=2; i++)
      printf("%g %g %g\n", q[i][0], q[i][1], q[i][2]);
    */
	
    /* 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;
        k = t[1]/t[0] + 0.5;
        l = t[2]/t[0] + 0.5;	
        if (k < 0) k = 0;
        if (k >= input->width) k = input->width - 1;
        if (l < 0) l = 0;
        if (l >= input->height) l = input->height - 1;	    
        ip = ImagePixel(input, k, l); 
        op = ImagePixel(output, x, y);
        memcpy(op, ip, 3);
      }
    return output;
}

void read_values(char *str, int *i)
{
    int j;
    if (strncasecmp(str, "rect", 4)) {
        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]);
    } else {
        str = str+4;
	if (strncasecmp(str, "_wh", 3)) {
	    j = 0;
	} else {
	    str = str+3;	  
	    j = 1;
	}
        sscanf(str, "%d,%d %d,%d", &i[0], &i[1], &i[4], &i[5]);
	if (j) {
	    i[4] = i[0] + i[4];
	    i[5] = i[1] + i[5];
	}
	i[2] = i[4];
	i[3] = i[1];
	i[6] = i[0];
	i[7] = i[5];
    }
}

/*
 * Fairly standard procedure. 
 */
void filter_proc(TextPromptInfo *info)
{
    Widget paint = Global.curpaint;
    int i;

    i = (atoi(info->prompts[2].rstr)!=0);

    /* read user supplied data, and write them back to prompt strings */
    read_values(info->prompts[i].rstr, u);
    read_values(info->prompts[1-i].rstr, v);

    for (i=0; i<NUM; i++) {
      if (!info->prompts[i].rstr) continue;
      if (info->prompts[i].rstr != info->prompts[i].str) {
        free(info->prompts[i].str);
        info->prompts[i].str = strdup(info->prompts[i].rstr);
      }
    }

    /* convert region image by user supplied function */    
    ImageRegionProcess(paint, function);
}

void FilterProcess(Image * input, Image * output)
{
    static int step =0;

    /* Define NUM prompt strings (but only at first invocation) */
    if (step==0) {
      prompts[0].prompt = "4 points  x1,y1  x2,y2  x3,y3  x4,y4";
      prompts[0].str =  strdup("");
      prompts[0].rstr =  NULL;
      prompts[0].len = 48;
      prompts[1].prompt = "their 4 transforms x'i,y'i  or  rect, rect_wh";
      prompts[1].str =  strdup("rect_wh 0,0 1200,800");
      prompts[1].rstr =  NULL;
      prompts[1].len = 48;
      prompts[2].prompt = "take reverse mapping 0/1";
      prompts[2].str =  strdup("0");
      prompts[2].rstr =  NULL;
      prompts[2].len = 48;
      ++step;
    }

    /* copy blindly image input into output before any further action */
    ImageCopyData(input, output);

    /* start popup with num values to adjust */
    PopupMenu("Projective transformation\n", NUM, prompts, filter_proc);
}


