2012-10-03 38 views
8

¿Hay alguna forma de extraer una ruta de las letras de texto en html 5, luego agarrar (x, y) las coordenadas a lo largo de esa ruta para que la (s) letra (s) se puedan formar por círculos a lo largo esa (s) ruta (s) de letrasextracto de la ruta del lienzo html de texto

Me gustaría tomar las coordenadas x, y y aplicar una forma en su ubicación para que se asemeje a la cadena de texto en un formato "pixelado" seguido de algunos efectos de animación.

Cualquier consejo sobre cómo obtener algún tipo de coordenadas x, y a lo largo de una ruta de caracteres en un lienzo sería genial.

edición: estoy esencialmente tratando de auto generar las coordenadas para hacer algo similar a esto: http://www.html5canvastutorials.com/labs/html5-canvas-google-bouncing-balls/

+0

posible duplicado de [intersecciones de texto lona Html5] (http://stackoverflow.com/questions/19954058/html5-canvas-text- intersecciones) –

Respuesta

8

Ésta es una tarea difícil de hacer manualmente mediante la colocación visual círculos a lo largo del camino de la carta.

Es aún más difícil de forma automática (automágicamente!) Que sin la intervención humana.

Aquí se explica cómo organizar automáticamente círculos para formar letras.

La respuesta está en 2 partes ...

  1. Encontrar el "letterform",

  2. Creación de círculos de relleno y contorno del letterform.

1. La parte difícil

Frederik De Bleser ha codificado una bonita biblioteca llamada opentype.js que toma un archivo de fuentes .ttf y analiza a cabo perfil del glifo de cualquier carácter especificado utilizando curvas cuadráticas sobre un lienzo: https://github.com/nodebox/opentype.js

2. La única parte un poco menos dura

Para cada letra:

  • Encuentra "muchos" puntos en cada curva cuadrática. Aquí está el algoritmo para calcular un [x, y] en la curva en un intervalo T. T va desde 0.00 al comienzo de la curva hasta 1.00 al final de la curva. T no producirá [x, y] espaciados uniformemente a lo largo de la curva, por lo que tendrá que sobremuestrear (por lo que "muchos" podría significar 1000 valores de T entre 0.00 y 1.00).

    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}); 
    } 
    
  • encontrar el ángulo que es tangente a la curva de ángulo en esos puntos. (Básicamente, calcule cuál sería el ángulo recto de la curva). Puede hacerlo con el siguiente derivado de la fórmula cuadrática:

    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)); 
    } 
    
  • Comenzando por el principio de la curva, el cálculo de cada distancia de la corriente [x, y] a la siguiente [x, y].Puede hacer esto con el teorema de Pitágoras:

    var dx=nextX-currentX; 
    var dy=nextY-currentY; 
    var distance=Math.sqrt(dx*dx+dy*dy); 
    
  • De-duplicar la matriz de modo que toda la restante [x, y] elementos son 1px distante del elemento anterior [x, y]. Puede hacerlo rellenando un segundo conjunto con valores del primero donde parseInt(nextInOriginalArray - lastDistanceInNewArray)==1;

  • Decida el radio de los círculos que conformarán cada letra. Esto es realmente más difícil de lo que parece. Para fuentes "en bloque", puede dibujar la letra "I" en el lienzo. A continuación, busque todos los píxeles usando getImageData. Calcule el ancho del trazo vertical de "I" buscando el recuento de píxeles opacos que se ejecutan horizontalmente en el medio vertical de la letra. Para tipos de letra blocky, var radius = horizontalOpaquePixelCount/2;. Para las fuentes con trazos de anchura variable, tendrá que ser inventivo. Tal vez var radius = horizontalOpaquePixelCount/3; o var radius = horizontalOpaquePixelCount/4;.

  • Iterate a través de la matriz de puntos y define un nuevo círculo cada radius*2 píxeles. Se calcula el punto central de cada círculo usando el ángulo tangente y la trigonometría como esto:

    var centerX = curvePointX + radius*Math.cos(tangentAngle); 
    var centerY = curvePointY + radius*Math.sin(tangentAngle); 
    
  • bien la creación de círculos, en algún momento las curvas de la carta se volverán sobre sí mismos, por lo que debe comprobar cada nuevo círculo se crea para estar seguro de que no se superpondrá a un círculo existente. Se puede calcular si un nuevo círculo se cruzará cada círculo existente como esto:

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

Puesta a punto: Cerca algunos puntos puntos finales en el camino de la carta, se encuentra que la siguiente círculo de radio completo se derrame fuera de la forma de letra. Si eso ocurre, podría reducir el radio de algunos círculos para que se ajuste a la forma de la letra. Si solo quiere un radio fijo para sus círculos, puede volver a calcular el radio fijo de todos los círculos en función de los radios promedio de todos los círculos, incluidos los que tenía que "encoger" para ajustarse a la forma de la letra.

Por ejemplo. Esta es la letra "L formado por 15 círculos.

enter image description here

Pero los 2 círculos rojos caen fuera de su letterform. Se podría (1) reducir el tamaño de los círculos rojos para encajar dentro del letterform o (2) volver a calcular un nuevo círculo fijo radios basado en los radios promedio que se adapta a la letterform:

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

se puede calcular la longitud del radio rojo que se ajuste a la letterform mediante el cálculo de la intersección de 2 líneas: (1) el segmento de línea formado conectando el último centro de círculos verdes y el centro de círculos rojos, (2) la línea formada perpendicularmente m el último punto de la curva. Aquí hay un algoritmo para calcular el punto de 2 líneas de intersección:

// 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

¿conoce alguna implementación del algoritmo que describió? – seltzlab

+0

Es más una aplicación que un algoritmo: una serie de algoritmos. Tengo aproximadamente el 75% de esta aplicación. No he tenido tiempo de terminarlo. :-) – markE

10

Pixel expandiendo los

Un enfoque simple para esto es hacer lo siguiente:

  • uso de una pequeña fuente, dibujar el texto utilizando un color sólido
  • Iterar todos los píxeles.Cualquier píxel con alfa = 255, almacenar en una matriz pero con x y y escalar con diámetro

Ahora tiene una serie aproximada de "bolas" que representan el texto y se pueden animar. No es muy preciso cuando se trata de espaciado entre letras, pero debería serlo para el propósito dado (siempre se puede medir cada letra y en el punto de separación aumentar el valor del extremo x usando un valor delta adicional).

Un tamaño de fuente mayor puede mejorar la calidad pero también generará más puntos. Un tipo de fuente diferente al genérico utilizado en la demostración a continuación también puede ser beneficioso para el aspecto general (¡experimento!). También puede ajustar el valor del umbral alfa para incluir píxeles que no son del todo sólidos, pero influyentes.

Y, por último, los diferentes navegadores representan el texto de manera diferente, por lo que es posible que desee tener eso en cuenta también (consulte más arriba sobre la medición de cada letra para agregar espacio extra entre ellos).

Demostración

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>