/*
 * plotPnm.c --
 *
 * This file contains procedures that generate PNM format files
 * to describe a section of layout.
 *
 *     ********************************************************************* 
 *     * Copyright (C) 2000 Cornell University                             *
 *     * Permission to use, copy, modify, and distribute this              * 
 *     * software and its documentation for any purpose and without        * 
 *     * fee is hereby granted, provided that the above copyright          * 
 *     * notice appear in all copies.  Cornell University                  * 
 *     * makes no representations about the suitability of this            * 
 *     * software for any purpose.  It is provided "as is" without         * 
 *     * express or implied warranty.  Export of this software outside     * 
 *     * of the United States of America may require an export license.    * 
 *     *********************************************************************
 */

#ifndef lint
static char rcsid[]="$Header: /ufs/repository/magic/plot/plotPNM.c,v 1.6 2006/02/08 03:47:58 fang Exp $";
#endif  /* not lint */

#include <stdio.h>
#include <math.h>
#include "misc/magic.h"
#include "utils/geometry.h"
#include "utils/geofast.h"
#include "tiles/tile.h"
#include "utils/hash.h"
#include "database/database.h"
#include "tech/tech.h"
#include "utils/malloc.h"
#include "utils/utils.h"
#include "utils/strdup.h"
#include "windows/windows.h"
#include "dbwind/dbwind.h"
#include "main/main.h"
#include "commands/commands.h"
#include "router/router.h"
#include "textio/textio.h"
#include "signals/signals.h"
#include "plot/plotInt.h"

#define LANCZOS_KERNEL_SIZE 1024
#define PI 3.14159265

#define PIXELSZ sizeof(unsigned char)
#define	STRLEN	200

int PlotPNMmaxmem = 10*1024; /* 10MB */


int PlotPNMBGr = 255;		/* background color for PNM plotting */
int PlotPNMBGg = 255;
int PlotPNMBGb = 255;


/*
 * Local variables, modified/shared by callbacks.
 *
 */
static int 		Init_Error;
static float 		lk[2*LANCZOS_KERNEL_SIZE+1];
static int		*lkstep; /* lanczos kernel steps */
static unsigned char 	*rtile;
static int		tile_xsize, tile_ysize;
static Rect		bb;
static unsigned long	BBinit;
static int		tile_yshift, tile_xshift;
static int		scale_over_2;
static int 		im_x, im_y;
static int		im_yoffset;
static int 		y_pixels;

#define ZERO(x)   (x = 0)

/* rgb colormap for pnm plotting */
static unsigned char 	plotCMAPr[128];
static unsigned char 	plotCMAPg[128];
static unsigned char 	plotCMAPb[128];
static int ncolors;

static void PlotLoadStyles();

static struct {
  char *name;
  int init;
  unsigned int wmask, color;
} Dstyles[128];
static int ndstyles = 0;

static struct {
  char *name;
  unsigned int wmask, color;
} PaintStyles[128];
static int npaintstyles = 0;

#if 0
static void
dumpRect (r)
     Rect *r;
{
  TxPrintf ("(%d %d) -> (%d %d)", r->r_xbot, r->r_ybot, r->r_xtop, r->r_ytop);
}
#endif

static void
pnmRenderTile (fp,scale,scale_over_2,temp)
     FILE *fp;
     float scale;
     int scale_over_2;
     float *temp;
{
  int i, j;
  int jmax;
  int x, y;
  int dx, dy;
  float r, g, b;
  
  i = 0;
  jmax = MIN(y_pixels, im_yoffset+1);

  /* x, y : pixel coords */

  if (scale_over_2 == 0) {
    for (j=0; j < jmax; j++) {
      y = bb.r_ybot + ((float)(im_yoffset-j)*scale) - tile_yshift;
      for (i=0; i < im_x; i++) {
        x = bb.r_xbot + ((float)i*scale) - tile_xshift;
	r = plotCMAPr[*(rtile + x + y*tile_xsize)];
	g = plotCMAPg[*(rtile + x + y*tile_xsize)];
	b = plotCMAPb[*(rtile + x + y*tile_xsize)];
        fprintf (fp, "%c%c%c", (unsigned char)r, (unsigned char)g,
	         (unsigned char)b);
      }
    }
  }
  else {
    for (j=0; j < jmax; j++) {
      y = bb.r_ybot + ((float)(im_yoffset-j)*scale) - tile_yshift;
      for (i=0; i < im_x; i++) {
        x = bb.r_xbot + ((float)i*scale) - tile_xshift;
        for (dx = -scale_over_2; dx < scale_over_2; dx++) {
	  r = 0.0; g = 0.0; b = 0.0;
	  for (dy = -scale_over_2; dy < scale_over_2; dy++) {
	    if (dy + y >= tile_ysize) continue;
	    /* grab rgb for (x + dx, y + dy) */
	    r += plotCMAPr[*(rtile + x + dx + (y + dy)*tile_xsize)]
	      *lk[lkstep[dy+scale_over_2]];
	    g += plotCMAPg[*(rtile + x + dx + (y + dy)*tile_xsize)]
	      *lk[lkstep[dy+scale_over_2]];
	    b += plotCMAPb[*(rtile + x + dx + (y + dy)*tile_xsize)]
	      *lk[lkstep[dy+scale_over_2]];
	  }
	  temp[3*(dx + scale_over_2)] = r;
	  temp[3*(dx + scale_over_2)+1] = g;
	  temp[3*(dx + scale_over_2)+2] = b;
        }
        r = 0.0; g = 0.0; b = 0.0;
        for (dx = 0; dx < 2*scale_over_2; dx++) {
	  r += temp[3*dx]*lk[lkstep[dx]];
	  g += temp[3*dx+1]*lk[lkstep[dx]];
	  b += temp[3*dx+2]*lk[lkstep[dx]];
        }
        r /= (scale_over_2*2)*(scale_over_2*2);
        g /= (scale_over_2*2)*(scale_over_2*2);
        b /= (scale_over_2*2)*(scale_over_2*2);
        fprintf (fp, "%c%c%c", (unsigned char)r, (unsigned char)g,
	         (unsigned char)b);
      }
    }
  }
}



/*
 * ----------------------------------------------------------------------------
 *
 * pnmPaint --
 *
 *      Draw a tile according to colors read in from plot style file.
 *
 * Results:
 *	Returns 1 on error, 0 if okay
 *
 * Side effects:
 *	Modifies rtile array.
 *
 * ----------------------------------------------------------------------------
 */

static int pnmPaint (type, r)
     TileType type;
     Rect *r;
{
  int i, j;
  int x, y, dx, dy;
  unsigned char *t;
  unsigned wmask, col;

  /* paint rectangle! */
  if (DBTypeLongNameTbl[type]) {
    for (i=0; i < npaintstyles; i++) {
      if (strcmp (PaintStyles[i].name, DBTypeLongNameTbl[type]) == 0) {
	wmask = PaintStyles[i].wmask;
	col = PaintStyles[i].color & wmask;
	/* 
	   match, paint rectangle.
	*/
	x = r->r_xbot - tile_xshift;
	y = r->r_ybot - tile_yshift;
	dx = r->r_xtop - r->r_xbot;
	dy = r->r_ytop - r->r_ybot;

	if (x < 0 || y < 0 || x >= tile_xsize || y >= tile_ysize) {
	  /* error */
	  return 1;
	}

	t = rtile + x + tile_xsize*y;
	for ( ; dy > 0; dy--) {
	  for (j=0; j < dx; j++) {
	    *t = (*t & ~wmask) | col;
	    t++;
	  }
	  t = t - dx + tile_xsize;
	}
	break;
      }
    }
  }
  /* okay */
  return 0;
}


/*
 * ----------------------------------------------------------------------------
 *
 * pnmBBOX --
 *
 *      Callback for DBTreeSrTiles; compute bounding box of plot
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Modifies BBinit, updates bounding box "bb"
 *
 * ----------------------------------------------------------------------------
 */

static int
pnmBBOX (tile,cxp)
     register Tile *tile;
     TreeContext *cxp;
{
  Rect targetRect, sourceRect;
  SearchContext *scx = cxp->tc_scx;
  Rect *arg;
  TileType type;

  if ((type = TiGetType(tile)) == TT_SPACE)
    return 0;
  
  /* grab rectangle from tile */
  TITORECT(tile,&targetRect);
  
  /* coordinate transform */
  GEOTRANSRECT(&scx->scx_trans, &targetRect, &sourceRect);

  /* Clip */
  arg = (Rect *)cxp->tc_filter->tf_arg;
  GEOCLIP(&sourceRect,arg);

  /* compute bbox */
  if (!BBinit) {
    bb = sourceRect;
  }
  else {
    bb.r_xbot = MIN(bb.r_xbot, sourceRect.r_xbot);
    bb.r_ybot = MIN(bb.r_ybot, sourceRect.r_ybot);
    bb.r_xtop = MAX(bb.r_xtop, sourceRect.r_xtop);
    bb.r_ytop = MAX(bb.r_ytop, sourceRect.r_ytop);
  }

  BBinit = 1;
  return 0;
}



/*
 * ----------------------------------------------------------------------------
 *
 * pnmTile --
 *
 *      Callback for DBTreeSrTiles; paints tiles in the current rtile buffer.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Modifies rtile array.
 *
 * ----------------------------------------------------------------------------
 */

static int
pnmTile (tile,cxp)
     register Tile *tile;
     TreeContext *cxp;
{
  Rect targetRect, sourceRect;
  SearchContext *scx = cxp->tc_scx;
  Rect *arg;
  TileType type;

  if ((type = TiGetType(tile)) == TT_SPACE)
    return 0;
  
  /* grab rectangle from tile */
  TITORECT(tile,&targetRect);
  
  /* coordinate transform */
  GEOTRANSRECT(&scx->scx_trans, &targetRect, &sourceRect);

  /* Clip */
  arg = (Rect *)cxp->tc_filter->tf_arg;
  GEOCLIP(&sourceRect,arg);

  /* RENDER TILE */
  return pnmPaint (type, &sourceRect);
}



/*
 * ----------------------------------------------------------------------------
 *
 * PlotPNM --
 *
 * 	This procedure generates a PNM file to describe an area of
 *	a layout.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 * ----------------------------------------------------------------------------
 */

void
PlotPNM(fileName, scx, layers, xMask, invscale)
    char *fileName;			/* Name of PNM file to write. */
    SearchContext *scx;			/* The use and area and transformation
					 * in this describe what to plot.
					 */
    TileTypeBitMask *layers;		/* Tells what layers to plot.  Only
					 * paint layers in this mask, and also
					 * expanded according to xMask, are
					 * plotted.  If L_LABELS is set, then
					 * labels on the layers are also
					 * plotted, if expanded according to
					 * xMask.  If L_CELL is set, then
					 * subcells that are unexpanded
					 * according to xMask are plotted as
					 * bounding boxes.
					 */
    int xMask;				/* An expansion mask, used to indicate
					 * the window whose expansion status
					 * will be used to determine
					 * visibility.  Zero means treat
					 * everything as expanded.
					 */
    float  invscale;		        /* Indicates the width of the
					 * plot; 0 = pixel width.
					 */
{
  FILE *fp;
  Rect bbox;
  int bb_ysize;
  int i, x, y;
  float *strip = NULL;
  float scale;

  if (Init_Error) {
    TxError ("PNM module initialization had failed; cannot plot\n");
    return;
  }

  if (invscale <= 0) {
    TxError ("PNM module given negative scale; cannot plot\n");
    return;
  }

  scale = 1.0 / invscale;
  if ((scale > 2) || (invscale != ceil(invscale))) {
    scale_over_2 = (int) ceil(scale / 2.0);
  }
  else
    scale_over_2 = 0;

  /* image:
   *    -----     
   *   | xxx |
   *   | xxx |
   *   | xxx |
   *    -----
   *
   * Use -scale/2 to scale/2 pixels for each pixel.
   *
   */

  /* Rendering Tile:
   *
   *    0.. bbox size + 2 * scale_over_2.
   *   
   * To sample, pixel (i,j) will be at:
   *     (scale_over_2 + scale*i, scale_over_2 + scale*j)
   * 
   * Given an initial pixel position at (i,j), we sample from
   *     -scale_over_2 to scale_over_2
   */

  /* Compute bounding box size in lambda */
  BBinit = 0;
  DBTreeSrTiles (scx, layers, xMask, pnmBBOX,(ClientData)&scx->scx_area);

  tile_xsize = bb.r_xtop - bb.r_xbot + 2*scale_over_2;
  bb_ysize = bb.r_ytop - bb.r_ybot;

  /* bump search context by scale_over_2 pixels on each side */
  scx->scx_area.r_xbot = bb.r_xbot - scale_over_2;
  scx->scx_area.r_ybot = bb.r_ybot - scale_over_2;
  scx->scx_area.r_xtop = bb.r_xtop + scale_over_2;
  scx->scx_area.r_ytop = bb.r_ytop + scale_over_2;

  /* check for empty region */
  if (BBinit == 0 || tile_xsize <= 0 || bb_ysize <= 0) {
    TxPrintf ("Empty region, no plot\n");
    return;
  }

  /* 
     Compute memory requirements; a single pixel line needs a tile
     that has size "xsize" by "scale." To keep inter-tile overlap low,
     we insist that a single tile must have at least 3*scale pixels in
     it.
  */
  if (PlotPNMmaxmem*1024 <
      ((3*scale+2*scale_over_2)*PIXELSZ*tile_xsize)) 
    {
      TxPrintf ("Insufficent memory to antialias image.\n");
      TxPrintf ("Current: %d KB; Required: %d KB\n", 
		PlotPNMmaxmem, 
		(int) (1023+(3*scale+2*scale_over_2)*PIXELSZ*tile_xsize)/1024);
      return;
    }

  /* 
     Compute the maximum y size for a tile.
  */
  tile_ysize = PlotPNMmaxmem*1024/(PIXELSZ*tile_xsize);

  if (tile_ysize > (bb_ysize + 2*scale_over_2))
    tile_ysize = bb_ysize + 2*scale_over_2;

  MALLOC (unsigned char *, rtile, tile_xsize*tile_ysize*PIXELSZ);


  /* bump search context by scale_over_2 pixels on each side */
  scx->scx_area.r_ybot = scx->scx_area.r_ytop - tile_ysize;
  tile_yshift = scx->scx_area.r_ybot;
  tile_xshift = scx->scx_area.r_xbot;

  /* open PNM file */

  fp = PaOpen (fileName, "w", ".pnm", ".", NULL, NULL);
  if (fp == NULL) {
    TxError ("Could not open file `%s' for writing\n", fileName);
    goto done;
  }
  
  fprintf (fp, "P6\n");
  fprintf (fp, "%d %d\n", im_x = (int)((bb.r_xtop - bb.r_xbot)/scale),
	   im_y = (int)((bb.r_ytop - bb.r_ybot)/scale));
  fprintf (fp, "255\n");

  im_yoffset = im_y-1;

  y_pixels = (tile_ysize - scale_over_2*2)/scale;
  if (y_pixels > im_y) y_pixels = im_y;

#if 0
  TxPrintf ("Image: %d x %d\n", im_x, im_y);
  TxPrintf ("Pixels per tile: %d\n", y_pixels);
#endif

  MALLOC (float *, strip, scale_over_2*2*3*sizeof(float));
  MALLOC (int *, lkstep, scale_over_2*2*sizeof(int));
  
  for (x = -scale_over_2; x < scale_over_2; x++) {
    lkstep[scale_over_2+x] = ((float)ABS(x))/scale*LANCZOS_KERNEL_SIZE;
    if (lkstep[scale_over_2+x] >= LANCZOS_KERNEL_SIZE)
      lkstep[scale_over_2+x] = LANCZOS_KERNEL_SIZE-1;
  }

  while (im_yoffset > 0) {
    /* clear tile */
    for (x=0; x < tile_xsize; x++)
      for (y=0; y < tile_ysize; y++) {
	ZERO (rtile[y + tile_ysize*x]);
      }

    if (SigInterruptPending) {
      TxPrintf (" *** interrupted ***\n");
      goto done;
    }
    DBTreeSrTiles (scx, layers, xMask, pnmTile,(ClientData)&scx->scx_area);

    /* anti-aliased rendering */
    pnmRenderTile (fp,scale,scale_over_2,strip);

    /* advance to the next strip */
    im_yoffset -= y_pixels;
    tile_yshift -= (y_pixels*scale);
    scx->scx_area.r_ybot -= (y_pixels*scale);
    scx->scx_area.r_ytop -= (y_pixels*scale);
  }

  /*TxPrintf ("Save to file `%s', scale = %f\n", fileName, scale);*/
  fclose (fp);

 done:
  FREE ((char*) rtile);
  FREE ((char*) strip);
  FREE ((char*) lkstep);
  return;
}



/*
 * ----------------------------------------------------------------------------
 *
 * lanczos_kernel --
 *
 * 	Compute the value of the lanczos kernel at the given position.
 *	
 *
 * Results:
 *	Returns kernel value at arg x.
 *
 * Side effects:
 *	None.
 *
 * ----------------------------------------------------------------------------
 */

float lanczos_kernel(x)
     int x;			/* position at which to evaluate the
				 * lanczos kernel 
				 */
{
  if (x == 0.0) return 1.0;

  return (float)(sin(PI*x)/(PI*x))*(sin(PI*0.5*x)/(PI*0.5*x));
}



/*
 * ----------------------------------------------------------------------------
 *
 * PlotPNMTechInit --
 *
 * 	Called when magic starts up.
 *	
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Initializes lk[...] array with the lanczos kernel.
 *
 * ----------------------------------------------------------------------------
 */

/* Void */
void
PlotPNMTechInit(void)
{
  int i;

  Init_Error = 0;
  /* Initialize Lanczos kernel */
  for (i=0; i <= 2*LANCZOS_KERNEL_SIZE; i++)
    lk[i] = lanczos_kernel ((i+0.0)/LANCZOS_KERNEL_SIZE);
  PlotLoadStyles ();
}



/*
 * ----------------------------------------------------------------------------
 *
 * PlotPnmTechLine --
 *
 * 	Parse a magic technology file line for the pnm plot style
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Modifies paintstyles[] array.
 *
 * ----------------------------------------------------------------------------
 */

	/* ARGSUSED */
bool
PlotPNMTechLine(sectionName, argc, argv)
    char *sectionName;		/* Name of this section (unused). */
    int argc;			/* Number of arguments on line. */
    char *argv[];		/* Pointers to fields of line. */
{
  int i, j;

  if (strcmp (argv[0], "draw") == 0) {
    for (i=0; i < ndstyles; i++) {
      if (strcmp (argv[1], Dstyles[i].name) == 0) {
	PaintStyles[npaintstyles].name = Dstyles[i].name;
	PaintStyles[npaintstyles].wmask = Dstyles[i].wmask;
	PaintStyles[npaintstyles++].color = Dstyles[i].color;
	break;
      }
    }
  }
  else if (strcmp (argv[0], "map") == 0) {
    PaintStyles[npaintstyles].name = StrDup (NULL, argv[1]);
    for (j=2; j < argc; j++) {
      for (i=0; i < npaintstyles; i++) {
	if (strcmp (argv[j],PaintStyles[i].name) == 0) {
	  PaintStyles[npaintstyles].wmask |= PaintStyles[i].wmask;
	  PaintStyles[npaintstyles].color |= PaintStyles[i].color;
	  break;
	}
      }
    }
    npaintstyles++;
  }
  return TRUE;
}




/*
 * ----------------------------------------------------------------------------
 *
 * PlotLoadStyles --
 *
 * 	Read in the plotting style + colormap for rendering. This
 *	always uses the 7bit colormap.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Initializes arrays for drawing/plotting.
 *
 * ----------------------------------------------------------------------------
 */

static void
PlotLoadStyles()
{
    FILE *inp;
    char fullName[256];
    char *buf = fullName;
    int newsec;
    int ord, mask, color, outline, nfill, stipple;
    char shortname;
    char longname[128];
    char fill[42];
    char *which;

    which = "display styles";
    (void) sprintf(fullName, "%.100s.7bit.mraster_dstyle5", DBWStyleType);
    inp = PaOpen(fullName, "r", (char *)NULL, ".", SysLibPath, (char **) NULL);
    if (inp == NULL)
      goto err;

    /* read in the dstyle5 file for mraster */
    newsec = FALSE;
    while (fgets (buf, 256, inp)) {
      if (buf[0] == '#') continue;
      if (StrIsWhite (buf, FALSE)) {
	newsec = TRUE;
	continue;
      }
      else if (newsec) {
	if (strcmp (buf, "display_styles 7\n") != 0) {
	  goto err;
	}
	newsec = FALSE;
      }
      else {
	if (sscanf (buf, "%d %o %o %o %40s %d %c %s",
		    &ord, &mask, &color, &outline, fill, &stipple,
		    &shortname, longname) !=  8) {
	  goto err;
	}
	if (ndstyles == 128) {
	  goto err;
	}
	Dstyles[ndstyles].wmask = mask;
	Dstyles[ndstyles].color = color;
	Dstyles[ndstyles++].name = StrDup (NULL, longname);
      }
    }

    fclose (inp);

    /* read in color map */
    (void) sprintf(fullName, "%.100s.7bit.mraster.cmap1", DBWStyleType);
    inp = PaOpen(fullName, "r", (char *) NULL, ".", SysLibPath, (char **) NULL);
    if (inp == NULL)
      {
	TxError("Couldn't open colormap file \"%s\"\n",
		fullName);
	Init_Error = 1;
	return;
    }

    ncolors = 0;
    which = "colormap";
    while (fgets (buf, 256, inp)) {
      if (buf[0] == '#') continue;
      if (StrIsWhite (buf, FALSE))
	continue;
      if (ncolors == 128) {
	goto err;
      }
      if (sscanf (buf, "%d %d %d", &mask, &color, &outline) != 3) {
	goto err;
      }
      plotCMAPr[ncolors] = mask;
      plotCMAPg[ncolors] = color;
      plotCMAPb[ncolors] = outline;
      ncolors ++;
    }
    fclose (inp);
    PlotPNMSetBG ();
    return;
 err:
    Init_Error = 1;
    if (inp) {
      TxError ("Format error in %s file\n", which);
      fclose (inp);
    }
    else {
      TxError ("PNM plot: Could not open %s file\n", which);
    }
    return;
}



/*
 * ----------------------------------------------------------------------------
 *
 * PlotPNMSetBG --
 *
 * 	Set background color for PNM file
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Sets colormap[0] entry to specified rgb value.
 *
 * ----------------------------------------------------------------------------
 */

/* Void */
void
PlotPNMSetBG(void)
{
  plotCMAPr[0] = PlotPNMBGr;
  plotCMAPg[0] = PlotPNMBGg;
  plotCMAPb[0] = PlotPNMBGb;
}
