├── README.md ├── bezier_fit.js ├── drawing.html └── mouseTrack.js /README.md: -------------------------------------------------------------------------------- 1 | javascript_bezierCurve 2 | ====================== 3 | 4 | this project ports potrace in Javascript to fit user drawing with bezier curves 5 | -------------------------------------------------------------------------------- /bezier_fit.js: -------------------------------------------------------------------------------- 1 | var pixelChain = new Array(); 2 | var bezierControlPoints = new Array(); 3 | var lon = new Array(); 4 | var polLine = new Array(); 5 | var INFTY = 10000000 6 | /* ---------------------------------------------------------------------- */ 7 | /* 8 | * B0, B1, B2, B3 : Bezier multipliers 9 | */ 10 | function B0(u) { return ( 1.0 - u ) * ( 1.0 - u ) * ( 1.0 - u ); } 11 | function B1(u) { return 3 * u * ( 1.0 - u ) * ( 1.0 - u ); } 12 | function B2(u) { return 3 * u * u * ( 1.0 - u ); } 13 | function B3(u) { return u * u * u; } 14 | 15 | function Point(x, y) { 16 | this.x = x; 17 | this.y = y; 18 | this.norm = function() { return Math.sqrt(this.x*this.x+this.y*this.y);} 19 | this.plus = function(p) { return new Point(this.x+p.x, this.y+p.y);} 20 | this.minus = function(p) { return new Point(this.x-p.x, this.y-p.y);} 21 | this.is_zero = function() { return this.x == 0 && this.y == 0;} 22 | this.neg = function() {return new Point(-this.x, -this.y);} 23 | this.equal = function(p){return this.x == p.x && this.y == p.y;} 24 | this.time = function(a){return new Point(this.x*a, this.y*a);} 25 | this.normalize = function(){var normV = this.norm();this.x /= normV; this.y /= normV;} 26 | this.copy = function(){return new Point(this.x, this.y);} 27 | this.set = function(p){this.x == p.x;this.y == p.y;} 28 | } 29 | 30 | function fitBezier(freeHandDrawnPath, bezier_points, tolerance) 31 | { 32 | pixelChain.length = 0; 33 | bezierControlPoints.length = 0; 34 | lon.length = 0; 35 | polLine.length = 0; 36 | //document.write("---Debug inside fitBezier") 37 | var points_size = freeHandDrawnPath.length 38 | //document.write(" size:" + points_size + "") 39 | 40 | //---- 41 | 42 | var p0, p; 43 | var closedCurve = false; 44 | var dist_x, dist_y; 45 | var len = freeHandDrawnPath.length; 46 | var i; 47 | 48 | p0 = freeHandDrawnPath[0]; 49 | 50 | //document.write("len:" + len + ""); 51 | for(i=1;i"); 84 | // } 85 | 86 | calc_lon(); 87 | bestpolygon(); 88 | smooth(1, tolerance); 89 | 90 | len = bezierControlPoints.length; 91 | //document.write("len:(" + len + ")"); 92 | for(i=0;i"); 97 | } 98 | 99 | return closedCurve; 100 | } 101 | 102 | function bresenham(p0, p1) 103 | { 104 | 105 | var x0 = p0.x; 106 | var y0 = p0.y; 107 | var x1 = p1.x; 108 | var y1 = p1.y; 109 | var computed_pixels = new Array(); 110 | var computed_pixels_size; 111 | 112 | var steep = Math.abs(y1 - y0) > Math.abs(x1 - x0); 113 | var swap_points = false; 114 | if (steep) 115 | { 116 | y0 = [x0, x0 = y0][0];//swap x0, y0 117 | y1 = [x1, x1 = y1][0];//swap x1, y1 118 | } 119 | if (x0 > x1) 120 | { 121 | swap_points = true; 122 | x1 = [x0, x0 = x1][0];//swap x0, x1 123 | y1 = [y0, y0 = y1][0];//swap y0, y1 124 | } 125 | 126 | var deltax = x1 - x0; 127 | var deltay = Math.abs(y1 - y0); 128 | var error = parseInt(-(deltax + 1) / 2, 10); 129 | var ystep; 130 | var y = y0; 131 | if (y0 < y1) 132 | ystep = 1; 133 | else 134 | ystep = -1; 135 | 136 | // document.write("x0y0:(" + x0 + "," + y0 + ")"); 137 | // document.write("x1y1:(" + x1 + "," + y1 + ")"); 138 | // document.write("steep:(" + steep + ")"); 139 | 140 | for (var x = x0; x <= x1; x++) 141 | { 142 | var p = new Point(0, 0); 143 | if (steep){ 144 | p.x = y; 145 | p.y = x; 146 | computed_pixels.push(p); 147 | } 148 | else { 149 | p.x = x; 150 | p.y = y; 151 | computed_pixels.push(p); 152 | } 153 | error = error + deltay; 154 | if (error >= 0){ 155 | y = y + ystep; 156 | error = error - deltax; 157 | } 158 | computed_pixels_size = computed_pixels.length; 159 | } 160 | computed_pixels_size = computed_pixels.length; 161 | 162 | if(swap_points) 163 | { 164 | //invert the pixels 165 | for (var i=computed_pixels_size-1; i>=0;i--) 166 | { 167 | pixelChain.push(computed_pixels[i]); 168 | //document.write("pixelChain1:(" + computed_pixels[i].x + "," + computed_pixels[i].y + ")"); 169 | } 170 | } 171 | else { 172 | for (var i=0; i= 0 and xprod(constraint[1], 194 | cur) <= 0. */ 195 | 196 | /* Remark for Potrace 1.1: the current implementation of calc_lon is 197 | more complex than the implementation found in Potrace 1.0, but it 198 | is considerably faster. The introduction of the "nc" data structure 199 | means that we only have to test the constraints for "corner" 200 | points. On a typical input file, this speeds up the calc_lon 201 | function by a factor of 31.2, thereby decreasing its time share 202 | within the overall Potrace algorithm from 72.6% to 7.82%, and 203 | speeding up the overall algorithm by a factor of 3.36. On another 204 | input file, calc_lon was sped up by a factor of 6.7, decreasing its 205 | time share from 51.4% to 13.61%, and speeding up the overall 206 | algorithm by a factor of 1.78. In any case, the savings are 207 | substantial. */ 208 | 209 | function calc_lon() 210 | { 211 | var len = pixelChain.length; 212 | 213 | var i; 214 | var j; 215 | var k, k1, dir, dir_index; 216 | var ct = new Array(); 217 | var constraint = new Array(); 218 | var cur; 219 | var off; 220 | var p_dir; 221 | var pivk = new Array(); 222 | var nc = new Array(); /* nc[len]: next corner */ 223 | var dk; /* direction of k-k1 */ 224 | var a, b, c, d; 225 | 226 | /* initialize the nc data structure. Point from each point to the 227 | furthest future point to which it is connected by a vertical or 228 | horizontal segment or is the next neighbour */ 229 | k = len-1; 230 | for (i=len-2; i>=0; i--) { 231 | if (pixelChain[i].x != pixelChain[k].x && pixelChain[i].y != pixelChain[k].y) { 232 | k = i+1; /* necessarily i"); 236 | } 237 | nc[len-1] = len-1; 238 | pivk[len-1] = len-1; 239 | /* determine pivot points: for each i, let pivk[i] be the furthest k 240 | such that all j with i=0; i--) { 247 | 248 | ct[0] = ct[1] = ct[2] = ct[3] = 0; 249 | 250 | p_dir = pixelChain[i+1].minus(pixelChain[i]); 251 | /* keep track of "directions" that have occurred */ 252 | p_dir = ddir(p_dir); 253 | dir_index= (4+3*(p_dir.x)+p_dir.y); 254 | //document.write("p_dir:" + p_dir.x + " " + p_dir.y +""); 255 | dir = parseInt((direction[dir_index]+0.5)/2) - 1; 256 | //document.write("dir:" + dir +""); 257 | ct[dir]=ct[dir]+1; 258 | 259 | if(direction[dir_index]%2>0) 260 | { 261 | ct[mod(dir+1,4)]=ct[mod(dir+1,4)]+1; 262 | } 263 | 264 | constraint[0] = new Point(0,0); 265 | constraint[1] = new Point(0,0); 266 | 267 | /* find the next k such that no straight line from i to k */ 268 | 269 | k = nc[i]; 270 | k1 = i; 271 | var gotoLabel = -1; 272 | while (1) { 273 | p_dir = pixelChain[i+1].minus(pixelChain[i]); 274 | p_dir = ddir(p_dir); 275 | dir_index= (4+3*p_dir.x+p_dir.y); 276 | dir = parseInt((direction[dir_index]+0.5)/2) - 1; 277 | 278 | //document.write("dir2:" + dir +""); 279 | ct[dir]++; 280 | if(direction[dir_index]%2>0) 281 | { 282 | ct[mod(dir+1,4)]++; 283 | } 284 | /* if all four "directions" have occurred, cut this path */ 285 | if (ct[0] && ct[1] && ct[2] && ct[3]) { 286 | pivk[i] = k1; 287 | //goto foundk; 288 | gotoLabel = 2; 289 | break; 290 | } 291 | 292 | cur = pixelChain[k].minus(pixelChain[i]); 293 | 294 | /* see if current constraint is violated */ 295 | if (xprod(constraint[0], cur) < 0 || xprod(constraint[1], cur) > 0) { 296 | //goto constraint_viol; 297 | gotoLabel = 1; 298 | break; 299 | } 300 | 301 | /* else, update constraint */ 302 | if (Math.abs(cur.x) <= 1 && Math.abs(cur.y) <= 1) { 303 | /* no constraint */ 304 | } else { 305 | off = new Point(); 306 | off.x = cur.x + ((cur.y>=0 && (cur.y>0 || cur.x<0)) ? 1 : -1); 307 | off.y = cur.y + ((cur.x<=0 && (cur.x<0 || cur.y<0)) ? 1 : -1); 308 | if (xprod(constraint[0], off) >= 0) { 309 | constraint[0] = off; 310 | } 311 | off = new Point(); 312 | off.x = cur.x + ((cur.y<=0 && (cur.y<0 || cur.x<0)) ? 1 : -1); 313 | off.y = cur.y + ((cur.x>=0 && (cur.x>0 || cur.y<0)) ? 1 : -1); 314 | if (xprod(constraint[1], off) <= 0) { 315 | constraint[1] = off; 316 | } 317 | } 318 | k1 = k; 319 | k = nc[k1]; 320 | if (!cyclic(k,i,k1)) { 321 | gotoLabel = 1; 322 | break; 323 | } 324 | } //end while 325 | //document.write("gotoLabel:" + gotoLabel +""); 326 | switch(gotoLabel) 327 | { 328 | case 1://constraint_viol 329 | { 330 | /* k1 was the last "corner" satisfying the current constraint, and 331 | k is the first one violating it. We now need to find the last 332 | point along k1..k which satisfied the constraint. */ 333 | dk = new Point(); 334 | cur = new Point(); 335 | dk.x = sign(pixelChain[k].x-pixelChain[k1].x); 336 | dk.y = sign(pixelChain[k].y-pixelChain[k1].y); 337 | cur.x = pixelChain[k1].x - pixelChain[i].x; 338 | cur.y = pixelChain[k1].y - pixelChain[i].y; 339 | 340 | /* find largest integer j such that xprod(constraint[0], cur+j*dk) 341 | >= 0 and xprod(constraint[1], cur+j*dk) <= 0. Use bilinearity 342 | of xprod. */ 343 | a = xprod(constraint[0], cur); 344 | b = xprod(constraint[0], dk); 345 | c = xprod(constraint[1], cur); 346 | d = xprod(constraint[1], dk); 347 | /* find largest integer j such that a+j*b>=0 and c+j*d<=0. This 348 | can be solved with integer arithmetic. */ 349 | j = INFTY; 350 | if (b<0) { 351 | j = floordiv(a,-b); 352 | } 353 | if (d>0) { 354 | j = Math.min(j, floordiv(-c,d)); 355 | } 356 | pivk[i] = mod(k1+j,len); 357 | break; 358 | } 359 | case 2://foundk 360 | break; 361 | } 362 | 363 | //document.write("pivk: " + pivk[i] + ""); 364 | 365 | } 366 | 367 | j=pivk[len-1]; 368 | lon[len-1]=j; 369 | i=len-2; 370 | for (i=len-2; i>=0; i--) { 371 | if (cyclic(i+1,pivk[i],j)) { 372 | j=pivk[i]; 373 | } 374 | lon[i]=j; 375 | } 376 | 377 | for(var i=0; i"); 380 | } 381 | 382 | } 383 | /*********************************************************************************************/ 384 | /* ---------------------------------------------------------------------- */ 385 | /* Stage 2: calculate the optimal polygon (Sec. 2.2.2-2.2.4). */ 386 | 387 | /* Auxiliary function: calculate the penalty of an edge from i to j in 388 | the given path. This needs the "lon" and "sum*" data. */ 389 | 390 | function penalty3(i, j) { 391 | 392 | /* assume 0<=ij) 401 | { 402 | var tmp = j; 403 | j = i; i = tmp; 404 | } 405 | if(j>len-1) 406 | j = len-1; 407 | 408 | var A, B, C; 409 | x0 = pixelChain[i].x; 410 | y0 = pixelChain[i].y; 411 | x1 = pixelChain[j].x; 412 | y1 = pixelChain[j].y; 413 | A = y0 - y1; 414 | B = x1 - x0; 415 | C = x0*y1 - x1*y0; 416 | 417 | AB = Math.sqrt(A*A+B*B); 418 | sum = 0; 419 | for(var k=i;k<=j;k++) 420 | { 421 | x = pixelChain[k].x; 422 | y = pixelChain[k].y; 423 | sum +=Math.abs(A*x+B*y+C)/AB; 424 | } 425 | sum = AB*Math.sqrt(sum/(j-i+1)); 426 | return sum; 427 | } 428 | 429 | /* find the optimal polygon. Fill in the m and po components. Return 1 430 | on failure with errno set, else 0. Non-cyclic version: assumes i=0 431 | is in the polygon. Fixme: ### implement cyclic version. */ 432 | function bestpolygon() 433 | { 434 | var i, j, m, k; 435 | var n = pixelChain.length; 436 | var pen = new Array(); // pen[n+1]: penalty vector 437 | var prev = new Array(); // prev[n+1]: best path pointer vector 438 | var clip0 = new Array(); // clip0[n]: longest segment pointer, non-cyclic 439 | var clip1 = new Array(); // clip1[n+1]: backwards segment pointer, non-cyclic 440 | var seg0 = new Array(); // seg0[m+1]: forward segment bounds, m<=n 441 | var seg1 = new Array(); // seg1[m+1]: backward segment bounds, m<=n 442 | var thispen; 443 | var best; 444 | var c; 445 | 446 | // calculate clipped paths 447 | for (i=0; i0; j--) { 480 | seg1[j] = i; 481 | i = clip1[i]; 482 | } 483 | seg1[0] = 0; 484 | 485 | // now find the shortest path with m segments, based on penalty3 486 | // note: the outer 2 loops jointly have at most n interations, thus 487 | //the worst-case behavior here is quadratic. In practice, it is 488 | //close to linear since the inner loop tends to be short. 489 | pen[0]=0; 490 | for (j=1; j<=m; j++) { 491 | for (i=seg1[j]; i<=seg0[j]; i++) { 492 | best = -1; 493 | for (k=seg0[j-1]; k>=clip1[i]; k--) { 494 | thispen = penalty3(k, i) + pen[k]; 495 | if (best < 0 || thispen < best) { 496 | prev[i] = k; 497 | best = thispen; 498 | } 499 | } 500 | pen[i] = best; 501 | } 502 | } 503 | 504 | // read off shortest path 505 | for (i=n, j=m-1; i>0; j--) { 506 | i = prev[i]; 507 | polLine[j] = i; 508 | //document.write("polline:" + i + ""); 509 | } 510 | } 511 | 512 | function smooth(zoom, tolerance) 513 | { 514 | var alphamax = 2; 515 | var m = polLine.length; 516 | 517 | var i, j, k; 518 | var dd, denom, alpha; 519 | 520 | var continuousCurve = new Array(); 521 | var isFirst = true; 522 | var isLast = false; 523 | //for (i=0; i1 ? (1 - 1.0/dd) : 0; 538 | alpha = alpha / 0.75; 539 | } 540 | else { 541 | alpha = 4/3.0; 542 | } 543 | //curve->alpha0[j] = alpha; /* remember "original" value of alpha */ 544 | 545 | if (alpha >= alphamax) { /* pointed corner */ 546 | //polLineCorners[j] = LINE; 547 | continuousCurve.push(pixelChain[polLine[j]]); 548 | fillBezierPoints(continuousCurve, zoom, tolerance, isFirst, isLast); 549 | isFirst = false; 550 | continuousCurve.length = 0; 551 | continuousCurve.push(pixelChain[polLine[j]]); 552 | } 553 | else { 554 | //polLineCorners[j] = CURVE; 555 | continuousCurve.push(pixelChain[polLine[j]]); 556 | } 557 | }//end for(i"); 578 | } 579 | 580 | //int Max_Beziers = (curveLen+3)/4; 581 | var Max_Beziers = curveLen; //one bezier for each couple of points 582 | var bezier = new Array(); 583 | var tolerance_sq = Math.abs(zoom)*tolerance; 584 | var tHat1 = new Point(0,0); 585 | var tHat2 = new Point(0,0); 586 | //int split_points[]; 587 | var nsegs; 588 | 589 | nsegs = bezier_fit_cubic(bezier, null, continuousCurve, curveLen, tHat1, tHat2, tolerance_sq, Max_Beziers); 590 | //document.write("bezier length="+bezier.length +""); 591 | //document.write("nsegs ="+nsegs +""); 592 | // for(var i=0;i"); 595 | // } 596 | for(var i=0;i"); 606 | bezierControlPoints.push(bezier[start+1].copy()); 607 | //document.write("bezier[" + bezier[start+1].x + "," + bezier[start+1].y + "]"); 608 | bezierControlPoints.push(bezier[start+2].copy()); 609 | //document.write("bezier[" + bezier[start+2].x + "," + bezier[start+2].y + "]"); 610 | } 611 | if(isLast && nsegs>0) 612 | { 613 | bezierControlPoints.push(bezier[nsegs*4-1]); 614 | } 615 | 616 | } 617 | 618 | /** 619 | * Fit a multi-segment Bezier curve to a set of digitized points, without 620 | * possible weedout of identical points and NaNs. 621 | * 622 | * \pre data is uniqued, i.e. not exist i: data[i] == data[i + 1]. 623 | * \param max_beziers Maximum number of generated segments 624 | * \param Result array, must be large enough for n. segments * 4 elements. 625 | */ 626 | function bezier_fit_cubic(bezier, split_points, data, len, tHat1, tHat2, error, max_beziers) 627 | { 628 | // document.write("bezier len1:" + bezier.length + "") 629 | // for(var i = 0; i < data.length; ++i) 630 | // { 631 | // document.write("data("+data[i].x+ ","+data[i].y + ")"); 632 | // } 633 | // document.write("len:"+len+""); 634 | //Point bezier[4]; 635 | //QVector data; 636 | var u = new Array(); 637 | //Point tHat1 = unconstrained_tangent; Point tHat2 = unconstrained_tangent; 638 | var dist; 639 | var difPoint; 640 | var maxIterations = 4; /* Max times to try iterating */ 641 | var nsegs1, nsegs2; 642 | 643 | if ( len < 2 ) { 644 | return 0;} 645 | 646 | if ( len == 2 ) { 647 | bezier[1] = new Point(0,0); 648 | bezier[2] = new Point(0,0); 649 | 650 | /* We have 2 points, which can be fitted by a line segment. */ 651 | bezier[0] = data[0].copy(); 652 | bezier[3] = data[len - 1].copy(); 653 | /* Straight line segment. */ 654 | var dPx, dPy; 655 | dPx = bezier[3].x - bezier[0].x; 656 | dPy = bezier[3].y - bezier[0].y; 657 | difPoint = new Point(dPx, dPy); 658 | 659 | bezier[1].x = bezier[2].x = (bezier[0].x + bezier[3].x)/2; 660 | bezier[1].y = bezier[2].y = bezier[0].y + (difPoint.y/(difPoint.x != 0 ? difPoint.x : 1))*(bezier[1].x - bezier[0].x); 661 | 662 | //curved Bezier 663 | /* 664 | dist = (difPoint.L2_norm2D() / 3.0 ); 665 | bezier[1] = ( tHat1.is_zero() 666 | ? ( bezier[0] * 2 + bezier[3] ) / 3. 667 | : bezier[0] + tHat1 * dist); 668 | bezier[2] = ( tHat2.is_zero() 669 | ? ( bezier[0] + bezier[3] * 2 ) / 3. 670 | : bezier[3] + tHat2 * dist );*/ 671 | 672 | return 1; 673 | } 674 | 675 | /* Parameterize points, and attempt to fit curve */ 676 | var splitPoint = new Array(); /* Point to split point set at. */ 677 | var is_corner; 678 | { 679 | chord_length_parameterize(data, u, len); 680 | if ( u[len - 1] == 0.0 ) { 681 | /* Zero-length path: every point in data[] is the same. 682 | * 683 | * (Clients aren't allowed to pass such data; handling the case is defensive 684 | * programming.) 685 | */ 686 | u.length = 0; 687 | return 0; 688 | } 689 | 690 | 691 | //std::cerr<<"\n tHat1 = ("<"); 700 | if ( Math.abs(maxErrorRatio) <= error ) { 701 | u.length = 0; 702 | return 1; 703 | } 704 | 705 | /* If error not too large, then try some reparameterization and iteration. */ 706 | if ( 0.0 <= maxErrorRatio && maxErrorRatio <= 3.0*tolerance ) { 707 | for (var i = 0; i < maxIterations; i++) { 708 | generate_bezier(bezier, data, u, len, tHat1, tHat2, error); 709 | reparameterize(data, len, u, bezier); 710 | maxErrorRatio = compute_max_error_ratio(data, u, len, bezier, tolerance, splitPoint); 711 | if ( Math.abs(maxErrorRatio) <= error ) { 712 | u.length=0; 713 | return 1; 714 | } 715 | } 716 | } 717 | 718 | u.length = 0; 719 | is_corner = (maxErrorRatio < 0); 720 | } 721 | //document.write("is_corner:"+is_corner+""); 722 | if (is_corner) { 723 | //std::cerr<<"\nis corner\n"; 724 | if (splitPoint[0] == 0) { 725 | if (tHat1.is_zero()) { 726 | /* Got spike even with unconstrained initial tangent. */ 727 | ++splitPoint[0]; 728 | } else { 729 | var val = bezier_fit_cubic(bezier, split_points, data, len, {x:0,y:0}, tHat2, error, max_beziers); 730 | return val; 731 | } 732 | } else if (splitPoint[0] == len - 1) { 733 | if (tHat2.is_zero()) { 734 | /* Got spike even with unconstrained final tangent. */ 735 | --splitPoint[0]; 736 | } else { 737 | var val = bezier_fit_cubic(bezier, split_points, data, len, tHat1, {x:0,y:0}, 738 | error, max_beziers); 739 | return val; 740 | } 741 | } 742 | } 743 | //return 1; 744 | if ( 1 < max_beziers ) { 745 | //std::cerr<<"RECURSIVE\n"; 746 | /* 747 | * Fitting failed -- split at max error point and fit recursively 748 | */ 749 | var rec_max_beziers1 = splitPoint[0]; 750 | 751 | var recTHat2, recTHat1; 752 | if (is_corner) { 753 | if(!(0 < splitPoint[0] && splitPoint[0] < (len - 1))) 754 | { 755 | return -1; 756 | } 757 | recTHat1 = new Point(0,0); 758 | recTHat2 = new Point(0,0); 759 | } 760 | else { 761 | /* Unit tangent vector at splitPoint. */ 762 | recTHat2 = estimate_center_tangent(data, splitPoint[0], len); 763 | recTHat1 = recTHat2.neg(); 764 | } 765 | var bezier1 = new Array(); 766 | nsegs1 = bezier_fit_cubic(bezier1, split_points, data, splitPoint[0] + 1, 767 | tHat1, recTHat2, error, rec_max_beziers1); 768 | /*std::cerr<<" nsegs1 = "<") 770 | if ( nsegs1 < 0 ) { 771 | return -1; 772 | } 773 | //assert( nsegs1 != 0 ); 774 | if (split_points != null) { 775 | split_points[nsegs1 - 1] = splitPoint[0]; 776 | } 777 | var rec_max_beziers2 = max_beziers - splitPoint[0] -1; 778 | 779 | var data_temp = new Array(); 780 | for(var i=0; i"); 786 | var bezier2 = new Array(); 787 | var nsegs2 = bezier_fit_cubic(bezier2, 788 | ( split_points == null ? null : split_points + nsegs1 ), 789 | data_temp, len - splitPoint[0], 790 | recTHat1, tHat2, error, rec_max_beziers2); 791 | 792 | //document.write("bezier nsegs2 len:" + bezier.length + "") 793 | for(var i = 0; i < bezier1.length; ++i) 794 | { 795 | bezier[i] = new Point(bezier1[i].x, bezier1[i].y); 796 | } 797 | for(var i = 0; i < bezier2.length; ++i) 798 | { 799 | bezier[bezier1.length+i] = new Point(bezier2[i].x, bezier2[i].y); 800 | } 801 | //bezier = bezier1.concat(bezier2); 802 | // for(var i = 0; i < bezier2.length; ++i) 803 | // { 804 | // document.write("->>bezier2:" + bezier2[i].x +","+bezier2[i].y+""); 805 | // } 806 | //std::cerr<<"nsegs2 = "< 0. ) { 885 | /* One iteration of Newton-Raphson: 886 | improved_u = u - f(u)/f'(u) */ 887 | improved_u = u - ( numerator / denominator ); 888 | } else { 889 | /* Using Newton-Raphson would move in the wrong direction (towards a local maximum rather 890 | than local minimum), so we move an arbitrary amount in the right direction. */ 891 | if ( numerator > 0. ) { 892 | improved_u = u * .98 - .01; 893 | } else if ( numerator < 0. ) { 894 | /* Deliberately asymmetrical, to reduce the chance of cycling. */ 895 | improved_u = .031 + u * .98; 896 | } else { 897 | improved_u = u; 898 | } 899 | } 900 | 901 | if (isNaN(improved_u) || improved_u>=INFTY) { 902 | improved_u = u; 903 | } else if ( improved_u < 0.0 ) { 904 | improved_u = 0.0; 905 | } else if ( improved_u > 1.0 ) { 906 | improved_u = 1.0; 907 | } 908 | 909 | /* Ensure that improved_u isn't actually worse. */ 910 | { 911 | var diff_lensq = dot(diff,diff); 912 | for (var proportion = .125; ; proportion += .125) { 913 | var temp = bezier_pt(3, Q, improved_u).minus(P) ; 914 | if ( dot(temp, temp) > diff_lensq ) { 915 | if ( proportion > 1.0 ) { 916 | //g_warning("found proportion %g", proportion); 917 | improved_u = u; 918 | break; 919 | } 920 | improved_u = ( ( 1 - proportion ) * improved_u + 921 | proportion * u ); 922 | } else { 923 | break; 924 | } 925 | } 926 | } 927 | 928 | 929 | return improved_u; 930 | } 931 | 932 | ////////////////////////////////////////////////////////////////////////////////// 933 | /** 934 | * Fill in bezier[] based on the given data and tangent requirements, using 935 | * a least-squares fit. 936 | * 937 | * Each of tHat1 and tHat2 should be either a zero vector or a unit vector. 938 | * If it is zero, then bezier[1 or 2] is estimated without constraint; otherwise, 939 | * it bezier[1 or 2] is placed in the specified direction from bezier[0 or 3]. 940 | * 941 | * \param tolerance_sq Used only for an initial guess as to tangent directions 942 | * when tHat1 or tHat2 is zero. 943 | */ 944 | function generate_bezier(bezier, data, u, len, tHat1, tHat2, tolerance_sq) 945 | { 946 | var est1 = (tHat1.x == 0 && tHat1.y == 0); 947 | var est2 = (tHat2.x == 0 && tHat2.y == 0); 948 | var est_tHat1, est_tHat2; 949 | est_tHat1 = est1 ? estimate_left_tangent(data, len, tolerance_sq):tHat1; 950 | est_tHat2 = est2 ? estimate_right_tangent(data, len, tolerance_sq):tHat2; 951 | //document.write("est_tHat1:" + est_tHat1.x +","+est_tHat1.y +""); 952 | //document.write("est_tHat2:" + est_tHat2.x +","+est_tHat2.y +""); 953 | estimate_lengths(bezier, data, u, len, est_tHat1, est_tHat2); 954 | 955 | 956 | 957 | /* We find that sp_darray_right_tangent tends to produce better results 958 | for our current freehand tool than full estimation. */ 959 | if (est1) { 960 | estimate_bi(bezier, 1, data, u, len); 961 | if (!bezier[1].equal(bezier[0])) { 962 | est_tHat1 = new Point((bezier[1].x - bezier[0].x), (bezier[1].y - bezier[0].y)); 963 | est_tHat1.normalize(); 964 | } 965 | estimate_lengths(bezier, data, u, len, est_tHat1, est_tHat2); 966 | } 967 | 968 | // for(var i = 0; i < 4; ++i) 969 | // { 970 | // //document.write("generate_bezier 4 ("+bezier[i].x+ ","+bezier[i].y + ") "); 971 | // } 972 | } 973 | 974 | /** 975 | * Estimates the (backward) tangent at d[center], by averaging the two 976 | * segments connected to d[center] (and then normalizing the result). 977 | * 978 | * \note The tangent is "backwards", i.e. it is with respect to 979 | * decreasing index rather than increasing index. 980 | * 981 | * \pre (0 \< center \< len - 1) and d is uniqued (at least in 982 | * the immediate vicinity of \a center). 983 | */ 984 | function estimate_center_tangent(d, center, len) 985 | { 986 | //assert( center != 0 ); 987 | //assert( center < len - 1 ); 988 | var ret; 989 | var P1 = d[center + 1].copy(); 990 | var P2 = d[center - 1].copy(); 991 | 992 | if( P1.equal(P2)) 993 | { 994 | /* Rotate 90 degrees in an arbitrary direction. */ 995 | var diff = d[center].minus(d[center - 1]); 996 | ret.x = -diff.y; 997 | ret.y = diff.x; 998 | } 999 | else 1000 | { 1001 | ret = d[center - 1].minus(d[center + 1]); 1002 | } 1003 | ret.normalize(); 1004 | return ret; 1005 | } 1006 | 1007 | /** 1008 | * Estimate the (forward) tangent at point d[0]. 1009 | * 1010 | * Unlike the center and right versions, this calculates the tangent in 1011 | * the way one might expect, i.e., wrt increasing index into d. 1012 | * 1013 | * \pre 2 \<= len. 1014 | * \pre d[0] != d[1]. 1015 | * \pre all[p in d] in_svg_plane(p). 1016 | * \post is_unit_vector(ret). 1017 | **/ 1018 | 1019 | function estimate_left_tangent(d, len, tolerance_sq) 1020 | { 1021 | //assert( 2 <= len ); 1022 | //assert( 0 <= tolerance_sq ); 1023 | 1024 | //tolerance_sq is always 0!!!) 1025 | //return estimate_left_tangent(d, len); 1026 | //std::cerr<<"estimate_left_tangent COMPUTE TANGENT LEFT: len = "<size(); 1096 | //assert( 2 <= len ); 1097 | 1098 | /* First let u[i] equal the distance travelled along the path from d[0] to d[i]. */ 1099 | u[0] = 0.0; 1100 | for (var i = 1; i < len; i++) { 1101 | var p = d[i].minus(d[i-1]); 1102 | u[i] = u[i-1] + p.norm(); 1103 | } 1104 | 1105 | /* Then scale to [0.0 .. 1.0]. */ 1106 | var tot_len = u[len - 1]; 1107 | //assert( tot_len != 0 ); 1108 | for (var i = 1; i < len; ++i) { 1109 | u[i] /= tot_len; 1110 | } 1111 | u[len - 1] = 1; 1112 | 1113 | // for(var i = 0; i < u.length; ++i) 1114 | // { 1115 | // document.write("chord_length_parameterize "+u[i]+""); 1116 | // } 1117 | } 1118 | 1119 | /*Functions used for computing the Bezier control points;*/ 1120 | 1121 | /** 1122 | * Estimate the (forward) tangent at point d[first + 0.5]. 1123 | * 1124 | * Unlike the center and right versions, this calculates the tangent in 1125 | * the way one might expect, i.e., wrt increasing index into d. 1126 | * \pre (2 \<= len) and (d[0] != d[1]). 1127 | **/ 1128 | function estimate_left_tangent2(d, len) 1129 | { 1130 | //compute the left tangent using apportioned chords 1131 | //the tangent ensures that the Bezier passes throught the second data point, also 1132 | var p; 1133 | var x0, x1, x2, x3; 1134 | var tHat1; 1135 | var L1, L2, L3; 1136 | var t1, t2, m1, n1, m2, n2; 1137 | //assert( len >= 2 ); 1138 | 1139 | switch (len) 1140 | { 1141 | case 2: 1142 | p = d[1].minus(d[0]); 1143 | p.normalize(); 1144 | break; 1145 | case 3: 1146 | x0 = d[0].copy(); 1147 | x1 = d[1].copy(); 1148 | x2 = d[1].copy(); 1149 | x3 = d[2].copy(); 1150 | L1 = x1.minus(x0); 1151 | L2 = x2.minus(x1); 1152 | t1 = L1.norm()/(L1.norm() + L2.norm()); 1153 | tHat1 = (x1.minus(x0.time(Math.pow(1-t1,3))).minus(x3.time(Math.pow(t1, 3)))).time(1.0/(3*t1*pow(1-t1,2))*(1-t1)); 1154 | p = tHat1.minus(x0); 1155 | p.normalize(); 1156 | default: 1157 | x0 = d[0].copy(); 1158 | x1 = d[1].copy(); 1159 | x2 = d[2].copy(); 1160 | x3 = d[3].copy(); 1161 | L1 = x1.minus(x0); 1162 | L2 = x2.minus(x1); 1163 | L3 = x3.minus(x2); 1164 | t1 = ( L1.norm() )/(L1.norm() + L2.norm() + L3.norm() ); 1165 | t2 = ( L1.norm() + L2.norm() )/(L1.norm() + L2.norm() + L3.norm() ); 1166 | 1167 | m1 = B1(t1); m2 = B1(t2); 1168 | n1 = B2(t1); n2 = B2(t2); 1169 | L1 = x1.minus(x0.time(B0(t1))).minus(x3.time(B3(t1))); 1170 | L2 = x2.minus(x0.time(B0(t2))).minus(x3.time(B3(t2))); 1171 | tHat1 = L2.time(1.0/(m2-m1/n1)).minus(L1.time(1.0/(n1*m2-m1))); 1172 | p = tHat1.minus(x0); 1173 | p.normalize(); 1174 | } 1175 | 1176 | return p; 1177 | 1178 | } 1179 | 1180 | /** 1181 | * Estimates the (backward) tangent at d[last - 0.5]. 1182 | * 1183 | * \note The tangent is "backwards", i.e. it is with respect to 1184 | * decreasing index rather than increasing index. 1185 | * 1186 | * \pre 2 \<= len. 1187 | * \pre d[len - 1] != d[len - 2]. 1188 | * \pre all[p in d] in_svg_plane(p). 1189 | */ 1190 | function estimate_right_tangent2(d, len) 1191 | { 1192 | //compute the right tangent using apportioned chords 1193 | //the tangent ensures that the Bezier passes throught the third data point, also 1194 | var p; 1195 | var x0, x1, x2, x3; 1196 | var tHat2; 1197 | var L1, L2, L3; 1198 | var t1, t2, m1, n1, m2, n2; 1199 | var last = len - 1; 1200 | //assert( len >= 2 ); 1201 | 1202 | switch (len) 1203 | { 1204 | case 2: 1205 | p = d[last-1].minus(d[last]); 1206 | p.normalize(); 1207 | break; 1208 | case 3: 1209 | x0 = d[last-2].copy(); 1210 | x1 = d[last-1].copy(); 1211 | x2 = d[last-1].copy(); 1212 | x3 = d[last].copy(); 1213 | L1 = x1.minus(x0); 1214 | L2 = x2.minus(x1); 1215 | t1 = L1.norm()/(L1.norm() + L2.norm()); 1216 | tHat2 = (x1.minus(x0.time(Math.pow(1-t1,3))).minus(x3.time(Math.pow(t1, 3)))).time(1.0/(3*t1*pow(1-t1,2))*(1-t1)); 1217 | p = tHat2-x3; 1218 | p.normalize(); 1219 | default: 1220 | x0 = d[last-3].copy(); 1221 | x1 = d[last-2].copy(); 1222 | x2 = d[last-1].copy(); 1223 | x3 = d[last].copy(); 1224 | L1 = x1.minus(x0); 1225 | L2 = x2.minus(x1); 1226 | L3 = x3.minus(x2); 1227 | t1 = ( L1.norm() )/(L1.norm() + L2.norm() + L3.norm() ); 1228 | t2 = ( L1.norm() + L2.norm() )/(L1.norm() + L2.norm() + L3.norm() ); 1229 | 1230 | m1 = B1(t1); m2 = B1(t2); 1231 | n1 = B2(t1); n2 = B2(t2); 1232 | 1233 | L1 = x1.minus(x0.time(B0(t1))).minus(x3.time(B3(t1))); 1234 | L2 = x2.minus(x0.time(B0(t2))).minus(x3.time(B3(t2))); 1235 | tHat2 = L1.time(m2).minus(L2.time(m1)).time(1/(m2*n1-m1*n2)); 1236 | 1237 | p = tHat2.minus(x3); 1238 | 1239 | p.normalize(); 1240 | } 1241 | return p; 1242 | 1243 | 1244 | //assert( 2 <= len ); 1245 | //unsigned const last = len - 1; 1246 | //unsigned const prev = last - 1; 1247 | //assert( d->at(last) != d->at(prev) ); 1248 | //Point p = (d->at(prev) - d->at(last)); p.normalize(); 1249 | //return p; 1250 | } 1251 | 1252 | function estimate_bi(bezier, ei, data, u, len) 1253 | { 1254 | if(!(1 <= ei && ei <= 2)) 1255 | return; 1256 | var oi = 3 - ei; 1257 | var num = new Point(0.,0.); 1258 | var den = 0.; 1259 | for (var i = 0; i < len; ++i) { 1260 | var ui = u[i]; 1261 | var b = [ 1262 | B0(ui), 1263 | B1(ui), 1264 | B2(ui), 1265 | B3(ui) 1266 | ]; 1267 | 1268 | { 1269 | num.x += b[ei] * (b[0] * bezier[0].x + 1270 | b[oi] * bezier[oi].x + 1271 | b[3] * bezier[3].x + 1272 | - data[i].x); 1273 | 1274 | num.y += b[ei] * (b[0] * bezier[0].y + 1275 | b[oi] * bezier[oi].y + 1276 | b[3] * bezier[3].y + 1277 | - data[i].y); 1278 | } 1279 | den -= b[ei] * b[ei]; 1280 | } 1281 | 1282 | if (den != 0.) { 1283 | 1284 | bezier[ei] = num.time(1/den); 1285 | } 1286 | else { 1287 | bezier[ei] = bezier[0].time(oi).plus(bezier[3].time(ei)).time(1.0/3.); 1288 | } 1289 | } 1290 | 1291 | /** 1292 | * Estimates length of tangent vectors P1, P2, when direction is given 1293 | * fills in bezier with the correct values for P1, P2 1294 | * Point bezier[4] 1295 | */ 1296 | function estimate_lengths(bezier, data, uPrime, len, tHat1, tHat2) 1297 | { 1298 | //document.write("estimate_lengths len:"+len+"");bezier 1299 | //document.write("estimate_lengths bezier len:"+bezier.length+""); 1300 | // for(var i = 0; i < len; ++i) 1301 | // { 1302 | // document.write("estimate_lengths data("+data[i].x+ ","+data[i].y + ") "); 1303 | // } 1304 | // for(var i = 0; i < bezier.length; ++i) 1305 | // { 1306 | // document.write("estimate_lengths bezier("+bezier[i].x+ ","+bezier[i].y + ") "); 1307 | // } 1308 | 1309 | var C = [];/* Matrix C. */ 1310 | for(var x = 0; x < 2; x++){ 1311 | C[x] = []; 1312 | for(var y = 0; y < 2; y++){ 1313 | C[x][y] = 0.0; 1314 | } 1315 | } 1316 | 1317 | var X = [0.0, 0.0]; /* Matrix X. */ 1318 | 1319 | /* First and last control points of the Bezier curve are positioned exactly at the first and 1320 | last data points. */ 1321 | bezier[0] = new Point(data[0].x, data[0].y); 1322 | bezier[3] = new Point(data[len - 1].x, data[len - 1].y); 1323 | 1324 | for (var i = 0; i < len; i++) { 1325 | /* Bezier control point coefficients. */ 1326 | var b0 = B0(uPrime[i]); 1327 | var b1 = B1(uPrime[i]); 1328 | var b2 = B2(uPrime[i]); 1329 | var b3 = B3(uPrime[i]); 1330 | 1331 | /* rhs for eqn */ 1332 | var a1 = {x:tHat1.x *b1, y:tHat1.y*b1}; 1333 | var a2 = {x:tHat2.x *b2, y:tHat2.y*b2}; 1334 | 1335 | C[0][0] += dot(a1, a1); 1336 | C[0][1] += dot(a1, a2); 1337 | C[1][0] = C[0][1]; 1338 | C[1][1] += dot(a2, a2); 1339 | 1340 | /* Additional offset to the data point from the predicted point if we were to set bezier[1] 1341 | to bezier[0] and bezier[2] to bezier[3]. */ 1342 | var shortfall = {x:(data[i].x-(bezier[0].x *(b0 + b1))-(bezier[3].x*(b2+b3))), y:(data[i].y-(bezier[0].y*(b0 + b1))-(bezier[3].y*(b2+b3)))}; 1343 | X[0] += dot(a1, shortfall); 1344 | X[1] += dot(a2, shortfall); 1345 | } 1346 | 1347 | /* We've constructed a pair of equations in the form of a matrix product C * alpha = X. 1348 | Now solve for alpha. */ 1349 | var alpha_l, alpha_r; 1350 | 1351 | /* Compute the determinants of C and X. */ 1352 | var det_C0_C1 = C[0][0] * C[1][1] - C[1][0] * C[0][1]; 1353 | if ( det_C0_C1 != 0 ) { 1354 | /* Apparently Kramer's rule. */ 1355 | var det_C0_X = C[0][0] * X[1] - C[0][1] * X[0]; 1356 | var det_X_C1 = X[0] * C[1][1] - X[1] * C[0][1]; 1357 | alpha_l = det_X_C1 / det_C0_C1; 1358 | alpha_r = det_C0_X / det_C0_C1; 1359 | } else { 1360 | /* The matrix is under-determined. Try requiring alpha_l == alpha_r. 1361 | * 1362 | * One way of implementing the constraint alpha_l == alpha_r is to treat them as the same 1363 | * variable in the equations. We can do this by adding the columns of C to form a single 1364 | * column, to be multiplied by alpha to give the column vector X. 1365 | * 1366 | * We try each row in turn. 1367 | */ 1368 | var c0 = C[0][0] + C[0][1]; 1369 | if (c0 != 0) { 1370 | alpha_l = alpha_r = X[0] / c0; 1371 | } else { 1372 | var c1 = C[1][0] + C[1][1]; 1373 | if (c1 != 0) { 1374 | alpha_l = alpha_r = X[1] / c1; 1375 | } else { 1376 | /* Let the below code handle this. */ 1377 | alpha_l = alpha_r = 0.; 1378 | } 1379 | } 1380 | } 1381 | 1382 | /* If alpha negative, use the Wu/Barsky heuristic (see text). (If alpha is 0, you get 1383 | coincident control points that lead to divide by zero in any subsequent 1384 | NewtonRaphsonRootFind() call.) */ 1385 | /// \todo Check whether this special-casing is necessary now that 1386 | /// NewtonRaphsonRootFind handles non-positive denominator. 1387 | if ( alpha_l < 1.0e-6 || 1388 | alpha_r < 1.0e-6 ) 1389 | { 1390 | var p = {x:data[len - 1].x- data[0].x, y:data[len - 1].y- data[0].y}; 1391 | alpha_l = alpha_r = ( Math.sqrt(p.x*p.x +p.y*p.y)/3.0 ); 1392 | } 1393 | 1394 | /* Control points 1 and 2 are positioned an alpha distance out on the tangent vectors, left and 1395 | right, respectively. */ 1396 | //alpha min is 4 ..so the points are far enough away 1397 | /*bezier[1] = tHat1 * max(alpha_l,4.) + bezier[0]; 1398 | bezier[2] = tHat2 * max(alpha_r,4.) + bezier[3];*/ 1399 | 1400 | bezier[1] = new Point(tHat1.x * alpha_l + bezier[0].x,tHat1.y * alpha_l + bezier[0].y); 1401 | bezier[2] = new Point(tHat2.x * alpha_r + bezier[3].x,tHat2.y * alpha_r + bezier[3].y); 1402 | 1403 | return; 1404 | } 1405 | 1406 | /* integer arithmetic */ 1407 | function mod(a, n) { 1408 | return a>=n ? a%n : a>=0 ? a : n-1-(-1-a)%n; 1409 | } 1410 | /*for two pixels that are not neighbours: P0, P1 1411 | determine a pixel neighbouring P0 that is on the line uniting P0 to P1 1412 | returns direction with center in P0: (-1,0,1) 1413 | */ 1414 | function ddir(a) 1415 | { 1416 | var p = new Point(); 1417 | p.x = 0; p.y = 0; 1418 | if(a.y==a.x==0) 1419 | return p; 1420 | //atan2 gives an error if both arguments are 0; 1421 | var theta = Math.atan2(a.y,a.x)* 180 / Math.PI; 1422 | if(theta<0) 1423 | { 1424 | if((a.x>=0)&&(a.y>=0)) 1425 | theta +=180; // 1426 | else if((a.x<=0)&&(a.y<=0)) 1427 | theta +=360; // 1428 | else 1429 | document.write("oops!"); 1430 | } 1431 | else 1432 | { 1433 | if((a.x>=0)&&(a.y<=0)&&theta<270) 1434 | theta = 360-theta; // 1435 | } 1436 | //document.write("angle:" + theta + ""); 1437 | //0/360 1438 | if(theta<22.5 || theta>=337.5) 1439 | { 1440 | p.x = 1; p.y = 0; 1441 | return p; 1442 | } 1443 | //45 1444 | if(theta>=22.5 && theta<67.5) 1445 | { 1446 | p.x = 1; p.y = 1; 1447 | return p; 1448 | } 1449 | //90 1450 | if(theta>=67.5 && theta<112.5) 1451 | { 1452 | p.x = 0; p.y = 1; 1453 | return p; 1454 | } 1455 | //135 1456 | if(theta>=112.5 && theta<157.5) 1457 | { 1458 | p.x = -1; p.y = 1; 1459 | return p; 1460 | } 1461 | //180 1462 | if(theta>=157.5 && theta<202.5) 1463 | { 1464 | p.x = -1; p.y = 0; 1465 | return p; 1466 | } 1467 | //225 1468 | if(theta>=202.5 && theta<247.5) 1469 | { 1470 | p.x = -1; p.y = -1; 1471 | return p; 1472 | } 1473 | //270 1474 | if(theta>=247.5 && theta<292.5) 1475 | { 1476 | p.x = 0; p.y = -1; 1477 | return p; 1478 | } 1479 | //315 1480 | if(theta>=292.5 && theta<337.5) 1481 | { 1482 | p.x = 1; p.y = -1; 1483 | return p; 1484 | } 1485 | return p; 1486 | } 1487 | 1488 | /* return 1 if a <= b < c < a, in a cyclic sense (mod n) */ 1489 | function cyclic(a, b, c) { 1490 | //document.write("a, b, c:" + a + "," + b + "," + c + ""); 1491 | if (a<=c) { 1492 | return (a<=b && b0 ? 1 : (x)<0 ? -1 : 0); 1506 | } 1507 | 1508 | /* return (p1-p0)x(p2-p0), the area of the parallelogram */ 1509 | function dpara(p0, p1, p2) { 1510 | var x1, y1, x2, y2; 1511 | 1512 | x1 = p1.x-p0.x; 1513 | y1 = p1.y-p0.y; 1514 | x2 = p2.x-p0.x; 1515 | y2 = p2.y-p0.y; 1516 | 1517 | return x1*y2 - x2*y1; 1518 | } 1519 | 1520 | /* ddenom/dpara have the property that the square of radius 1 centered 1521 | at p1 intersects the line p0p2 iff |dpara(p0,p1,p2)| <= ddenom(p0,p2) */ 1522 | function ddenom(p0, p2) { 1523 | var r = new Point(); 1524 | r.y = sign(p2.x-p0.x); 1525 | r.x = -sign(p2.y-p0.y); 1526 | return r.y*(p2.x-p0.x) - r.x*(p2.y-p0.y); 1527 | } 1528 | 1529 | function dot(P1, P2) {return P1.x*P2.x + P1.y*P2.y;} 1530 | 1531 | function floordiv(a, n) { 1532 | return (a>=0 ? parseInt(a/n) : -1-parseInt((-1-a)/n)); 1533 | } 1534 | 1535 | /** 1536 | * Evaluate a Bezier curve at parameter value \a t. 1537 | * 1538 | * \param degree The degree of the Bezier curve: 3 for cubic, 2 for quadratic etc. 1539 | * \param V The control points for the Bezier curve. Must have (\a degree+1) 1540 | * elements. 1541 | * \param t The "parameter" value, specifying whereabouts along the curve to 1542 | * evaluate. Typically in the range [0.0, 1.0]. 1543 | * 1544 | * Let s = 1 - t. 1545 | * BezierII(1, V) gives (s, t) * V, i.e. t of the way 1546 | * from V[0] to V[1]. 1547 | * BezierII(2, V) gives (s**2, 2*s*t, t**2) * V. 1548 | * BezierII(3, V) gives (s**3, 3 s**2 t, 3s t**2, t**3) * V. 1549 | * 1550 | * The derivative of BezierII(i, V) with respect to t 1551 | * is i * BezierII(i-1, V'), where for all j, V'[j] = 1552 | * V[j + 1] - V[j]. 1553 | */ 1554 | function bezier_pt(degree, V, t) 1555 | { 1556 | /** Pascal's triangle. */ 1557 | var pascal = new Array(); 1558 | pascal[0] = [1]; 1559 | pascal[1] = [1,1]; 1560 | pascal[2] = [1,2,1]; 1561 | pascal[3] = [1,3,3,1]; 1562 | 1563 | //assert( degree < 4 ); 1564 | var s = 1.0 - t; 1565 | 1566 | /* Calculate powers of t and s. */ 1567 | var spow = new Array(); 1568 | var tpow = new Array(); 1569 | spow[0] = 1.0; spow[1] = s; 1570 | tpow[0] = 1.0; tpow[1] = t; 1571 | for (var i = 1; i < degree; ++i) { 1572 | spow[i + 1] = spow[i] * s; 1573 | tpow[i + 1] = tpow[i] * t; 1574 | } 1575 | 1576 | var ret = new Point(V[0].x * spow[degree], V[0].y * spow[degree]); 1577 | for (var i = 1; i <= degree; ++i) { 1578 | var p = new Point(V[i].x, V[i].y); 1579 | ret = ret.plus(p.time(pascal[degree][i] * spow[degree - i] * tpow[i])); 1580 | } 1581 | //document.write("ret:"+ret.x + "," + ret.y+""); 1582 | return ret; 1583 | } 1584 | 1585 | /** 1586 | * Find the maximum squared distance of digitized points to fitted curve, and (if this maximum 1587 | * error is non-zero) set \a *splitPoint to the corresponding index. 1588 | * 1589 | * \pre 2 \<= len. 1590 | * \pre u[0] == 0. 1591 | * \pre u[len - 1] == 1.0. 1592 | * \post ((ret == 0.0) 1593 | * || ((*splitPoint \< len - 1) 1594 | * \&\& (*splitPoint != 0 || ret \< 0.0))). 1595 | */ 1596 | function compute_max_error_ratio(d, u, len, bezCurve, tolerance, splitPoint) 1597 | 1598 | { 1599 | // for(var i = 0; i < d.length; ++i) 1600 | // { 1601 | // document.write("("+d[i].x+ ","+d[i].y + ")"); 1602 | // } 1603 | // 1604 | // for(var i = 0; i < u.length; ++i) 1605 | // { 1606 | // document.write(u[i]+""); 1607 | // } 1608 | // 1609 | // for(var i = 0; i < 4; ++i) 1610 | // { 1611 | // document.write("("+bezCurve[i].x+ ","+bezCurve[i].y + ")"); 1612 | // } 1613 | // document.write(""); 1614 | 1615 | //assert( 2 <= len ); 1616 | var last = len - 1; 1617 | //assert( u[0] == 0.0 ); 1618 | //assert( u[last] == 1.0 ); 1619 | /* I.e. assert that the error for the first & last points is zero. 1620 | * Otherwise we should include those points in the below loop. 1621 | * The assertion is also necessary to ensure 0 < splitPoint < last. 1622 | */ 1623 | 1624 | var maxDistsq = 0.0; /* Maximum error */ 1625 | var max_hook_ratio = 0.0; 1626 | var snap_end = 0; 1627 | var prev = new Point(bezCurve[0].x, bezCurve[0].y); 1628 | for (var i = 1; i <= last; i++) { 1629 | var curr = bezier_pt(3, bezCurve, u[i]); 1630 | var distsq = dot( curr.minus(d[i]), curr.minus(d[i]) ); 1631 | //document.write("distsq:"+distsq+""); 1632 | if ( distsq > maxDistsq ) { 1633 | maxDistsq = distsq; 1634 | //*splitPoint = i; 1635 | splitPoint[0] = i; 1636 | } 1637 | var hook_ratio = compute_hook(prev, curr, .5 * (u[i - 1] + u[i]), bezCurve, tolerance); 1638 | 1639 | if (max_hook_ratio < hook_ratio) { 1640 | max_hook_ratio = hook_ratio; 1641 | snap_end = i; 1642 | } 1643 | prev = curr.copy(); 1644 | } 1645 | var dist_ratio = Math.sqrt(maxDistsq) / tolerance; 1646 | var ret; 1647 | if (max_hook_ratio <= dist_ratio) { 1648 | ret = dist_ratio; 1649 | } else { 1650 | //assert(0 < snap_end); 1651 | ret = -max_hook_ratio; 1652 | splitPoint[0] = snap_end - 1; 1653 | } 1654 | // document.write("splitPoint:"+splitPoint[0]+""); 1655 | // //assert( ret == 0.0 || ( ( splitPoint < last ) && ( splitPoint != 0 || ret < 0. ) ) ); 1656 | // document.write("maxDistsq:"+maxDistsq+""); 1657 | // document.write("ret:"+ret+""); 1658 | return ret; 1659 | } 1660 | 1661 | 1662 | /** 1663 | * Whereas compute_max_error_ratio() checks for itself that each data point 1664 | * is near some point on the curve, this function checks that each point on 1665 | * the curve is near some data point (or near some point on the polyline 1666 | * defined by the data points, or something like that: we allow for a 1667 | * "reasonable curviness" from such a polyline). "Reasonable curviness" 1668 | * means we draw a circle centred at the midpoint of a..b, of radius 1669 | * proportional to the length |a - b|, and require that each point on the 1670 | * segment of bezCurve between the parameters of a and b be within that circle. 1671 | * If any point P on the bezCurve segment is outside of that allowable 1672 | * region (circle), then we return some metric that increases with the 1673 | * distance from P to the circle. 1674 | * 1675 | * Given that this is a fairly arbitrary criterion for finding appropriate 1676 | * places for sharp corners, we test only one point on bezCurve, namely 1677 | * the point on bezCurve with parameter halfway between our estimated 1678 | * parameters for a and b. (Alternatives are taking the farthest of a 1679 | * few parameters between those of a and b, or even using a variant of 1680 | * NewtonRaphsonFindRoot() for finding the maximum rather than minimum 1681 | * distance.) 1682 | */ 1683 | function compute_hook(a, b, u, bezCurve, tolerance) 1684 | { 1685 | var P = bezier_pt(3, bezCurve, u); 1686 | var diff = a.plus(b).time(0.5).minus(P); 1687 | var dist = diff.norm(); 1688 | if (dist < tolerance) { 1689 | return 0; 1690 | } 1691 | P = b.minus(a); 1692 | var allowed = P.norm() + tolerance; 1693 | return dist / allowed; 1694 | /** \todo 1695 | * effic: Hooks are very rare. We could start by comparing 1696 | * distsq, only resorting to the more expensive L2 in cases of 1697 | * uncertainty. 1698 | */ 1699 | } 1700 | 1701 | 1702 | -------------------------------------------------------------------------------- /drawing.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Paint 6 | 10 | 11 | 12 | 13 | 14 | Unfortunately, your browser is currently unsupported by our web 15 | application. We are sorry for the inconvenience. Please use one of the 16 | supported browsers listed below, or draw the image you want using an 17 | offline tool. 18 | Supported browsers: Opera, Firefox, Safari, and Konqueror. 22 | 23 | 24 | 25 | 26 | Show Control Point 27 | 28 | -------------------------------------------------------------------------------- /mouseTrack.js: -------------------------------------------------------------------------------- 1 | /* © 2009 ROBO Design 2 | * http://www.robodesign.ro 3 | */ 4 | var showControl = false; 5 | var curves = new Array(); 6 | var context; 7 | var canvas; 8 | function updateShowControl() 9 | { 10 | var elem = document.getElementById("button1"); 11 | var txt = elem.textContent || elem.innerText; 12 | if (txt == "Show Control Point") txt = "Hide Control Point"; 13 | else txt = "Show Control Point"; 14 | elem.textContent = txt; 15 | elem.innerText = txt; 16 | showControl = !showControl; 17 | drawCurves(); 18 | } 19 | function drawCircle(center_x, center_y, radius) 20 | { 21 | context.beginPath(); 22 | context.arc(center_x,center_y, radius, 0, 2 * Math.PI, false); 23 | context.fillStyle = "yellow"; 24 | context.fill(); 25 | context.lineWidth = 1; 26 | context.strokeStyle = "black"; 27 | context.stroke(); 28 | } 29 | 30 | function drawLine(start_x, start_y, end_x, end_y) 31 | { 32 | context.lineWidth = 1; 33 | context.beginPath(); 34 | context.moveTo(start_x,start_y); 35 | context.lineTo(end_x,end_y); 36 | context.stroke(); 37 | } 38 | 39 | function drawCurves() 40 | { 41 | context.clearRect(0, 0, canvas.width, canvas.height); 42 | for(var s = 0; s < curves.length; ++s) 43 | { 44 | var bPoints = curves[s]; 45 | for(var i = 0; i < bPoints.length-1; i += 3) 46 | { 47 | context.beginPath(); 48 | context.moveTo(bPoints[i].x,bPoints[i].y); 49 | context.bezierCurveTo(bPoints[i+1].x,bPoints[i+1].y, bPoints[i+2].x,bPoints[i+2].y, bPoints[i+3].x,bPoints[i+3].y); 50 | context.stroke(); 51 | if(showControl){ 52 | //draw control point edge 53 | drawLine(bPoints[i].x, bPoints[i].y, bPoints[i+1].x, bPoints[i+1].y); 54 | drawLine(bPoints[i+3].x, bPoints[i+3].y, bPoints[i+2].x, bPoints[i+2].y); 55 | } 56 | } 57 | if(showControl){ 58 | for(var i = 0; i < bPoints.length; i++) 59 | { 60 | //draw control point 61 | drawCircle(bPoints[i].x, bPoints[i].y, 3); 62 | } 63 | } 64 | } 65 | } 66 | 67 | if(window.addEventListener) { 68 | window.addEventListener('load', function () { 69 | var tool; 70 | var stroke = new Array(); 71 | 72 | function init () { 73 | // Find the canvas element. 74 | canvas = document.getElementById('imageView'); 75 | if (!canvas) { 76 | alert('Error: I cannot find the canvas element!'); 77 | return; 78 | } 79 | 80 | if (!canvas.getContext) { 81 | alert('Error: no canvas.getContext!'); 82 | return; 83 | } 84 | canvas.width = 800; 85 | canvas.height = 600; 86 | // Get the 2D canvas context. 87 | context = canvas.getContext('2d'); 88 | if (!context) { 89 | alert('Error: failed to getContext!'); 90 | return; 91 | } 92 | 93 | // Pencil tool instance. 94 | tool = new tool_pencil(); 95 | 96 | // Attach the mousedown, mousemove and mouseup event listeners. 97 | canvas.addEventListener('mousedown', ev_canvas, false); 98 | canvas.addEventListener('mousemove', ev_canvas, false); 99 | canvas.addEventListener('mouseup', ev_canvas, false); 100 | } 101 | 102 | // This painting tool works like a drawing pencil which tracks the mouse 103 | // movements. 104 | function tool_pencil () { 105 | var tool = this; 106 | this.started = false; 107 | 108 | // This is called when you start holding down the mouse button. 109 | // This starts the pencil drawing. 110 | this.mousedown = function (ev) { 111 | context.beginPath(); 112 | context.moveTo(ev._x, ev._y); 113 | stroke.push(new Point(ev._x, ev._y)); 114 | tool.started = true; 115 | }; 116 | 117 | // This function is called every time you move the mouse. Obviously, it only 118 | // draws if the tool.started state is set to true (when you are holding down 119 | // the mouse button). 120 | this.mousemove = function (ev) { 121 | if (tool.started) { 122 | context.lineTo(ev._x, ev._y); 123 | context.stroke(); 124 | stroke.push(new Point(ev._x, ev._y)); 125 | } 126 | }; 127 | 128 | // This is called when you release the mouse button. 129 | this.mouseup = function (ev) { 130 | if (tool.started) { 131 | tool.mousemove(ev); 132 | tool.started = false; 133 | context.font = '18pt Calibri'; 134 | context.fillStyle = 'black'; 135 | //document.write(p.x + "," + p.y+""); 136 | var bezier_points = new Array(); 137 | var tolerance=10; 138 | if(stroke.length>2) 139 | fitBezier(stroke, bezier_points, tolerance); 140 | curves.push(bezier_points.slice(0)); 141 | 142 | drawCurves(); 143 | stroke.length = 0; 144 | bezier_points.length = 0; 145 | } 146 | } 147 | } 148 | // The general-purpose event handler. This function just determines the mouse 149 | // position relative to the canvas element. 150 | function ev_canvas (ev) { 151 | if (ev.layerX || ev.layerX == 0) { // Firefox 152 | ev._x = ev.layerX; 153 | ev._y = ev.layerY; 154 | } else if (ev.offsetX || ev.offsetX == 0) { // Opera 155 | ev._x = ev.offsetX; 156 | ev._y = ev.offsetY; 157 | } 158 | 159 | // Call the event handler of the tool. 160 | var func = tool[ev.type]; 161 | if (func) { 162 | func(ev); 163 | } 164 | } 165 | 166 | init(); 167 | 168 | }, false); 169 | } 170 | 171 | // vim:set spell spl=en fo=wan1croql tw=80 ts=2 sw=2 sts=2 sta et ai cin fenc=utf-8 ff=unix: 172 | --------------------------------------------------------------------------------
Unfortunately, your browser is currently unsupported by our web 15 | application. We are sorry for the inconvenience. Please use one of the 16 | supported browsers listed below, or draw the image you want using an 17 | offline tool.
Supported browsers: Opera, Firefox, Safari, and Konqueror.