HTML Canvas로 bokeh 펜 도구 px-brush를 만들었습니다.

소개



HTML의 Canvas는 편리한 API가 많이 준비되어 있어 간단하게 묘화 툴 등을 자작할 수 있습니다만, 통상의 방법에서는 안티앨리어싱이 강제적으로 걸려 버려, 굳이 픽셀감이 있는 이른바 「쟈기루」선을 쓸 수 없습니다.

StackOverflow에서도 이것이 질문으로 올라가고 있습니다.
h tps : // s t c ゔ ぇ rf ぉ w. 코 m / 쿠에 s Chion s / 195262 / Kan-I-Tr-N-O-F-Anchia Ashin-G-on-An-HTML-kan-s-E-man

부드러운 선만 쓸 수 있습니다.





여기가 일반적으로 Canvas에 구현한 펜의 예입니다.
여기에서는 context.imageSmoothingEnabled 라는 API를 사용하고 있습니다만, 이것은 이미지의 확대 축소로 하려고 하는 것이며, 펜의 구현에는 전혀 효과가 없는 것을 알 수 있습니다.

자기하고 싶다.





Pixilart라는 서비스가 있어서 조금 복고풍 분위기로 그림을 그리는 서비스입니다.
여기에서는 사각형의 펜을 실장하고 있으므로, 묘화시의 좌표로부터 부동 소수점수를 제거하면 확실히 이것으로 가능하죠.
그러나 사각형이 아닌 둥근 펜촉 도구를 구현하고 싶습니다.

스탬프처럼 해 보는 것은 어떨까?





Pixilart에서 배운 것은 펜촉을 스탬프로 만든다. 요컨대 이미지를 마우스의 움직임에 맞추어 연속적으로 묘화하면 되지 않을까 생각했습니다.
그러나, 천천히 움직인 경우는 문제 없습니다만, 신속하게 움직이면 스탬프의 틈이 생겨 버려 선이 되지 않았습니다.

틈을 계산하고 채우기



그래서 다른 HTML 캔버스를 사용하는 서비스가 어떻게이 틈 문제를 해결하는지 조사한 결과 다음 기사를 발견했습니다.

여러가지 펜의 실장이 있어 매우 참고가 됩니다만, 바로 틈 문제를 해결하는 방법이 실려 있어, 요컨대 마우스의 움직임의 각도와 거리를 계산해 틈에도 스탬프를 묘화한다고 하는 수법으로 했다.

실제 코드는 여기
function distanceBetween (point1, point2) {
  return Math.sqrt(Math.pow(point2.x - point1.x, 2) + Math.pow(point2.y - point1.y, 2))
}
function angleBetween (point1, point2) {
  return Math.atan2(point2.x - point1.x, point2.y - point1.y)
}
const dist = distanceBetween(beginPosition, destPosition)
const angle = angleBetween(beginPosition, destPosition)
for (let i = 0; i < dist; i += 5) {
  const x = beginPosition.x + (Math.sin(angle) * i) - halfSizeOfBrush
  const y = beginPosition.y + (Math.cos(angle) * i) - halfSizeOfBrush
  this.context.drawImage(stampImage, Math.round(x), Math.round(y))
}

이것을 적용한 펜 구현이 여기입니다.



매우 좋은 느낌입니다. 확실히 요구하고 있던 쟈기루 펜 툴의 가능성이 보아 왔습니다.

크기와 색상을 동적으로 만들고 싶습니다.



지금까지의 구현에서는 포토샵에서 GIF 화상을 작성해, 그것을 스탬프로서 사용하고 있었습니다만, 이것으로는 사이즈도 색도 고정이 되어 버리고 있습니다.
사이즈와 색을 동적으로 변경할 수 없는 것인가, 라고 구구하고 있던 곳 이하의 페이지를 발견했습니다.

아무래도 Bresenham’s line algorithm이라고 하는 것으로 안티앨리어싱이 걸려 있지 않은 원형의 묘화를 할 수 있는 것 같습니다.



이런 느낌이군요.

동적 스탬프 생성이 마침내 완료되었습니다.



할 수 있었어요. 알고리즘의 코드를 참고로 구현해 보았는데 약간 고전했습니다만 실현할 수 있었습니다.



크기 변경 슬라이더를 움직이면 펜촉이 차례로 생성됩니다.
완전히 이겼습니다.

Retina 디스플레이라고 흐리게



쭉 외부 모니터로 개발하고 있어, MBP의 화면에서 사람에게 설명하려고 하면 갑자기 노망이므로 엄청 초조했습니다만, Retina 디스플레이 때문에 window.devicePixelRatio 의 값이 2가 되어 있었기 때문이었습니다.
이것은 canvas 요소의 width, height 속성을 2배 한 뒤 style로 원래의 사이즈까지 축소해 표시시켜, 한층 더 context의 scale를 2배로 한다는 의미를 모르는 것을 하면 해결합니다.

이런 느낌입니다.
const canvas = document.createElement('canvas')
const context = canvas.getContext('2d')
const dpr = window.devicePixelRatio

const { width, height } = canvas

// canvas要素のwidth,heightをdevicePixelRatio倍する
canvas.width = width * dpr
canvas.height = height * dpr

// styleで元のサイズに戻して見た目は大きくしないようする
canvas.style.width = `${width}px`
canvas.style.height = `${height}px`

// contextのscaleをdevicePixelRatioに合わせておく
context.scale(dpr, dpr)

px-brush라는 npm 패키지로 만들었습니다.



그래서 "이런 느낌에 가만히 있으면, 좋은 느낌으로 움직이지 않을까~"라고 생각해 본 결과, 슈퍼 잘 되었기 때문에 전세계 여러분에게도 사용해 주셨으면 npm 패키지로 했습니다.

버그나 요청 등이 있으시면 Pull Request 보내주세요.

좋은 웹페이지 즐겨찾기