Extract Path From Text Html Canvas
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
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...
Finding the "letterform",
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. Maybevar radius = horizontalOpaquePixelCount/3;
orvar 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.
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"