Поглаживание текста на холсте - PullRequest
4 голосов
/ 17 апреля 2020

Мне попался интересный эффект смешивания setLineDash и strokeText в холсте

ctx = document.querySelector("canvas").getContext("2d")
ctx.font = "110px Arial";
i = speed = 3

function loop() {
  ctx.clearRect(0, 0, 600, 160)
  ctx.beginPath()
  ctx.setLineDash([i, 600]);
  ctx.strokeText("WORLD ふ", 10, 100);
  i += speed
  if (i > 600 || i < 2)
    speed *= -1
}

setInterval(loop, 50)
<canvas id="c" width=600 height=160></canvas>

Как вы можете видеть, W занимает больше времени, чем O в этом фрагменте.
Есть ли свойство для получения номера пикселей (длина) самой длинной строки буквы?

Ответы [ 2 ]

2 голосов
/ 17 апреля 2020

Вы можете рисовать символы каждый закадровый и «подсчитывать» вхождения пикселей (ненулевые значения):

function getAmount (char, { width, height, font, color }) {
  // create temporary offscreen canvas
  const canvas = document.createElement('canvas')
  canvas.width = width
  canvas.height = height

  // draw the character
  const ctx = canvas.getContext("2d")
  ctx.font = font
  ctx.strokeText(char, 0, 90)

  // get the pixels data
  const imageData = ctx.getImageData(0, 0, width, height)
  let sum = 0
  imageData.data.forEach(point => {
    if (point > 0) sum++
  })
  return sum
}

const width = 90
const height = 90
const font = "90px Arial"

getAmount('W', { font, width, height }) // 940
getAmount('O', { font, width, height }) // 660
getAmount('R', { font, width, height }) // 673
getAmount('L', { font, width, height }) // 296
getAmount('D', { font, width, height }) // 613

Вы можете использовать эти значения примерно как увеличение скорости и рисовать каждый символ индивидуально но имейте в виду, что вам придется управлять размещением et c. дополнительно. Также это только обнаруживает любое ненулевое значение. Если вы используете градиенты для своего удара, вам придется обнаружить данные изображения в пределах диапазона градиента.

Редактировать:

Поскольку источник истины не найден, мы можем использовать Еще один трюк:

Найдите число i, которое создает закадровое изображение с таким же количеством пикселей, что и полный штриховой символ.

/**
 * draws a stroked text by given params to a context
 **/

function draw (char, ctx, minValue, maxValue) {
  ctx.clearRect(0, 0, 600, 160)
  ctx.beginPath()
  if (minValue && maxValue) {
    ctx.setLineDash([minValue, maxValue])
  }
  ctx.strokeText(char, 10, 100);
}

/**
 * Returns the amount of pixels for a given character
 */
const offscreenCanvas = document.createElement('canvas')
function getAmount (char, { value, max,  width, height, font }) {
  // draw offscreen, then detect border pixels
  offscreenCanvas.width = width
  offscreenCanvas.height = height

  // draw the character
  const ctx = offscreenCanvas.getContext("2d")
  ctx.font = font
  draw(char, ctx, value, max)

  // get the pixels data
  const imageData = ctx.getImageData(0, 0, width, height)
  let sum = 0
  imageData.data.forEach(point => {
    if (point > 0) sum++
  })

  return sum
}

/**
 * Returns the number of iterations required to complete a character
 **/

function getIterations (char, { font, width, height }) {
  // get amount when word is fully drawn
  const fullAmount = getAmount(char, { value: undefined, max: undefined, width, height, font })

  let iterations = 1
  let amount = 0
  do {
    amount = getAmount(char, { value: iterations, max: 1000, width, height, font })
    iterations++
  } while ((amount - fullAmount < -3) && iterations < 2000);

  return iterations
}

Отсюда мы можем определить i значение параметра setLineDash:

const font = "110px Arial";
const width = 110
const height = 110

const check = char => {
  const amount = getIterations(char, { font, width, height })
  console.log(char, amount)
}


check('W')  // 620
check('O')  // 243
check('R')  // 331
check('L')  // 248
check('D')  // 248
check('ふ') // 185

Используя эти значения, вы можете создать относительный параметр speed, который позволит вам одновременно выполнять штрихи.

Примечание То, что этот подход очень жадный и не оптимизирован по производительности, скорее доказательство концепции.

0 голосов
/ 18 апреля 2020

Вот оптимизированная версия @ Jankapunkt подтверждение концепции:

function letterLen(text, font) {
  const oc = document.createElement('canvas');
  oc.width = oc.height = 200;
  const octx = oc.getContext("2d");
  octx.globalCompositeOperation = 'copy';
  octx.imageSmoothingEnabled = false;
  octx.font = font;

  function pixels(char) {
    octx.strokeText(char, 20, oc.height - 20);
    const id = octx.getImageData(0, 0, oc.width, oc.height)
    return id.data.reduce((a, c) => c ? a + 1 : a, 0)
  }

  function length(char) {
    octx.setLineDash([]);
    const full = pixels(char)
    if (full == 0) return 0
    let max = full
    let min = 0
    do {
      let iter = Math.round((min + max) / 2)
      octx.setLineDash([iter, 800]);
      if (pixels(char) > full - 10)
        max = iter
      else
        min = iter
    } while (min + 1 < max)
    return max
  }

  const strArray = text.split('');
  return strArray.map(s => length(s));
}

ctx = document.getElementById("c").getContext("2d")
ctx.font = "110px Arial";
i = delta = 3
const world = "WORLDふ"
const wlen = letterLen(world, ctx.font)
const minlen = Math.min(...wlen)

function loop() {
  ctx.clearRect(0, 0, 900, 600)
  const letters = world.split('');
  let ix = 10
  for (j = 0; j < letters.length; j++) {
    ctx.beginPath()
    ctx.setLineDash([i * wlen[j] / minlen, 800]);
    ctx.strokeText(letters[j], ix, 120);
    ix += ctx.measureText(letters[j]).width
  }
  i += delta
  if (i > minlen + 10 || i < 2)
    delta *= -1
}

setInterval(loop, 50)
<canvas id="c" width=600 height=160></canvas>

Как мы теперь видим, все буквы fini sh рисуют одновременно

...