/* libppm5.c - ppm utility library part 5 ** ** This library module contains the ppmdraw routines. ** ** Copyright (C) 1989, 1991 by Jef Poskanzer. ** ** Permission to use, copy, modify, and distribute this software and its ** documentation for any purpose and without fee is hereby granted, provided ** that the above copyright notice appear in all copies and that both that ** copyright notice and this permission notice appear in supporting ** documentation. This software is provided "as is" without express or ** implied warranty. ** ** The character drawing routines are by John Walker ** Copyright (C) 1994 by John Walker, kelvin@fourmilab.ch */ #include "pm_c_util.h" #include "mallocvar.h" #include "ppm.h" #include "ppmdfont.h" #include "ppmdraw.h" #define DDA_SCALE 8192 struct penpos { int x; int y; }; void ppmd_point_drawproc(pixel** const pixels, int const cols, int const rows, pixval const maxval, int const x, int const y, const void* const clientdata ) { if ( x >= 0 && x < cols && y >= 0 && y < rows ) pixels[y][x] = *( (pixel*) clientdata ); } /* Simple fill routine. */ void ppmd_filledrectangle(pixel** const pixels, int const cols, int const rows, pixval const maxval, int const x, int const y, int const width, int const height, void (*drawprocP)(pixel**, int, int, pixval, int, int, const void*), const void* const clientdata ) { register int cx, cy, cwidth, cheight, col, row; /* Clip. */ cx = x; cy = y; cwidth = width; cheight = height; if ( cx < 0 ) { cx = 0; cwidth += x; } if ( cy < 0 ) { cy = 0; cheight += y; } if ( cx + cwidth > cols ) cwidth = cols - cx; if ( cy + cheight > rows ) cheight = rows - cy; /* Draw. */ for ( row = cy; row < cy + cheight; ++row ) for ( col = cx; col < cx + cwidth; ++col ) if ( drawprocP == PPMD_NULLDRAWPROC ) pixels[row][col] = *( (pixel*) clientdata ); else (*drawprocP)( pixels, cols, rows, maxval, col, row, clientdata ); } /* Outline drawing stuff. */ static int linetype = PPMD_LINETYPE_NORMAL; int ppmd_setlinetype(int const type) { int old; old = linetype; linetype = type; return old; } static int lineclip = 1; int ppmd_setlineclip(int const clip) { int old; old = lineclip; lineclip = clip; return old; } void ppmd_line(pixel** const pixels, int const cols, int const rows, pixval const maxval, int const x0, int const y0, int const x1, int const y1, void (*drawprocP)(pixel**, int, int, pixval, int, int, const void*), const void * const clientdata) { register int cx0, cy0, cx1, cy1; /* Special case zero-length lines. */ if ( x0 == x1 && y0 == y1 ) { if ( ( ! lineclip ) || ( x0 >= 0 && x0 < cols && y0 >= 0 && y0 < rows ) ) { if ( drawprocP == PPMD_NULLDRAWPROC ) ppmd_point_drawproc(pixels, cols, rows, maxval, x0, y0, clientdata ); else (*drawprocP)( pixels, cols, rows, maxval, x0, y0, clientdata ); } return; } /* Clip. */ cx0 = x0; cy0 = y0; cx1 = x1; cy1 = y1; if ( lineclip ) { if ( cx0 < 0 ) { if ( cx1 < 0 ) return; cy0 = cy0 + ( cy1 - cy0 ) * ( -cx0 ) / ( cx1 - cx0 ); cx0 = 0; } else if ( cx0 >= cols ) { if ( cx1 >= cols ) return; cy0 = cy0 + ( cy1 - cy0 ) * ( cols - 1 - cx0 ) / ( cx1 - cx0 ); cx0 = cols - 1; } if ( cy0 < 0 ) { if ( cy1 < 0 ) return; cx0 = cx0 + ( cx1 - cx0 ) * ( -cy0 ) / ( cy1 - cy0 ); cy0 = 0; } else if ( cy0 >= rows ) { if ( cy1 >= rows ) return; cx0 = cx0 + ( cx1 - cx0 ) * ( rows - 1 - cy0 ) / ( cy1 - cy0 ); cy0 = rows - 1; } if ( cx1 < 0 ) { cy1 = cy1 + ( cy0 - cy1 ) * ( -cx1 ) / ( cx0 - cx1 ); cx1 = 0; } else if ( cx1 >= cols ) { cy1 = cy1 + ( cy0 - cy1 ) * ( cols - 1 - cx1 ) / ( cx0 - cx1 ); cx1 = cols - 1; } if ( cy1 < 0 ) { cx1 = cx1 + ( cx0 - cx1 ) * ( -cy1 ) / ( cy0 - cy1 ); cy1 = 0; } else if ( cy1 >= rows ) { cx1 = cx1 + ( cx0 - cx1 ) * ( rows - 1 - cy1 ) / ( cy0 - cy1 ); cy1 = rows - 1; } /* Check again for zero-length lines. */ if ( cx0 == cx1 && cy0 == cy1 ) { if ( drawprocP == PPMD_NULLDRAWPROC ) ppmd_point_drawproc( pixels, cols, rows, maxval, cx0, cy0, clientdata ); else (*drawprocP)( pixels, cols, rows, maxval, cx0, cy0, clientdata ); return; } } /* Draw, using a simple DDA. */ if ( abs( cx1 - cx0 ) > abs( cy1 - cy0 ) ) { /* Loop over X domain. */ register long dy, srow; register int dx, col, row, prevrow; if ( cx1 > cx0 ) dx = 1; else dx = -1; dy = ( cy1 - cy0 ) * DDA_SCALE / abs( cx1 - cx0 ); prevrow = row = cy0; srow = row * DDA_SCALE + DDA_SCALE / 2; col = cx0; for ( ; ; ) { if ( linetype == PPMD_LINETYPE_NODIAGS && row != prevrow ) { if ( drawprocP == PPMD_NULLDRAWPROC ) pixels[prevrow][col] = *( (pixel*) clientdata ); else (*drawprocP)( pixels, cols, rows, maxval, col, prevrow, clientdata ); prevrow = row; } if ( drawprocP == PPMD_NULLDRAWPROC ) pixels[row][col] = *( (pixel*) clientdata ); else (*drawprocP)( pixels, cols, rows, maxval, col, row, clientdata ); if ( col == cx1 ) break; srow += dy; row = srow / DDA_SCALE; col += dx; } } else { /* Loop over Y domain. */ register long dx, scol; register int dy, col, row, prevcol; if ( cy1 > cy0 ) dy = 1; else dy = -1; dx = ( cx1 - cx0 ) * DDA_SCALE / abs( cy1 - cy0 ); row = cy0; prevcol = col = cx0; scol = col * DDA_SCALE + DDA_SCALE / 2; for ( ; ; ) { if ( linetype == PPMD_LINETYPE_NODIAGS && col != prevcol ) { if ( drawprocP == PPMD_NULLDRAWPROC ) pixels[row][prevcol] = *( (pixel*) clientdata ); else (*drawprocP)( pixels, cols, rows, maxval, prevcol, row, clientdata ); prevcol = col; } if ( drawprocP == PPMD_NULLDRAWPROC ) pixels[row][col] = *( (pixel*) clientdata ); else (*drawprocP)( pixels, cols, rows, maxval, col, row, clientdata ); if ( row == cy1 ) break; row += dy; scol += dx; col = scol / DDA_SCALE; } } } #define SPLINE_THRESH 3 void ppmd_spline3(pixel** const pixels, int const cols, int const rows, pixval const maxval, int const x0, int const y0, int const x1, int const y1, int const x2, int const y2, void (*drawprocP)(pixel**, int, int, pixval, int, int, const void*), const void* const clientdata ) { register int xa, ya, xb, yb, xc, yc, xp, yp; xa = ( x0 + x1 ) / 2; ya = ( y0 + y1 ) / 2; xc = ( x1 + x2 ) / 2; yc = ( y1 + y2 ) / 2; xb = ( xa + xc ) / 2; yb = ( ya + yc ) / 2; xp = ( x0 + xb ) / 2; yp = ( y0 + yb ) / 2; if ( abs( xa - xp ) + abs( ya - yp ) > SPLINE_THRESH ) ppmd_spline3( pixels, cols, rows, maxval, x0, y0, xa, ya, xb, yb, drawprocP, clientdata ); else ppmd_line( pixels, cols, rows, maxval, x0, y0, xb, yb, drawprocP, clientdata); xp = ( x2 + xb ) / 2; yp = ( y2 + yb ) / 2; if ( abs( xc - xp ) + abs( yc - yp ) > SPLINE_THRESH ) ppmd_spline3( pixels, cols, rows, maxval, xb, yb, xc, yc, x2, y2, drawprocP, clientdata ); else ppmd_line( pixels, cols, rows, maxval, xb, yb, x2, y2, drawprocP, clientdata ); } void ppmd_polyspline(pixel** const pixels, int const cols, int const rows, pixval const maxval, int const x0, int const y0, int const nc, int* const xc, int* const yc, int const x1, int const y1, void (*drawprocP)(pixel**, int, int, pixval, int, int, const void*), const void* const clientdata ) { register int i, x, y, xn, yn; x = x0; y = y0; for ( i = 0; i < nc - 1; ++i ) { xn = ( xc[i] + xc[i + 1] ) / 2; yn = ( yc[i] + yc[i + 1] ) / 2; ppmd_spline3( pixels, cols, rows, maxval, x, y, xc[i], yc[i], xn, yn, drawprocP, clientdata ); x = xn; y = yn; } ppmd_spline3( pixels, cols, rows, maxval, x, y, xc[nc - 1], yc[nc - 1], x1, y1, drawprocP, clientdata ); } void ppmd_circle(pixel** const pixels, int const cols, int const rows, pixval const maxval, int const cx, int const cy, int const radius, void (*drawprocP)(pixel**, int, int, pixval, int, int, const void*), const void* const clientdata) { register int x0, y0, x, y, prevx, prevy, nopointsyet; register long sx, sy, e; x0 = x = radius; y0 = y = 0; sx = x * DDA_SCALE + DDA_SCALE / 2; sy = y * DDA_SCALE + DDA_SCALE / 2; e = DDA_SCALE / radius; if ( drawprocP == PPMD_NULLDRAWPROC ) pixels[y + cy][x + cx] = *( (pixel*) clientdata ); else (*drawprocP)( pixels, cols, rows, maxval, x + cx, y + cy, clientdata ); nopointsyet = 1; do { prevx = x; prevy = y; sx += e * sy / DDA_SCALE; sy -= e * sx / DDA_SCALE; x = sx / DDA_SCALE; y = sy / DDA_SCALE; if ( x != prevx || y != prevy ) { nopointsyet = 0; if ( drawprocP == PPMD_NULLDRAWPROC ) pixels[y + cy][x + cx] = *( (pixel*) clientdata ); else (*drawprocP)( pixels, cols, rows, maxval, x + cx, y + cy, clientdata ); } } while ( nopointsyet || x != x0 || y != y0 ); } /* Arbitrary fill stuff. */ typedef struct { short x; short y; short edge; } coord; typedef struct { int n; int size; int curedge; int segstart; int ydir; int startydir; coord* coords; } fillobj; #define SOME 1000 static int oldclip; char* ppmd_fill_init( ) { fillobj* fh; MALLOCVAR(fh); if (fh == NULL) pm_error("out of memory allocating a fillhandle"); fh->n = 0; fh->size = SOME; MALLOCARRAY(fh->coords, fh->size); if (fh->coords == NULL) pm_error("out of memory allocating a fillhandle"); fh->curedge = 0; /* Turn off line clipping. */ oldclip = ppmd_setlineclip(0); return (char*) fh; } void ppmd_fill_drawproc(pixel** const pixels, int const cols, int const rows, pixval const maxval, int const x, int const y, const void* const clientdata) { register fillobj* fh; register coord* cp; register coord* ocp; fh = (fillobj*) clientdata; if ( fh->n > 0 ) { /* If these are the same coords we saved last time, don't bother. */ ocp = &(fh->coords[fh->n - 1]); if ( x == ocp->x && y == ocp->y ) return; } /* Ok, these are new; check if there's room for two more coords. */ if ( fh->n + 1 >= fh->size ) { fh->size += SOME; fh->coords = (coord*) realloc( (char*) fh->coords, fh->size * sizeof(coord) ); if ( fh->coords == 0 ) pm_error( "out of memory enlarging a fillhandle" ); } /* Check for extremum and set the edge number. */ if ( fh->n == 0 ) { /* Start first segment. */ fh->segstart = fh->n; fh->ydir = 0; fh->startydir = 0; } else { register int dx, dy; dx = x - ocp->x; dy = y - ocp->y; if ( dx < -1 || dx > 1 || dy < -1 || dy > 1 ) { /* Segment break. Close off old one. */ if ( fh->startydir != 0 && fh->ydir != 0 ) if ( fh->startydir == fh->ydir ) { /* Oops, first edge and last edge are the same. ** Renumber the first edge in the old segment. */ register coord* fcp; int oldedge; fcp = &(fh->coords[fh->segstart]); oldedge = fcp->edge; for ( ; fcp->edge == oldedge; ++fcp ) fcp->edge = ocp->edge; } /* And start new segment. */ ++(fh->curedge); fh->segstart = fh->n; fh->ydir = 0; fh->startydir = 0; } else { /* Segment continues. */ if ( dy != 0 ) { if ( fh->ydir != 0 && fh->ydir != dy ) { /* Direction changed. Insert a fake coord, old ** position but new edge number. */ ++(fh->curedge); cp = &(fh->coords[fh->n]); cp->x = ocp->x; cp->y = ocp->y; cp->edge = fh->curedge; ++(fh->n); } fh->ydir = dy; if ( fh->startydir == 0 ) fh->startydir = dy; } } } /* Save this coord. */ cp = &(fh->coords[fh->n]); cp->x = x; cp->y = y; cp->edge = fh->curedge; ++(fh->n); } static int yx_compare ARGS((const void* c1, const void* c2)); static int yx_compare( const void *c1, const void *c2 ) { if ( ((coord*)c1)->y > ((coord*)c2)->y ) return 1; if ( ((coord*)c1)->y < ((coord*)c2)->y ) return -1; if ( ((coord*)c1)->x > ((coord*)c2)->x ) return 1; if ( ((coord*)c1)->x < ((coord*)c2)->x ) return -1; return 0; } void ppmd_fill(pixel** const pixels, int const cols, int const rows, pixval const maxval, char* const fillhandle, void (*drawprocP)(pixel**, int, int, pixval, int, int, const void*), const void* const clientdata) { register fillobj* fh; int pedge, eq; register int i, leftside, edge, lx, rx, py; register coord* cp; fh = (fillobj*) fillhandle; /* Close off final segment. */ if ( fh->n > 0 && fh->startydir != 0 && fh->ydir != 0 ) if ( fh->startydir == fh->ydir ) { /* Oops, first edge and last edge are the same. */ register coord* fcp; int lastedge, oldedge; lastedge = fh->coords[fh->n - 1].edge; fcp = &(fh->coords[fh->segstart]); oldedge = fcp->edge; for ( ; fcp->edge == oldedge; ++fcp ) fcp->edge = lastedge; } /* Restore clipping now. */ (void) ppmd_setlineclip( oldclip ); /* Sort the coords by Y, secondarily by X. */ qsort( (char*) fh->coords, fh->n, sizeof(coord), yx_compare ); /* Find equal coords with different edge numbers, and swap if necessary. */ edge = -1; for ( i = 0; i < fh->n; ++i ) { cp = &(fh->coords[i]); if ( i > 1 && eq && cp->edge != edge && cp->edge == pedge ) { /* Swap .-1 and .-2. */ coord t; t = fh->coords[i-1]; fh->coords[i-1] = fh->coords[i-2]; fh->coords[i-2] = t; } if ( i > 0 ) { if ( cp->x == lx && cp->y == py ) { eq = 1; if ( cp->edge != edge && cp->edge == pedge ) { /* Swap . and .-1. */ coord t; t = *cp; *cp = fh->coords[i-1]; fh->coords[i-1] = t; } } else eq = 0; } lx = cp->x; py = cp->y; pedge = edge; edge = cp->edge; } /* Ok, now run through the coords filling spans. */ for ( i = 0; i < fh->n; ++i ) { cp = &(fh->coords[i]); if ( i == 0 ) { lx = rx = cp->x; py = cp->y; edge = cp->edge; leftside = 1; } else { if ( cp->y != py ) { /* Row changed. Emit old span and start a new one. */ ppmd_filledrectangle( pixels, cols, rows, maxval, lx, py, rx - lx + 1, 1, drawprocP, clientdata); lx = rx = cp->x; py = cp->y; edge = cp->edge; leftside = 1; } else { if ( cp->edge == edge ) { /* Continuation of side. */ rx = cp->x; } else { /* Edge changed. Is it a span? */ if ( leftside ) { rx = cp->x; leftside = 0; } else { /* Got a span to fill. */ ppmd_filledrectangle( pixels, cols, rows, maxval, lx, py, rx - lx + 1, 1, drawprocP, clientdata); lx = rx = cp->x; leftside = 1; } edge = cp->edge; } } } } /* All done. Free up the fillhandle and leave. */ free( fh->coords ); free( fh ); } /* Table used to look up sine of angles from 0 through 90 degrees. The value returned is the sine * 65536. Symmetry is used to obtain sine and cosine for arbitrary angles using this table. */ static long sintab[] = { 0, 1143, 2287, 3429, 4571, 5711, 6850, 7986, 9120, 10252, 11380, 12504, 13625, 14742, 15854, 16961, 18064, 19160, 20251, 21336, 22414, 23486, 24550, 25606, 26655, 27696, 28729, 29752, 30767, 31772, 32768, 33753, 34728, 35693, 36647, 37589, 38521, 39440, 40347, 41243, 42125, 42995, 43852, 44695, 45525, 46340, 47142, 47929, 48702, 49460, 50203, 50931, 51643, 52339, 53019, 53683, 54331, 54963, 55577, 56175, 56755, 57319, 57864, 58393, 58903, 59395, 59870, 60326, 60763, 61183, 61583, 61965, 62328, 62672, 62997, 63302, 63589, 63856, 64103, 64331, 64540, 64729, 64898, 65047, 65176, 65286, 65376, 65446, 65496, 65526, 65536 }; static int extleft, exttop, extright, extbottom; /* To accumulate extents */ /* LINTLIBRARY */ /* ISIN -- Return sine of an angle in integral degrees. The value returned is 65536 times the sine. */ #if __STDC__ static long isin(int deg) #else static long isin(deg) int deg; #endif { /* Domain reduce to 0 to 360 degrees. */ if (deg < 0) { deg = (360 - ((- deg) % 360)) % 360; } else if (deg >= 360) { deg = deg % 360; } /* Now look up from table according to quadrant. */ if (deg <= 90) { return sintab[deg]; } else if (deg <= 180) { return sintab[180 - deg]; } else if (deg <= 270) { return -sintab[deg - 180]; } return -sintab[360 - deg]; } /* ICOS -- Return cosine of an angle in integral degrees. The value returned is 65536 times the cosine. */ #if __STDC__ static long icos(int deg) #else static long icos(deg) int deg; #endif { return isin(deg + 90); } #define SCHAR(x) (u = (x), (((u) & 0x80) ? ((u) | (-1 ^ 0xFF)) : (u))) #define Scalef 21 /* Font design size */ #define Descend 9 /* Descender offset */ static void drawGlyph(const struct ppmd_glyph * const glyphP, int * const xP, int const y, pixel ** const pixels, unsigned int const cols, unsigned int const rows, pixval const maxval, int const height, int const xpos, int const ypos, long const rotcos, long const rotsin, void (*drawprocP)(pixel**, int, int, pixval, int, int, const void*), 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. -----------------------------------------------------------------------------*/ struct penpos penPos; 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; for (commandNum = 0; commandNum < glyphP->header.commandCount; ++commandNum) { const struct ppmd_glyphCommand * const commandP = &glyphP->commandList[commandNum]; switch (commandP->verb) { case CMD_NOOP: 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. */ 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_line(pixels, cols, rows, maxval, tx1, ty1, tx2, ty2, drawprocP, clientdata); penPos.x = nx; penPos.y = ny; } break; case CMD_MOVEPEN: penPos.x = x + SCHAR(commandP->x); penPos.y = y + SCHAR(commandP->y); break; } } x += glyphP->header.skipAfter; *xP = x; } /* 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 drawprocP 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, void (*drawprocP)(pixel**, int, int, pixval, int, int, const void*), const void* const clientdata) { const struct ppmd_font * const fontP = ppmd_get_font(); long rotsin, rotcos; int x, y; const char * s; x = y = 0; rotsin = isin(-angle); rotcos = icos(-angle); s = sArg; while (*s) { unsigned char const ch = *s++; if (ch >= fontP->header.firstCodePoint && ch < fontP->header.firstCodePoint + fontP->header.characterCount) { 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, drawprocP, clientdata); } else if (ch == '\n') { /* Move to the left edge of the next line down */ y += Scalef + Descend; x = 0; } } } /* EXTENTS_DRAWPROC -- Drawproc which just accumulates the extents rectangle bounding the text. */ static void extents_drawproc (pixel** const pixels, int const cols, int const rows, pixval const maxval, int const x, int const y, const void * const clientdata) { extleft = MIN(extleft, x); exttop = MIN(exttop, y); extright = MAX(extright, x); extbottom = MAX(extbottom, y); } /* PPMD_TEXT_BOX -- Calculate extents rectangle for a given piece of text. For most applications where extents are needed, angle should be zero to obtain the unrotated extents. If you need the extents box for post-rotation text, however, you can set angle nonzero and it will be calculated correctly. */ void ppmd_text_box(int const height, int const angle, const char * const s, int * const left, int * const top, int * const right, int * const bottom) { extleft = 32767; exttop = 32767; extright = -32767; extbottom = -32767; ppmd_text(NULL, 32767, 32767, 255, 1000, 1000, height, angle, s, extents_drawproc, NULL); *left = extleft - 1000; *top = exttop - 1000; *right = extright - 1000; *bottom = extbottom - 1000; }