

static char rcsid[] = "ptl.c";

#include "ptl.h"
#include "chunk.h"
#include "magic.h"
#include "math.h"
#include "ctype.h"


#define MXLINE  300			     /* max line length in ptl input */


static Chunk     pointlistchunk = NULLCHUNK; /* static vars and functions */
static Chunk     elementchunk   = NULLCHUNK;
static GrayImage N_for_color    = (GrayImage) NULL;

static void trimline(register char *line);
static char *getline(char *line, FILE *fp);
static void rgb_to_hsv(float r, float g, float b, float *h, float *s, float *v);
static void hsv_to_rgb(float *r, float *g, float *b, float h, float s, float v);
static RGB combine(RGB rgb1, int n, RGB rgb2);
static boolean plot_color_point_1(RGBImage img, GrayImage n, float xf, float yf,
                                  RGB rgb);
static boolean plot_color_point(RGBImage img, GrayImage n, float xf, float yf,
                                float weight, RGB rgb);
static void fswap(float *f1, float *f2);
static boolean plot_color_line(RGBImage img, GrayImage n, float x1, float y1,
                               float x2, float y2, float weight, RGB rgb);
static boolean plot_binary_point_1(BinaryImage img, float xf, float yf);
static boolean plot_binary_point(BinaryImage img, float xf, float yf, float weight);
static boolean plot_binary_line(BinaryImage img, float x1, float y1, float x2, float y2,
                                float weight);
static boolean plot_color_ellipse(RGBImage img, GrayImage n, float x, float y,
                                  float a, float b, float weight, RGB rgb);
static boolean plot_binary_ellipse(BinaryImage img, float x, float y,
                                   float a, float b, float weight);
static boolean do_color_fill(RGBImage img, GrayImage n, float x, float y, RGB rgb);
static boolean do_binary_fill(BinaryImage img, float x, float y);



extern inline boolean ptlGoodElemType(ptlElemType t)
{
  return (t == PTL_LINE     ||
          t == PTL_POINT    ||
          t == PTL_ELLIPSE  ||
          t == PTL_FILL);
}

extern inline int fround(float x)
{
  return ((int) ((x >= 0.0) ? (x + 0.5) : (x - 0.5)));
}


/******************************* ADT Functions ***********************************/

void *ptlLoadAndRender(FILE *fp, boolean iscolor, ImageType type, float sx, float sy,
		       RGB bcolor, boolean *warn)
{
  float fxbase, fybase, fwidth, fheight;
  int xbase, ybase, width, height;
  void *image;
  ImageHeader *header;
  boolean tmp_warn;
  static String docstring = NULL;


  if (warn == (boolean *) NULL) {
    warn = &tmp_warn;
  }

  /* assume we've already done magicStart() and eaten the magic string */
  
  if (magicGetFloat(fp, &fxbase)     ||
      magicGetFloat(fp, &fybase)     ||
      magicGetFloat(fp, &fwidth)     ||
      magicGetFloat(fp, &fheight)) {
    return NULL;
  }

  docstring = magicComment();

  xbase  = fround(fxbase * sx);
  ybase  = fround(fybase * sy);
  width  = (int) ceil(fwidth * sx);
  height = (int) ceil(fheight * sy);
  *warn  = FALSE;
  
  if (width <= 0  ||  height <= 0  ||  sx <= 0.0  ||  sy <= 0.0 ||
      (type != IMAGE_BINARY  &&  type != IMAGE_RGB)) {
    return NULL;
  }

  if (ptlStartRendering(type, sx, sy, xbase, ybase, width, height)) {
    return NULL;
  }

  if (type == IMAGE_RGB) {		     /* color */
    RGBImage img;

    img = (RGBImage) imNewOffset(IMAGE_RGB, width, height, xbase, ybase);

    if (img == (RGBImage) NULL) {
      return NULL;
    }

    imInit(img, bcolor);    
    image  = (void *) img;
    header = img->header;
  }
  else {				     /* binary */
    BinaryImage img;

    img = (BinaryImage) imNewOffset(IMAGE_BINARY, width, height, xbase, ybase);

    if (img == (BinaryImage) NULL) {
      return NULL;
    }

    image  = (void *) img;
    header = img->header;
  }

  header->docstring = docstring;

  { PointListElem el;
    typeof (*el) elem;
    boolean error;
    
    while (ptlGetElem(&elem, iscolor, fp, &error) != (PointListElem) NULL) {
      boolean newwarn = ptl_RenderElem_(&elem, image, header, sx, sy);

      *warn = *warn || newwarn;
    }
  
    if (error) {
      im_Free_(image, header->tag);
      return NULL;
    }
  }

  return image;
}


void *ptlRender(PointList ptl, ImageType type, float sx, float sy, RGB bcolor,
		boolean *warn)
{
  List data;
  ListNode ln;
  int xbase, ybase, width, height;
  void *image;
  ImageHeader *header;
  boolean tmp_warn;


  if (warn == (boolean *) NULL) {
    warn = &tmp_warn;
  }

  xbase  = fround(ptl->xbase * sx);
  ybase  = fround(ptl->ybase * sy);
  width  = (int) ceil(ptl->width * sx);
  height = (int) ceil(ptl->height * sy);
  *warn  = FALSE;
  
  if (width <= 0  ||  height <= 0  ||  sx <= 0.0  ||  sy <= 0.0 ||
      (type != IMAGE_BINARY  &&  type != IMAGE_RGB)) {
    return NULL;
  }

  if (ptlStartRendering(type, sx, sy, xbase, ybase, width, height)) {
    return NULL;
  }

  if (type == IMAGE_RGB) {		     /* color */
    RGBImage img;

    img = (RGBImage) imNewOffset(IMAGE_RGB, width, height, xbase, ybase);

    if (img == (RGBImage) NULL) {
      return NULL;
    }

    imInit(img, bcolor);    
    image  = (void *) img;
    header = img->header;
  }
  else {				     /* binary */
    BinaryImage img;

    img = (BinaryImage) imNewOffset(IMAGE_BINARY, width, height, xbase, ybase);

    if (img == (BinaryImage) NULL) {
      return NULL;
    }

    image  = (void *) img;
    header = img->header;
  }

  data = ptl->data;
  foreach(ln, data) {
    PointListElem elem = (PointListElem) liGet(ln);
    boolean newwarn = ptl_RenderElem_(elem, image, header, sx, sy);

    *warn = *warn || newwarn;
  }
  header->docstring = strNewCopy(ptl->docstring);

  return image;
}


int ptl_RenderElem_(PointListElem elem, void *img,
		    ImageHeader *header, float sx, float sy)
{


  if (header->tag != IMAGE_RGB  &&  header->tag != IMAGE_BINARY) {
    return -1;
  }

  if (header->tag == IMAGE_RGB) {
    if (elem->type == PTL_POINT) {
      return plot_color_point((RGBImage) img, N_for_color, elem->data.point.x * sx,
			      elem->data.point.y * sy, elem->weight * (sx + sy)/2.0,
                              elem->color);
    }
    else if (elem->type == PTL_LINE) {
      return plot_color_line((RGBImage) img, N_for_color, elem->data.line.x1*sx,
			     elem->data.line.y1*sy, elem->data.line.x2*sx,
			     elem->data.line.y2*sy, elem->weight * (sx + sy)/2.0,
                             elem->color);
    }
    else if (elem->type == PTL_ELLIPSE) {
      return plot_color_ellipse((RGBImage) img, N_for_color,
                                elem->data.ellipse.x*sx, elem->data.ellipse.y*sy, 
                                elem->data.ellipse.a*sx, elem->data.ellipse.b*sy, 
                                elem->weight * (sx + sy)/2.0, elem->color);
    }
    else if (elem->type == PTL_FILL) {
      return do_color_fill((RGBImage) img, N_for_color, elem->data.ellipse.x*sx,
                           elem->data.ellipse.y*sy, elem->color);
    }
    else {
      return -1;
    }
  }
  else if (header->tag == IMAGE_BINARY) {
    if (elem->type == PTL_POINT) {
      return plot_binary_point((BinaryImage) img, elem->data.point.x*sx,
			       elem->data.point.y*sy, elem->weight * (sx + sy)/2.0);
    }
    else if (elem->type == PTL_LINE) {
      return plot_binary_line((BinaryImage) img, elem->data.line.x1*sx,
			      elem->data.line.y1*sy, elem->data.line.x2*sx,
			      elem->data.line.y2*sy, elem->weight * (sx + sy)/2.0);
    }
    else if (elem->type == PTL_ELLIPSE) {
      return plot_binary_ellipse((BinaryImage) img,
                                 elem->data.ellipse.x*sx, elem->data.ellipse.y*sy, 
                                 elem->data.ellipse.a*sx, elem->data.ellipse.b*sy, 
                                 elem->weight * (sx + sy)/2.0);
    }
    else if (elem->type == PTL_FILL) {
      return do_binary_fill((BinaryImage) img,
                            elem->data.ellipse.x*sx, elem->data.ellipse.y*sy);
    }
    else {
      return -1;
    }
  }
  else {
    return -1;
  }
}


int ptlStartRendering(ImageType type, float sx, float sy,
		      int xbase, int ybase, int width, int height)
{
  if (width <= 0  ||  height <= 0  ||  sx <= 0.0  ||  sy <= 0.0 || 
      (type != IMAGE_BINARY  &&  type != IMAGE_RGB)) {
    return -1;
  }

  if (type == IMAGE_RGB) {		     /* color */
    if (N_for_color == (GrayImage) NULL    ||
	imGetXBase(N_for_color)  != xbase  ||
	imGetYBase(N_for_color)  != ybase  ||
	imGetWidth(N_for_color)  != width  ||
	imGetHeight(N_for_color) != height) {
      if (N_for_color != (GrayImage) NULL)
	imFree(N_for_color);
      
      N_for_color = (GrayImage) imNewOffset(IMAGE_GRAY, width, height, xbase, ybase);

      if (N_for_color == (GrayImage) NULL)
	return -1;
    }

    imInit(N_for_color, 0);
  }

  return 0;
}


PointList ptlLoad(char *filename)
{
  FILE *fp;
  PointList ptl;


  fp = ((filename == (char *) NULL) ? stdin : fopen(filename, "r"));
  if (fp == (FILE *) NULL) {
    return (PointList) NULL;
  }

  ptl = ptlLoadF(fp);

  if (filename != (char *) NULL  &&  fclose(fp) == EOF  &&  ptl != (PointList) NULL) {
    ptlFree(ptl);
    return (PointList) NULL;
  }

  return ptl;
}


PointList ptlLoadF(FILE *fp)
{
  boolean iscolor;
  float xbase, ybase, width, height;
  String docstring = NULLSTRING;
  PointList ptl;
  PointListElem elem;
  boolean error;
  

  assert(fp != (FILE *) NULL);
  if (ptlReadHeader(fp, &iscolor, &xbase, &ybase, &width, &height, &docstring)) {
    return (PointList) NULL;
  }

  ptl = ptlNew(iscolor, xbase, ybase, width, height, docstring);
  if (ptl == (PointList) NULL) {
    return (PointList) NULL;
  }

  while ((elem = ptlGetElem(NULL, iscolor, fp, &error)) != (PointListElem) NULL) {
    ptlAddElem(ptl, elem);
  }

  if (error) {
    ptlFree(ptl);
    return (PointList) NULL;
  }

  return ptl;  
}


PointListElem ptlGetElem(PointListElem elem, boolean iscolor, FILE *fp, boolean *err)
{
  float weight, x1, y1, x2, y2;
  uint rawcolor;
  RGB color;
  ptlElemType type;
  static char line[MXLINE];
  char *linep;
  int nchars;
#if 0
  extern double strtod(const char *s, char **p);
  extern char tolower(char c);
#endif

  assert(fp != (FILE *) NULL);
  *err = FALSE;

 try_next_line:

  if (getline(line, fp) == NULL) {	     /* end of input */
    return (PointListElem) NULL;
  }

  weight = (float) strtod(line, &linep);
  rawcolor = 0xFFFFFF;
  x2 = y2 = 0;

  while (isspace(*linep))
    linep++;

  if (tolower(*linep) == 'p') {		     /* point */
    type = PTL_POINT;

    if (sscanf(linep+1, "%f %f%n", &x1, &y1, &nchars) != 2) {
      goto error;
    }
  }
  else if (tolower(*linep) == 'l') {	     /* line */
    type = PTL_LINE;

    if (sscanf(linep+1, "%f %f %f %f%n", &x1, &y1, &x2, &y2, &nchars) != 4) {
      goto error;
    }
  }
  else if (tolower(*linep) == 'e') {	     /* ellipse */
    type = PTL_ELLIPSE;

    if (sscanf(linep+1, "%f %f %f %f%n", &x1, &y1, &x2, &y2, &nchars) != 4) {
      goto error;
    }
  }
  else if (tolower(*linep) == 'f') {	     /* fill */
    type = PTL_FILL;

    if (sscanf(linep+1, "%f %f%n", &x1, &y1, &nchars) != 2) {
      goto error;
    }
  }
  else {				     /* error */
    goto try_next_line;     /* was error, but dph complained... */
  }                         /* really should pass warning back ... */
    
  if (iscolor  &&  sscanf(linep+1+nchars, "%i", &rawcolor) != 1) {
    goto error;
  }
  
  color.r = (uchar) ((rawcolor >> 16) & 0xFF);
  color.g = (uchar) ((rawcolor >>  8) & 0xFF);
  color.b = (uchar) (rawcolor & 0xFF);
  
  if (elem == (PointListElem) NULL) {
    return ptlNewElem(type, color, weight, x1, y1, x2, y2);
  }
  else {
    if (ptlSetElem(elem, type, color, weight, x1, y1, x2, y2)) { /* error */
      goto error;
    }
    else {
      return elem;
    }
  }

 error:
  *err = TRUE;
  return (PointListElem) NULL;
}


int ptlReadHeader(FILE *fp, boolean *iscolor, float *xbase, float *ybase,
		  float *width, float *height, String *docstring)
{
  static String magic = NULL;

  if (magicStart()                   ||
      magicGetString(fp, &magic)     ||
      (!strcmp(strGet(magic), "PTL")  &&  !strcmp(strGet(magic), "CPTL")) ||
      magicGetFloat(fp, xbase)       ||
      magicGetFloat(fp, ybase)       ||
      magicGetFloat(fp, width)       ||
      magicGetFloat(fp, height)) {
    return -1;
  }

  *iscolor = !strcmp(strGet(magic), "CPTL");

  if (docstring != (String *) NULL) {
    *docstring = magicComment();
  }

  return 0; 
}


PointList ptlNew(boolean wantcolor, float xbase, float ybase, float width,
		 float height, String docstring)
{
  PointList ptl;
  
  if (pointlistchunk == NULLCHUNK) {
    PointList ptl;
    pointlistchunk = chNew(sizeof(*ptl));
  }

  if (width <= 0  ||  height <= 0) {
    return (PointList) NULL;
  }

  ptl = (PointList) chAlloc(pointlistchunk);

  if (ptl != (PointList) NULL) {
    ptl->xbase     = xbase;
    ptl->ybase     = ybase;
    ptl->width     = width;
    ptl->height    = height;
    ptl->docstring = docstring;
    ptl->iscolor   = wantcolor;
    ptl->data      = liNew();

    if (ptl->data == NULLLIST) {
      chFree(pointlistchunk, (void *) ptl);
      return (PointList) NULL;
    }
  }

  return ptl;
}


int ptlSetElem(PointListElem elem, ptlElemType type, RGB color,
	       float weight, float x1, float y1, float x2, float y2)
{
  assert(elem != (PointListElem) NULL);

  if (!ptlGoodElemType(type)) {
    return -1;
  }
  
  elem->type   = type;
  elem->weight = weight < 0.0 ? 0.0 : weight;
  elem->color  = color;

  if (type == PTL_POINT) {		     /* point */
    elem->data.point.x = x1;
    elem->data.point.y = y1;
  }
  else if (type == PTL_LINE) {               /* line */
    elem->data.line.x1 = x1;
    elem->data.line.y1 = y1;
    elem->data.line.x2 = x2;
    elem->data.line.y2 = y2;
  }
  else if (type == PTL_ELLIPSE) {            /* ellipse */
    elem->data.ellipse.x = x1;
    elem->data.ellipse.y = y1;
    elem->data.ellipse.a = x2;
    elem->data.ellipse.b = y2;
  }
  else if (type == PTL_FILL) {               /* fill */
    elem->data.fill.x = x1;
    elem->data.fill.y = y1;
  }

  return 0;
}


PointListElem ptlNewElem(ptlElemType type, RGB color, float weight,
			 float x1, float y1, float x2, float y2)
{
  PointListElem elem;
  
  if (elementchunk == NULLCHUNK) {
    PointListElem e;
    elementchunk = chNew(sizeof(*e));
  }

  if (!ptlGoodElemType(type)) {
    return (PointListElem) NULL;
  }
  
  elem = (PointListElem) chAlloc(elementchunk);

  if (elem != (PointListElem) NULL) {
    ptlSetElem(elem, type, color, weight, x1, y1, x2, y2);
  }

  return elem;
}


void ptlFreeElem(PointListElem elem)
{
  assert(elementchunk != NULLCHUNK);

  if (elem != (PointListElem) NULL)
    chFree(elementchunk, (void *) elem);
}


void ptlFree(PointList ptl)
{
  ListNode ln;
  List data;

  assert(pointlistchunk != NULLCHUNK);

  if (ptl != (PointList) NULL) {
    data = ptlGetData(ptl);
    
    foreach(ln, data) {
      ptlFreeElem((PointListElem) liGet(ln));
    }

    liFree(data);

    if (ptlDocString(ptl) != (String) NULL) {
      strFree(ptlDocString(ptl));
    }

    chFree(pointlistchunk, (void *) ptl);
  }
}


void ptlAddElem(PointList ptl, PointListElem elem)
{
  assert(ptl != (PointList) NULL  &&  elem != (PointListElem) NULL);

  liAdd(ptl->data, (void *) elem);
}


int ptlSave(PointList ptl, char *filename)
{
  FILE *fp;

  assert(ptl != (PointList) NULL);

  fp = ((filename == (char *) NULL) ? stdout : fopen(filename, "w"));
  if (fp == (FILE *) NULL) {
    return(-1);
  }
  
  if (ptlSaveF(ptl, fp)  && filename != (char *) NULL) {
    fclose(fp);
    return -1;
  }

  if (filename != (char *) NULL  && fclose(fp) == EOF) {
    return -1;
  }

  return 0;
}


int ptlSaveF(PointList ptl, FILE *fp)
{
  List data;
  ListNode node;
  boolean iscolor;

  
  assert(ptl != (PointList) NULL);
  assert(fp != (FILE *) NULL);

  data  = ptlGetData(ptl);
  iscolor = ptlIsColor(ptl);

  if (ptlWriteHeader(fp, iscolor, ptlGetXBase(ptl), ptlGetYBase(ptl),
		     ptlGetWidth(ptl), ptlGetHeight(ptl), ptlDocString(ptl))) {
    return -1;
  }

  foreach(node, data) {
    PointListElem elem = (PointListElem) liGet(node);
    
    if (ptlPutElem(elem, iscolor, fp)) {
      return -1;
    }
  }

  return 0;
}


int ptlPutElem(PointListElem elem, boolean iscolor, FILE *fp)
{

  if (!ptlGoodElemType(elem->type)) {
    return -1;
  }

  if (elem->weight != 0.0) {
    if (fprintf(fp, "%f", elem->weight) == EOF) {
      return -1;
    }
  }
  
  if (elem->type == PTL_POINT) {
    if (fprintf(fp, "p\t%f %f", elem->data.point.x, elem->data.point.y) == EOF) {
      return -1;
    }      
  }
  else if (elem->type == PTL_LINE) {
    if (fprintf(fp, "l\t%f %f %f %f", elem->data.line.x1, elem->data.line.y1,
		elem->data.line.x2, elem->data.line.y2) == EOF) {
      return -1;
    }      
  }
  else if (elem->type == PTL_ELLIPSE) {
    if (fprintf(fp, "e\t%f %f %f %f", elem->data.ellipse.x, elem->data.ellipse.y,
		elem->data.ellipse.a, elem->data.ellipse.b) == EOF) {
      return -1;
    }      
  }
  else if (elem->type == PTL_FILL) {
    if (fprintf(fp, "f\t%f %f", elem->data.fill.x, elem->data.fill.y) == EOF) {
      return -1;
    }      
  }

  if (iscolor) {
    uint rawcolor = (elem->color.r << 16) | (elem->color.g << 8) | (elem->color.b);
    if (fprintf(fp, "\t0x%6.6x\n", rawcolor & 0xFFFFFF) == EOF) {
      return -1;
    }
  }
  else {
    if (fprintf(fp, "\n") == EOF) {
      return -1;
    }
  }

  return 0;
}


int ptlWriteHeader(FILE *fp, boolean iscolor, float xbase, float ybase,
		   float width, float height, String docstring)
{
  char *doc;

  
  if (fp == (FILE *) NULL) {
    return -1;
  }

  if (fprintf(fp, "%sPTL %f %f %f %f\n\n", (iscolor ? "C" : ""),
	      xbase, ybase, width, height) == EOF) {
    return -1;
  }

  doc = strGet(docstring);
  if (doc != (char *) NULL) {
    if (putc('#', fp) == EOF)
      return (-1);
    
    for ( ; *doc != (char) 0; doc++) {
      if (putc(*doc, fp) == EOF)
	return (-1);

      if ((*doc == '\n'  ||  *doc == '\r') && doc[1] != '#') {
	if (putc('#', fp) == EOF)
	  return (-1);
      }
    }

    if (putc('\n', fp) == EOF  ||  putc('\n', fp) == EOF)
      return (-1);
  }

  return 0;
}

  

/*******************************  Static Functions **********************************/

static void trimline(register char *line)    /* kill whitespace at begining of line */
{
  register char *l = line;

  while (*l && isspace(*l)) l++;
  while ((*(line++) = *(l++))) ;
}

static char *getline(char *line, FILE *fp)   /* get next line with data */
{
  do {
    if (fgets(line, MXLINE, fp) == NULL)     /* EOF or error */
      return (NULL);
    trimline(line);
  } while (*line == '\0' || *line == '#');   /* skip blanks and comments */

  return (line);
}


static void rgb_to_hsv(float r, float g, float b, float *h, float *s, float *v)
{
  float max, min, delta;

  max = MAX(r, MAX(g, b));
  min = MIN(r, MIN(g, b));

  *v = max;

  if (max != 0.0)
    *s = (max - min)/max;
  else
    *s = 0.0;

  if (*s == 0.0)
    *h = -1.0;
  else
  {
    delta = max - min;
    if (r == max)
      *h = (g - b)/delta;
    else if (g == max)
      *h = 2.0 + (b - r)/delta;
    else if (b == max)
      *h = 4.0 + (r - g)/delta;

    *h *= 60.0;
    if (*h < 0.0)
      *h += 360.0;
  }
}


static void hsv_to_rgb(float *r, float *g, float *b, float h, float s, float v)
{
  float i, f, p, q, t;

  if (s == 0.0)
    *r = *g = *b = v;
  else
  {
    if (h >= 360.0)
      h -= 360.0;

    h /= 60.0;
    i = floor(h);
    f = h - i;
    p = v*(1.0 - s);
    q = v*(1.0 - s*f);
    t = v*(1.0 - s*(1.0 - f));

    switch ((int) i)
    {
    case 0: *r = v; *g = t; *b = p; break;
    case 1: *r = q; *g = v; *b = p; break;
    case 2: *r = p; *g = v; *b = t; break;
    case 3: *r = p; *g = q; *b = v; break;
    case 4: *r = t; *g = p; *b = v; break;
    case 5: *r = v; *g = p; *b = q; break;
    default: assert(0); break;
    }
  }
}
 

static RGB combine(RGB rgb1, int n, RGB rgb2) /* combine two RGB values, weighting */
{					       /* the first by n */
  float h1, s1, v1, h2, s2, v2;
  float r, g, b;
  RGB result;


  if (n == 0) {
    return rgb2;
  }

  rgb_to_hsv(rgb1.r/255.0, rgb1.g/255.0, rgb1.b/255.0, &h1, &s1, &v1);
  rgb_to_hsv(rgb2.r/255.0, rgb2.g/255.0, rgb2.b/255.0, &h2, &s2, &v2);

  if      (h1 - h2 > 180.0)  h2 -= 360.0;
  else if (h2 - h1 > 180.0)  h2 += 360.0;
  h1 = (((float) n) * h1  +  h2)/((float) (n + 1));
  while (h1 >= 360.0)  h1 -= 360.0;
  while (h1 < 0.0)     h1 += 360.0;
    
  s1 = (((float) n)*s1 + s2)/((float) (n + 1));
  v1 = (((float) n)*v1 + v2)/((float) (n + 1));

  hsv_to_rgb(&r, &g, &b, h1, s1, v1);

  result.r = MAX(MIN(fround(256.0*r), 255), 0);
  result.g = MAX(MIN(fround(256.0*g), 255), 0);
  result.b = MAX(MIN(fround(256.0*b), 255), 0);

  return result;
}


static boolean plot_color_point_1(RGBImage img, GrayImage n, float xf, float yf, RGB rgb)
{
  int x, y;


  x = fround(xf);			     /* round coordinates */
  y = fround(yf);
  
  if (x < imGetXBase(img)  ||  y < imGetYBase(img)  ||
      x > imGetXMax(img)   ||  y > imGetYMax(img)) {
    return TRUE;
  }

  imRef(img, x, y)  = combine(imRef(img, x, y), imRef(n, x, y), rgb);
  imRef(n, x, y)   += 1;
  return FALSE;
}
  

static boolean plot_color_point(RGBImage img, GrayImage n, float xf, float yf,
                                float weight, RGB rgb)
{
  int w = (int) (0.5*weight + 0.5);
  int x, y;
  boolean err = FALSE;
  

  weight = SQUARE(0.5*weight + 0.5);
  err |= plot_color_point_1(img, n, xf, yf, rgb);
  for (x = 0; x < w; x++) {
    for (y = 0; y < w; y++) {
      if (x*x + y*y  <= weight) {
        err |= plot_color_point_1(img, n, xf + x, yf + y, rgb);
        err |= plot_color_point_1(img, n, xf + x, yf - y, rgb);
        err |= plot_color_point_1(img, n, xf - x, yf + y, rgb);
        err |= plot_color_point_1(img, n, xf - x, yf - y, rgb);
      }
    }
  }

  return err;
}


static void fswap(float *f1, float *f2)
{
  float tmp;

  tmp = *f1;
  *f1 = *f2;
  *f2 = tmp;
}


static boolean plot_color_line(RGBImage img, GrayImage n, float x1, float y1,
                               float x2, float y2, float weight, RGB rgb)
{
  float dx, dy, delta;
  boolean err = FALSE;


  delta = 1.0;				     /* iterate over axis with greater range */
  dx = x2 - x1;
  dy = y2 - y1;

  if ((dx == 0.0) && (dy == 0.0)) {
    return plot_color_point(img, n, x1, y1, weight, rgb);
  }
  else if (ABS(dx) > ABS(dy)) {		     /* iterate over x */
    float i, m = dy/dx;

    if (x1 > x2) {			     /* make sure x1 <= x2 */
      fswap(&x1, &x2);
      fswap(&y1, &y2);
    }

    for (i = x1; i <= x2; i += delta)	     /* plot each point */
      err |= plot_color_point(img, n, i, y1 + (i - x1)*m, weight, rgb);
  }
  else {				     /* iter over y */
    float i, m = dx/dy;

    if (y1 > y2) {			     /* make sure y1 <= y2 */
      fswap(&x1, &x2);
      fswap(&y1, &y2);
    }
    
    for (i = y1; i <= y2; i += delta)	     /* plot each point */
      err |= plot_color_point(img, n, x1 + (i - y1)*m, i, weight, rgb);
  }

  return err;
}


static boolean plot_binary_point_1(BinaryImage img, float xf, float yf)
{
  int x, y;


  x = (int) (xf + 0.5);			     /* round coordinates */
  y = (int) (yf + 0.5);

  if (x < imGetXBase(img)  ||  y < imGetYBase(img)  ||
      x > imGetXMax(img)   ||  y > imGetYMax(img)) {
    return TRUE;
  }

  imRef(img, x, y) = 1;			     /* plot the point */
  return FALSE;
}


static boolean plot_binary_point(BinaryImage img, float xf, float yf, float weight)
{
  int w = (int) (0.5*weight + 0.5);
  int x, y;
  boolean err = FALSE;
  

  weight = SQUARE(0.5*weight + 0.5);
  err |= plot_binary_point_1(img, xf, yf);
  for (x = 0; x < w; x++) {
    for (y = 0; y < w; y++) {
      if (x*x + y*y  <= weight) {
        err |= plot_binary_point_1(img, xf + x, yf + y);
        err |= plot_binary_point_1(img, xf + x, yf - y);
        err |= plot_binary_point_1(img, xf - x, yf + y);
        err |= plot_binary_point_1(img, xf - x, yf - y);
      }
    }
  }

  return err;
}


static boolean plot_binary_line(BinaryImage img, float x1, float y1, float x2, float y2,
                                float weight)
{
  float dx, dy, delta;  
  boolean err = FALSE;
  

  delta = 0.5;				     /* iterate over axis with greater range */
  dx = x2 - x1;
  dy = y2 - y1;

  if ((dx == 0.0) && (dy == 0.0)) {
    return plot_binary_point(img, x1, y1, weight);
  }
  else if (ABS(dx) > ABS(dy)) {		     /* iterate over x */
    float i, m = dy/dx;

    if (x1 > x2) {			     /* make sure x1 <= x2 */
      fswap(&x1, &x2);
      fswap(&y1, &y2);
    }

    for (i = x1; i <= x2; i += delta)	     /* plot each point */
      err = err || plot_binary_point(img, i, y1 + (i - x1)*m, weight);
  }
  else {				     /* iter over y */
    float i, m = dx/dy;

    if (y1 > y2) {			     /* make sure y1 <= y2 */
      fswap(&x1, &x2);
      fswap(&y1, &y2);
    }

    for (i = y1; i <= y2; i += delta)	     /* plot each point */
      err = err || plot_binary_point(img, x1 + (i - y1)*m, i, weight);
  }

  return err;
}


extern double atan2(double y, double x);


static boolean plot_color_ellipse(RGBImage img, GrayImage n, float x, float y,
                                  float a, float b, float weight, RGB rgb)
{
  boolean err = FALSE;
  float theta, xx, yy;
  const float pi_over_2 = M_PI/2.0;
  const float delta = (pi_over_2 /
                       ((int) (pi_over_2/atan2(1.0, ABS(MAX(a, b))) + 1.0)));

  
  for (theta = 0.0; theta < pi_over_2; theta += delta) {
    xx = a * cos(theta);
    yy = b * sin(theta);
    err |= plot_color_point(img, n, x + xx, y + yy, weight, rgb);
    err |= plot_color_point(img, n, x + xx, y - yy, weight, rgb);
    err |= plot_color_point(img, n, x - xx, y + yy, weight, rgb);
    err |= plot_color_point(img, n, x - xx, y - yy, weight, rgb);
  }

  return err;
}


static boolean plot_binary_ellipse(BinaryImage img,float x, float y,
                                   float a, float b, float weight)
{
  boolean err = FALSE;
  float theta, xx, yy;
  const float pi_over_2 = M_PI/2.0;
  const float delta = (pi_over_2 /
                       ((int) (pi_over_2/atan2(1.0, ABS(MAX(a, b))) + 1.0)));


  for (theta = 0.0; theta < pi_over_2; theta += delta) {
    xx = a * cos(theta);
    yy = b * sin(theta);
    err |= plot_binary_point(img, x + xx, y + yy, weight);
    err |= plot_binary_point(img, x + xx, y - yy, weight);
    err |= plot_binary_point(img, x - xx, y + yy, weight);
    err |= plot_binary_point(img, x - xx, y - yy, weight);
  }

  return err;
}


static boolean do_color_fill(RGBImage img, GrayImage n, float x, float y, RGB rgb)
{
  boolean err = FALSE;

  return err;
}


typedef struct {
  short x, y;
} StkElem;

static boolean do_binary_fill(BinaryImage img, float x, float y)
{
  StkElem *stack, *stktop;
  short xx, yy;


  stktop = stack = (StkElem *) malloc(imGetStoreLen(img) * sizeof(StkElem));

  if (stack == (StkElem *) NULL)
    return TRUE;

  xx = fround(x);
  yy = fround(y);
  *(stktop++) = ((StkElem) {xx, yy});

  while (stktop > stack) {
    stktop--;
    xx = stktop->x;
    yy = stktop->y;
    imRef(img, xx, yy) = 1;

    if (xx > imGetXBase(img)  &&  imRef(img, xx-1, yy) == 0) 
      *(stktop++) = ((StkElem) {xx-1, yy});

    if (xx < imGetXMax(img)   &&  imRef(img, xx+1, yy) == 0) 
      *(stktop++) = ((StkElem) {xx+1, yy});

    if (yy > imGetYBase(img)  &&  imRef(img, xx, yy-1) == 0) 
      *(stktop++) = ((StkElem) {xx, yy-1});

    if (yy < imGetYMax(img)   &&  imRef(img, xx, yy+1) == 0) 
      *(stktop++) = ((StkElem) {xx, yy+1});
  }

  free(stack);

  return FALSE;
}
