1

Тема: HTML5, Canvas. Малювання "пензлем" (тонкі кінці ліній)

Хай, не було чим зайнятись, та й надумав зробити можливість малювати так, аби кінці ліній утоншувались.
Ідея проста:
1. Коли кляцаємо на полотно вперше, то запам'ятовуємо його вміст
2. Коли рухаємо мишку (малюємо), то записуємо координати в масив, і малюємо лінію з однорідною товщиною
3. Коли закінчуємо малювати, то копіюємо на полотно те, що запам'ятали в пункті 1, після чого перемальовуємо лінію, координати котрої заносили в масив, але тепер модифікуємо товщину.

Як модифікувати товщину - то вже як хочете, так і робіть. Я спершу спробував лінійно змінювати товщину від максимально можливої до 0, але потім використав косинус, аби товщина починала зменшуватись ближче до кінця.
Осьо код

html
<canvas id="myCanvas" width="200" height="200"></canvas>
<button id="clear">Clear</button>
css
body {
  background: gray;
  margin: 0;
}
canvas {
  border: 1px solid black;
}
js
const canvas = document.getElementById('myCanvas');
const clear = document.getElementById('clear');
const ctx = canvas.getContext('2d');

let lastCanvas;

clear.addEventListener('click', () => {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
});

let down = false;
let lastPoints = [];
ctx.lineCap = 'round';

canvas.addEventListener('mousedown', ({offsetX: x, offsetY: y}) => {
    lastPoints.push({x, y});
  ctx.lineWidth = 5;
  lastCanvas = ctx.getImageData(0, 0, canvas.width, canvas.height);
  down = true;
  ctx.beginPath();
  ctx.moveTo(x, y);
});

canvas.addEventListener('mouseup', (event) => {
    ctx.putImageData(lastCanvas, 0, 0);
  redraw();
  lastPoints = [];
  down = false;
});

canvas.addEventListener('mousemove', ({offsetX: x, offsetY: y}) => {
    if (!down) return;
  lastPoints.push({x, y});
    ctx.lineTo(x, y);
  ctx.stroke();
});

function redraw() {
    if (lastPoints.length === 0) return;
    const length = lastPoints.length;
  const {x, y} = lastPoints[0];
  ctx.beginPath();
  ctx.moveTo(x, y);
  for (let i = 0; i < length; i++) {
      const {x, y} = lastPoints[i];
    ctx.lineWidth =  easyOut(1-(i / length )) * 5;
    console.log(`x`, i / length, `easyOut`, easyOut((i / length )));
      ctx.lineTo(x, y);
    ctx.stroke();
  }
}

function easyOut(x) { 
   return -Math.cos(x*Math.PI*0.5+Math.PI*0.5);
}

https://jsfiddle.net/pts6e990/1/

Тепер хочу зробити, аби товщина кінців залежала від швидкості малювання. Якщо малюємо повільно, то товщина максимальна, а якщо швидко, то кінець тоншає.

Подякували: sensei, /KIT\, ostap34PHP, P.Y., LoganRoss, leofun016

2

Re: HTML5, Canvas. Малювання "пензлем" (тонкі кінці ліній)

Прошу допомоги!

Зробив заокруглені кінці для кожного відрізка, і ширина обраховується на основі відстані між точками.
Зараз малюється прямокутник, обидва кінці котрого закінчуються на коло.
Але воно малює якусь серделю. А я хочу, аби воно плавно змінювало товщину в залежності від відстані між точками.

https://jsfiddle.net/pts6e990/37/

HTML і CSS ті самі

js
const canvas = document.getElementById('myCanvas');
const clear = document.getElementById('clear');
const ctx = canvas.getContext('2d');

let lastCanvas;

clear.addEventListener('click', () => {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
});

let down = false;
let lastPoints = [];
ctx.lineCap = 'round';

canvas.addEventListener('mousedown', ({offsetX: x, offsetY: y}) => {
    lastPoints.push({x, y});
  ctx.lineWidth = 5;
  ctx.shadowColor = 'transparent';
  lastCanvas = ctx.getImageData(0, 0, canvas.width, canvas.height);
  down = true;
  ctx.beginPath();
  ctx.moveTo(x, y);
});

canvas.addEventListener('mouseup', (event) => {
    ctx.putImageData(lastCanvas, 0, 0);
  redraw();
  lastPoints = [];
  down = false;
});

canvas.addEventListener('mousemove', ({offsetX: x, offsetY: y}) => {
    if (!down) return;
  lastPoints.push({x, y});
    ctx.lineTo(x, y);
  ctx.stroke();
});

function redraw() {
    if (lastPoints.length === 0) return;
    const length = lastPoints.length;
  const {x, y} = lastPoints[0];
  ctx.beginPath();
  ctx.moveTo(x, y);
  let oldX = x, oldY = y;
  
  for (let i = 0; i < length-1; i++) {
      const {x, y} = lastPoints[i];
    const {x: nX, y: nY} = lastPoints[i+1];
    if (oldX && (x !== oldX || y !== oldY)) {
      ctx.beginPath();
        const dis = Math.sqrt((x-oldX)*(x-oldX) + (y-oldY)*(y-oldY));
      const nDis = Math.sqrt((nX-x)*(nX-x) + (nY-y)*(nY-y));
      
      console.log(`dis`, dis, `nDis`, nDis, `nX`, nX);
      
      ctx.shadowColor = 'rgba(0, 0, 0, 0.07)';
      ctx.shadowBlur = dis;
      ctx.lineWidth =  1;
          
      const normX = (x - oldX)/dis;
      const normY = (y - oldY)/dis;

      const xP = -normY;
      const yP = normX;

      const x1 = dis*xP + oldX;
      const y1 = dis*yP + oldY;

      const x2 = -dis*xP + oldX;
      const y2 = -dis*yP + oldY;
      
      const x3 = nDis*xP + x;
      const y3 = nDis*yP + y;
      
      const x4 = -nDis*xP + x;
      const y4 = -nDis*yP + y;

      ctx.lineTo(x1, y1);
      ctx.arc(oldX, oldY, dis, 0, Math.PI*2);
      ctx.lineTo(x2, y2);
      ctx.lineTo(x4, y4);
      ctx.arc(x, y, nDis, 0, Math.PI*2);
      ctx.lineTo(x3, y3);
      ctx.lineTo(x1, y1);
      ctx.fill();
    } 
    
        oldX = x;
      oldY = y;

  }
}

function easyOut(x) { 
   return -Math.cos(x*Math.PI*0.5+Math.PI*0.5);
}

3

Re: HTML5, Canvas. Малювання "пензлем" (тонкі кінці ліній)

FakiNyan написав:

Прошу допомоги!

Зробив заокруглені кінці для кожного відрізка, і ширина обраховується на основі відстані між точками.
Зараз малюється прямокутник, обидва кінці котрого закінчуються на коло.
Але воно малює якусь серделю. А я хочу, аби воно плавно змінювало товщину в залежності від відстані між точками.

https://jsfiddle.net/pts6e990/37/

HTML і CSS ті самі

js
const canvas = document.getElementById('myCanvas');
const clear = document.getElementById('clear');
const ctx = canvas.getContext('2d');

let lastCanvas;

clear.addEventListener('click', () => {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
});

let down = false;
let lastPoints = [];
ctx.lineCap = 'round';

canvas.addEventListener('mousedown', ({offsetX: x, offsetY: y}) => {
    lastPoints.push({x, y});
  ctx.lineWidth = 5;
  ctx.shadowColor = 'transparent';
  lastCanvas = ctx.getImageData(0, 0, canvas.width, canvas.height);
  down = true;
  ctx.beginPath();
  ctx.moveTo(x, y);
});

canvas.addEventListener('mouseup', (event) => {
    ctx.putImageData(lastCanvas, 0, 0);
  redraw();
  lastPoints = [];
  down = false;
});

canvas.addEventListener('mousemove', ({offsetX: x, offsetY: y}) => {
    if (!down) return;
  lastPoints.push({x, y});
    ctx.lineTo(x, y);
  ctx.stroke();
});

function redraw() {
    if (lastPoints.length === 0) return;
    const length = lastPoints.length;
  const {x, y} = lastPoints[0];
  ctx.beginPath();
  ctx.moveTo(x, y);
  let oldX = x, oldY = y;
  
  for (let i = 0; i < length-1; i++) {
      const {x, y} = lastPoints[i];
    const {x: nX, y: nY} = lastPoints[i+1];
    if (oldX && (x !== oldX || y !== oldY)) {
      ctx.beginPath();
        const dis = Math.sqrt((x-oldX)*(x-oldX) + (y-oldY)*(y-oldY));
      const nDis = Math.sqrt((nX-x)*(nX-x) + (nY-y)*(nY-y));
      
      console.log(`dis`, dis, `nDis`, nDis, `nX`, nX);
      
      ctx.shadowColor = 'rgba(0, 0, 0, 0.07)';
      ctx.shadowBlur = dis;
      ctx.lineWidth =  1;
          
      const normX = (x - oldX)/dis;
      const normY = (y - oldY)/dis;

      const xP = -normY;
      const yP = normX;

      const x1 = dis*xP + oldX;
      const y1 = dis*yP + oldY;

      const x2 = -dis*xP + oldX;
      const y2 = -dis*yP + oldY;
      
      const x3 = nDis*xP + x;
      const y3 = nDis*yP + y;
      
      const x4 = -nDis*xP + x;
      const y4 = -nDis*yP + y;

      ctx.lineTo(x1, y1);
      ctx.arc(oldX, oldY, dis, 0, Math.PI*2);
      ctx.lineTo(x2, y2);
      ctx.lineTo(x4, y4);
      ctx.arc(x, y, nDis, 0, Math.PI*2);
      ctx.lineTo(x3, y3);
      ctx.lineTo(x1, y1);
      ctx.fill();
    } 
    
        oldX = x;
      oldY = y;

  }
}

function easyOut(x) { 
   return -Math.cos(x*Math.PI*0.5+Math.PI*0.5);
}

Звичайно цікаво у Вас наразі вийшло. Я ніби повернувся в глубоке дитинство, коли користувались чорнильними ручками, і та ручка ніби норм пише а потім як псикне на пів сторінки :)

Подякували: koala, LoganRoss, leofun013

4

Re: HTML5, Canvas. Малювання "пензлем" (тонкі кінці ліній)

FakiNyan написав:

Прошу допомоги!

Зробив заокруглені кінці для кожного відрізка, і ширина обраховується на основі відстані між точками.
Зараз малюється прямокутник, обидва кінці котрого закінчуються на коло.
Але воно малює якусь серделю. А я хочу, аби воно плавно змінювало товщину в залежності від відстані між точками.

Визначіть, що таке "плавна зміна товщини в залежності від відстані".
Якщо ви маєте дві точки (0,0) і (0,100), яка має бути лінія? А якщо 3 - (0,0), (0,50) і (0,100), яка тоді? Візуально і те, і інше - пряма, але відстань у першому випадку 100, а в другому - дві по 50.

Подякували: leofun011

5

Re: HTML5, Canvas. Малювання "пензлем" (тонкі кінці ліній)

"На основі", "в залежності" - це загальна фраза. От я хочу зрозуміти: якщо я проводжу коротеньку лінію, вона має бути вузькою, якщо довгу - широкою, а якщо у мене дві короткі, які складають одну довгу - вони розглядаються саме як дві короткі, хоча візуально це не так?

6

Re: HTML5, Canvas. Малювання "пензлем" (тонкі кінці ліній)

я так бачу, що мені тре розглядати 3 точки одночасно

Подякували: ostap34PHP1

7

Re: HTML5, Canvas. Малювання "пензлем" (тонкі кінці ліній)

коротше, той алгоритм працює погано якось, бо лінія ніфіга не плавна.
https://jsfiddle.net/pts6e990/76/

і питання не в різкій зміні товщини, а в тому, що кожний відрізок різко закінчується і переходить в інший відрізок, без згладжування переходу

8

Re: HTML5, Canvas. Малювання "пензлем" (тонкі кінці ліній)

А якщо так
• для кожного відрізка визначаємо його товщину, виходячи з довжини l
• спільним точкам відрізків призначаємо товщини, рівні напівсумі товщин суміжних відрізків
• вибираємо певне k таке, що 0 < k ≤ 0.5
• на ділянці від lk до l(1-k) малюємо відрізок його товщиною
• на ділянках від 0 до lk і від l(1-k) до l міняємо товщину від тієї, яка призначена відповідній спільній точці, до товщини, призначеній відрізку.

Як приклад, якщо відрізок один (товщини кінцевих точок 0), а k = 0.5 (нема ділянки постійної товщини), то вийде ромб.

9

Re: HTML5, Canvas. Малювання "пензлем" (тонкі кінці ліній)

ReAl написав:

А якщо так
• для кожного відрізка визначаємо його товщину, виходячи з довжини l
• спільним точкам відрізків призначаємо товщини, рівні напівсумі товщин суміжних відрізків
• вибираємо певне k таке, що 0 < k ≤ 0.5
• на ділянці від lk до l(1-k) малюємо відрізок його товщиною
• на ділянках від 0 до lk і від l(1-k) до l міняємо товщину від тієї, яка призначена відповідній спільній точці, до товщини, призначеній відрізку.

Як приклад, якщо відрізок один (товщини кінцевих точок 0), а k = 0.5 (нема ділянки постійної товщини), то вийде ромб.

занадто складно  %)

10 Востаннє редагувалося FakiNyan (19.06.2018 16:04:01)

Re: HTML5, Canvas. Малювання "пензлем" (тонкі кінці ліній)

чуєте, а щоб зробити зміну товщини серделі плавною, то це ж мені треба спочатку обрахувати найбільшу і найменшу товщину, і далі щось зробити з цим, і на основі результату змінювати товщину?
я тут спробував додати згладжування косинусом, але різниця не дуже помітна
https://jsfiddle.net/pts6e990/120/

а ось тут я зробив крок більшим, і між кроками малюються додаткові кругляжки, і зміна товщини між кругляжками має бути плавною, але ніфіга
https://jsfiddle.net/pts6e990/121/

11 Востаннє редагувалося FakiNyan (19.06.2018 16:10:47)

Re: HTML5, Canvas. Малювання "пензлем" (тонкі кінці ліній)

ось тут я малюю графік зміни радіусу, знизу
і воно гладеньке, але зверху тупо ковбаса
https://jsfiddle.net/pts6e990/126/

12

Re: HTML5, Canvas. Малювання "пензлем" (тонкі кінці ліній)

я так розумію, що нові кругляшки, радіус котрих великий, зачіпають старі кругляшки, і тому воно виглядає ковбасячно

13 Востаннє редагувалося koala (19.06.2018 17:03:04)

Re: HTML5, Canvas. Малювання "пензлем" (тонкі кінці ліній)

Вибачте, але я вперто не розумію, чого саме ви хочете домогтися. Можете намалювати в Paint, як воно має виглядати, на кшталт
https://cdn.pbrd.co/images/HqD5gAF.png
Тільки коментарів трохи більше - там, чи має всередині бути стала ширина, чи з країв воно має бути гострим, чи мають бути всередині звуження і т.д.

14

Re: HTML5, Canvas. Малювання "пензлем" (тонкі кінці ліній)

так, я вирішив малювати його паличками, але все одно не дуже гладко якось, я ще додав більшу відстань між кроками
https://jsfiddle.net/pts6e990/141/

я хочу, аби товщина ковбаси змінювалась в залежності від відстані між точками, якось тако
https://cdn.discordapp.com/attachments/333936584481177600/458649281478393857/unknown.png

15

Re: HTML5, Canvas. Малювання "пензлем" (тонкі кінці ліній)

По-перше, ви маєте суттєву проблему. Коли людина малює мишкою, вона не ставить точки, вона проводить криву лінію. Комп'ютер, щоб обробити цю лінію, має виділити на ній якісь точки, але це - лише комп'ютерне наближення; якщо ви хочете відштовхуватися саме від точок, ви маєте дати користувачу можливість саме ставити точки, а не вести лінію.
По-друге, з малюнку нічого не зрозуміло. Маємо 7 ділянок лінії. Вам потрібно, щоб ширина залежала від довжини, я так розумію, монотонно (тобто довші фрагменти мають бути ширшими за вужчі); але вже перші дві ділянки це правило порушують - друга явно ширша за першу, так само, як і 4-а ширша за 5-у. Якщо це просто невдале представлення, то проблема, як я розумію, переважно у стиковці ділянок: ширина лінії в середині ділянки має залежати від її довжини, а на краях бути середньою між ширинами поточної і наступної. Все так?

Подякували: leofun011

16 Востаннє редагувалося FakiNyan (19.06.2018 20:00:24)

Re: HTML5, Canvas. Малювання "пензлем" (тонкі кінці ліній)

не зовсім.
точки ставляться автоматично, коли людина рухає мишкою під час натисненою кнопки миші
Для отримання ширини - нам треба дві точки.
Якщо є A, B, C (три точки)
то спочатку ми рахуємо довжину між AB, а потім BC, після чого малюємо лінію від A до B, при цьому в точці A ширина має відповідати відстані AB, а ширина в точці B має відповідати відстані BC

17

Re: HTML5, Canvas. Малювання "пензлем" (тонкі кінці ліній)

FakiNyan написав:

не зовсім.
точки ставляться автоматично, коли людина рухає мишкою під час натисненою кнопки миші

Ви не зрозуміли, що я сказав. Точки ставляться автоматично, бо це є наближення того, що зробила людина. Але людина не мала на увазі ставити саме ці точки. Якщо я проведу мишкою по прямій, ви можете отримати картини
.                                                           .
або
.    .                                                      .
або
.                                          .                .
або
.            .          .            .                      .
Всі ці картини - коректні апроксимації мого руху; але малюнки по них за вашими правилами будуть зовсім різні. Ви певні, що хочете, щоб на один рух могли бути різні результати?

FakiNyan написав:

Для отримання ширини - нам треба дві точки.
Якщо є A, B, C (три точки)
то спочатку ми рахуємо довжину між AB, а потім BC, після чого малюємо лінію від A до B, при цьому в точці A ширина має відповідати відстані AB, а ширина в точці B має відповідати відстані BC

Це якось дивно. Тобто якщо я поставлю три точки рухом в один бік, а потім ці ж точки рухом в інший бік, то малюнки будуть відрізнятися? І вам саме так і треба?

Подякували: leofun011

18

Re: HTML5, Canvas. Малювання "пензлем" (тонкі кінці ліній)

koala написав:
FakiNyan написав:

не зовсім.
точки ставляться автоматично, коли людина рухає мишкою під час натисненою кнопки миші

Ви не зрозуміли, що я сказав. Точки ставляться автоматично, бо це є наближення того, що зробила людина. Але людина не мала на увазі ставити саме ці точки. Якщо я проведу мишкою по прямій, ви можете отримати картини
.                                                           .
або
.    .                                                      .
або
.                                          .                .
або
.            .          .            .                      .
Всі ці картини - коректні апроксимації мого руху; але малюнки по них за вашими правилами будуть зовсім різні. Ви певні, що хочете, щоб на один рух могли бути різні результати?

FakiNyan написав:

Для отримання ширини - нам треба дві точки.
Якщо є A, B, C (три точки)
то спочатку ми рахуємо довжину між AB, а потім BC, після чого малюємо лінію від A до B, при цьому в точці A ширина має відповідати відстані AB, а ширина в точці B має відповідати відстані BC

Це якось дивно. Тобто якщо я поставлю три точки рухом в один бік, а потім ці ж точки рухом в інший бік, то малюнки будуть відрізнятися? І вам саме так і треба?

я вже не знаю
я просто хтів симулювати "натискання", типу, чим сильніше натискаєте, тим жирніша лінія, але в мишки немає такого параметру, тому замість нього я хотів використати "швидкість" малювання, чим швидше - тим тонша лінія, а швидкість мала б бути представлена відстанню між точками, тому що точки ставляться автоматично, через деякий час.

19

Re: HTML5, Canvas. Малювання "пензлем" (тонкі кінці ліній)

FakiNyan написав:

... я хотів використати "швидкість" малювання, чим швидше - тим тонша лінія, а швидкість мала б бути представлена відстанню між точками, тому що точки ставляться автоматично, через деякий час.

Швидкість = відстань / час.
Я не знайшов в вашому коді операцій з часом. Ви не враховуєте те, що в різних користувачів позиція вказівника мишки оновлюється з різною частотою.
Уявіть:
Користувач_1 має мишку з частотою оновлення 8 с-1;
Користувач_2 має мишку з частотою оновлення 32 с-1;
Два користувачі проводять мишкою лінію однакової форми з однаковою швидкістю, але за рахунок різних частот користувачі отримали зосім різні результати.

Потрібно засікати і позицію і час.

Подякували: koala, FakiNyan2

20 Востаннє редагувалося koala (20.06.2018 23:06:30)

Re: HTML5, Canvas. Малювання "пензлем" (тонкі кінці ліній)

leofun, я далеко не певен, що апаратні відмінності мають аж таке значення в браузерах. Треба тестувати, бо браузери можуть приховувати апаратні події.
Хоча в цілому, звісно, час треба враховувати.