2012-10-03 160 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畫布文本交點(的