|
Graphviz
2.29.20120524.0446
|
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
1.7.5