/* * Draw.c -- Implementation of common drawing routines. * * Authors : Patrick Lecoanet. * Creation date : Sat Dec 10 12:51:30 1994 * * $Id$ */ /* * 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 #include #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 = (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); }