/* This file is part of the GNU libxmi package. Copyright (C) 1985, 1986, 1987, 1988, 1989, X Consortium. For an associated permission notice, see the accompanying file README-X. GNU enhancements Copyright (C) 1998, 1999, 2000, 2005, Free Software Foundation, Inc. The GNU libxmi package is free software. You may redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software foundation; either version 2, or (at your option) any later version. The GNU libxmi package 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 General Public License for more details. You should have received a copy of the GNU General Public License along with the GNU plotutils package; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301, USA. */ /* This module contains the miZeroLine() and miZeroDash() functions. They a rasterize single-pixel (i.e., `zero-width') Bresenham polyline, either solid or dashed. The cap mode and join mode in the graphics context are ignored, except that if the cap mode is MI_CAP_NOT_LAST, the final pixel of the polyline is omitted. All painting goes through the low-level MI_PAINT_SPANS() and MI_COPY_AND_PAINT_SPANS() macros. */ /* Historical note: this is a merger of MI code from X11, written by Ken Whaley, with low-level X11 CFB (color frame-buffer) code, of unknown authorship. (I wrote Ken, and he remembers writing the monochrome frame buffer code, but not the color code.) That's because the X11 MI code included miZeroLine(), but not miZeroDash(). I based the latter on the CFB routines. I needed to hack them extensively so that they would generate a list of spans to paint. I also removed clipping code. --rsm */ #include "sys-defines.h" #include "extern.h" #include "xmi.h" #include "mi_spans.h" #include "mi_gc.h" #include "mi_api.h" #include "mi_line.h" /* Comment on drawing solid lines (from Ken Whaley): NON-SLOPED LINES Horizontal lines are always drawn left to right; we have to move the endpoints right by one after they're swapped. Vertical lines are always drawn top to bottom (y-increasing). This requires adding one to the y-coordinate of each endpoint after swapping. */ /* forward references */ static void cfbBresD (miPaintedSet *paintedSet, const miGC *pGC, int *pdashNum, int *pdashIndex, const unsigned int *pDash, int numInDashList, int *pdashOffset, bool isDoubleDash, int signdx, int signdy, int axis, int x1, int y1, int e, int e1, int e2, int len); static void cfbBresS (miPaintedSet *paintedSet, const miGC *pGC, int signdx, int signdy, int axis, int x1, int y1, int e, int e1, int e2, int len); static void cfbHorzS (miPaintedSet *paintedSet, const miGC *pGC, int x1, int y1, int len); static void cfbVertS (miPaintedSet *paintedSet, const miGC *pGC, int x1, int y1, int len); /* Macro for painting a single point. Used for ending line segments. */ #define MI_PAINT_POINT(paintedSet, pixel, xx, yy) \ {\ miPoint *point;\ unsigned int *width;\ point = (miPoint *)mi_xmalloc(sizeof(miPoint));\ width = (unsigned int *)mi_xmalloc(sizeof(unsigned int));\ *width = 1;\ point->x = xx;\ point->y = yy;\ MI_PAINT_SPANS(paintedSet, pixel, 1, point, width)\ } /* Macro for generating a list of spans, used when the successive points on a Bresenham line are generated. Assumes the availability of working storage, accessed via pointers `ppt' and `pwidths', and also variables numSpans (initted to 0), firstspan (initted to true), and ycurr. The arrays should be at least as large as the longest generated Bresenham line segment. */ #define MI_ADD_POINT(xx, yy, ppt, pwidth, numSpans, ycurr, firstspan, signdy) \ {\ if (!firstspan && yy == ycurr)\ {\ int xdelta = xx - ppt->x;\ if (xdelta < 0)\ {\ (*pwidth) -= xdelta;\ ppt->x = xx;\ }\ else if (xdelta > 0)\ { \ unsigned int widthcurr = *pwidth; \ (*pwidth) = UMAX(widthcurr, (unsigned int)(1 + xdelta));\ } \ }\ else\ {\ if (!firstspan)\ {\ ppt += signdy;\ pwidth += signdy;\ }\ else\ firstspan = false;\ ppt->x = xx;\ ppt->y = yy;\ *pwidth = 1;\ ycurr = yy;\ ++numSpans;\ }\ } /* * Draw a solid Bresenham polyline, i.e. a `zero-width' solid polyline, * in paint type #1. */ /* ARGS: mode = Origin or Previous npt = number of points pPts = point array */ void miZeroLine (miPaintedSet *paintedSet, const miGC *pGC, miCoordMode mode, int npt, const miPoint *pPts) { const miPoint *ppt; /* pointer to point within array */ /* temporaries */ int xstart, ystart; int x1, x2; int y1, y2; /* ensure we have >=1 points */ if (npt <= 0) return; /* loop through points, drawing a solid Bresenham segment for each line segment */ ppt = pPts; xstart = ppt->x; ystart = ppt->y; x2 = xstart; y2 = ystart; while (--npt) { x1 = x2; y1 = y2; ++ppt; x2 = ppt->x; y2 = ppt->y; if (mode == MI_COORD_MODE_PREVIOUS) /* convert from relative coordinates */ { x2 += x1; y2 += y1; } if (x1 == x2) /* vertical line */ { if (y1 > y2) /* make line go top to bottom, keeping endpoint semantics */ { int tmp; tmp = y2; y2 = y1 + 1; y1 = tmp + 1; } /* draw line */ if (y1 != y2) cfbVertS (paintedSet, pGC, x1, y1, y2 - y1); /* restore final point */ y2 = ppt->y; } else if (y1 == y2) /* horizontal line */ { if (x1 > x2) /* force line from left to right, keeping endpoint semantics */ { int tmp; tmp = x2; x2 = x1 + 1; x1 = tmp + 1; } /* draw line */ if (x1 != x2) cfbHorzS (paintedSet, pGC, x1, y1, x2 - x1); /* restore final point */ x2 = ppt->x; } else /* sloped line */ { int adx; /* abs values of dx and dy */ int ady; int signdx; /* sign of dx and dy */ int signdy; int e, e1, e2; /* Bresenham error and increments */ int axis; /* major axis */ int len; /* length of segment */ AbsDeltaAndSign(x2, x1, adx, signdx); AbsDeltaAndSign(y2, y1, ady, signdy); if (adx > ady) { axis = X_AXIS; e1 = ady << 1; e2 = e1 - (adx << 1); e = e1 - adx; FIXUP_X_MAJOR_ERROR(e, signdx, signdy); } else { axis = Y_AXIS; e1 = adx << 1; e2 = e1 - (ady << 1); e = e1 - ady; FIXUP_Y_MAJOR_ERROR(e, signdx, signdy); } /* we have Bresenham parameters and two points, so all we need to do now is draw */ if (axis == X_AXIS) len = adx; else len = ady; cfbBresS (paintedSet, pGC, signdx, signdy, axis, x1, y1, e, e1, e2, len); } /* sloped line */ } /* while (--npt) */ /* Paint the last point if the end style isn't CapNotLast. (I.e. assume that a round/butt/projecting/triangular cap that is one pixel wide is the same as the single pixel of the endpoint.) */ if (pGC->capStyle != (int)MI_CAP_NOT_LAST && (xstart != x2 || ystart != y2 || ppt == pPts + 1)) MI_PAINT_POINT(paintedSet, pGC->pixels[1], x2, y2) } /* * Draw a dashed Bresenham polyline, i.e. a `zero-width' dashed polyline. */ /* ARGS: mode = Origin or Previous npt = number of points pPts = point array */ void miZeroDash (miPaintedSet *paintedSet, const miGC *pGC, miCoordMode mode, int npt, const miPoint *pPts) { const miPoint *ppt; /* pointer to current point */ /* temporaries */ int xstart, ystart; int x1, x2, y1, y2; const unsigned int *pDash; int dashNum, dashIndex; int dashOffset; int numInDashList; bool isDoubleDash; /* ensure we have >=1 points */ if (npt <= 0) return; /* perform initial offsetting into the dash array; compute dash values */ pDash = pGC->dash; numInDashList = pGC->numInDashList; isDoubleDash = (pGC->lineStyle == (int)MI_LINE_DOUBLE_DASH ? true : false); dashNum = 0; dashIndex = 0; dashOffset = 0; miStepDash (pGC->dashOffset, &dashNum, &dashIndex, pDash, numInDashList, &dashOffset); /* loop through points, drawing a dashed Bresenham segment for each line segment of nonzero length */ ppt = pPts; xstart = ppt->x; ystart = ppt->y; x2 = xstart; y2 = ystart; while (--npt) { x1 = x2; y1 = y2; ++ppt; x2 = ppt->x; y2 = ppt->y; if (mode == MI_COORD_MODE_PREVIOUS) /* convert from relative coordinates */ { x2 += x1; y2 += y1; } /* use Bresenham algorithm for sloped lines (no special treatment for horizontal or vertical lines, unlike the undashed case) */ { int adx; /* abs values of dx and dy */ int ady; int signdx; /* sign of dx and dy */ int signdy; int e, e1, e2; /* Bresenham error and increments */ int axis; /* major axis */ int len; AbsDeltaAndSign(x2, x1, adx, signdx); AbsDeltaAndSign(y2, y1, ady, signdy); if (adx > ady) { axis = X_AXIS; e1 = ady << 1; e2 = e1 - (adx << 1); e = e1 - adx; len = adx; FIXUP_X_MAJOR_ERROR(e, signdx, signdy); } else { axis = Y_AXIS; e1 = adx << 1; e2 = e1 - (ady << 1); e = e1 - ady; len = ady; FIXUP_Y_MAJOR_ERROR(e, signdx, signdy); } /* we have Bresenham parameters and two points, so all we need to do now is draw (updating dashNum, dashIndex and dashOffset) */ cfbBresD (paintedSet, pGC, &dashNum, &dashIndex, pDash, numInDashList, &dashOffset, isDoubleDash, signdx, signdy, axis, x1, y1, e, e1, e2, len); } } /* while (nline--) */ /* paint the last point if the end style isn't CapNotLast. (Assume that a projecting, butt, or round cap that is one pixel wide is the same as the single pixel of the endpoint.) */ if (pGC->capStyle != (int)MI_CAP_NOT_LAST && (xstart != x2 || ystart != y2 || ppt == pPts + 1)) { if (dashNum & 1) { /* background dash */ if (isDoubleDash) /* paint, in paint type #0 */ MI_PAINT_POINT(paintedSet, pGC->pixels[0], x2, y2); } else /* foreground dash */ { /* use a paint type that cycles through 1..(numPixels-1) */ int numPixels = pGC->numPixels; int paintType = 1 + ((dashNum / 2) % (numPixels - 1)); MI_PAINT_POINT(paintedSet, pGC->pixels[paintType], x2, y2); } } } /* Internal: draw solid Bresenham line segment, in paint type #1. Called by miZeroLine(). Endpoint semantics are used, i.e. we paint only len pixels (i.e. |dx| or |dy| pixels), not including the endpoint. */ /* ARGS: signdx,signdy = signs of directions axis = major axis (Y_AXIS or X_AXIS) x1,y1 = initial point e = error accumulator e1,e2 = Bresenham increments len = length of line in pixels */ static void cfbBresS (miPaintedSet *paintedSet, const miGC *pGC, int signdx, int signdy, int axis, int x1, int y1, int e, int e1, int e2, int len) { miPoint *pptInit, *pptLast; unsigned int *pwidthInit, *pwidthLast; int x, y; int e3; /* variables in span generation code, i.e. in MI_ADD_POINT() */ int numSpans; int ycurr; miPoint *ppt; unsigned int *pwidth; bool firstspan; if (len == 0) return; /* set up work arrays */ pptInit = (miPoint *)mi_xmalloc(len * sizeof(miPoint)); pwidthInit = (unsigned int *)mi_xmalloc(len * sizeof(unsigned int)); pptLast = pptInit + (len - 1); pwidthLast = pwidthInit + (len - 1); /* reset variables used in MI_ADD_POINT() */ numSpans = 0; ycurr = 0; firstspan = true; if (signdy >= 0) { ppt = pptInit; pwidth = pwidthInit; } else { ppt = pptLast; pwidth = pwidthLast; } e3 = e2 - e1; e = e - e1; /* make looping easier */ #define BresStep(minor,major) \ {if ((e += e1) >= 0) { e += e3; minor; } major;} #define Loop_x(counter,store) while (counter--) \ {store; BresStep(y+=signdy,x+=signdx) } #define Loop_y(counter,store) while (counter--) \ {store; BresStep(x+=signdx, y+=signdy) } /* point to first point, and generate len pixels */ x = x1; y = y1; switch (axis) { case X_AXIS: default: Loop_x(len, MI_ADD_POINT(x, y, ppt, pwidth, numSpans, ycurr, firstspan, signdy)) break; case Y_AXIS: Loop_y(len, MI_ADD_POINT(x, y, ppt, pwidth, numSpans, ycurr, firstspan, signdy)) break; } if (numSpans > 0) { if (signdy < 0) /* spans are offset, so shift downward */ { miPoint *ppt_src = pptLast - (numSpans - 1); miPoint *ppt_dst = pptInit; unsigned int *pwidth_src = pwidthLast - (numSpans - 1); unsigned int *pwidth_dst = pwidthInit; int count = numSpans; while (count--) { *ppt_dst++ = *ppt_src++; *pwidth_dst++ = *pwidth_src++; } } MI_PAINT_SPANS(paintedSet, pGC->pixels[1], numSpans, pptInit, pwidthInit) } } /* Internal: draw dashed Bresenham line segment. Called by miZeroDash(). Endpoint semantics are used. */ /* ARGS: pdashNum = absolute dash number pdashIndx = index into dash array pDash = dash array numInDashList = length of dash array pdashOffset = offset into current dash signdx,signdy = signs of directions axis = major axis (Y_AXIS or X_AXIS) x1,y1 = initial point e = error accumulator e1,e2 = Bresenham increments len = length of line in pixels */ static void cfbBresD (miPaintedSet *paintedSet, const miGC *pGC, int *pdashNum, int *pdashIndex, const unsigned int *pDash, int numInDashList, int *pdashOffset, bool isDoubleDash, int signdx, int signdy, int axis, int x1, int y1, int e, int e1, int e2, int len) { miPoint *pptInit_fg, *pptInit_bg = (miPoint *)NULL; miPoint *pptLast_fg, *pptLast_bg = (miPoint *)NULL; unsigned int *pwidthInit_fg, *pwidthInit_bg = (unsigned int *)NULL; unsigned int *pwidthLast_fg, *pwidthLast_bg = (unsigned int *)NULL; int x, y; int e3; int dashNum, dashIndex; int dashOffset; int dashRemaining; int thisDash; /* variables in span generation code, i.e. in MI_ADD_POINT() */ int numSpans_fg, numSpans_bg = 0; int ycurr_fg, ycurr_bg = 0; miPoint *ppt_fg, *ppt_bg = (miPoint *)NULL; unsigned int *pwidth_fg, *pwidth_bg = (unsigned int *)NULL; bool firstspan_fg, firstspan_bg = false; /* set up work arrays */ pptInit_fg = (miPoint *)mi_xmalloc(len * sizeof(miPoint)); pwidthInit_fg = (unsigned int *)mi_xmalloc(len * sizeof(unsigned int)); pptLast_fg = pptInit_fg + (len - 1); pwidthLast_fg = pwidthInit_fg + (len - 1); if (isDoubleDash) { pptInit_bg = (miPoint *)mi_xmalloc(len * sizeof(miPoint)); pwidthInit_bg = (unsigned int *)mi_xmalloc(len * sizeof(unsigned int)); pptLast_bg = pptInit_bg + (len - 1); pwidthLast_bg = pwidthInit_bg + (len - 1); } dashNum = *pdashNum; /* absolute number of current dash */ dashIndex = *pdashIndex; /* index of current dash */ dashOffset = *pdashOffset; /* offset into current dash */ dashRemaining = (int)(pDash[dashIndex]) - dashOffset; /* how much is left */ if (len <= (thisDash = dashRemaining)) /* line segment will be solid, not dashed */ { thisDash = len; dashRemaining -= len; } #define NextDash {\ dashNum++; \ dashIndex++; \ if (dashIndex == numInDashList) \ dashIndex = 0; \ dashRemaining = (int)(pDash[dashIndex]); \ if ((thisDash = dashRemaining) >= len) \ { \ dashRemaining -= len; \ thisDash = len; \ } \ } e3 = e2-e1; e = e - e1; /* make looping easier */ /* point to first point */ x = x1; y = y1; /* loop, generating dashes (in the absence of dashing, would generate len pixels in all) */ for ( ; ; ) { len -= thisDash; /* reset variables used in MI_ADD_POINT() */ numSpans_fg = 0; ycurr_fg = 0; firstspan_fg = true; if (signdy >= 0) { ppt_fg = pptInit_fg; pwidth_fg = pwidthInit_fg; } else { ppt_fg = pptLast_fg; pwidth_fg = pwidthLast_fg; } if (isDoubleDash) { numSpans_bg = 0; ycurr_bg = 0; firstspan_bg = true; ppt_bg = pptInit_bg; pwidth_bg = pwidthInit_bg; if (signdy >= 0) { ppt_bg = pptInit_bg; pwidth_bg = pwidthInit_bg; } else { ppt_bg = pptLast_bg; pwidth_bg = pwidthLast_bg; } } switch (axis) { case X_AXIS: default: if (dashIndex & 1) { if (isDoubleDash) { /* create background dash */ Loop_x(thisDash, MI_ADD_POINT(x, y, ppt_bg, pwidth_bg, numSpans_bg, ycurr_bg, firstspan_bg, signdy)) } else /* not double dashing; no background dash */ Loop_x(thisDash, ;); } else /* create foreground dash */ Loop_x(thisDash, MI_ADD_POINT(x, y, ppt_fg, pwidth_fg, numSpans_fg, ycurr_fg, firstspan_fg, signdy)) break; case Y_AXIS: if (dashIndex & 1) { if (isDoubleDash) { /* create background dash */ Loop_y(thisDash, MI_ADD_POINT(x, y, ppt_bg, pwidth_bg, numSpans_bg, ycurr_bg, firstspan_bg, signdy)) } else /* not double dashing; no background dash */ Loop_y(thisDash, ;); } else /* create foreground dash */ Loop_y(thisDash, MI_ADD_POINT(x, y, ppt_fg, pwidth_fg, numSpans_fg, ycurr_fg, firstspan_fg, signdy)) break; } /* end switch */ if (numSpans_fg > 0) /* have a foreground dash to paint */ { miPoint *pptStart_fg; unsigned int *pwidthStart_fg; if (signdy >= 0) { pptStart_fg = pptInit_fg; pwidthStart_fg = pwidthInit_fg; } else { pptStart_fg = pptLast_fg - (numSpans_fg - 1); pwidthStart_fg = pwidthLast_fg - (numSpans_fg - 1); } /* for foreground dash, use a paint type that cycles through 1..(numPixels-1) */ { int numPixels = pGC->numPixels; int paintType = 1 + ((dashNum / 2) % (numPixels - 1)); MI_COPY_AND_PAINT_SPANS(paintedSet, pGC->pixels[paintType], numSpans_fg, pptStart_fg, pwidthStart_fg) } } if (isDoubleDash && numSpans_bg > 0) /* have a background dash to paint */ { miPoint *pptStart_bg; unsigned int *pwidthStart_bg; if (signdy >= 0) { pptStart_bg = pptInit_bg; pwidthStart_bg = pwidthInit_bg; } else { pptStart_bg = pptLast_bg - (numSpans_bg - 1); pwidthStart_bg = pwidthLast_bg - (numSpans_bg - 1); } /* for background dash, use paint type #0 */ MI_COPY_AND_PAINT_SPANS(paintedSet, pGC->pixels[0], numSpans_bg, pptStart_bg, pwidthStart_bg) } if (len == 0) break; /* break out of dash-generating loop */ NextDash /* update dashNum, dashIndex, thisDash, dashRemaining */ } /* end infinite dash-generating loop */ *pdashNum = dashNum; *pdashIndex = dashIndex; *pdashOffset = (int)(pDash[dashIndex]) - dashRemaining; free (pwidthInit_fg); free (pptInit_fg); if (isDoubleDash) { free (pwidthInit_bg); free (pptInit_bg); } } /* Internal: draw horizontal zero-width solid line segment, in paint type #1. Called by miZeroLine(). Called with len >= 1, and len=x2-x1. Endpoint semantics are used, so we paint only len pixels, i.e. x1..x2-1. */ /* ARGS: x1,y1 = initial point, len = length of line */ static void cfbHorzS (miPaintedSet *paintedSet, const miGC *pGC, int x1, int y1, int len) { miPoint *ppt; unsigned int *pwidth; ppt = (miPoint *)mi_xmalloc(sizeof(miPoint)); pwidth = (unsigned int *)mi_xmalloc(sizeof(unsigned int)); ppt->x = x1; ppt->y = y1; *pwidth = (unsigned int)len; MI_PAINT_SPANS(paintedSet, pGC->pixels[1], 1, ppt, pwidth) } /* Internal: draw vertical zero-width solid line segment, in paint type #1. Called by miZeroLine(). Called with len >= 1, and len=y2-y1. Endpoint semantics are used, so we paint only len pixels, i.e. y1..y2-1. */ /* ARGS: x1,y1 = initial point, len = length of line */ static void cfbVertS (miPaintedSet *paintedSet, const miGC *pGC, int x1, int y1, int len) { miPoint *ppt, *pptInit; unsigned int *pwidth, *pwidthInit; int i; ppt = pptInit = (miPoint *)mi_xmalloc(len * sizeof(miPoint)); pwidth = pwidthInit = (unsigned int *)mi_xmalloc(len * sizeof(unsigned int)); for (i = 0; i < len; i++) { ppt->x = x1; ppt->y = y1 + i; ppt++; *pwidth++ = (unsigned int)1; } MI_PAINT_SPANS(paintedSet, pGC->pixels[1], len, pptInit, pwidthInit) }