Graphviz  2.31.20130523.0446
lib/common/taper.c
Go to the documentation of this file.
00001 /* vim:set shiftwidth=4 ts=8: */
00002 
00003 
00004 /*************************************************************************
00005  * Copyright (c) 2011 AT&T Intellectual Property 
00006  * All rights reserved. This program and the accompanying materials
00007  * are made available under the terms of the Eclipse Public License v1.0
00008  * which accompanies this distribution, and is available at
00009  * http://www.eclipse.org/legal/epl-v10.html
00010  *
00011  * Contributors: See CVS logs. Details at http://www.graphviz.org/
00012  *************************************************************************/
00013 
00014 /*
00015  * Tapered edges, based on lines.ps written by Denis Moskowitz.
00016  */
00017 
00018 #ifdef HAVE_CONFIG_H
00019 #include "config.h"
00020 #endif
00021 
00022 #include <math.h>
00023 #include <stdio.h>
00024 #include <stdlib.h>
00025 #include <types.h>
00026 #include <memory.h>
00027 #include <logic.h>
00028 #include <agxbuf.h>
00029 #include <utils.h>
00030 
00031   /* sample point size; should be dynamic based on dpi or under user control */
00032 #define BEZIERSUBDIVISION 20
00033 
00034   /* initial guess of array size */
00035 #define INITSZ 2000
00036 
00037   /* convert degrees to radians and vice versa */
00038 #ifndef M_PI
00039 #define M_PI            3.14159265358979323846
00040 #endif
00041 #define D2R(d)    (M_PI*(d)/180.0)
00042 #define R2D(r)    (180.0*(r)/M_PI)
00043 
00044 static double currentmiterlimit = 10.0;
00045 
00046 #define moveto(p,x,y) addto(p,x,y)
00047 #define lineto(p,x,y) addto(p,x,y)
00048 
00049 static void addto (stroke_t* p, double x, double y)
00050 {
00051     pointf pt;
00052 
00053     if (p->nvertices >= p->flags) {
00054         p->flags =+ INITSZ;
00055         p->vertices = RALLOC(p->flags,p->vertices,pointf);
00056     }
00057     pt.x = x;
00058     pt.y = y;
00059     p->vertices[p->nvertices++] = pt;
00060 }
00061 
00062 static void arcn (stroke_t* p, double x, double y, double r, double a1, double a2)
00063 {
00064     double theta;
00065     int i;
00066 
00067     addto (p, x+r*cos(a1), y+r*sin(a1));
00068     if (r == 0) return;
00069     while (a2 > a1) a2 -= 2*M_PI;
00070     theta = a1 - a2; 
00071     while (theta > 2*M_PI) theta -= 2*M_PI;
00072     theta /= (BEZIERSUBDIVISION-1);
00073     for (i = 1; i < BEZIERSUBDIVISION; i++)
00074         addto (p, x+r*cos(a1-i*theta), y+r*sin(a1-i*theta));
00075 }
00076 
00077 #if 0
00078 static void closepath (stroke_t* p)
00079 {
00080     pointf pt = p->vertices[0];
00081 
00082     addto (p, pt.x, pt.y);
00083     if (p->flags > p->nvertices)
00084         p->vertices = RALLOC(p->nvertices,p->vertices,pointf);
00085 }
00086 #endif
00087 
00088 /*
00089  * handle zeros
00090  */
00091 static double myatan (double y, double x)
00092 {
00093     double v;
00094     if ((x == 0) && (y == 0))
00095         return 0;
00096     else {
00097         v = atan2 (y, x);
00098         if (v >= 0) return v;
00099         else return (v + 2*M_PI);
00100     }
00101 }
00102 
00103 /*
00104  *  mod that accepts floats and makes negatives positive
00105  */
00106 static double mymod (double original, double modulus)
00107 {
00108     double v;
00109     if ((original < 0) || (original >= modulus)) {
00110         v = -floor(original/modulus);
00111         return ((v*modulus) + original);
00112     }
00113     return original;
00114 }
00115 
00116 /*
00117  * allow division by zero
00118  */
00119 static double zdiv (double num, double denom)
00120 {
00121     if (denom == 0) return 0;
00122     else return (num/denom);
00123 }
00124 
00125 typedef struct {
00126     double x;
00127     double y;
00128     double lengthsofar;
00129     char type;
00130     double dir;
00131     double lout;
00132     int bevel;
00133     double dir2;
00134 } pathpoint;
00135 
00136 typedef struct {
00137     pathpoint* pts;
00138     int cnt;
00139     int sz;
00140 } vararr_t;
00141 
00142 
00143 static vararr_t*
00144 newArr ()
00145 {
00146     vararr_t* arr = NEW(vararr_t);
00147 
00148     arr->cnt = 0;
00149     arr->sz = INITSZ;
00150     arr->pts = N_NEW(INITSZ,pathpoint);
00151 
00152     return arr;
00153 }
00154 
00155 static void
00156 insertArr (vararr_t* arr, pointf p, double l)
00157 {
00158     if (arr->cnt >= arr->sz) {
00159         arr->sz *= 2;
00160         arr->pts = RALLOC(arr->sz,arr->pts,pathpoint);
00161     }
00162 
00163     arr->pts[arr->cnt].x = p.x; 
00164     arr->pts[arr->cnt].y = p.y; 
00165     arr->pts[arr->cnt++].lengthsofar = l; 
00166 }
00167 
00168 #ifdef DEBUG
00169 static void
00170 printArr (vararr_t* arr, FILE* fp)
00171 {
00172     int i;
00173     pathpoint pt;
00174 
00175     fprintf (fp, "cnt %d sz %d\n", arr->cnt, arr->sz);
00176     for (i = 0; i < arr->cnt; i++) {
00177         pt = arr->pts[i];
00178         fprintf (fp, "  [%d] x %.02f y  %.02f d %.02f\n", i, pt.x, pt.y, pt.lengthsofar);
00179     }
00180 }
00181 #endif
00182 
00183 static void
00184 fixArr (vararr_t* arr)
00185 {
00186     if (arr->sz > arr->cnt)
00187         arr->pts = RALLOC(arr->cnt,arr->pts,pathpoint);
00188 }
00189 
00190 static void
00191 freeArr (vararr_t* arr)
00192 {
00193     free (arr->pts);
00194     free (arr);
00195 }
00196 
00197 static double l2dist (pointf p0, pointf p1)
00198 {
00199     double delx = p0.x - p1.x;
00200     double dely = p0.y - p1.y;
00201     return sqrt(delx*delx + dely*dely);
00202 }
00203 
00204 /* analyze current path, creating pathpoints array
00205  * turn all curves into lines
00206  */
00207 static vararr_t* pathtolines (bezier* bez, double initwid)
00208 {
00209     int i, j, step;
00210     double seglen, linelen = 0;
00211     vararr_t* arr = newArr();
00212     pointf p0, p1, V[4];
00213     int n = bez->size;
00214     pointf* A = bez->list;
00215 
00216     insertArr (arr, A[0], 0);
00217     V[3] = A[0];
00218     for (i = 0; i + 3 < n; i += 3) {
00219         V[0] = V[3];
00220         for (j = 1; j <= 3; j++)
00221             V[j] = A[i + j];
00222         p0 = V[0];
00223         for (step = 1; step <= BEZIERSUBDIVISION; step++) {
00224             p1 = Bezier(V, 3, (double) step / BEZIERSUBDIVISION, NULL, NULL);
00225             seglen = l2dist(p0, p1);
00226             if (seglen > initwid/10) {
00227                 linelen += seglen;
00228                 insertArr (arr, p1, linelen); 
00229             }
00230             p0 = p1;
00231         }
00232     }
00233     fixArr (arr);
00234     /* printArr (arr, stderr); */
00235     return arr;
00236 }
00237 
00238 static void drawbevel(double x, double y, double lineout, int forward, double dir, double dir2, int linejoin, stroke_t* p)
00239 {
00240     double a, a1, a2;
00241 
00242     if (forward) {
00243         a1 = dir;
00244         a2 = dir2;
00245     } else {
00246         a1 = dir2;
00247         a2 = dir;
00248     }
00249     if (linejoin == 1) {
00250         a = a1 - a2;
00251         if (a <= D2R(0.1)) a += D2R(360);
00252         if (a < D2R(180)) {
00253             a1 = a + a2;
00254             arcn (p,x,y,lineout,a1,a2);
00255         } else {
00256             lineto (p, x + lineout*cos(a2), x + lineout*sin(a2));
00257         }
00258     } else {
00259         lineto (p, x + lineout*cos(a2), x + lineout*sin(a2));
00260     }
00261 }
00262 
00263 typedef double (*radfunc_t) (double curlen, double totallen, double initwid);
00264 
00265 /* taper:
00266  * Given a B-spline bez, returns a polygon that represents spline as a tapered
00267  * edge, starting with width initwid.
00268  * The radfunc determines the half-width along the curve. Typically, this will
00269  * decrease from initwid to 0 as the curlen goes from 0 to totallen.
00270  * The linejoin and linecap parameters have roughly the same meaning as in postscript.
00271  *  - linejoin = 0 or 1 
00272  *  - linecap = 0 or 1 or 2 
00273  *
00274  * Calling function needs to free the allocated stoke_t.
00275  */
00276 stroke_t* taper (bezier* bez, radfunc_t radfunc, double initwid, int linejoin, int linecap)
00277 {
00278     int i, l, n;
00279     int pathcount, bevel;
00280     double direction, direction_2;
00281     vararr_t* arr = pathtolines (bez, initwid);
00282     pathpoint* pathpoints;
00283     pathpoint cur_point, last_point, next_point;
00284     double x, y, dist;
00285     double nx, ny, ndir;
00286     double lx, ly, ldir;
00287     double lineout, linerad, linelen;
00288     double theta, phi;
00289     stroke_t* p;
00290 
00291     pathcount = arr->cnt;
00292     pathpoints = arr->pts;
00293     linelen = pathpoints[pathcount-1].lengthsofar;
00294 
00295     /* determine miter and bevel points and directions */
00296     for (i = 0; i < pathcount; i++) {
00297         l = mymod(i-1,pathcount);
00298         n = mymod(i+1,pathcount);
00299 
00300         cur_point = pathpoints[i];
00301         x = cur_point.x;
00302         y = cur_point.y;
00303         dist = cur_point.lengthsofar;
00304 
00305         next_point = pathpoints[n];
00306         nx = next_point.x;
00307         ny = next_point.y;
00308         ndir = myatan (ny-y, nx-x);
00309 
00310         last_point = pathpoints[l];
00311         lx = last_point.x;
00312         ly = last_point.y;
00313         ldir = myatan (ly-y, lx-x);
00314 
00315         bevel = FALSE;
00316         direction_2 = 0;
00317 
00318             /* effective line radius at this point */
00319         linerad = radfunc(dist, linelen, initwid);
00320 
00321         if ((i == 0) || (i == pathcount-1)) {
00322             lineout = linerad;
00323             if (i == 0) {
00324                 direction = ndir + D2R(90);
00325                 if (linecap == 2) {
00326                     x -= cos(ndir)*lineout;
00327                     y -= sin(ndir)*lineout;
00328                 }
00329             } else {
00330                 direction = ldir - D2R(90);
00331                 if (linecap == 2) {
00332                     x -= cos(ldir)*lineout;
00333                     y -= sin(ldir)*lineout;
00334                 }
00335             }
00336             direction_2 = direction;
00337         } else {
00338             theta = ndir-ldir;
00339             if (theta < 0) {
00340                 theta += D2R(360);
00341             }
00342             phi = D2R(90)-(theta/2);
00343                  /* actual distance to junction point */
00344             if (cos(phi) == 0) {
00345                 lineout = 0;
00346             } else {
00347                 lineout = linerad/(cos(phi));
00348             }
00349                  /* direction to junction point */
00350             direction = ndir+D2R(90)+phi;
00351             if ((0 != linejoin) || (zdiv(lineout,linerad) > currentmiterlimit)) {
00352                 bevel = TRUE;
00353                 lineout = linerad;
00354                 direction = mymod(ldir-D2R(90),D2R(360));
00355                 direction_2 = mymod(ndir+D2R(90),D2R(360));
00356                 if (i == pathcount-1) {
00357                     bevel = FALSE;
00358                 }
00359             } else {
00360                 direction_2 = direction;
00361             }
00362         }
00363         pathpoints[i].x = x;
00364         pathpoints[i].y = y;
00365         pathpoints[i].lengthsofar = dist;
00366         pathpoints[i].type = 'l';
00367         pathpoints[i].dir = direction;
00368         pathpoints[i].lout = lineout;
00369         pathpoints[i].bevel = bevel;
00370         pathpoints[i].dir2 = direction_2;
00371     }
00372 
00373          /* draw line */
00374     p = NEW(stroke_t);
00375          /* side 1 */
00376     for (i = 0; i < pathcount; i++) {
00377         cur_point = pathpoints[i];
00378         x = cur_point.x;
00379         y = cur_point.y;
00380         direction = cur_point.dir;
00381         lineout = cur_point.lout;
00382         bevel = cur_point.bevel;
00383         direction_2 = cur_point.dir2;
00384         if (i == 0) {
00385             moveto (p, x+cos(direction)*lineout, y+sin(direction)*lineout);
00386         } else {
00387             lineto (p, x+cos(direction)*lineout, y+sin(direction)*lineout);
00388         }
00389         if (bevel) {
00390             drawbevel (x, y, lineout, TRUE, direction, direction_2, linejoin, p);
00391         }
00392     }
00393          /* end circle as needed */
00394     if (linecap == 1) {
00395         arcn (p, x,y,lineout,direction,direction+D2R(180));
00396     } else {
00397         direction += D2R(180);
00398         lineto (p, x+cos(direction)*lineout, y+sin(direction)*lineout);
00399     }
00400          /* side 2 */
00401     for (i = pathcount-2; i >= 0; i--) {
00402         cur_point = pathpoints[i];
00403         x = cur_point.x;
00404         y = cur_point.y;
00405         direction = cur_point.dir + D2R(180);
00406         lineout = cur_point.lout;
00407         bevel = cur_point.bevel;
00408         direction_2 = cur_point.dir2 + D2R(180);
00409         lineto (p, x+cos(direction_2)*lineout, y+sin(direction_2)*lineout);
00410         if (bevel) { 
00411             drawbevel (x, y, lineout, FALSE, direction, direction_2, linejoin, p);
00412         }
00413     }
00414          /* start circle if needed */
00415     if (linecap == 1) {
00416         arcn (p, x,y,lineout,direction,direction+D2R(180));
00417     }
00418     /* closepath (p); */
00419     freeArr (arr);
00420     return p;
00421 }
00422 
00423 static double halffunc (double curlen, double totallen, double initwid)
00424 {
00425     return ((1 - (curlen/totallen))*initwid/2.0);
00426 }
00427 
00428 stroke_t* taper0 (bezier* bez, double initwid)
00429 {
00430     return taper(bez, halffunc, initwid, 0, 0);
00431 }
00432 
00433 #ifdef TEST
00434 static pointf pts[] = {
00435   {100,100},
00436   {150,150},
00437   {200,100},
00438   {250,200},
00439 };
00440 
00441 main ()
00442 {
00443     stroke_t* sp;
00444     bezier bez;
00445     int i;
00446 
00447     bez.size = sizeof(pts)/sizeof(pointf);
00448     bez.list = pts;
00449     sp = taper0 (&bez, 20);
00450     printf ("newpath\n");
00451     printf ("%.02f %.02f moveto\n", sp->vertices[0].x, sp->vertices[0].y);
00452     for (i=1; i<sp->nvertices; i++)
00453         printf ("%.02f %.02f lineto\n", sp->vertices[i].x, sp->vertices[i].y);
00454     printf ("fill showpage\n");
00455 }
00456 #endif