The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
/*
 * Draw.c -- Implementation of common drawing routines.
 *
 * Authors              : Patrick Lecoanet.
 * Creation date        : Sat Dec 10 12:51:30 1994
 *
 * $Id: Draw.c,v 1.64 2005/09/12 13:19:13 Lecoanet Exp $
 */

/*
 *  Copyright (c) 1993 - 2005 CENA, Patrick Lecoanet --
 *
 * See the file "Copyright" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 */


/*
 **********************************************************************************
 *
 * The algorihms used to draw the arrows, to do the 3d effects and to
 * smooth the polygons are adapted from Tk.
 *
 **********************************************************************************
 */

#include "Types.h"
#include "Draw.h"
#include "Geo.h"
#include "List.h"
#include "WidgetInfo.h"
#include "Image.h"
#include "tkZinc.h"

#include <math.h>
#include <stdarg.h>


#define POLYGON_RELIEF_DRAW     0
#define POLYGON_RELIEF_RENDER   1
#define POLYGON_RELIEF_DIST     2
#define POLYGON_RELIEF_BBOX     3
#define POLYGON_RELIEF_IN_BBOX  4

#define TOP_CONTRAST            13
#define BOTTOM_CONTRAST         6
#define MAX_INTENSITY           65535

#define ARROW_SHAPE_B           10.0
#define ARROW_SHAPE_C           5.0
#define OPEN_ARROW_SHAPE_A      4.0
#define CLOSED_ARROW_SHAPE_A    ARROW_SHAPE_B

#define LIGHTNING_SHAPE_A_RATIO 10.0
#define LIGHTNING_SHAPE_B_RATIO 8.0

#define LIGHTNING_POINTS        4
#define CORNER_POINTS           3
#define DOUBLE_CORNER_POINTS    4
#define STRAIGHT_POINTS         2


/*
 **********************************************************************************
 *
 * ZnSetLineStyle -- 
 *
 **********************************************************************************
 */
void
ZnSetLineStyle(ZnWInfo     *wi,
               ZnLineStyle line_style)
{
  if (wi->render) {
#ifdef GL
    switch (line_style) {
    case ZN_LINE_DASHED :
      glLineStipple(1, 0xF0F0);      
      glEnable(GL_LINE_STIPPLE);
      break;
    case ZN_LINE_MIXED :
      glLineStipple(1, 0x27FF);      
      glEnable(GL_LINE_STIPPLE);
      break;
    case ZN_LINE_DOTTED :
      glLineStipple(1, 0x18C3);      
      glEnable(GL_LINE_STIPPLE);
      break;
    default:
      glDisable(GL_LINE_STIPPLE);
    }
#endif
  }
  else {
    XGCValues           values;
    static const char   dashed[] = { 8 };
    static const char   dotted[] = { 2, 5 };
    static const char   mixed[]  = { 8, 5, 2, 5 };
    
    values.line_style = LineOnOffDash;
    switch (line_style) {
    case ZN_LINE_DASHED :
      XSetDashes(wi->dpy, wi->gc, 0, dashed, 1);
      break;
    case ZN_LINE_MIXED :
      XSetDashes(wi->dpy, wi->gc, 0, mixed, 4);
      break;
    case ZN_LINE_DOTTED :
      XSetDashes(wi->dpy, wi->gc, 0, dotted, 2);
      break;
    default:
      values.line_style = LineSolid;
      break;
    }
    XChangeGC(wi->dpy, wi->gc, GCLineStyle, &values);
  }
}


/*
 **********************************************************************************
 *
 * ZnLineShapePoints --
 *      Compute the points describing the given line shape between point p1 and p2.
 *      If bbox is non null, it is filled with the bounding box of the shape.
 *
 * For the time being this procedure handles straight lines, right and left
 * lightnings, right and left corners, right and left double corners..
 *
 *
 * Here are the parameters for lightnings:
 *
 *                                        *******
 *                                 *******     *
 *                           ******           *
 *                     ******      ******+   *
 *               ******      ******     *   *|
 *         ******      ******          *   * | LIGHTNING_SHAPE_A
 *   ******      ******               *   *  |
 *         ******                    *   *   |
 * ..******.........................+.+.*........................******..
 *   |                             *   *                    ******
 *   |                            *   *               ******      ******
 *   |                           *   *          ******      ******
 *   |                          *   *     ******      ******
 *   |                         *   *******      ******
 *   |                        *          ******
 *   |                       *     ******
 *   |                      ********   
 *   |                         |    | |
 *   |                         |----| | LIGHTNING_SHAPE_B
 *   |                                |
 *   |--------------------------------| LENGTH / 2
 *
 **********************************************************************************
 */
void
ZnLineShapePoints(ZnPoint       *p1,
                  ZnPoint       *p2,
                  ZnDim         line_width,
                  ZnLineShape   shape,
                  ZnBBox        *bbox,
                  ZnList        to_points)
{
  ZnPoint       *points;
  unsigned int  num_points, i;

  /*
   * Compute all line points according to shape.
   */
  if ((shape == ZN_LINE_LEFT_LIGHTNING) ||
      (shape == ZN_LINE_RIGHT_LIGHTNING)) {
    double      alpha, theta;
    double      length, length2;
    double      shape_a, shape_b;
    double      dx, dy;
    double      temp;

    num_points = LIGHTNING_POINTS;
    ZnListAssertSize(to_points, num_points);
    points = (ZnPoint *) ZnListArray(to_points);

    points[0] = *p1;
    points[3] = *p2;

    dx = p2->x - p1->x;
    dy = p2->y - p1->y;
    length = hypot(dx, dy);
    shape_a = length/LIGHTNING_SHAPE_A_RATIO + line_width/2.0;
    shape_b = length/LIGHTNING_SHAPE_B_RATIO + line_width/2.0;

    if (shape == ZN_LINE_LEFT_LIGHTNING)
      alpha = atan2(shape_a, shape_b);
    else
      alpha = -atan2(shape_a, shape_b);
    length2 = hypot(shape_a, shape_b);
    theta = atan2(-dy, dx);

    dx = p1->x + dx/2;
    dy = p1->y + dy/2;
    temp = cos(theta + alpha) * length2;
    points[1].x = dx + temp;
    points[2].x = dx - temp;
    temp = sin(theta + alpha) * length2;
    points[1].y = dy - temp;
    points[2].y = dy + temp;
  }
  else if (shape == ZN_LINE_LEFT_CORNER ||
           shape == ZN_LINE_RIGHT_CORNER) {
    num_points = CORNER_POINTS;
    ZnListAssertSize(to_points, num_points);
    points = (ZnPoint *) ZnListArray(to_points);

    points[0] = *p1;
    points[2] = *p2;

    if (shape == ZN_LINE_LEFT_CORNER) {
      points[1].x = p1->x;
      points[1].y = p2->y;
    }
    else {
      points[1].x = p2->x;
      points[1].y = p1->y;
    }
  }
  else if (shape == ZN_LINE_DOUBLE_LEFT_CORNER ||
           shape == ZN_LINE_DOUBLE_RIGHT_CORNER) {
    int dx, dy;

    num_points = DOUBLE_CORNER_POINTS;
    ZnListAssertSize(to_points, num_points);
    points = (ZnPoint *) ZnListArray(to_points);

    points[0] = *p1;
    points[3] = *p2;

    if (shape == ZN_LINE_DOUBLE_LEFT_CORNER) {
      dy = (int) (p2->y - p1->y);
      points[1].x = p1->x;
      points[2].x = p2->x;
      points[1].y = points[2].y = p1->y + dy/2;
    }
    else {
      dx = (int) (p2->x - p1->x);
      points[1].x = points[2].x = p1->x + dx/2;
      points[1].y = p1->y;
      points[2].y = p2->y;
    }
  }
  else /* if (shape) == ZN_LINE_STRAIGHT) */ {
    num_points = STRAIGHT_POINTS;
    ZnListAssertSize(to_points, num_points);
    points = (ZnPoint *) ZnListArray(to_points);

    points[0] = *p1;
    points[1] = *p2;
  }

  /*
   * Fill in the bbox, if requested.
   */
  if (bbox) {
    ZnResetBBox(bbox);
    for (i = 0; i < num_points; i++) {
      ZnAddPointToBBox(bbox, points[i].x, points[i].y);
    }
    
    /* Enlarge to take line_width into account. */
    if (line_width > 1) {
      ZnDim lw_2 = (line_width+1)/2;
      
      bbox->orig.x -= lw_2;
      bbox->orig.y -= lw_2;
      bbox->corner.x += lw_2;
      bbox->corner.y += lw_2;
    }
  }
}


/*
 **********************************************************************************
 *
 * ZnDrawLineShape --
 *      Draw a line given the points describing its path. It is designed to work
 *      with GetLineShape albeit it does fairly trivial things. In the future some
 *      shapes might need cooperation between the two and the clients will be ready
 *      for that.
 *
 *
 **********************************************************************************
 */
void
ZnDrawLineShape(ZnWInfo         *wi,
                ZnPoint         *p,
                unsigned int    num_p,
                ZnLineStyle     line_style,
                int             foreground_pixel,
                ZnDim           line_width,
                ZnLineShape     shape)
{
  XPoint        *xpoints;
  unsigned int  i;
  XGCValues     values;

  /*
   * Setup GC.
   */
  ZnSetLineStyle(wi, line_style);
  values.foreground = foreground_pixel;
  values.line_width = (line_width == 1) ? 0 : (int) line_width;
  values.fill_style = FillSolid;
  values.join_style = JoinRound;
  values.cap_style = CapRound;
  XChangeGC(wi->dpy, wi->gc,
            GCFillStyle|GCLineWidth|GCJoinStyle|GCCapStyle|GCForeground, &values);
  ZnListAssertSize(ZnWorkXPoints, num_p);
  xpoints = (XPoint *) ZnListArray(ZnWorkXPoints);
  for (i = 0; i < num_p; i++) {
    xpoints[i].x = (short) p[i].x;
    xpoints[i].y = (short) p[i].y;
  }
  XDrawLines(wi->dpy, wi->draw_buffer, wi->gc, xpoints, (int) num_p, CoordModeOrigin);
}


/*
 **********************************************************************************
 *
 * ZnGetLineEnd --
 *      Compute the points describing the given line end style at point p1 for
 *      the line p1,p2. Point p1 is adjusted to fit the line end.
 *      If bbox is non null, it is filled with the bounding box of the end.
 *
 * For the time being this procedure handles open/filled arrows.
 *
 * Here are the parameters describing arrows.
 *
 *              *  | ARROW_SHAPE_C
 *            **   |
 *          * ***************************
 *        *  *
 *      *   *  +p1                      +p2
 *      | * |*
 *      |   * ***************************
 *      |   | **
 *      |   |   *
 *      |   |   |
 *      |---|   | ARROW_SHAPE_A
 *      |       |
 *      |-------| ARROW_SHAPE_B
 *
 **********************************************************************************
 */
void
ZnGetLineEnd(ZnPoint    *p1,
             ZnPoint    *p2,
             ZnDim      line_width,
             int        cap_style,
             ZnLineEnd  end_style,
             ZnPoint    *points)
{
  ZnReal        dx, dy, length, temp, backup;
  ZnReal        frac_height, sin_theta, cos_theta;
  ZnReal        vert_x, vert_y;
  ZnReal        shape_a, shape_b, shape_c;
  
  if (end_style != NULL) {
    shape_a = end_style->shape_a + 0.001;
    shape_b = end_style->shape_b + 0.001;
    shape_c = end_style->shape_c + line_width/2.0 + 0.001;

    frac_height = (line_width/2.0) / shape_c;
    dx = p1->x - p2->x;
    dy = p1->y - p2->y;
    length = hypot(dx, dy);
    if (length == 0) {
      sin_theta = cos_theta = 0.0;
    }
    else {
      sin_theta = dy/length;
      cos_theta = dx/length;
    }

    if (cap_style != CapProjecting) {
      temp = frac_height;
    }
    else {
      temp = line_width / shape_c;
    }
    backup = temp * shape_b + shape_a * (1.0 - temp) / 2.0;
    points[0].x = points[5].x = p1->x + backup * cos_theta;
    points[0].y = points[5].y = p1->y + backup * sin_theta;
    
    vert_x = points[0].x - shape_a*cos_theta;
    vert_y = points[0].y - shape_a*sin_theta;
    temp = shape_c*sin_theta;
    points[1].x = ZnNearestInt(points[0].x - shape_b*cos_theta + temp);
    points[4].x = ZnNearestInt(points[1].x - 2*temp);
    temp = shape_c*cos_theta;
    points[1].y = ZnNearestInt(points[0].y - shape_b*sin_theta - temp);
    points[4].y = ZnNearestInt(points[1].y + 2*temp);
    points[2].x = ZnNearestInt(points[1].x*frac_height + vert_x*(1.0-frac_height));
    points[2].y = ZnNearestInt(points[1].y*frac_height + vert_y*(1.0-frac_height));
    points[3].x = ZnNearestInt(points[4].x*frac_height + vert_x*(1.0-frac_height));
    points[3].y = ZnNearestInt(points[4].y*frac_height + vert_y*(1.0-frac_height));
  }
}

static ZnReal
SegmentPosInRelief(ZnReal               x1,
                     ZnReal             y1,
                     ZnReal             x2,
                     ZnReal             y2,
                     ZnReliefStyle      relief,
                     int                light_angle)
{
  ZnReal        angle, angle_step, origin, position;
  int           num_colors, color_index;
  
  num_colors = ZN_RELIEF_STEPS*2+1;
  angle_step = M_PI / (num_colors-1);
  origin = -(ZnDegRad(light_angle))-(angle_step/2.0);
  if (relief == ZN_RELIEF_SUNKEN) {
    origin += M_PI;
  }

  angle = ZnProjectionToAngle(y1 - y2, x2 - x1) + M_PI - origin;
  while (angle < 0.0) {
    angle += 2*M_PI;
  }
  while (angle > 2*M_PI) {
    angle -= 2*M_PI;
  }     

  color_index = (int) (angle/angle_step);
  if (color_index > num_colors-1) {
    color_index = 2*(num_colors-1)-color_index;
  }
  if ((color_index < 0) || (color_index >= num_colors)) {
    fprintf(stderr, "Color index out of gradient (should not happen).\n");
    if (color_index < 0) {
      color_index = 0;
    }
    if (color_index >= num_colors) {
      color_index = num_colors-1;
    }
  }
  position = 100*color_index/num_colors;
  /*printf("position %g, angle %g(%g), origin %g\n",
         position,
         RadianToDegrees(angle),
         angle,
         RadianToDegrees(origin));*/
  return position;
}

/*
 * ReliefColorOfSegment --
 * ReliefPixelOfSegment --
 */
static XColor *
ReliefColorOfSegment(ZnReal             x1,
                     ZnReal             y1,
                     ZnReal             x2,
                     ZnReal             y2,
                     ZnReliefStyle      relief,
                     ZnGradient         *gradient,
                     int                light_angle)
{
  return ZnGetGradientColor(gradient,
                            SegmentPosInRelief(x1, y1, x2, y2, relief, light_angle),
                            NULL);
}

static int
ReliefPixelOfSegment(ZnReal             x1,
                     ZnReal             y1,
                     ZnReal             x2,
                     ZnReal             y2,
                     ZnReliefStyle      relief,
                     ZnGradient         *gradient,
                     int                light_angle)
{
  return ZnGetGradientPixel(gradient,
                            SegmentPosInRelief(x1, y1, x2, y2, relief, light_angle));
}


/*
 **********************************************************************************
 *
 * ZnDrawRectangleRelief --
 *      Draw the bevels inside bbox.
 *
 **********************************************************************************
 */
void
ZnDrawRectangleRelief(ZnWInfo           *wi,
                      ZnReliefStyle     relief,
                      ZnGradient        *gradient,
                      XRectangle        *bbox,
                      ZnDim             line_width)
{
  XPoint        bevel[4];
  
  /*
   * If we haven't enough space to draw, exit.
   */
  if ((bbox->width < 2*line_width) || (bbox->height < 2*line_width)) {
    return;
  }
  
  /*
   * Grooves and ridges are drawn with two recursives calls with
   * half the width of the original one.
   */
  if ((relief == ZN_RELIEF_RIDGE) || (relief == ZN_RELIEF_GROOVE)) {
    ZnDim        new_line_width;
    unsigned int offset;
    XRectangle   internal_bbox;
    
    new_line_width = line_width/2.0;
    offset = (unsigned) (line_width - new_line_width);
    ZnDrawRectangleRelief(wi,
                          (ZnReliefStyle) ((relief==ZN_RELIEF_GROOVE)?ZN_RELIEF_SUNKEN:ZN_RELIEF_RAISED),
                          gradient, bbox, new_line_width);
    internal_bbox = *bbox;
    internal_bbox.x += offset;
    internal_bbox.y += offset;
    internal_bbox.width -= offset*2;
    internal_bbox.height -= offset*2;
    ZnDrawRectangleRelief(wi,
                          (ZnReliefStyle) ((relief==ZN_RELIEF_GROOVE)?ZN_RELIEF_RAISED:ZN_RELIEF_SUNKEN),
                          gradient, &internal_bbox, new_line_width);
    return;
  }

  XSetFillStyle(wi->dpy, wi->gc, FillSolid);
  
  bevel[0].x = bbox->x;
  bevel[0].y = bevel[1].y = bbox->y;
  bevel[1].x = bbox->x + bbox->width;
  bevel[2].y = bevel[3].y = bbox->y + (short) line_width;
  bevel[2].x = bevel[1].x - (short) line_width;
  bevel[3].x = bevel[0].x + (short) line_width;  
  XSetForeground(wi->dpy, wi->gc,
                 ReliefPixelOfSegment((ZnReal) bevel[1].x, (ZnReal) bevel[1].y,
                                      (ZnReal) bevel[0].x, (ZnReal) bevel[0].y,
                                      relief, gradient, wi->light_angle));
  XFillPolygon(wi->dpy, wi->draw_buffer, wi->gc, bevel, 4, Convex, CoordModeOrigin);

  bevel[0] = bevel[1];
  bevel[3] = bevel[2];
  bevel[1].y += bbox->height;
  bevel[2].y = bevel[1].y - (short) line_width;
  XSetForeground(wi->dpy, wi->gc,
                 ReliefPixelOfSegment((ZnReal) bevel[1].x, (ZnReal) bevel[1].y,
                                      (ZnReal) bevel[0].x, (ZnReal) bevel[0].y,
                                      relief, gradient, wi->light_angle));
  XFillPolygon(wi->dpy, wi->draw_buffer, wi->gc, bevel, 4, Convex, CoordModeOrigin);

  bevel[0] = bevel[1];
  bevel[3] = bevel[2];
  bevel[1].x -= bbox->width;
  bevel[2].x = bevel[1].x + (short) line_width;
  XSetForeground(wi->dpy, wi->gc,
                 ReliefPixelOfSegment((ZnReal) bevel[1].x, (ZnReal) bevel[1].y,
                                      (ZnReal) bevel[0].x, (ZnReal) bevel[0].y,
                                      relief, gradient, wi->light_angle));
  XFillPolygon(wi->dpy, wi->draw_buffer, wi->gc, bevel, 4, Convex, CoordModeOrigin);

  bevel[0] = bevel[1];
  bevel[3] = bevel[2];
  bevel[1].x = bbox->x;
  bevel[1].y = bbox->y;
  bevel[2].x = bevel[3].x;
  bevel[2].y = bbox->y + (short) line_width;
  XSetForeground(wi->dpy, wi->gc,
                 ReliefPixelOfSegment((ZnReal) bevel[1].x, (ZnReal) bevel[1].y,
                                      (ZnReal) bevel[0].x, (ZnReal) bevel[0].y,
                                      relief, gradient, wi->light_angle));
  XFillPolygon(wi->dpy, wi->draw_buffer, wi->gc, bevel, 4, Convex, CoordModeOrigin);
}


typedef struct {
  ZnWInfo       *wi;
  ZnPoint       *pp;
  ZnPoint       *p0;
  ZnPoint       *p1;
  double        dist;
  ZnBBox        *bbox;
  ZnReliefStyle relief;
  ZnGradient    *gradient;
  unsigned short alpha;
  ZnBool        smooth;
  int           result;
  int           count;
  ZnBool        toggle;
} PolygonData;

static void
DoPolygon(ZnPoint       *p,
          unsigned int  num_points,
          ZnDim         line_width,
          ZnBool        (*cb)(ZnPoint *bevels, PolygonData *pd),
          PolygonData   *pd)
{
  int           i;
  unsigned int  processed_points;
  ZnPoint       *p1, *p11=NULL, *p2;
  ZnPoint       pp1, pp2, new_pp1, new_pp2;
  ZnPoint       perp, c, shift1, shift2;
  ZnPoint       bevel_points[4];
  ZnBool        folded, closed, colinear;
  ZnReal        dx, dy;

  if (num_points < 2) {
    return;
  }

  /*
   * If the polygon is closed (last point is the same as first) open it by
   * dropping the last point. The algorithm closes the path automatically.
   * We remember this to decide if we draw the last bevel or not and if we
   * need to generate ends perpendicular to the path..
   */
  closed = False;
  if ((p->x == p[num_points-1].x) && (p->y == p[num_points-1].y)) {
    closed = True;
    num_points--;
  }
  /*printf("num_points=%d(%s)\n", num_points, closed?"closed":"");*/
  
  /*
   * We loop on all vertices of the polygon.
   * At each step we try to compute the corresponding border
   * corner `corner'. Then we build a polygon for the bevel.
   * Things look like this:
   *
   *          bevel[1]     /
   *             *        /
   *             |       /
   *             |      /
   *         pp1 *    * p[i-1]
   *             |    | bevel[0]
   *             |    |
   *             |    |
   *             |    | bevel[3]
   *             |    | p[i]
   *             |    | p1                 p2
   *         pp2 *    *--------------------*
   *             |
   *             |
   *      corner *----*--------------------*
   *     bevel[2]   new_pp1             new_pp2
   *
   * pp1 and pp2 are the ends of a segment // to p1 p2 at line_width
   * from it. These points are *NOT* necessarily on the perpendicular
   * going through p1 or p2.
   * This loop needs a bootstrap phase of two iterations (i.e we need to
   * process two points). This is why we start at the point before the last
   * and then wrap to the first point.
   * The algorithm discards any duplicate contiguous points.
   * It makes a special case if two consecutives edges are folded:
   *
   *  bevel[1]      pp1            pp2        a bevel[2]
   *    *-----------*--------------*----------*
   *                                           \
   *                                            \
   *     p[i-1]                                  \  bevel[3]
   *       *--------*-------------------------*---* corner
   *    bevel[0]    p2                       p1  /
   *                                            /
   *                                           /
   *      ----------*-----------*-------------*
   *             new_pp1     new_pp2          c
   *
   * In such a case we need to compute a, c, corner from pp1, pp2, new_pp1
   * and new_pp2. We compute the perpendicular to p1,p2 through p1, intersect
   * it with pp1,pp2 to obtain a, intersect it with new_pp1, new_pp2 to
   * obtain c, shift a,c and intersect it with p1,p2 to obtain corner.
   *
   */

  processed_points = 0;
  if (!closed) {
    i = 0;
    p1 = p;
  }
  else {
    i = -2;
    p1 = &p[num_points-2];
  }

  for (p2 = p1+1; i < (int) num_points; i++, p2++) {
    /*
     * When it is time to wrap, do it
     */
    if ((i == -1) || (i == (int) num_points-1)) {
      p2 = p;
    }
    /*
     * Skip over close vertices.
     */
    dx = p2->x - p1->x;
    dy = p2->y - p1->y;
    if ((ABS(dx) < 1.0) && (ABS(dy) < 1.0)) {
      continue;
    }

    ZnShiftLine(p1, p2, line_width, &new_pp1, &new_pp2);
    bevel_points[3] = *p1;
    folded = False;
    colinear = False;
    /*
     * The first two cases are for `open' polygons. We compute
     * a bevel closure that is perpendicular to the path.
     */
    if ((processed_points == 0) && !closed) {
      perp.x = p1->x + (p2->y - p1->y);
      perp.y = p1->y - (p2->x - p1->x);
      ZnIntersectLines(p1, &perp, &new_pp1, &new_pp2, &bevel_points[2]);
    }
    else if ((processed_points == num_points-1) && !closed) {
      perp.x = p1->x + (p11->y - p1->y);
      perp.y = p1->y - (p11->x - p1->x);
      ZnIntersectLines(p1, &perp, &pp1, &pp2, &bevel_points[2]);      
    }
    else if (processed_points >= 1) {
      ZnReal    dotp, dist, odx, ody;
      
      /*
       * The dot product of the two faces tell if the are
       * folded or colinear. The
       */
      odx = p11->x - p1->x;
      ody = p11->y - p1->y;
      dotp = odx*dx + ody*dy;
      dist = ZnLineToPointDist(p11, p2, p1, NULL);
      if ((dist < 4.0) && (dotp <= 0)) {
        perp.x = p1->x + (p2->y - p1->y);
        perp.y = p1->y - (p2->x - p1->x);
        ZnIntersectLines(p1, &perp, &new_pp1, &new_pp2, &bevel_points[2]);
        colinear = True;
      }
      else {
        folded = !ZnIntersectLines(&new_pp1, &new_pp2, &pp1, &pp2, &bevel_points[2]);
        /*printf("new_pp1 %g@%g, new_pp2 %g@%g, pp1 %g@%g, pp2 %g@%g, inter %g@%g\n",
               new_pp1.x, new_pp1.y, new_pp2.x, new_pp2.y,
               pp1.x, pp1.y, pp2.x, pp2.y,
               bevel_points[2].x, bevel_points[2].y);*/
        folded = folded && (dotp < 0);
        if (folded) {
          /*printf("DoPolygonRelief: folded edges detected, %g@%g, %g@%g, %g@%g, %g@%g\n",
                 pp1.x, pp1.y, pp2.x, pp2.y, new_pp1.x, new_pp1.y,
                 new_pp2.x, new_pp2.y);*/
          perp.x = p1->x + (p2->y - p1->y);
          perp.y = p1->y - (p2->x - p1->x);
          ZnIntersectLines(p1, &perp, &pp1, &pp2, &bevel_points[2]);
          ZnIntersectLines(p1, &perp, &new_pp1, &new_pp2, &c);
          ZnShiftLine(p1, &perp, line_width, &shift1, &shift2);
          ZnIntersectLines(p1, p2, &shift1, &shift2, &bevel_points[3]);
        }
      }
    }

    if ((processed_points >= 2) || (!closed && (processed_points == 1))) {
      if ((processed_points == num_points-1) && !closed) {
        pd->p0 = pd->p1 = NULL;
      }
      else {
        pd->p0 = p1;
        pd->p1 = p2;
      }
      if ((*cb)(bevel_points, pd)) {
        return;
      }
    }
    
    p11 = p1;
    p1 = p2;
    pp1 = new_pp1;
    pp2 = new_pp2;
    bevel_points[0] = bevel_points[3];
    if (folded) {
      bevel_points[1] = c;
    }
    else if ((processed_points >= 1) || !closed) {
      bevel_points[1] = bevel_points[2];
    }

    processed_points++;
  }
}


/*
 **********************************************************************************
 *
 * ZnGetPolygonReliefBBox --
 *      Returns the bevelled polygon bounding box.
 *
 **********************************************************************************
 */
static ZnBool
PolygonBBoxCB(ZnPoint           *bevels,
              PolygonData       *pd)
{
  int    i;

  for (i = 0; i < 4; i++) {
    ZnAddPointToBBox(pd->bbox, bevels[i].x, bevels[i].y);
  }
  return 0;
}

void
ZnGetPolygonReliefBBox(ZnPoint          *points,
                       unsigned int     num_points,
                       ZnDim            line_width,
                       ZnBBox           *bbox)
{
  PolygonData   pd;

  pd.bbox = bbox;
  ZnResetBBox(bbox);
  DoPolygon(points, num_points, line_width, PolygonBBoxCB, &pd);
}


/*
 **********************************************************************************
 *
 * ZnPolygonReliefInBBox --
 *      Returns (-1) if the relief is entirely outside the bbox, (1) if it is
 *      entirely inside or (0) if in between
 *
 **********************************************************************************
 */
static ZnBool
PolygonInBBoxCB(ZnPoint         *bevels,
                PolygonData     *pd)
{
  if (pd->count == 0) {
    pd->count++;
    pd->result = ZnPolygonInBBox(bevels, 4, pd->bbox, NULL);
    if (pd->result == 0) {
      return 1;
    }
  }
  else {
    if (ZnPolygonInBBox(bevels, 4, pd->bbox, NULL) != pd->result) {
      pd->result = 0;
      return 1;
    }
  }
  return 0;
}

int
ZnPolygonReliefInBBox(ZnPoint           *points,
                      unsigned int      num_points,
                      ZnDim             line_width,
                      ZnBBox            *area)
{
  PolygonData   pd;

  pd.bbox = area;
  pd.count = 0;

  DoPolygon(points, num_points, line_width, PolygonInBBoxCB, &pd);

  return pd.result;
}


/*
 **********************************************************************************
 *
 * ZnPolygonReliefToPointDist --
 *      Returns the distance between the given point and
 *      the bevelled polygon.
 *
 **********************************************************************************
 */
static ZnBool
PolygonDistCB(ZnPoint           *bevels,
              PolygonData       *pd)
{
  double        new_dist;

  new_dist = ZnPolygonToPointDist(bevels, 4, pd->pp);
  if (new_dist < 0.0) {
    new_dist = 0.0;
  }
  if (new_dist < pd->dist) {
    pd->dist = new_dist;
  }
  return 0;
}

double
ZnPolygonReliefToPointDist(ZnPoint      *points,
                           unsigned int num_points,
                           ZnDim        line_width,
                           ZnPoint      *pp)
{
  PolygonData   pd;

  pd.dist = 1.0e40;
  pd.pp = pp;
  DoPolygon(points, num_points, line_width, PolygonDistCB, &pd);

  return pd.dist;
}


/*
 **********************************************************************************
 *
 * ZnDrawPolygonRelief --
 *      Draw the bevels around path.
 *
 **********************************************************************************
 */
static ZnBool
PolygonDrawCB(ZnPoint           *bevels,
              PolygonData       *pd)
{
  ZnWInfo       *wi = pd->wi;
  XPoint        bevel_xpoints[5];
  XGCValues     values;
  int           j;

  values.foreground = ReliefPixelOfSegment(bevels[0].x, bevels[0].y,
                                           bevels[3].x, bevels[3].y,
                                           pd->relief, pd->gradient,
                                           pd->wi->light_angle);

  values.fill_style = FillSolid;
  XChangeGC(wi->dpy, wi->gc, GCFillStyle|GCForeground, &values);
        
  for (j = 0; j < 4; j++) {
    bevel_xpoints[j].x = ZnNearestInt(bevels[j].x);
    bevel_xpoints[j].y = ZnNearestInt(bevels[j].y);
  }

  XFillPolygon(wi->dpy, wi->draw_buffer, wi->gc, bevel_xpoints, 4,
               Convex, CoordModeOrigin);

  return 0;
}

void
ZnDrawPolygonRelief(ZnWInfo             *wi,
                    ZnReliefStyle       relief,
                    ZnGradient          *gradient,
                    ZnPoint             *points,
                    unsigned int        num_points,
                    ZnDim               line_width)
{
  PolygonData   pd;

  pd.wi = wi;
  pd.gradient = gradient;

  /*
   * Grooves and ridges are drawn with two calls. The first
   * with the original width, the second with half the width.
   */
  if ((relief == ZN_RELIEF_RIDGE) || (relief == ZN_RELIEF_GROOVE)) {
    pd.relief = (relief==ZN_RELIEF_GROOVE)?ZN_RELIEF_RAISED:ZN_RELIEF_SUNKEN;
    DoPolygon(points, num_points, line_width, PolygonDrawCB, &pd);
    pd.relief = (relief==ZN_RELIEF_GROOVE)?ZN_RELIEF_SUNKEN:ZN_RELIEF_RAISED;
    DoPolygon(points, num_points, line_width/2, PolygonDrawCB, &pd);
  }
  else {
    pd.relief = relief;
    DoPolygon(points, num_points, line_width, PolygonDrawCB, &pd);
  }
}

/*
 **********************************************************************************
 *
 * ZnRenderPolygonRelief --
 *      Draw the bevels around path using alpha enabled rendering.
 *
 **********************************************************************************
 */
#ifdef GL
static ZnBool
PolygonRenderCB(ZnPoint         *bevels,
                PolygonData     *pd)
{
  int           i;
  ZnPoint       p[6];
  XColor        *c[8];
  XColor        *color = ZnGetGradientColor(pd->gradient, 51.0, NULL);
  ZnReliefStyle relief, int_relief;
  ZnBool        two_faces, round, rule;

  rule = pd->relief & ZN_RELIEF_RULE;
  round = pd->relief & ZN_RELIEF_ROUND;
  two_faces = pd->relief & ZN_RELIEF_TWO_FACES;
  relief = pd->relief & ZN_RELIEF_MASK;
  for (i = 0; i < 4; i++) {
    p[i].x = ZnNearestInt(bevels[i].x);
    p[i].y = ZnNearestInt(bevels[i].y);
  }

  if (two_faces) {
    p[4].x = (p[0].x+p[1].x)/2;
    p[4].y = (p[0].y+p[1].y)/2;
    p[5].x = (p[2].x+p[3].x)/2;
    p[5].y = (p[2].y+p[3].y)/2;

    if (relief == ZN_RELIEF_SUNKEN) {
      int_relief = ZN_RELIEF_RAISED;
    }
    else {
      int_relief = ZN_RELIEF_SUNKEN;
    }
    c[0]=c[1]=c[2]=c[3] = ReliefColorOfSegment(bevels[0].x, bevels[0].y,
                                               bevels[3].x, bevels[3].y,
                                               relief, pd->gradient,
                                               pd->wi->light_angle);
    c[4]=c[5]=c[6]=c[7] = ReliefColorOfSegment(bevels[0].x, bevels[0].y,
                                               bevels[3].x, bevels[3].y,
                                               int_relief, pd->gradient,
                                               pd->wi->light_angle);
    if (pd->smooth && pd->p0) {
      c[2]=c[3] = ReliefColorOfSegment(pd->p0->x, pd->p0->y,
                                       pd->p1->x, pd->p1->y,
                                       relief, pd->gradient,
                                       pd->wi->light_angle);
      c[6]=c[7] = ReliefColorOfSegment(pd->p0->x, pd->p0->y,
                                       pd->p1->x, pd->p1->y,
                                       int_relief, pd->gradient,
                                       pd->wi->light_angle);
    }
    if (round) {
      if (!rule) {
        c[0]=c[3]=c[5]=c[6]=color;
      }
      else {
        c[1]=c[2]=c[4]=c[7]=color;
      }
    }
    glBegin(GL_QUADS);
    glColor4us(c[0]->red, c[0]->green, c[0]->blue, pd->alpha);
    glVertex2d(p[0].x, p[0].y);

    glColor4us(c[1]->red, c[1]->green, c[1]->blue, pd->alpha);
    glVertex2d(p[4].x, p[4].y);

    glColor4us(c[2]->red, c[2]->green, c[2]->blue, pd->alpha);
    glVertex2d(p[5].x, p[5].y);

    glColor4us(c[3]->red, c[3]->green, c[3]->blue, pd->alpha);
    glVertex2d(p[3].x, p[3].y);

    glColor4us(c[4]->red, c[4]->green, c[4]->blue, pd->alpha);
    glVertex2d(p[4].x, p[4].y);

    glColor4us(c[5]->red, c[5]->green, c[5]->blue, pd->alpha);
    glVertex2d(p[1].x, p[1].y);

    glColor4us(c[6]->red, c[6]->green, c[6]->blue, pd->alpha);
    glVertex2d(p[2].x, p[2].y);

    glColor4us(c[7]->red, c[7]->green, c[7]->blue, pd->alpha);
    glVertex2d(p[5].x, p[5].y);
    glEnd();
  }
  else { /* Single face */
    c[0]=c[1]=c[2]=c[3] = ReliefColorOfSegment(bevels[0].x, bevels[0].y,
                                               bevels[3].x, bevels[3].y,
                                               relief, pd->gradient,
                                               pd->wi->light_angle);
    if (pd->smooth && pd->p0) {
      c[2]=c[3] = ReliefColorOfSegment(pd->p0->x, pd->p0->y,
                                       pd->p1->x, pd->p1->y,
                                       relief, pd->gradient,
                                       pd->wi->light_angle);
    }
    if (round) {
      c[1]=c[2] = color;
    }
    glBegin(GL_QUADS);
    glColor4us(c[0]->red, c[0]->green, c[0]->blue, pd->alpha);
    glVertex2d(p[0].x, p[0].y);
    glColor4us(c[1]->red, c[1]->green, c[1]->blue, pd->alpha);
    glVertex2d(p[1].x, p[1].y);
    glColor4us(c[2]->red, c[2]->green, c[2]->blue, pd->alpha);
    glVertex2d(p[2].x, p[2].y);
    glColor4us(c[3]->red, c[3]->green, c[3]->blue, pd->alpha);
    glVertex2d(p[3].x, p[3].y);
    glEnd();
  }

  return 0;
}

void
ZnRenderPolygonRelief(ZnWInfo           *wi,
                      ZnReliefStyle     relief,
                      ZnGradient        *gradient,
                      ZnBool            smooth,
                      ZnPoint           *points,
                      unsigned int      num_points,
                      ZnDim             line_width)
{
  PolygonData   pd;

  pd.wi = wi;
  pd.gradient = gradient;
  ZnGetGradientColor(gradient, 0.0, &pd.alpha);
  pd.alpha = ZnComposeAlpha(pd.alpha, wi->alpha);
  pd.smooth = smooth;
  glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);      
  pd.relief = relief;
  pd.count = 0;

  DoPolygon(points, num_points, line_width, PolygonRenderCB, &pd);
}

void
ZnRenderPolyline(ZnWInfo        *wi,
                 ZnPoint        *points,
                 unsigned int   num_points,
                 ZnDim          line_width,
                 ZnLineStyle    line_style,
                 int            cap_style,
                 int            join_style,
                 ZnLineEnd      first_end,
                 ZnLineEnd      last_end,
                 ZnGradient     *gradient)
{
  int           num_clips = ZnListSize(wi->clip_stack);
  ZnPoint       end_points[ZN_LINE_END_POINTS];
  ZnBool        need_rcaps, thin, closed, transparent;
  int           pass, num_passes, i, k, m;
  ZnPoint       c1, c2;    
  XColor        *color;
  unsigned short alpha;
  ZnGLContextEntry *ce = ZnGetGLContext(wi->dpy);

  /*
   * The code below draws curves thiner than the min
   * of GL_SMOOTH_LINE_WIDTH_RANGE and GL_SMOOTH_POINT_SIZE_RANGE
   * with a mix of anti-aliased lines and points. The curves that
   * are thicker are drawn using regular polygons.
   * TODO: The joints are drawn only rounded.
   * The caps can be either round or butt (but not projecting).
   */
  thin = ((line_width <= ce->max_line_width) &&
          (line_width <= ce->max_point_width));
  closed = (points->x == points[num_points-1].x) && (points->y == points[num_points-1].y);
  color = ZnGetGradientColor(gradient, 0.0, &alpha);
  alpha = ZnComposeAlpha(alpha, wi->alpha);
  glColor4us(color->red, color->green, color->blue, alpha);
  ZnSetLineStyle(wi, line_style);
  glLineWidth((GLfloat) line_width);
  /*
   * Do not use AA under this transparency value.
   */
  transparent = alpha < (65535 * 0.8);
  if (thin && transparent) {
    /*
     * This makes a special case for transparent lines.
     * In this case we need to avoid drawing twice a
     * single pixel. To achieve this we use the stencil
     * buffer to protect already drawn pixels, unfortunately
     * using antialiasing write in the stencil even if
     * the pixel area is not fully covered resulting in
     * a crack that can't be covered by points later on.
     * To handle this case we need to disable the stencil
     * which in turn result in erroneous alpha coverage.
     *
     * We have chosen to drawn transparent lines with a
     * correct coverage but NOT antialiased.
     */
    glPointSize((GLfloat)(line_width>1.0?line_width-1:line_width));
    glDisable(GL_LINE_SMOOTH);
  }
  else {
    glPointSize((GLfloat)(line_width>1.0?line_width-1:line_width));
  }

  num_passes = 1;
  if (transparent) {
    num_passes = 2;
  }
    
  for (pass = 0; pass < num_passes; pass++) {
    if (transparent) {
      if (pass == 0) {
        ZnGlStartClip(num_clips, True);
      }
      else {
        ZnGlRestoreStencil(num_clips, False);
      }
    }
    if (first_end) {
      ZnGetLineEnd(&points[0], &points[1], line_width, cap_style,
                   first_end, end_points);
      glBegin(GL_TRIANGLE_FAN);
      for (m = 0; m < ZN_LINE_END_POINTS; m++) {
        glVertex2d(end_points[m].x, end_points[m].y);
      }
      glEnd();
    }
    if (last_end) {
      ZnGetLineEnd(&points[num_points-1], &points[num_points-2],
                   line_width, cap_style, last_end, end_points);
      glBegin(GL_TRIANGLE_FAN);
      for (m = 0; m < ZN_LINE_END_POINTS; m++) {
        glVertex2d(end_points[m].x, end_points[m].y);
      }
      glEnd();
    }
    if (thin) {
      glBegin(GL_LINE_STRIP);
      for (i = 0; i < (int) num_points; i++) {
        glVertex2d(points[i].x, points[i].y);
      }
      glEnd();
    }
    else {
      glBegin(GL_QUADS);
      for (i = 0; i < (int) num_points-1; i++) {
        ZnGetButtPoints(&points[i+1], &points[i], line_width, False, &c1, &c2);
        glVertex2d(c1.x, c1.y);
        glVertex2d(c2.x, c2.y);
        ZnGetButtPoints(&points[i], &points[i+1], line_width, False, &c1, &c2);
        glVertex2d(c1.x, c1.y);
        glVertex2d(c2.x, c2.y);
      }
      glEnd();
    }

    /* if (pass == 0) {
      ZnGlRenderClipped();
    }
    else {
      ZnGlEndClip(num_clips);
      break;
    }*/
    need_rcaps = ((line_width > 1) && (cap_style == CapRound));
    i = 0;
    k = num_points;
    if (closed) {
      k--;
    }
    if (!need_rcaps || first_end) {
      i++;
    }
    if ((!need_rcaps && !closed) || last_end) {
      k--;
    }

    if (thin) {
      glBegin(GL_POINTS);
      for ( ; i < k; i++) {
        glVertex2d(points[i].x, points[i].y);
      }
      glEnd();
    }
    else {
      int       num_cpoints;
      ZnReal    lw_2 = line_width / 2.0;
      ZnPoint   *cpoints = ZnGetCirclePoints(3, ZN_CIRCLE_COARSE,
                                             0.0, 2*M_PI, &num_cpoints, NULL);
      
      for ( ; i < k; i++) {
        glBegin(GL_TRIANGLE_FAN);
        glVertex2d(points[i].x, points[i].y);
        for (m = 0; m < num_cpoints; m++) {
          glVertex2d(points[i].x + cpoints[m].x*lw_2,
                     points[i].y + cpoints[m].y*lw_2);
        }
        glEnd();
      }
    }
  }
  
  ZnGlEndClip(num_clips);
  if (thin) {
    glEnable(GL_LINE_SMOOTH);
  }
}


void
ZnRenderIcon(ZnWInfo    *wi,
             ZnImage    image,
             ZnGradient *gradient,
             ZnPoint    *origin,
             ZnBool     modulate)
{
  ZnPoint       p[4];
  int           width, height;

  ZnSizeOfImage(image, &width, &height);
  p[0] = *origin;
  p[1].x = origin->x;
  p[1].y = origin->y + height;
  p[2].x = origin->x + width;
  p[2].y = p[1].y;
  p[3].x = p[2].x;
  p[3].y = origin->y;
  ZnRenderImage(wi, image, gradient, p, modulate);
}


void
ZnRenderImage(ZnWInfo    *wi,
              ZnImage    image,
              ZnGradient *gradient,
              ZnPoint    *quad,
              ZnBool     modulate)
{
  XColor        *color;
  unsigned short alpha;
  ZnReal        t, s;
  GLuint        texobj;
  
  color = ZnGetGradientColor(gradient, 0.0, &alpha);
  alpha = ZnComposeAlpha(alpha, wi->alpha);
  texobj = ZnImageTex(image, &t, &s);
  glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
  glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
  glEnable(GL_TEXTURE_2D);
  glBindTexture(GL_TEXTURE_2D, texobj);
  if (modulate) {
    glColor4us(color->red, color->green, color->blue, alpha);
  }
  else {
    glColor4us(65535, 65535, 65535, alpha);
  }
  glBegin(GL_QUADS);
  glTexCoord2d(0.0, 0.0);
  glVertex2d(quad[0].x, quad[0].y);
  glTexCoord2d(0.0, t);
  glVertex2d(quad[1].x, quad[1].y);
  glTexCoord2d(s, t);
  glVertex2d(quad[2].x, quad[2].y);
  glTexCoord2d(s, 0.0);
  glVertex2d(quad[3].x, quad[3].y);
  glEnd();
  glDisable(GL_TEXTURE_2D);
}

void
ZnRenderTile(ZnWInfo    *wi,
             ZnImage    tile,
             ZnGradient *gradient,
             void       (*cb)(void *),
             void       *closure,
             ZnPoint    *quad) /* Right now it's a ZnBBox */
{
  ZnReal        x, y, nx, ny, lx, ly, s, t, tiles, tilet;
  int           width, height, num_clips = ZnListSize(wi->clip_stack);
  unsigned short alpha;
  GLuint        texobj;
  XColor        *color;

  if (gradient) {
    color = ZnGetGradientColor(gradient, 0.0, &alpha);
    alpha = ZnComposeAlpha(alpha, wi->alpha);
  }
  else {
    color = NULL;
    alpha = ZnComposeAlpha(100, wi->alpha);
  }
  
  if (cb) {
    /*
     * Setup the stencil buffer with the shape to be drawn.
     */
    ZnGlStartClip(num_clips, False);

    (*cb)(closure);  
    ZnGlRestoreStencil(num_clips, True);
  }
  
  /*
   * Then texture map the quad through the shape.
   * The rectangle is drawn using quads, each
   * quad matching the size of the texture tile.
   */
  ZnSizeOfImage(tile, &width, &height);
  texobj = ZnImageTex(tile, &tilet, &tiles);
  glEnable(GL_TEXTURE_2D);
  if (color && ZnImageIsBitmap(tile)) {
    glColor4us(color->red, color->green, color->blue, alpha);
  }
  else {
    glColor4us(65535, 65535, 65535, alpha);
  }
  glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
  glBindTexture(GL_TEXTURE_2D, texobj);
  
  y = quad[0].y;
  lx = quad[1].x;
  ly = quad[1].y;
  glBegin(GL_QUADS);
  do {
    x = quad[0].x;
    t = 1.0;
    ny = y + height;
    if (ny > ly) {
      ny = ly;
      t = (ly - y) / (ZnReal) height;
    }
    t *= tilet;
    do {
      s = 1.0;
      nx = x + width;
      if (nx > lx) {
        nx = lx;
        s = (lx - x) / (ZnReal) width;
      }
      s *= tiles;
      glTexCoord2d(0.0, 0.0);
      glVertex2d(x, y);
      glTexCoord2d(0.0, t);
      glVertex2d(x, ny);
      glTexCoord2d(s, t);
      glVertex2d(nx, ny);
      glTexCoord2d(s, 0.0);
      glVertex2d(nx, y);
      x = nx;
    }
    while (x != lx);
    y = ny;
  }
  while (y != ly);
  glEnd();

  if (cb) {
    ZnGlEndClip(num_clips);
  }
  glDisable(GL_TEXTURE_2D);
}


static void
ComputeAxialGradient(ZnWInfo    *wi,
                     ZnPoly     *shape,
                     ZnReal     angle,
                     ZnPoint    *grad_geo)
{
  ZnTransfo     *transfo1, *transfo2;
  ZnContour     *c;
  ZnBBox        bbox;
  ZnPoint       *points, p[4];
  unsigned int  i;
  
  transfo1 = ZnTransfoNew();
  transfo2 = ZnTransfoNew();
  ZnRotateDeg(transfo1, angle);
  ZnRotateDeg(transfo2, -angle);
  c = shape->contours;
  ZnResetBBox(&bbox);
  for (i = 0; i < shape->num_contours; i++, c++) {
    ZnListAssertSize(ZnWorkPoints, c->num_points);
    points = ZnListArray(ZnWorkPoints);
    ZnTransformPoints(transfo1, c->points, points, c->num_points);
    ZnAddPointsToBBox(&bbox, points, c->num_points);
  }
  bbox.orig.x--;
  bbox.orig.y--;
  bbox.corner.x++;
  bbox.corner.y++;
  p[0] = bbox.orig;
  p[2] = bbox.corner;
  p[1].x = p[2].x;
  p[1].y = p[0].y;
  p[3].x = p[0].x;
  p[3].y = p[2].y;
  ZnTransfoSetIdentity(transfo1);
  ZnTransfoCompose(transfo1, transfo2, wi->current_transfo);
  ZnTransformPoints(transfo1, p, grad_geo, 4);
  ZnTransfoFree(transfo1);
  ZnTransfoFree(transfo2);
}

static void
ComputeCircularGradient(ZnWInfo         *wi,
                        ZnPoly          *shape,
                        ZnBool          oval,
                        ZnPoint         *focal_pp, /* in percent of bbox */
                        ZnReal          angle,
                        ZnPoint         *grad_geo)
{
  ZnReal        dist, new, x, y, ff;
  ZnBBox        bbox;
  ZnContour     *c;
  ZnPoint       offset, radius, focal_point;
  ZnPoint       *points;
  ZnTransfo     t1;
  unsigned int  i, j;

  /*
   * Compute the shape bbox (which should be in the item space).
   */
  ZnResetBBox(&bbox);
  c = shape->contours;
  dist = 0.0;
  for (j = 0; j < shape->num_contours; j++, c++) {
    ZnAddPointsToBBox(&bbox, c->points, c->num_points); 
  }

  /*
   * Find the gradient focal point in the item space.
   * The excursion of the focal point outside the item
   * bbox is clamped to avoid distorsions that take
   * place due to the rather simple algorithm used to
   * compute the maximum radius of the gradient.
   */
  focal_pp->x = fmod(focal_pp->x, 500.0);
  focal_pp->y = fmod(focal_pp->y, 500.0);
  offset.x = focal_pp->x * (bbox.corner.x-bbox.orig.x)/100.0;
  offset.y = focal_pp->y * (bbox.corner.y-bbox.orig.y)/100.0;
  focal_point.x = (bbox.corner.x+bbox.orig.x)/2 + offset.x; 
  focal_point.y = (bbox.corner.y+bbox.orig.y)/2 + offset.y; 

  /*
   * Find the max distance from the focal point.
   */
  if (oval) {
    /*
     * radius.x and radius.y are the shape radiuses.
     * ff is the distance from the bbox center to
     * the focal point.
     */
    radius.x = (bbox.corner.x-bbox.orig.x)/2;
    radius.y = (bbox.corner.y-bbox.orig.y)/2;
    ff = sqrt(offset.x*offset.x + offset.y*offset.y);
    /*
     * Compute the farthest point from the focal point 
     * on a unit circle, then map it to the oval and
     * compute the distance between the two points.
     */
    if (ff > PRECISION_LIMIT) {
      x = offset.x/ff;
      y = offset.y/ff;
      x *= radius.x;
      y *= radius.y;
      x = x + offset.x;
      y = y + offset.y;
    }
    else {
      x = 0;
      y = MAX(radius.x, radius.y);
    }
    dist = x*x + y*y;
  }
  else {
    /*
     * Use the given shape
     */
    c = shape->contours;
    for (j = 0; j < shape->num_contours; j++, c++) {
      for (i = 0, points = c->points; i < c->num_points; i++, points++) {
        x = points->x - focal_point.x;
        y = points->y - focal_point.y;
        new = x*x+y*y;
        if (new > dist) {
          dist = new;
        }
      }
    }
  }
  
  /*
   * Create a transform to map a unit circle to another one that
   * could fill the item when centered at the focal point.
   */
  dist = sqrt(dist); /* Max radius plus a fuzz factor */
  ZnTransfoSetIdentity(&t1);
  ZnScale(&t1, dist, dist);
  ZnRotateDeg(&t1, -angle);

  /*
   * Then, center the oval on the focal point.
   */
  ZnTranslate(&t1, focal_point.x, focal_point.y, False);
  /*
   * Last, compose with the current transform.
   */
  ZnTransfoCompose((ZnTransfo *) grad_geo, &t1, wi->current_transfo);
}

static void
ComputePathGradient(ZnWInfo     *wi,
                    ZnPoly      *shape,
                    ZnPoint     *focal_pp, /* in percent of the bbox */
                    ZnPoint     *grad_geo)
{
  ZnBBox        bbox;
  ZnContour     *c;
  ZnPoint       focal_point;
  unsigned int  j;

  /*
   * Compute the shape bbox (which should be in the item space).
   */
  ZnResetBBox(&bbox);
  c = shape->contours;
  for (j = 0; j < shape->num_contours; j++, c++) {
    ZnAddPointsToBBox(&bbox, c->points, c->num_points); 
  }

  /*
   * Find the gradient center in the item space.
   */
  focal_point.x = (bbox.corner.x+bbox.orig.x)/2 + focal_pp->x * (bbox.corner.x-bbox.orig.x)/100.0;
  focal_point.y = (bbox.corner.y+bbox.orig.y)/2 + focal_pp->y * (bbox.corner.y-bbox.orig.y)/100.0;
  /*
   * Then convert it to device space.
   */
  ZnTransformPoint(wi->current_transfo, &focal_point, &grad_geo[0]);
}

void
ZnComputeGradient(ZnGradient    *grad,
                  ZnWInfo       *wi,
                  ZnPoly        *shape,
                  ZnPoint       *grad_geo)
{
  switch (grad->type) {
  case ZN_AXIAL_GRADIENT:
    ComputeAxialGradient(wi, shape, grad->angle, grad_geo);
    break;
  case ZN_RADIAL_GRADIENT:
  case ZN_CONICAL_GRADIENT:
    ComputeCircularGradient(wi, shape, False, &grad->p, grad->angle, grad_geo);
    break;
  case ZN_PATH_GRADIENT:
    ComputePathGradient(wi, shape, &grad->p, grad_geo);
    break;
  }
}

void
ZnRenderGradient(ZnWInfo        *wi,
                 ZnGradient     *gradient,      /* The gradient to be drawn (static
                                                 * parameters). */
                 void           (*cb)(void *),  /* A callback called to clip the shape
                                                 * containing the gradient. */
                 void           *closure,       /* The callback parameter. */
                 ZnPoint        *quad,          /* The gradient geometric parameters
                                                 * (dynamic). */
                 ZnPoly         *poly           /* Used only by ZN_PATH_GRADIENT */
                 )
{
  unsigned short alpha, alpha2;
  int           angle;
  unsigned int  i, j;
  int           type = gradient->type;
  XColor        *color;
  ZnPoint       dposa, dposb, dposc, dposd;
  ZnPoint       p, dcontrol;
  ZnReal        npos, pos, control;
  unsigned int  num_clips = ZnListSize(wi->clip_stack);
  ZnPoint       iquad[4];
  
  if (!cb && (type == ZN_AXIAL_GRADIENT)) { /* Render an aligned
                                             * axial gradient in the quad */
    angle = gradient->angle;
    /*
     * Adjust the quad for 90 180 and 270 degrees axial
     * gradients. Other angles not supported.
     */
    switch (angle) {
    case 90:
      iquad[0] = quad[3];
      iquad[3] = quad[2];
      iquad[2] = quad[1];
      iquad[1] = quad[0];
      quad = iquad;
      break;
    case 180:
      iquad[0] = quad[2];
      iquad[3] = quad[1];
      iquad[2] = quad[0];
      iquad[1] = quad[3];
      quad = iquad;
      break;
    case 270:
      iquad[0] = quad[1];
      iquad[3] = quad[0];
      iquad[2] = quad[3];
      iquad[1] = quad[2];      
      quad = iquad;
      break;
    }
  }

  if (cb) {
    /*
     * Draw the gradient shape in the stencil using the provided
     * callback (clipping).
     */
    ZnGlStartClip(num_clips, False);
    (*cb)(closure);
    ZnGlRestoreStencil(num_clips, True);
  }

  if (type == ZN_AXIAL_GRADIENT) {
    /*
     * Then fill the axial gradient using the provided
     * quad and colors. The stencil will be restored
     * to its previous state in the process.
     */
    glBegin(GL_QUAD_STRIP);
    for (i = 0; i < gradient->num_actual_colors; i++) {
      color = gradient->actual_colors[i].rgb;
      alpha = ZnComposeAlpha(gradient->actual_colors[i].alpha, wi->alpha);
      glColor4us(color->red, color->green, color->blue, alpha);

      pos = gradient->actual_colors[i].position;
      control = gradient->actual_colors[i].control;
      dposa.x = (quad[1].x - quad[0].x)*pos/100.0;
      dposa.y = (quad[1].y - quad[0].y)*pos/100.0;
      p.x = quad[0].x + dposa.x;
      p.y = quad[0].y + dposa.y;
      glVertex2d(p.x, p.y);
      
      dposb.x = (quad[2].x - quad[3].x)*pos/100.0;
      dposb.y = (quad[2].y - quad[3].y)*pos/100.0;      
      p.x = quad[3].x + dposb.x;
      p.y = quad[3].y + dposb.y;
      glVertex2d(p.x, p.y);
      
      if ((control != 50.0) && (i != gradient->num_actual_colors-1)) {
        color = gradient->actual_colors[i].mid_rgb;
        alpha = ZnComposeAlpha(gradient->actual_colors[i].mid_alpha, wi->alpha);
        glColor4us(color->red, color->green, color->blue, alpha);
        
        npos = gradient->actual_colors[i+1].position;
        dposc.x = (quad[1].x - quad[0].x)*npos/100.0;
        dposc.y = (quad[1].y - quad[0].y)*npos/100.0;
        dcontrol.x = (dposc.x - dposa.x)*control/100.0;
        dcontrol.y = (dposc.y - dposa.y)*control/100.0;
        p.x = quad[0].x + dposa.x + dcontrol.x;
        p.y = quad[0].y + dposa.y + dcontrol.y;
        glVertex2d(p.x, p.y);
        
        dposd.x = (quad[2].x - quad[3].x)*npos/100.0;
        dposd.y = (quad[2].y - quad[3].y)*npos/100.0;
        dcontrol.x = (dposd.x - dposb.x)*control/100.0;
        dcontrol.y = (dposd.y - dposb.y)*control/100.0;      
        p.x = quad[3].x + dposb.x + dcontrol.x;
        p.y = quad[3].y + dposb.y + dcontrol.y;
        glVertex2d(p.x, p.y);
      }
      
    }
    glEnd();
  }
  else if (type == ZN_RADIAL_GRADIENT) {
    ZnReal       x, y, position, position2, position3;
    unsigned int num_p;
    ZnPoint      *genarc, *tarc, p, focalp;
    XColor       *color2;

    genarc = ZnGetCirclePoints(3, ZN_CIRCLE_FINE, 0.0, 2*M_PI, &num_p, NULL);
    ZnListAssertSize(ZnWorkPoints, num_p);
    tarc = ZnListArray(ZnWorkPoints);
    ZnTransformPoints((ZnTransfo *) quad, genarc, tarc, num_p);
    p.x = p.y = 0;
    ZnTransformPoint((ZnTransfo *) quad, &p, &focalp);
    
    position = 0.0;
    color = gradient->actual_colors[0].rgb;
    alpha = ZnComposeAlpha(gradient->actual_colors[0].alpha, wi->alpha);
    control = gradient->actual_colors[0].control;
    for (j = 1; j < gradient->num_actual_colors; j++) {
      position2 = gradient->actual_colors[j].position/100.0;
      if ((control != 50) && (j != gradient->num_actual_colors-1)) {
        glBegin(GL_QUAD_STRIP);
        color2 = gradient->actual_colors[j-1].mid_rgb;
        alpha2 = ZnComposeAlpha(gradient->actual_colors[j-1].mid_alpha, wi->alpha);
        position3 = position + (position2-position)*control/100.0;
        for (i = 0; i < num_p; i++) {
          x = focalp.x + (tarc[i].x-focalp.x) * position;
          y = focalp.y + (tarc[i].y-focalp.y) * position;
          glColor4us(color->red, color->green, color->blue, alpha);
          glVertex2d(x, y);
          x = focalp.x + (tarc[i].x-focalp.x) * position3;
          y = focalp.y + (tarc[i].y-focalp.y) * position3;
          glColor4us(color2->red, color2->green, color2->blue, alpha);
          glVertex2d(x, y);
        }
        position = position3;
        color = color2;
        alpha = alpha2;
        glEnd();
      }
      glBegin(GL_QUAD_STRIP);
      color2 = gradient->actual_colors[j].rgb;
      alpha2 = ZnComposeAlpha(gradient->actual_colors[j].alpha, wi->alpha);
      for (i = 0; i < num_p; i++) {
        x = focalp.x + (tarc[i].x-focalp.x) * position;
        y = focalp.y + (tarc[i].y-focalp.y) * position;
        glColor4us(color->red, color->green, color->blue, alpha);
        glVertex2d(x, y);
        x = focalp.x + (tarc[i].x-focalp.x) * position2;
        y = focalp.y + (tarc[i].y-focalp.y) * position2;
        glColor4us(color2->red, color2->green, color2->blue, alpha2);
        glVertex2d(x, y);
      }
      glEnd();
      position = position2;
      color = color2;
      alpha = alpha2;
      control = gradient->actual_colors[j].control;
    }
  }
  else if (type == ZN_PATH_GRADIENT) {
    ZnPoint      p, pp, p2, pp2, p3, pp3;
    unsigned int num_p, k, ii;
    ZnPoint      *points;
    ZnReal       position;
    
    for (k = 0; k < poly->num_contours; k++) {
      /*if (poly->contours[k].cw) {
        continue;
        }*/
      points = poly->contours[k].points;
      num_p = poly->contours[k].num_points;
      
      for (i = 0; i < num_p; i++) {
        if (i == num_p-1) {
          ii = 0;
        }
        else {
          ii = i+1;
        }
        
        glBegin(GL_QUAD_STRIP);
        p.x = p.y = pp.x = pp.y = 0;
        control = gradient->actual_colors[0].control;
        position = gradient->actual_colors[0].position;
        alpha = ZnComposeAlpha(gradient->actual_colors[0].alpha, wi->alpha);
        color = gradient->actual_colors[0].rgb;
        glColor4us(color->red, color->green, color->blue, alpha);
        glVertex2d(quad[0].x+p.x, quad[0].y+p.y);
        glVertex2d(quad[0].x+pp.x, quad[0].y+pp.y);
        for (j = 0; j < gradient->num_actual_colors-1; j++) {
          position = gradient->actual_colors[j+1].position;
          p2.x = (points[i].x-quad[0].x)*position/100.0;
          p2.y = (points[i].y-quad[0].y)*position/100.0;
          pp2.x = (points[ii].x-quad[0].x)*position/100.0;
          pp2.y = (points[ii].y-quad[0].y)*position/100.0;
          if (control != 50) {
            color = gradient->actual_colors[j].mid_rgb;
            alpha = ZnComposeAlpha(gradient->actual_colors[j].mid_alpha, wi->alpha);
            p3.x = p.x+(p2.x-p.x)*control/100.0;
            p3.y = p.y+(p2.y-p.y)*control/100.0;
            pp3.x = pp.x+(pp2.x-pp.x)*control/100.0;
            pp3.y = pp.y+(pp2.y-pp.y)*control/100.0;
            glColor4us(color->red, color->green, color->blue, alpha);
            glVertex2d(quad[0].x+p3.x, quad[0].y+p3.y);
            glVertex2d(quad[0].x+pp3.x, quad[0].y+pp3.y);
          }
          control = gradient->actual_colors[j+1].control;
          alpha = ZnComposeAlpha(gradient->actual_colors[j+1].alpha, wi->alpha);
          color = gradient->actual_colors[j+1].rgb;
          p = p2;
          pp = pp2;
          glColor4us(color->red, color->green, color->blue, alpha);
          glVertex2d(quad[0].x+p.x, quad[0].y+p.y);
          glVertex2d(quad[0].x+pp.x, quad[0].y+pp.y);
        }
        glEnd();
      }
    }
  }
  else if (type == ZN_CONICAL_GRADIENT) {
    ZnReal       position;
    unsigned int num_p;
    ZnPoint      *genarc, *tarc, p, focalp;
    XColor       col;

    genarc = ZnGetCirclePoints(3, ZN_CIRCLE_FINEST, 0.0, 2*M_PI, &num_p, NULL);
    ZnListAssertSize(ZnWorkPoints, num_p);
    tarc = ZnListArray(ZnWorkPoints);
    ZnTransformPoints((ZnTransfo *) quad, genarc, tarc, num_p);
    p.x = p.y = 0;
    ZnTransformPoint((ZnTransfo *) quad, &p, &focalp);
    
    glBegin(GL_TRIANGLE_STRIP);
    for (i = 0; i < num_p; i++) {
      position = i*100.0/(num_p-1);
      ZnInterpGradientColor(gradient, position, &col, &alpha);
      alpha = ZnComposeAlpha(alpha, wi->alpha);

      /*printf("position: %g --> color: %d %d %d, alpha: %d\n",
        position, col.red, col.green, col.blue, alpha);*/

      glColor4us(col.red, col.green, col.blue, alpha);
      glVertex2d(tarc[i].x, tarc[i].y);
      glVertex2d(focalp.x, focalp.y);
    }
    glEnd();
  }
  
  if (cb) {
    /*
     * Restore the previous GL state.
     */
    ZnGlEndClip(num_clips);
  }
}


void
ZnRenderHollowDot(ZnWInfo       *wi,
                  ZnPoint       *p,
                  ZnReal        size)
{
  int   num_clips = ZnListSize(wi->clip_stack);
  
  ZnGlStartClip(num_clips, False);
  
  glPointSize((GLfloat) (size-2));
  glBegin(GL_POINTS);
  glVertex2d(p->x, p->y);
  glEnd();

  ZnGlRenderClipped();

  glPointSize((GLfloat) size);
  glBegin(GL_POINTS);
  glVertex2d(p->x, p->y);
  glEnd();

  ZnGlRestoreStencil(num_clips, False);

  glBegin(GL_POINTS);
  glVertex2d(p->x, p->y);
  glEnd();

  ZnGlEndClip(num_clips);
}
#endif


#ifdef GL
void
ZnRenderGlyph(ZnTexFontInfo     *tfi,
              int               c)
{
  ZnTexGVI *tgvi;

  tgvi = ZnTexFontGVI(tfi, c);
  if (!tgvi) {
    return;
  }
  //printf("%c --> x0,y0: %d %d, tx0,ty0: %g %g, x1,y1: %d %d, tx1,ty1: %g %g, advance: %g\n",
    //     c, tgvi->v0x, tgvi->v0y, tgvi->t0x, tgvi->t0y,
    //     tgvi->v1x, tgvi->v1y, tgvi->t1x, tgvi->t1y,
    //     tgvi->advance);
  glBegin(GL_QUADS);
  glTexCoord2f(tgvi->t0x, tgvi->t0y); glVertex2s(tgvi->v0x, tgvi->v0y);
  glTexCoord2f(tgvi->t0x, tgvi->t1y); glVertex2s(tgvi->v0x, tgvi->v1y);
  glTexCoord2f(tgvi->t1x, tgvi->t1y); glVertex2s(tgvi->v1x, tgvi->v1y);
  glTexCoord2f(tgvi->t1x, tgvi->t0y); glVertex2s(tgvi->v1x, tgvi->v0y);
  glEnd();
  glTranslatef(tgvi->advance, 0.0, 0.0);
}

#ifdef PTK_800
void
ZnRenderString(ZnTexFontInfo    *tfi,
               unsigned char    *string,
               unsigned int     len)
{
  while (len) {
    ZnRenderGlyph(tfi, *string);
    string++;
    len--;
  }
}
#else
void
ZnRenderString(ZnTexFontInfo    *tfi,
               unsigned char    *string,
               unsigned int     len)
{
  unsigned int  clen;
  Tcl_UniChar   c;

  while (len) {
    clen = Tcl_UtfToUniChar(string, &c);

    ZnRenderGlyph(tfi, c);

    string += clen;
    len -= clen;
  }
}
#endif
#endif

/*
 **********************************************************************************
 *
 * RenderTriangle --
 *      This routine maps an image onto a triangle.
 *      Image coordinates are chosen for  each vertex of the triangle.
 *      A simple affine tex mapping is used as in Zinc there is no way
 *      to specify perspective deformation. No filtering is attempted
 *      on the output pixels. 
 *
 *      In the comments below u and v are image coordinates and x and
 *      y are triangle coordinates.
 *      RenderAffineScanline is an helper function that draws a whole
 *      scan line to the image with linear interpolation.
 *
 **********************************************************************************
 */
static void
RenderAffineScanline(XImage     *image,
                     XImage     *mapped_image,
                     ZnReal     x1,
                     ZnReal     x2,
                     ZnReal     u1,
                     ZnReal     u2,
                     ZnReal     v1,
                     ZnReal     v2,
                     int        y)
{
  ZnReal        du, dv, width;
  int           intx1, intx2, intu, intv;
  int           i;
  
  /* Revert span ends if needed */
  if (x2 < x1) {
    ZnReal      tmp;
    tmp = x1; x1 = x2; x2 = tmp;
    tmp = u1; u1 = u2; u2 = tmp;
    tmp = v1; v1 = v2; v2 = tmp;
  }
  
  /* Compute the interpolation factors */
  width = x2 - x1;
  if (width) {
    du = (u2 - u1) / width;
    dv = (v2 - v1) / width;
  }
  else {
    du = dv = 0;
  }
  intx1 = (int) floor(x1);
  intx2 = (int) floor(x2);
  
  /* Draw the line */
  for (i = intx1; i < intx2; i++) {
    intu = (int) floor(u1);
    intv = (int) floor(v1);
    XPutPixel(mapped_image, i, y, XGetPixel(image, intu, intv));
    u1 += du;
    v1 += dv;
  }
}

static void
RenderTriangle(XImage   *image,
               XImage   *mapped_image,
               ZnPoint  *tri,
               ZnPoint  *im_coords)
{
  ZnReal        dx_A, dx_B;     /* Interpolation factor in x / y */
  ZnReal        du_A, du_B;     /* in u / y */
  ZnReal        dv_A, dv_B;     /* in v / y */
  ZnReal        x1, x2;         /* Span in x */
  ZnReal        u1, u2;         /* Span in u */
  ZnReal        v1, v2;         /* Span in v */
  int           height_A;       /* Scan line # from top top vertex A */
  int           height_B;       /* Scan line # from top top vertex B */
  int           y;              /* Current scan line */
  int           top, a, b;      /* Top triangle vertex and other two */
  int           i;

  /* Find top vertex and deduce the others. */
  top = 0;
  for (i = 1; i < 3; i++) {
    if (tri[i].y <= tri[top].y)
      top = i;
  }
  a = (top+1)%3;
  b = top-1;
  if (b < 0)
    b = 2;
  
  /* Initialize conversion parameters. */
  y = ZnNearestInt(tri[top].y);
  height_A = ZnNearestInt(tri[a].y - tri[top].y);
  height_B = ZnNearestInt(tri[b].y - tri[top].y);
  x1 = x2 = tri[top].x;
  u1 = u2 = im_coords[top].x;
  v1 = v2 = im_coords[top].y;
  if (height_A) {
    dx_A = (tri[a].x - tri[top].x) / height_A;
    du_A = (im_coords[a].x - im_coords[top].x) / height_A;
    dv_A = (im_coords[a].y - im_coords[top].y) / height_A;
  }
  else {
    dx_A = du_A = dv_A = 0;
  }
  if (height_B) {
    dx_B = (tri[b].x - tri[top].x) / height_B;
    du_B = (im_coords[b].x - im_coords[top].x) / height_B;
    dv_B = (im_coords[b].y - im_coords[top].y) / height_B;
  }
  else {
    dx_B = du_B = dv_B = 0;
  }
  
  /* Convert from top to bottom */
  for (i = 2; i > 0; ) {
    while (height_A && height_B) {

      /* Draw a scanline */
      RenderAffineScanline(image, mapped_image, x1, x2, u1, u2, v1, v2, y);

      /* Step the parameters*/
      y++;
      height_A--;
      height_B--;
      x1 += dx_A;
      x2 += dx_B;
      u1 += du_A;
      u2 += du_B;
      v1 += dv_A;
      v2 += dv_B;
    }
    
    /* If either height_A or height_B steps to zero, we have
     * encountered a vertex (A or B) and we are starting conversion
     * along a new edge. Update the parameters before proceeding. */
    if (!height_A) {
      int       na = (a+1)%3;
      
      height_A = ZnNearestInt(tri[na].y - tri[a].y);
      if (height_A) {
        dx_A = (tri[na].x - tri[a].x) / height_A;
        du_A = (im_coords[na].x - im_coords[a].x) / height_A;
        dv_A = (im_coords[na].y - im_coords[a].y) / height_A;
      }
      else {
        dx_A = du_A = dv_A = 0;
      }
      x1 = tri[a].x;
      u1 = im_coords[a].x;
      v1 = im_coords[a].y;
      a = na;
      /* One less vertex to do */
      i--;
    }
    
    if (!height_B) {
      int       nb = b - 1;
      
      if (nb < 0)
        nb = 2;
      height_B = ZnNearestInt(tri[nb].y - tri[b].y);
      if (height_B) {
        dx_B = (tri[nb].x - tri[b].x) / height_B;
        du_B = (im_coords[nb].x - im_coords[b].x) / height_B;
        dv_B = (im_coords[nb].y - im_coords[b].y) / height_B;
      }
      else {
        dx_B = du_B = dv_B = 0;
      }
      x2 = tri[b].x;
      u2 = im_coords[b].x;
      v2 = im_coords[b].y;
      b = nb;
      /* One less vertex to do */
      i--;
    }
  }
}


/*
 **********************************************************************************
 *
 * MapImage --
 *      This procedure maps an image on a parallelogram given in poly.
 *      The given parallelogram should fit in the destination image.
 *      The parallelogram vertices must be ordered as for a triangle
 *       strip: 
 *
 *    v0 ------------ v2
 *       |          |
 *       |          |
 *    v1 ------------ v3
 *
 *      The mapping is done by a simple affine mapping of the image on the
 *      two triangles obtained by cutting the parallelogram along the diogonal
 *      from the second vertex to the third vertex.
 *
 **********************************************************************************
 */
void
ZnMapImage(XImage       *image,
           XImage       *mapped_image,
           ZnPoint      *poly)
{
  ZnPoint       triangle[3];
  ZnPoint       im_coords[3];
  
  triangle[0] = poly[0];
  triangle[1] = poly[1];
  triangle[2] = poly[2];
  im_coords[0].x = 0.0;
  im_coords[0].y = 0.0;
  im_coords[1].x = 0.0;
  im_coords[1].y = image->height-1;
  im_coords[2].x = image->width-1;
  im_coords[2].y = 0.0;
  RenderTriangle(image, mapped_image, triangle, im_coords);

  triangle[0] = poly[1];
  triangle[1] = poly[2];
  triangle[2] = poly[3];
  im_coords[0].x = 0.0;
  im_coords[0].y = image->height-1;
  im_coords[1].x = image->width-1;
  im_coords[1].y = 0.0;
  im_coords[2].x = image->width-1;
  im_coords[2].y = image->height-1;
  RenderTriangle(image, mapped_image, triangle, im_coords);
}