diff options
Diffstat (limited to 'generic')
-rw-r--r-- | generic/Draw.c | 732 |
1 files changed, 732 insertions, 0 deletions
diff --git a/generic/Draw.c b/generic/Draw.c new file mode 100644 index 0000000..f0ace86 --- /dev/null +++ b/generic/Draw.c @@ -0,0 +1,732 @@ +/* + * Draw.c -- Implementation of common drawing routines. + * + * Authors : Patrick Lecoanet. + * Creation date : Sat Dec 10 12:51:30 1994 + * + * $Id$ + */ + +/* + * Copyright (c) 1993 - 1999 CENA, Patrick Lecoanet -- + * + * This code is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this code; if not, write to the Free + * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + + +/* + ********************************************************************************** + * + * The algorihms used to draw the arrows, to do the 3d effects and to + * smooth the polygons are adapted from Tk. + * + ********************************************************************************** + */ + +#include "config.h" +#include "Types.h" +#include "Draw.h" +#include "Geo.h" +#include "List.h" +#include "WidgetInfo.h" + +#include <math.h> +#include <stdarg.h> +#include <alloca.h> + + +#define POLYGON_RELIEF_DRAW 0 +#define POLYGON_RELIEF_DIST 1 +#define POLYGON_RELIEF_BBOX 2 +#define POLYGON_RELIEF_IN_BBOX 3 + +#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 + + +/* + ********************************************************************************** + * + * SetLineStyle -- + * + ********************************************************************************** + */ +void +SetLineStyle(Display *display, + GC gc, + LineStyle line_style) +{ + 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 LINE_DASHED : + XSetDashes(display, gc, 0, dashed, 1); + break; + case LINE_MIXED : + XSetDashes(display, gc, 0, mixed, 4); + break; + case LINE_DOTTED : + XSetDashes(display, gc, 0, dotted, 2); + break; + default: + values.line_style = LineSolid; + break; + } + XChangeGC(display, gc, GCLineStyle, &values); +} + + +/* + ********************************************************************************** + * + * GetLineShape -- + * 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 +GetLineShape(RadarPoint *p1, + RadarPoint *p2, + unsigned int line_width, + LineShape shape, + RadarBBox *bbox, + RadarList to_points) +{ + RadarPoint *points; + int num_points, i; + + /* + * Compute all line points according to shape. + */ + if ((shape == LINE_LEFT_LIGHTNING) || + (shape == LINE_RIGHT_LIGHTNING)) { + double alpha, theta; + double length, length2; + double shape_a, shape_b; + double dx, dy; + double temp; + + num_points = LIGHTNING_POINTS; + RadarListAssertSize(to_points, num_points); + points = (RadarPoint *) RadarListArray(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 + ((double) line_width)/2; + shape_b = length/LIGHTNING_SHAPE_B_RATIO + ((double) line_width)/2; + + if (shape == 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 == LINE_LEFT_CORNER || + shape == LINE_RIGHT_CORNER) { + num_points = CORNER_POINTS; + RadarListAssertSize(to_points, num_points); + points = (RadarPoint *) RadarListArray(to_points); + + points[0] = *p1; + points[2] = *p2; + + if (shape == 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 == LINE_DOUBLE_LEFT_CORNER || + shape == LINE_DOUBLE_RIGHT_CORNER) { + int dx, dy; + + num_points = DOUBLE_CORNER_POINTS; + RadarListAssertSize(to_points, num_points); + points = (RadarPoint *) RadarListArray(to_points); + + points[0] = *p1; + points[3] = *p2; + + if (shape == LINE_DOUBLE_LEFT_CORNER) { + dy = 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 = 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) == LINE_STRAIGHT) */ { + num_points = STRAIGHT_POINTS; + RadarListAssertSize(to_points, num_points); + points = (RadarPoint *) RadarListArray(to_points); + + points[0] = *p1; + points[1] = *p2; + } + + /* + * Fill in the bbox, if requested. + */ + if (bbox) { + ResetBBox(bbox); + for (i = 0; i < num_points; i++) { + AddPointToBBox(bbox, points[i].x, points[i].y); + } + + /* Enlarge to take line_width into account. */ + if (line_width > 1) { + int 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; + } + } +} + + +/* + ********************************************************************************** + * + * DrawLineShape -- + * 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 +DrawLineShape(Display *display, + Drawable draw_buffer, + GC gc, + RadarPoint *p, + int num_p, + LineStyle line_style, + RadarColor foreground, + unsigned int line_width, + LineShape shape) +{ + XPoint *xpoints; + int i; + XGCValues values; + + /* + * Setup GC. + */ + SetLineStyle(display, gc, line_style); + values.foreground = RadarPixel(foreground); + values.line_width = (line_width == 1) ? 0 : line_width; + values.fill_style = FillSolid; + values.join_style = JoinRound; + values.cap_style = CapRound; + XChangeGC(display, gc, + GCFillStyle | GCLineWidth | GCJoinStyle | GCCapStyle | GCForeground, &values); + xpoints = (XPoint *) alloca(num_p * sizeof(XPoint)); + for (i = 0; i < num_p; i++) { + xpoints[i].x = p[i].x; + xpoints[i].y = p[i].y; + } + XDrawLines(display, draw_buffer, gc, xpoints, num_p, CoordModeOrigin); +} + + +/* + ********************************************************************************** + * + * DrawRectangleRelief -- + * Draw the bevels inside bbox. + * + ********************************************************************************** + */ +void +DrawRectangleRelief(WidgetInfo *wi, + ReliefStyle relief, + RadarColorGradient gradient, + XRectangle *bbox, + unsigned int line_width) +{ + XGCValues gc_values; + XPoint top_points[7]; + + /* + * 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 == RELIEF_RIDGE) || (relief == RELIEF_GROOVE)) { + unsigned int new_line_width; + int offset; + XRectangle internal_bbox; + + new_line_width = line_width/2; + offset = line_width - new_line_width; + DrawRectangleRelief(wi, + (relief==RELIEF_GROOVE)?RELIEF_BEVEL_IN:RELIEF_BEVEL_OUT, + 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; + DrawRectangleRelief(wi, + (relief==RELIEF_GROOVE)?RELIEF_BEVEL_OUT:RELIEF_BEVEL_IN, + gradient, &internal_bbox, new_line_width); + return; + } + + gc_values.fill_style = FillSolid; + if (relief == RELIEF_BEVEL_IN) { + gc_values.foreground = RadarColorGradientPixel(gradient, wi->win, -1); + } + else { + gc_values.foreground = RadarColorGradientPixel(gradient, wi->win, 0); + } + XChangeGC(wi->dpy, wi->gc, GCFillStyle|GCForeground, &gc_values); + XFillRectangle(wi->dpy, wi->draw_buffer, wi->gc, + bbox->x, bbox->y + bbox->height - line_width, + bbox->width, line_width); + XFillRectangle(wi->dpy, wi->draw_buffer, wi->gc, + bbox->x + bbox->width - line_width, bbox->y, + line_width, bbox->height); + + if (relief == RELIEF_BEVEL_IN) { + gc_values.foreground = RadarColorGradientPixel(gradient, wi->win, 0); + } + else { + gc_values.foreground = RadarColorGradientPixel(gradient, wi->win, -1); + } + XChangeGC(wi->dpy, wi->gc, GCFillStyle|GCForeground, &gc_values); + top_points[0].x = top_points[1].x = top_points[6].x = bbox->x; + top_points[0].y = top_points[6].y = bbox->y + bbox->height; + top_points[1].y = top_points[2].y = bbox->y; + top_points[2].x = bbox->x + bbox->width; + top_points[3].x = bbox->x + bbox->width - line_width; + top_points[3].y = top_points[4].y = bbox->y + line_width; + top_points[4].x = top_points[5].x = bbox->x + line_width; + top_points[5].y = bbox->y + bbox->height - line_width; + XFillPolygon(wi->dpy, wi->draw_buffer, wi->gc, top_points, 7, + Nonconvex, CoordModeOrigin); +} + + +static void +DoPolygonRelief(RadarPoint *p, + int num_points, + unsigned int line_width, + int what_to_do, + ...) +{ + int i, j, processed_points, *result; + RadarPoint *p1, *p2; + RadarPoint pp1, pp2, new_pp1, new_pp2; + RadarPoint perp, c, shift1, shift2; + RadarPoint bevel_points[4]; + XPoint bevel_xpoints[4]; + RadarBool parallel, closed; + WidgetInfo *wi = NULL; + ReliefStyle relief = 0; + RadarColorGradient gradient = NULL; + RadarPoint *pp = NULL; + double *dist = NULL; + RadarBBox *bbox = NULL; + va_list var; + + va_start(var, what_to_do); + if (what_to_do == POLYGON_RELIEF_DIST) { + pp = va_arg(var, RadarPoint *); + dist = va_arg(var, double *); + *dist = 1.0e40; + } + if (what_to_do == POLYGON_RELIEF_IN_BBOX) { + bbox = va_arg(var, RadarBBox *); + result = va_arg(var, int *); + } + else if (what_to_do == POLYGON_RELIEF_BBOX) { + bbox = va_arg(var, RadarBBox *); + ResetBBox(bbox); + } + else if (what_to_do == POLYGON_RELIEF_DRAW) { + wi = va_arg(var, WidgetInfo *); + relief = va_arg(var, int); + gradient = va_arg(var, RadarColorGradient); + } + va_end(var); + + /* + * 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--; + } + + /* + * 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 parallel: + * + * 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, b, corner from pp1, pp2, new_pp1 + * and new_pp2. We compute the perpendicular to p&,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 < num_points; i++, p1 = p2, p2++) { + /* + * When it is time to wrap, do it + */ + if ((i == -1) || (i == num_points-1)) { + p2 = p; + } + + /* + * Skip over duplicate vertices. + */ + if ((p2->x == p1->x) && (p2->y == p1->y)) { + continue; + } + + ShiftLine(p1, p2, line_width, &new_pp1, &new_pp2); + bevel_points[3] = *p1; + parallel = 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); + IntersectLines(p1, &perp, &new_pp1, &new_pp2, &bevel_points[2]); + } + else if ((processed_points == num_points-1) && !closed) { + perp.x = p1->x + ((p1-1)->y - p1->y); + perp.y = p1->y - ((p1-1)->x - p1->x); + IntersectLines(p1, &perp, &pp1, &pp2, &bevel_points[2]); + } + else if (processed_points >= 1) { + parallel = !IntersectLines(&new_pp1, &new_pp2, &pp1, &pp2, &bevel_points[2]); + if (parallel) { + perp.x = p1->x + (p2->y - p1->y); + perp.y = p1->y - (p2->x - p1->x); + IntersectLines(p1, &perp, &pp1, &pp2, &bevel_points[2]); + IntersectLines(p1, &perp, &new_pp1, &new_pp2, &c); + ShiftLine(p1, &perp, line_width, &shift1, &shift2); + IntersectLines(p1, p2, &shift1, &shift2, &bevel_points[3]); + } + } + + if ((processed_points >= 2) || (!closed && (processed_points == 1))) { + if (what_to_do == POLYGON_RELIEF_DIST) { + double new_dist; + + new_dist = PolygonToPointDist(bevel_points, 4, pp); + if (new_dist < 0) { + new_dist = 0; + } + *dist = MIN(*dist, new_dist); + } + else if (what_to_do == POLYGON_RELIEF_IN_BBOX) { + if (processed_points <= 2) { + *result = PolygonInBBox(bevel_points, 4, bbox); + if (*result == 0) { + return; + } + } + else { + if (PolygonInBBox(bevel_points, 4, bbox) != *result) { + *result = 0; + return; + } + } + } + else if (what_to_do == POLYGON_RELIEF_BBOX) { + int i; + + for (i = 0; i < 4; i++) { + AddPointToBBox(bbox, bevel_points[i].x, bevel_points[i].y); + } + } + else if (what_to_do == POLYGON_RELIEF_DRAW) { + RadarReal dx, dy; + RadarBool light_on_left; + XGCValues gc_values; + + dx = bevel_points[3].x - bevel_points[0].x; + dy = bevel_points[3].y - bevel_points[0].y; + if (dx > 0.0) { + light_on_left = (dy <= dx); + } + else { + light_on_left = (dy < dx); + } + + gc_values.fill_style = FillSolid; + if (light_on_left ^ (relief == RELIEF_BEVEL_OUT)) { + gc_values.foreground = RadarColorGradientPixel(gradient, wi->win, -1); + } + else { + gc_values.foreground = RadarColorGradientPixel(gradient, wi->win, 0); + } + XChangeGC(wi->dpy, wi->gc, GCFillStyle|GCForeground, &gc_values); + + for (j = 0; j < 4; j++) { + bevel_xpoints[j].x = REAL_TO_INT(bevel_points[j].x); + bevel_xpoints[j].y = REAL_TO_INT(bevel_points[j].y); + } + XFillPolygon(wi->dpy, wi->draw_buffer, wi->gc, bevel_xpoints, 4, + Convex, CoordModeOrigin); + } + } + + pp1 = new_pp1; + pp2 = new_pp2; + bevel_points[0] = bevel_points[3]; + if (parallel) { + bevel_points[1] = c; + } + else if ((processed_points >= 1) || !closed) { + bevel_points[1] = bevel_points[2]; + } + + processed_points++; + } +} + + +/* + ********************************************************************************** + * + * GetPolygonReliefBBox -- + * Returns the bevelled polygon bounding box. + * + ********************************************************************************** + */ +void +GetPolygonReliefBBox(RadarList points, + unsigned int line_width, + RadarBBox *bbox) +{ + DoPolygonRelief(RadarListArray(points), RadarListSize(points), + line_width, POLYGON_RELIEF_BBOX, bbox); +} + + +/* + ********************************************************************************** + * + * PolygonReliefInBBox -- + * Returns (-1) if the relief is entirely outside the bbox, (1) if it is + * entirely inside or (0) if in between + * + ********************************************************************************** + */ +int +PolygonReliefInBBox(RadarList points, + unsigned int line_width, + RadarBBox *area) +{ + int result; + + DoPolygonRelief(RadarListArray(points), RadarListSize(points), + line_width, POLYGON_RELIEF_IN_BBOX, area, &result); + + return result; +} + + +/* + ********************************************************************************** + * + * PolygonReliefToPointDist -- + * Returns the distance between the given point and + * the bevelled polygon. + * + ********************************************************************************** + */ +double +PolygonReliefToPointDist(RadarList points, + unsigned int line_width, + RadarPoint *pp) +{ + double dist; + + DoPolygonRelief(RadarListArray(points), RadarListSize(points), + line_width, POLYGON_RELIEF_DIST, pp, &dist); + + return dist; +} + + +/* + ********************************************************************************** + * + * DrawPolygonRelief -- + * Draw the bevels around path. + * + ********************************************************************************** + */ +void +DrawPolygonRelief(WidgetInfo *wi, + ReliefStyle relief, + RadarColorGradient gradient, + RadarPoint *points, + int num_points, + int line_width) +{ + /* + * Grooves and ridges are drawn with two calls. The first + * with the original width, the second with half the width. + */ + if ((relief == RELIEF_RIDGE) || (relief == RELIEF_GROOVE)) { + DoPolygonRelief(points, num_points, line_width, POLYGON_RELIEF_DRAW, wi, + (int) (relief==RELIEF_GROOVE)?RELIEF_BEVEL_OUT:RELIEF_BEVEL_IN, + gradient); + DoPolygonRelief(points, num_points, line_width/2, POLYGON_RELIEF_DRAW, wi, + (int) (relief==RELIEF_GROOVE)?RELIEF_BEVEL_IN:RELIEF_BEVEL_OUT, + gradient); + } + else { + DoPolygonRelief(points, num_points, line_width, POLYGON_RELIEF_DRAW, wi, + (int) relief, gradient); + } +} + |