Извлечь путь из текста html canvas

В любом случае, чтобы извлечь путь из текстовых букв (ов) в html 5, затем захватите (x, y) координаты вдоль этого пути, чтобы буквы (буквы) могли быть образованы кругами вдоль этого пути (букв)?

Я хотел бы взять координаты x, y и применить форму в своем местоположении так, чтобы она напоминала строку текста в "пикселизованном" формате, за которым следовали некоторые анимационные эффекты.

Любые советы по получению каких-либо координат x, y вдоль пути символов на холсте будут большими.

edit: Я пытаюсь автоматически генерировать координаты, чтобы сделать что-то похожее на это: http://www.html5canvastutorials.com/labs/html5-canvas-google-bouncing-balls/

Ответ 1

Масштабирование пикселей

Простой подход к этому заключается в следующем:

  • Используйте небольшой шрифт, нарисуйте текст с помощью сплошного цвета
  • Итерировать все пиксели. Любой пиксель с альфа = 255, хранится в массиве, но с x и y масштабируется с диаметром

Теперь у вас есть грубый массив "шаров", представляющих текст, и их можно анимировать. Это не очень точно, когда дело доходит до расстояния между буквами, но оно должно выполняться для данной цели (вы всегда можете измерить каждую букву и в точке разделения увеличить значение конца x с помощью дополнительного значения дельта).

Более крупный размер шрифта может улучшить качество, но также будет генерировать больше очков. Другой тип шрифта, чем общий, используемый в демонстрации ниже, также может быть полезен для общего вида (эксперимент!). Вы также можете настроить альфа-пороговое значение, чтобы включить пиксели, которые не являются полностью сплошными, но влиятельными.

И, наконец, разные браузеры визуализируют текст по-разному, чтобы вы могли подумать об этом (см. выше об измерении каждой буквы, чтобы добавить дополнительное пространство между ними). ​​

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

Ответ 2

Это сложная задача сделать вручную, визуально размещая круги по пути буквы.

Еще труднее автоматически (автоматически) без вмешательства человека.

Здесь, как автоматически упорядочить круги для формирования букв.

Ответ состоит из двух частей...

  • Поиск "letterform",

  • Создание кругов для заполнения и наброска формы письма.

1. Тяжелая часть

Frederik De Bleser закодировал красивую библиотеку под названием opentype.js, которая берет файл шрифта .ttf и анализирует любой заданный характер символьного символа с использованием квадратичных кривых на холсте: https://github.com/nodebox/opentype.js

2. Только немного менее жесткая часть

Для каждой буквы:

  • Найдите "много" точек на каждой квадратичной кривой. Здесь алгоритм вычисления [x, y] на кривой с интервалом T. T будет составлять от 0,00 в начале кривой до 1,00 в конце кривой. T не будет производить равномерно разнесенные [x, y] вдоль кривой, поэтому вам нужно будет перевыбрать ( "Многие" могут означать 1000 значений T между 0.00 и 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} );
    }
    
  • Найти угол, который касается угла кривой в этих точках. (В принципе вычислите, что будет прямым углом к ​​кривой). Вы можете сделать это со следующей производной квадратичной формулы:

    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

Но два красных круга выпадают из его буквенного формата. Вы могли (1) сжимать красные круги, чтобы они соответствовали буквой, или (2) пересчитать новые радиусы фиксированной окружности на основе средних радиусов, которые соответствуют буквенной форме:

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

Вы можете рассчитать длину красного радиуса, который будет соответствовать буквенной форме, путем вычисления пересечения двух линий: (1) отрезок линии, образованный путем соединения последнего центра зеленых кругов и центра красных кругов, (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)
    });
}