2012-10-03 103 views
8

有没有办法从html 5中的文本字符中提取路径,然后沿该路径抓取(x,y)坐标,以便字母可以沿着圆形成那封信的路径?从文本中提取路径html canvas

我想采用x,y坐标并在它们的位置应用一个形状,以便它类似于“像素化”格式的文本字符串,然后是一些动画效果。

任何有关在画布上沿角色路径获取某种x,y坐标的建议都会很棒。

编辑:我基本上是试图自动生成坐标做类似这样:http://www.html5canvastutorials.com/labs/html5-canvas-google-bouncing-balls/

+0

可能重复http://stackoverflow.com/questions/19954058/html5-canvas-text-十字路口) –

回答

8

这是一个艰巨的任务,做手工通过视觉将圆沿信道。

在没有人为干预的情况下自动更新(automagically!)就更加困难了。

以下是如何自动排列圆形以形成字母的方法。

答案是分为两部分...

  1. 寻找 “信纸”,

  2. 创建圈子填充和轮廓的信纸。

1.困难的部分

弗雷德里克·德Bleser已编码的一个很好的库调用opentype.js,需要一个.TTF字体文件,并使用画布上二次曲线解析出任何指定字符的字形轮廓: https://github.com/nodebox/opentype.js

2.仅略小于硬部

对于每个字母:

  • 在每条二次曲线上找到“许多”点。下面是在间隔T处计算曲线上[x,y]的算法.T的范围从曲线起点处的0.00到曲线末端的1.00。 T不会在曲线上产生均匀间隔[x,y],所以您需要过采样(因此“许多”可能意味着T在0.00和1.00之间的1000个值)。

    function getQuadraticBezierXYatT(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}); 
    } 
    
  • 在这些点上找到与曲线角度相切的角度。 (基本上计算什么是曲线的直角)。可以做到这一点与二次公式的下一个衍生物:

    function quadraticBezierTangentAngle(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); 
        return Math.tan(Math.atan2(dy,dx)); 
    } 
    
  • 在曲线的起点开始,计算从当前的每个距离[X,Y]到下一[X,Y]。你可以用勾股定理做到这一点:

    var dx=nextX-currentX; 
    var dy=nextY-currentY; 
    var distance=Math.sqrt(dx*dx+dy*dy); 
    
  • 去重复阵列,使所有的剩余[X,Y]元素是1px的远离以前的[X,Y]的元素。您可以通过填充第二个数组来填充第二个数组,其中第一个数字为parseInt(nextInOriginalArray - lastDistanceInNewArray)==1;

  • 决定组成每个字母的圆圈的半径。这实际上比看起来更难。对于“块状”字体,您可以在画布上绘制字母“I”。然后使用getImageData获取所有像素。通过在字母的垂直中间搜索水平运行的不透明像素的计数来计算“I”竖直笔划的宽度。对于块状字体,var radius = horizontalOpaquePixelCount/2;。对于可变宽度笔画的字体,您必须具有创造性。也许var radius = horizontalOpaquePixelCount/3;var radius = horizontalOpaquePixelCount/4;

  • 遍历点数组并定义一个新的圆,每个radius*2像素。你计算使用切线角度和三角这样每个圆圈的中心点:

    var centerX = curvePointX + radius*Math.cos(tangentAngle); 
    var centerY = curvePointY + radius*Math.sin(tangentAngle); 
    
  • 在创建圈子,在某些时候这封信的曲线会回头在他们自己,所以你必须检查每创建一个新的圈子以确保它不会覆盖现有的圈子。您可以计算一个新的圆是否会相交现有的圈子是这样的:

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

微调:近在信的路径一些端点点,你会发现下一个完整的半径圈将洒出来的字母表。如果发生这种情况,可以缩小一些圆的半径以适应字体。如果你只想为你的圆圈固定一个半径,那么你可以根据所有圆圈的平均半径重新计算所有圆圈的固定半径,包括你必须“缩小”以适应字体的圆圈半径。

例如。这是字母“L由15圈形成。

enter image description here

但2个红色圆圈掉下来的信纸的。你可以(1)缩小红色圆圈,以适应信纸或(2)重新计算内基于平均半径的新的固定圆半径适合的信纸:

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

可以计算红色半径,将通过计算2线的交点适合信纸的长度:(1)线段由连接最后一个绿色圆圈中心和红色圆圈中心形成,(2)垂直形成的线条m是曲线上的最后一个点。这里有一个算法来计算的2线的交点:

// Get interseting point of 2 line segments (if any) 
// Attribution: http://paulbourke.net/geometry/pointlineplane/ 
function line2lineIntersection(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) return null; 

    // 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) 
    }); 
} 
+0

你知道你描述的算法的任何实现吗? – seltzlab

+0

它不仅仅是一种应用程序,而是一种算法 - 一系列算法。我有约75%的这个应用程序。我没有时间去完成它。 :-) – markE

10

像素缩放

的简单方法,这是做到以下几点:

  • 使用小字体,绘制文本使用纯色
  • 迭代所有像素。任何alpha = 255的像素都可以存储到一个数组中,但是x和y的直径缩放

现在您有一个粗糙的“球”数组,代表文本并且可以进行动画处理。关于字母间距,它不是很准确,但它应该用于给定的目的(您可以始终测量每个字母,并在分隔点使用额外的增量值增加末端x值)。

较大的字体大小可以提高质量,但也会生成更多的点。与以下演示中使用的通用字体不同的字体类型也可能对整体外观(实验!)有所帮助。您还可以调整Alpha阈值以包含不完全稳定但影响力大的像素。

最后,不同的浏览器呈现不同的文本,所以你可能也想记住这一点(见上面有关测量每个字母以在它们之间增加额外空间)。

演示

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 color 
 
generate(inp.value)         // init default text 
 
inp.onkeyup = function() {generate(this.value)}; // get some text to demo 
 

 
function generate(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 = new Uint32Array(ctx.getImageData(0, 0, w, h).data.buffer); 
 
    
 
    // loop through each pixel. We will only store the ones with alpha = 255 
 
    for(i = 0; i < data32.length; i++) { 
 
    if (data32[i] & 0xff000000) {    // check alpha mask 
 
     balls.push({       // add new ball if a solid pixel 
 
     x: (i % w) * radius * 2 + radius,  // use position and radius to 
 
     y: ((i/w)|0) * radius * 2 + radius, // pre-calc final position and size 
 
     radius: radius, 
 
     a: (Math.random() * 250)|0   // just to demo animation capability 
 
     }); 
 
    } 
 
    } 
 
    // return array - here we'll animate it directly to show the resulting objects: 
 
} 
 

 
(function animate() { 
 
    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: <input value="PIXELS"></label><br> 
 
<canvas width=1024></canvas>

[HTML5画布文本交点(的