diff options
Diffstat (limited to 'lib/libppmd.c')
-rw-r--r-- | lib/libppmd.c | 363 |
1 files changed, 237 insertions, 126 deletions
diff --git a/lib/libppmd.c b/lib/libppmd.c index ea5127d5..3f86f5b6 100644 --- a/lib/libppmd.c +++ b/lib/libppmd.c @@ -26,7 +26,6 @@ #include "ppmdraw.h" -#define DDA_SCALE 8192 struct penpos { int x; @@ -54,6 +53,7 @@ makePoint(int const x, } + static ppmd_point middlePoint(ppmd_point const a, ppmd_point const b) { @@ -77,6 +77,16 @@ pointsEqual(ppmd_point const a, +static bool +pointIsWithinBounds(ppmd_point const p, + unsigned int const cols, + unsigned int const rows) { + + return (p.x >= 0 && p.x < cols && p.y >= 0 && p.y < rows); +} + + + static ppmd_point vectorSum(ppmd_point const a, ppmd_point const b) { @@ -86,6 +96,48 @@ vectorSum(ppmd_point const a, +static long int const DDA_SCALE = 8192; + +#define PPMD_MAXCOORD 32767 +/* + Several factors govern the limit of x, y coordination values. + + The limit must be representable as (signed) int for coordinates to + be carried in struct penpos (immediately above). + + The following calculation, done with long ints, must not overflow: + cy0 = cy0 + (y1 - cy0) * (cols - 1 - cx0) / (x1 - cx0); + + The following must not overflow when DDA_SCALE is set to 8092: + dy = (y1 - y0) * DDA_SCALE / abs(x1 - x0); + + Overflow conditions for ppmd_text are rather complicated, for commands + come from an external PPMD font file. See comments below. +*/ + + + +void +ppmd_validateCoord(int const c) { + + if (c < -PPMD_MAXCOORD || c > PPMD_MAXCOORD) + pm_error("Coordinate out of bounds: %d", c); +} + + + +void +ppmd_validatePoint(ppmd_point const p) { + + if (p.x < -PPMD_MAXCOORD || p.x > PPMD_MAXCOORD) + pm_error("x coordinate of (%d, %d) out of bounds", p.x, p.y); + + if (p.y < -PPMD_MAXCOORD || p.y > PPMD_MAXCOORD) + pm_error("y coordinate of (%d, %d) out of bounds", p.x, p.y); +} + + + static void drawPoint(ppmd_drawprocp drawproc, const void * const clientdata, @@ -574,6 +626,11 @@ ppmd_linep(pixel ** const pixels, ppmd_point c0, c1; bool noLine; /* There's no line left after clipping */ + ppmd_validateCoord(cols); + ppmd_validateCoord(rows); + ppmd_validatePoint(p0); + ppmd_validatePoint(p1); + if (lineclip) { clipLine(p0, p1, cols, rows, &c0, &c1, &noLine); } else { @@ -785,38 +842,69 @@ ppmd_circlep(pixel ** const pixels, unsigned int const radius, ppmd_drawprocp drawProc, const void * const clientData) { +/*---------------------------------------------------------------------------- + If lineclip mode is on, draw only points within the image. + If lineclip is off, "draw" all points (by designated drawproc). Note + that the drawproc can't actually draw a point outside the image, but + it might maintain state that is affected by imaginary points outside + the image. + + Initial point is 3 o'clock. +-----------------------------------------------------------------------------*/ + if (radius >= DDA_SCALE) + pm_error("Error drawing circle. Radius %d is too large.", radius); + + ppmd_validateCoord(center.x + radius); + ppmd_validateCoord(center.y + radius); + ppmd_validateCoord(center.x - radius); + ppmd_validateCoord(center.y - radius); if (radius > 0) { long const e = DDA_SCALE / radius; - ppmd_point p0; /* The starting point around the circle */ - ppmd_point p; /* Current drawing position in the circle */ - bool nopointsyet; - long sx, sy; - - p0.x = radius; - p0.y = 0; + ppmd_point const p0 = makePoint(radius, 0); /* 3 o'clock */ + /* The starting point around the circle, assuming (0, 0) center */ + ppmd_point p; + /* Current drawing position in the circle, assuming (0,0) center */ + bool onFirstPoint; + bool prevPointExists; + ppmd_point prevPoint; + /* Previous drawing position, assuming (0, 0) center*/ + long sx, sy; /* 'p', scaled by DDA_SCALE */ p = p0; sx = p.x * DDA_SCALE + DDA_SCALE / 2; sy = p.y * DDA_SCALE + DDA_SCALE / 2; - drawPoint(drawProc, clientData, - pixels, cols, rows, maxval, vectorSum(center, p)); - nopointsyet = true; - do { - ppmd_point const prev = p; + onFirstPoint = TRUE; + prevPointExists = FALSE; + + while (onFirstPoint || !pointsEqual(p, p0)) { + pm_message("Doing point (%d, %d)", p.x, p.y); + + if (prevPointExists && pointsEqual(p, prevPoint)) { + /* We're on the same point we were on last time (we moved less + than a point's worth). Just keep moving. + */ + } else { + ppmd_point const imagePoint = vectorSum(center,p); + if (!lineclip || pointIsWithinBounds(imagePoint, cols, rows)) + drawPoint(drawProc, clientData, + pixels, cols, rows, maxval, imagePoint); + + prevPoint = p; + prevPointExists = TRUE; + } + + if (!pointsEqual(p, p0)) + onFirstPoint = FALSE; + sx += e * sy / DDA_SCALE; sy -= e * sx / DDA_SCALE; p = makePoint(sx / DDA_SCALE, sy / DDA_SCALE); - if (!pointsEqual(p, prev)) { - nopointsyet = false; - drawPoint(drawProc, clientData, - pixels, cols, rows, maxval, vectorSum(center, p)); - } + pm_message("next point is (%d, %d)", p.x, p.y); } - while (nopointsyet || !pointsEqual(p, p0)); } } @@ -833,10 +921,14 @@ ppmd_circle(pixel ** const pixels, ppmd_drawproc drawProc, const void * const clientData) { - struct drawProcXY const xy = makeDrawProcXY(drawProc, clientData); + if (radius < 0) + pm_error("Error drawing circle. Radius %d is negative.", radius); + else { + struct drawProcXY const xy = makeDrawProcXY(drawProc, clientData); - ppmd_circlep(pixels, cols, rows, maxval, makePoint(cx, cy), radius, - drawProcPointXY, &xy); + ppmd_circlep(pixels, cols, rows, maxval, makePoint(cx, cy), radius, + drawProcPointXY, &xy); + } } @@ -845,9 +937,8 @@ ppmd_circle(pixel ** const pixels, typedef struct { - short x; - short y; - short edge; + ppmd_point point; + int edge; } coord; typedef struct fillobj { @@ -927,19 +1018,21 @@ ppmd_fill_drawprocp(pixel ** const pixels, fh = (fillobj*) clientdata; + /* If these are the same coords we saved last time, don't bother. */ if (fh->n > 0) { - /* If these are the same coords we saved last time, don't bother. */ - ocp = &(fh->coords[fh->n - 1]); - if (p.x == ocp->x && p.y == ocp->y) + ppmd_point const lastPoint = fh->coords[fh->n - 1].point; + if (pointsEqual(p, lastPoint)) return; } - /* Ok, these are new; check if there's room for two more coords. */ + /* Ok, these are new; make room for two more coords. */ if (fh->n + 1 >= fh->size) { fh->size += SOME; REALLOCARRAY(fh->coords, fh->size); if (fh->coords == NULL) pm_error("out of memory enlarging a fillhandle"); + + ocp = &(fh->coords[fh->n - 1]); } /* Check for extremum and set the edge number. */ @@ -951,8 +1044,8 @@ ppmd_fill_drawprocp(pixel ** const pixels, } else { int dx, dy; - dx = p.x - ocp->x; - dy = p.y - ocp->y; + dx = p.x - ocp->point.x; + dy = p.y - ocp->point.y; if (dx < -1 || dx > 1 || dy < -1 || dy > 1) { /* Segment break. Close off old one. */ if (fh->startydir != 0 && fh->ydir != 0) @@ -960,12 +1053,14 @@ ppmd_fill_drawprocp(pixel ** const pixels, /* Oops, first edge and last edge are the same. Renumber the first edge in the old segment. */ + const coord * const fcpLast= &(fh->coords[fh->n -1]); coord * fcp; + int oldedge; fcp = &(fh->coords[fh->segstart]); oldedge = fcp->edge; - for ( ; fcp->edge == oldedge; ++fcp ) + for (; fcp <= fcpLast && fcp->edge == oldedge ; ++fcp) fcp->edge = ocp->edge; } /* And start new segment. */ @@ -982,8 +1077,7 @@ ppmd_fill_drawprocp(pixel ** const pixels, */ ++fh->curedge; cp = &fh->coords[fh->n]; - cp->x = ocp->x; - cp->y = ocp->y; + cp->point = ocp->point; cp->edge = fh->curedge; ++fh->n; } @@ -996,15 +1090,13 @@ ppmd_fill_drawprocp(pixel ** const pixels, /* Save this coord. */ cp = &fh->coords[fh->n]; - cp->x = p.x; - cp->y = p.y; + cp->point = p; cp->edge = fh->curedge; ++fh->n; } - void ppmd_fill_drawproc(pixel** const pixels, int const cols, @@ -1032,15 +1124,18 @@ yx_compare(const void * const c1Arg, const coord * const c1P = c1Arg; const coord * const c2P = c2Arg; + ppmd_point const p1 = c1P->point; + ppmd_point const p2 = c2P->point; + int retval; - if (c1P->y > c2P->y) + if (p1.y > p2.y) retval = 1; - else if (c1P->y < c2P->y) + else if (p1.y < p2.y) retval = -1; - else if (c1P->x > c2P->x) + else if (p1.x > p2.x) retval = 1; - else if (c1P->x < c2P->x) + else if (p1.x < p2.x) retval = -1; else retval = 0; @@ -1070,12 +1165,13 @@ ppmd_fill(pixel ** const pixels, if (fh->startydir == fh->ydir) { /* Oops, first edge and last edge are the same. */ coord * fcp; + const coord * const fcpLast = & (fh->coords[fh->n - 1]); int lastedge, oldedge; lastedge = fh->coords[fh->n - 1].edge; fcp = &(fh->coords[fh->segstart]); oldedge = fcp->edge; - for ( ; fcp->edge == oldedge; ++fcp ) + for ( ; fcp<=fcpLast && fcp->edge == oldedge; ++fcp ) fcp->edge = lastedge; } } @@ -1098,7 +1194,7 @@ ppmd_fill(pixel ** const pixels, fh->coords[i-2] = t; } if (i > 0) { - if (cp->x == lx && cp->y == py) { + if (cp->point.x == lx && cp->point.y == py) { eq = TRUE; if (cp->edge != edge && cp->edge == pedge) { /* Swap . and .-1. */ @@ -1111,8 +1207,8 @@ ppmd_fill(pixel ** const pixels, } else eq = FALSE; } - lx = cp->x; - py = cp->y; + lx = cp->point.x; + py = cp->point.y; pedge = edge; edge = cp->edge; } @@ -1121,35 +1217,35 @@ ppmd_fill(pixel ** const pixels, for (i = 0; i < fh->n; ++i) { cp = &fh->coords[i]; if (i == 0) { - lx = rx = cp->x; - py = cp->y; + lx = rx = cp->point.x; + py = cp->point.y; edge = cp->edge; leftside = TRUE; } else { - if (cp->y != py) { + if (cp->point.y != py) { /* Row changed. Emit old span and start a new one. */ ppmd_filledrectangle( pixels, cols, rows, maxval, lx, py, rx - lx + 1, 1, drawProc, clientdata); - lx = rx = cp->x; - py = cp->y; + lx = rx = cp->point.x; + py = cp->point.y; edge = cp->edge; leftside = TRUE; } else { if (cp->edge == edge) { /* Continuation of side. */ - rx = cp->x; + rx = cp->point.x; } else { /* Edge changed. Is it a span? */ if (leftside) { - rx = cp->x; + rx = cp->point.x; leftside = FALSE; } else { /* Got a span to fill. */ ppmd_filledrectangle( pixels, cols, rows, maxval, lx, py, rx - lx + 1, 1, drawProc, clientdata); - lx = rx = cp->x; + lx = rx = cp->point.x; leftside = TRUE; } edge = cp->edge; @@ -1224,39 +1320,31 @@ static long icos(int deg) static void drawGlyph(const struct ppmd_glyph * const glyphP, - int * const xP, - int const y, + ppmd_point * const glyphCornerP, pixel ** const pixels, unsigned int const cols, unsigned int const rows, pixval const maxval, int const height, - int const xpos, - int const ypos, + ppmd_point const pos, long const rotcos, long const rotsin, - ppmd_drawproc drawProc, + ppmd_drawprocp drawProc, const void * const clientdata ) { /*---------------------------------------------------------------------------- - *xP is the column number of the left side of the glyph in the - output upon entry, and we update it to the left side of the next - glyph. - - 'y' is the row number of either the top or the bottom of the glyph - (I can't tell which right now) in the output. + *pP is either the top left or bottom left corner of the glyph cell + in the output upon entry, and we update it so as to move to the left + edge of the next glyph cell. -----------------------------------------------------------------------------*/ - struct penpos penPos; + ppmd_point const glyphCorner = *glyphCornerP; + ppmd_point p; unsigned int commandNum; - int x; int u; /* Used by the SCHAR macro */ - x = *xP; /* initial value */ - - x -= SCHAR(glyphP->header.skipBefore); - - penPos.x = x; - penPos.y = y; + p = makePoint(glyphCorner.x - SCHAR(glyphP->header.skipBefore), + glyphCorner.y); + /* initial value */ for (commandNum = 0; commandNum < glyphP->header.commandCount; @@ -1270,74 +1358,72 @@ drawGlyph(const struct ppmd_glyph * const glyphP, break; case CMD_DRAWLINE: { - int const nx = x + SCHAR(commandP->x); - int const ny = y + SCHAR(commandP->y); - - int mx1, my1, mx2, my2; - int tx1, ty1, tx2, ty2; - - /* Note that up until this moment we've been - working in an arbitrary model co-ordinate - system with fixed size and no rotation. - Before drawing the stroke, transform to - viewing co-ordinates to honour the height - and angle specifications. + ppmd_point const n = vectorSum(p, makePoint(SCHAR(commandP->x), + SCHAR(commandP->y))); + int const mx1 = (p.x * height) / Scalef; + int const my1 = ((p.y - Descend) * height) / Scalef; + int const mx2 = (n.x * height) / Scalef; + int const my2 = ((n.y - Descend) * height) / Scalef; + + /* Note that all points above are with reference to an arbitrary + model co-ordinate system with fixed size and no rotation. + Following are the points that honor the height and angle + specifications. */ - - mx1 = (penPos.x * height) / Scalef; - my1 = ((penPos.y - Descend) * height) / Scalef; - mx2 = (nx * height) / Scalef; - my2 = ((ny - Descend) * height) / Scalef; - tx1 = xpos + (mx1 * rotcos - my1 * rotsin) / 65536; - ty1 = ypos + (mx1 * rotsin + my1 * rotcos) / 65536; - tx2 = xpos + (mx2 * rotcos - my2 * rotsin) / 65536; - ty2 = ypos + (mx2 * rotsin + my2 * rotcos) / 65536; + ppmd_point const t1 = + makePoint(pos.x + (mx1 * rotcos - my1 * rotsin) / 65536, + pos.y + (mx1 * rotsin + my1 * rotcos) / 65536); + ppmd_point const t2 = + makePoint(pos.x + (mx2 * rotcos - my2 * rotsin) / 65536, + pos.y + (mx2 * rotsin + my2 * rotcos) / 65536); - ppmd_line(pixels, cols, rows, maxval, tx1, ty1, tx2, ty2, - drawProc, clientdata); + ppmd_validatePoint(t1); + ppmd_validatePoint(t2); + + ppmd_linep(pixels, cols, rows, maxval, t1, t2, + drawProc, clientdata); - penPos.x = nx; - penPos.y = ny; + p = n; } - break; + break; case CMD_MOVEPEN: - penPos.x = x + SCHAR(commandP->x); - penPos.y = y + SCHAR(commandP->y); + p = vectorSum(p, makePoint(SCHAR(commandP->x), + SCHAR(commandP->y))); break; } } - x += glyphP->header.skipAfter; + p.x += glyphP->header.skipAfter; - *xP = x; + *glyphCornerP = makePoint(p.x + glyphP->header.skipAfter, glyphCorner.y); } -/* PPMD_TEXT -- Draw the zero-terminated string s, with its baseline - starting at point (x, y), inclined by angle degrees to - the X axis, with letters height pixels high (descenders - will extend below the baseline). The supplied drawproc - and cliendata are passed to ppmd_line which performs the - actual drawing. */ void -ppmd_text(pixel** const pixels, - int const cols, - int const rows, - pixval const maxval, - int const xpos, - int const ypos, - int const height, - int const angle, - const char * const sArg, - ppmd_drawproc drawProc, - const void * const clientdata) { - +ppmd_textp(pixel** const pixels, + int const cols, + int const rows, + pixval const maxval, + ppmd_point const pos, + int const height, + int const angle, + const char * const sArg, + ppmd_drawprocp drawProc, + const void * const clientdata) { +/*---------------------------------------------------------------------------- + Draw the zero-terminated string s, with its baseline starting at point + 'pos', inclined by angle degrees to the X axis, with letters height pixels + high (descenders will extend below the baseline). We pass the supplied + drawproc and clientdata to ppmd_linep, which performs the actual drawing. +-----------------------------------------------------------------------------*/ const struct ppmd_font * const fontP = ppmd_get_font(); long rotsin, rotcos; - int x, y; + ppmd_point p; const char * s; - x = y = 0; + ppmd_validatePoint(pos); + + p = makePoint(0, 0); rotsin = isin(-angle); rotcos = icos(-angle); @@ -1351,17 +1437,42 @@ ppmd_text(pixel** const pixels, const struct ppmd_glyph * const glyphP = &fontP->glyphTable[ch - fontP->header.firstCodePoint]; - drawGlyph(glyphP, &x, y, pixels, cols, rows, maxval, - height, xpos, ypos, rotcos, rotsin, + ppmd_validatePoint(p); + + drawGlyph(glyphP, &p, pixels, cols, rows, maxval, + height, pos, rotcos, rotsin, drawProc, clientdata); } else if (ch == '\n') { /* Move to the left edge of the next line down */ - y += Scalef + Descend; - x = 0; + p.y += Scalef + Descend; + p.x = 0; } } } + + +void +ppmd_text(pixel** const pixels, + int const cols, + int const rows, + pixval const maxval, + int const xpos, + int const ypos, + int const height, + int const angle, + const char * const sArg, + ppmd_drawproc drawProc, + const void * const clientData) { + + struct drawProcXY const xy = makeDrawProcXY(drawProc, clientData); + + ppmd_textp(pixels, cols, rows, maxval, makePoint(xpos, ypos), + height, angle, sArg, drawProcPointXY, &xy); +} + + + /* EXTENTS_DRAWPROC -- Drawproc which just accumulates the extents rectangle bounding the text. */ |