Reputation: 506
I'm making a painting app, and using the canvas path as a brush:
function onMove(){
ctx.beginPath();
ctx.moveTo(pos.x, pos.y); // from position
pos=getPosition(event);
ctx.lineTo(pos.x, pos.y); // to position
ctx.stroke(); // draw it!
}
I'm stuck between in a dilemma, if I join all the paths into one I lose intersection overlap, and if I draw them real time separately I get those start/end overlaps spheres.
Is there a clean way I can solve this problem? Thank you.
Upvotes: 0
Views: 136
Reputation: 77
I had a similar problem making a brush for an app draw, so since it was a pain to solve this problem I would like to share the solution here.
The solution i found was to instead of draw only one point when the move event is trigger, I draw multiple points between de last draw point and the current one, this makes that there is not gaps and gives the impression of a continuous line and even allows a dynamic opacity and size of the stroke.
const canvas = document.querySelector("canvas")
const ctx = canvas.getContext("2d")
// prevent swap bug when use touch or pen
canvas.addEventListener('touchmove', e => {
e.preventDefault()
})
let lastX = 0
let lastY = 0
let lastPressure = 0
let drawing = false
function onMove(e) {
if (!drawing) return
const startX = lastX
const startY = lastY
const startPressure = lastPressure
const endX = e.offsetX
const endY = e.offsetY
const endPressure = e.pressure
const gap = 0.05
const distance = Math.sqrt((endX - startX) ** 2 + (endY - startY) ** 2)
const steps = Math.floor(distance / gap)
const points = []
for (let i = 0; i < steps; i++) {
const t = i / steps
const x = startX + t * (endX - startX)
const y = startY + t * (endY - startY)
const pressure = startPressure + t * (endPressure - startPressure)
points.push({ x, y, pressure })
}
for (let i = 0; i < points.length - 1; i++) {
const p1 = points[i]
const p2 = points[i + 1]
const size = 20
const opacity = Math.min(p1.pressure, .1)
ctx.globalAlpha = opacity
ctx.strokeStyle = `rgba(0, 0, 0, ${opacity})`
ctx.lineWidth = size
ctx.lineCap = "round"
ctx.lineJoin = "round"
ctx.beginPath()
ctx.moveTo(p1.x, p1.y)
ctx.lineTo(p2.x, p2.y)
ctx.stroke()
ctx.closePath()
}
lastX = e.offsetX
lastY = e.offsetY
}
canvas.addEventListener("pointerdown", (e) => {
drawing = true
lastX = e.offsetX
lastY = e.offsetY
lastPressure = e.pressure
})
canvas.addEventListener("pointerup", () => {
drawing = false
})
canvas.addEventListener("pointeout", () => {
drawing = false
})
canvas.addEventListener("pointermove", onMove)
<canvas width="500" height="500" style="border: 1px solid black"></canvas>
This code can be optimized increasing the gap var value, the less the gap the more points are draw.
Upvotes: 1
Reputation: 2140
Okey, then it is getting complicated since we are dealing with alpha values here..
Basicly you can draw the "missing part" of the new line in a buffer canvas by using destination-in
and source-in
blend modes.
The idea is: to "compute" the overlapp of the N-1 the Nth line and then only draw line N outside this overlap: (for demonstation the Nth line will be drawn in red.)
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const canvas2 = document.createElement('canvas')
canvas2.width = 300
canvas2.height = 300
const ctx2 = canvas2.getContext('2d');
ctx.lineWidth = 30
ctx.lineCap = 'round';
ctx.strokeStyle = 'rgba(0,0,0,0.5)';
ctx.imageSmoothingEnabled = false;
ctx.lineJoin = "round"
function drawLine1(ctx) {
ctx.beginPath();
ctx.moveTo(250, 250);
ctx.lineTo(100, 250);
ctx.stroke();
}
function drawLine2(ctx) {
ctx.beginPath();
ctx.moveTo(100, 250);
ctx.lineTo(100, 50);
ctx.stroke();
}
// draw image until line n-1 on screen
ctx.beginPath();
ctx.moveTo(0, 100);
ctx.lineTo(250, 100); // 1st
ctx.lineTo(250, 250); // 2nd
ctx.lineTo(100, 250); // n-1
ctx.stroke();
// clear buffer canvas and setup
ctx2.clearRect(0, 0, canvas2.width, canvas2.height);
ctx2.lineWidth = 30
ctx2.lineCap = 'round';
ctx2.imageSmoothingEnabled = false;
// draw line n-1 on buffer in opaque color
ctx2.strokeStyle = 'rgba(0,0,0,1)';
drawLine1(ctx2)
// draw line n on buffer in "destination-in" blend mode opaque
ctx2.globalCompositeOperation = 'destination-in';
ctx2.strokeStyle = 'rgba(0,0,0,1)';
drawLine2(ctx2)
// draw line n on buffer in "source-out" blend mode and original color
ctx2.globalCompositeOperation = 'source-out';
ctx2.strokeStyle = 'rgba(255,0,0,0.5)';
drawLine2(ctx2)
ctx.drawImage(canvas2, 0 ,0)
<canvas id="canvas" width="400" height="400"></canvas>
It is not perfect, but it is a starting point..
Upvotes: 1