Skip to content Skip to sidebar Skip to footer

Extract Path From Text Html Canvas

Is there anyway to extract a path from text letter(s) in html 5, then grab (x,y) coordinates along that path so that the letter(s) can be formed by circles along that letter(s) pat

Solution 1:

Pixel scaling

A simple approach to this is to do the following:

  • Use a small font, draw the text using a solid color
  • Iterate all pixels. Any pixel with alpha = 255, store to an array but with x and y scaled with diameter

Now you have a rough array of "balls" representing the text and can be animated. It's not super accurate when it comes to letter spacing but it should do for the given purpose (you could always measure each letter and at separation point increase the end x value using an additional delta value).

A larger font size can improve quality but will also generate more points. A different font type than the generic used in the demo below can also be beneficial for the overall look (experiment!). You can also tweak the alpha threshold value to include pixels that are not entirely solid, but influential.

And finally, different browsers render text differently so you might want to have that in mind too (see above about measuring each letter to add extra space between them).

Demo

snapshot

var ctx = document.querySelector("canvas").getContext("2d"),
    inp = document.querySelector("input"),
    w = ctx.canvas.width,
    h = ctx.canvas.height,
    balls = [];                                     // global ball array

ctx.fillStyle = "rgb(0, 154, 253)";                 // fill must be a solid colorgenerate(inp.value)                                 // init default text
inp.onkeyup = function() {generate(this.value)};    // get some text to demofunctiongenerate(txt) {
  var i, radius = 5,                                // ball radius
      data32;                                       // we'll use uint32 for speed
  
  balls = [];                                       // clear ball array
  ctx.clearRect(0, 0, w, h);                        // clear canvas so we can
  ctx.fillText(txt.toUpperCase(), 0, 10);           // draw the text (default 10px)// get a Uint32 representation of the bitmap:
  data32 = newUint32Array(ctx.getImageData(0, 0, w, h).data.buffer);
  
  // loop through each pixel. We will only store the ones with alpha = 255for(i = 0; i < data32.length; i++) {
    if (data32[i] & 0xff000000) {             // check alpha mask
      balls.push({                            // add new ball if a solid pixelx: (i % w) * radius * 2 + radius,     // use position and radius toy: ((i / w)|0) * radius * 2 + radius, //  pre-calc final position and sizeradius: radius,
        a: (Math.random() * 250)|0// just to demo animation capability
      });
    }
  }
  // return array - here we'll animate it directly to show the resulting objects:
}

(functionanimate() {
  ctx.clearRect(0, 0, w, h);
  ctx.beginPath();
  for(var i = 0, ball; ball = balls[i]; i++) {
    var dx = Math.sin(ball.a * 0.2) + ball.radius,   // do something funky
        dy = Math.cos(ball.a++ * 0.2) + ball.radius;
    ctx.moveTo(ball.x + ball.radius + dx, ball.y + dy);
    ctx.arc(ball.x + dx, ball.y + dy, ball.radius, 0, 6.28);
    ctx.closePath();
  }
  ctx.fill();
  requestAnimationFrame(animate);
})();
body {font:bold 16px sans-serif}
<label>Type some text: <inputvalue="PIXELS"></label><br><canvaswidth=1024></canvas>

Solution 2:

This is a hard task to do manually by visually placing circles along the path of a letter.

It's even harder to automatically (automagically!) to without human intervention.

Here's how to automatically arrange circles to form letters.

The answer is in 2 parts...

  1. Finding the "letterform",

  2. Creating circles to fill and outline the letterform.

1. The hard part

Frederik De Bleser has coded a nice library called opentype.js that takes a .ttf font file and parses out any specified character's glyph outline using quadratic curves on a canvas: https://github.com/nodebox/opentype.js

2. The only slightly less hard part

For each letter:

  • Find "many" points on each quadratic curve. Here's the algorithm to calculate an [x,y] on the curve at an interval T. T will range from 0.00 at the start of the curve to 1.00 at the end of the curve. T will not produce evenly spaced [x,y]'s along the curve so you will need to oversample (So "many" might mean 1000 values of T between 0.00 and 1.00).

    functiongetQuadraticBezierXYatT(startPt,controlPt,endPt,T) {
        var x = Math.pow(1-T,2) * startPt.x + 2 * (1-T) * T * controlPt.x + Math.pow(T,2) * endPt.x; 
        var y = Math.pow(1-T,2) * startPt.y + 2 * (1-T) * T * controlPt.y + Math.pow(T,2) * endPt.y; 
        return( {x:x,y:y} );
    }
    
  • Find the angle that is tangent to the curve's angle at those points. (Basically calculate what would be a right angle to the curve). You can do that with the next derivative of the quadratic formula:

    functionquadraticBezierTangentAngle(t, p0, p2, p1) {
        var tt = 1 - t;
        var dx = (tt * p1.x + t * p2.x) - (tt * p0.x + t * p1.x);
        var dy = (tt * p1.y + t * p2.y) - (tt * p0.y + t * p1.y);
        returnMath.tan(Math.atan2(dy,dx));
    }
    
  • Starting at the beginning of the curve, calculate each distance from the current [x,y] to the next [x,y]. You can do this with the Pythagorean Theorem:

    var dx=nextX-currentX;
    var dy=nextY-currentY;
    var distance=Math.sqrt(dx*dx+dy*dy);
    
  • De-duplicate the array so that all the remaining [x,y] elements are 1px distant from the previous [x,y] element. You can do this by filling a second array with values from the first where parseInt( nextInOriginalArray - lastDistanceInNewArray)==1;

  • Decide on a radius for your circles that will make up each letter. This is actually harder than it might seem. For "blocky" fonts, you can draw the letter "I" on the canvas. Then fetch all pixles using getImageData. Calculate the width of the "I"'s vertical stroke by searching for the count of opaque pixels running horizontally at the vertical middle of the letter. For blocky fonts, var radius = horizontalOpaquePixelCount/2;. For fonts with variable width strokes, you'll have to be inventive. Maybe var radius = horizontalOpaquePixelCount/3; or var radius = horizontalOpaquePixelCount/4;.

  • Iterate through the points array and define a new circle every radius*2 pixels. You calculate the center point for each circle using the tangent angle and trigonometry like this:

    var centerX = curvePointX + radius*Math.cos(tangentAngle);
    var centerY = curvePointY + radius*Math.sin(tangentAngle);
    
  • While creating circles, at some point the letter's curves will turn back upon themselves, so you must check each new circle you create to be sure it won't overlap an existing circle. You can calculate whether a new circle will intersect each existing circle like this:

    var dx = newCircleCenterX - existingCircleCenterX;
    var dy = newCircleCenterY - existingCircleCenterY;
    var distance=Math.sqrt(dx*dx+dy*dy);
    var circlesAreIntersecting=(distance<=newCircleRadius+existingCircleRadius);
    

Fine tuning: Near some endpoints points in the letter's path, you will find that a next full radius circle will spill out of the letterform. If that occurs you could shrink the radius of some circles to fit the letterform. If you want purely a fixed radius for your circles then you can recalculate the fixed radius of all circles based on the average radii of all circles--including the ones you had to "shrink" to fit the letterform.

For example. This is the letter "L formed by 15 circles.

enter image description here

But the 2 red circles fall out of its letterform. You could (1) shrink the red circles to fit inside the letterform or (2) recalculate a new fixed circle radii based on the average radii that fits the letterform:

var total=0;
total += greenRadii * 13;
total += verticalRedRadiusResizedToFitInsideLetterform;
total += horizontalRedRadiusResizedToFitInsideLetterform;
var newRadius = total / 15;

You can calculate the length of the red radius that will fit the letterform by calculating the intersection of 2 lines: (1) the line segment formed by connecting the last green circles center and the red circles center, (2) the line formed perpendicularly from the last point on the curve. Here's an algorithm to calculate the intersection point of 2 lines:

// Get interseting point of 2 line segments (if any)// Attribution: http://paulbourke.net/geometry/pointlineplane/functionline2lineIntersection(p0,p1,p2,p3) {

    var unknownA = (p3.x-p2.x) * (p0.y-p2.y) - (p3.y-p2.y) * (p0.x-p2.x);
    var unknownB = (p1.x-p0.x) * (p0.y-p2.y) - (p1.y-p0.y) * (p0.x-p2.x);
    var denominator  = (p3.y-p2.y) * (p1.x-p0.x) - (p3.x-p2.x) * (p1.y-p0.y);        

    // Test if Coincident// If the denominator and numerator for the ua and ub are 0//    then the two lines are coincident.    if(unknownA==0 && unknownB==0 && denominator==0){return(null);}

    // Test if Parallel // If the denominator for the equations for ua and ub is 0//     then the two lines are parallel. if (denominator == 0) returnnull;

    // If the intersection of line segments is required // then it is only necessary to test if ua and ub lie between 0 and 1.// Whichever one lies within that range then the corresponding// line segment contains the intersection point. // If both lie within the range of 0 to 1 then // the intersection point is within both line segments. 
    unknownA /= denominator;
    unknownB /= denominator;

    var isIntersecting=(unknownA>=0 && unknownA<=1 && unknownB>=0 && unknownB<=1)

    if(!isIntersecting){return(null);}

    return({
        x: p0.x + unknownA * (p1.x-p0.x),
        y: p0.y + unknownA * (p1.y-p0.y)
    });
}

Post a Comment for "Extract Path From Text Html Canvas"