Smudge Brush
by Ben Gillbanks
HTML
<div class="split">
<canvas id="canvas"></canvas>
<div>
<div class="controls">
<div>
<div><input type="range" id="radius" min="2" max="40" value="16"><label for="radius">radius</label></div>
<div><input type="range" id="hardness" min="0" max="1" step="0.01" value="0.5"><label for="radius">hardness</label></div>
<div><input type="range" id="alpha" min="0" max="1" step="0.01" value="0.5"><label for="alpha">alpha</label></div>
<button type="button" id="reset">reset</button>
</div>
<div style="text-align: right;">
<canvas id="brush-display" width="80" height="80"></canvas>
</div>
</div>
</div>
</div>
CSS
#canvas { border: 1px solid black; }
.controls { margin-left: 5px; }
.split { display: flex; }
* { user-select: none; }
JavaScript
const ctx = document.querySelector('#canvas').getContext('2d');
const brushDisplayCtx = document.querySelector('#brush-display').getContext('2d');
function reset() {
const {width, height} = ctx.canvas;
const wd2 = width / 2
ctx.globalAlpha = 1;
ctx.fillStyle = 'white';
ctx.fillRect(wd2, 0, wd2, height);
const gradient = ctx.createLinearGradient(0, 0, 0, height);
gradient.addColorStop(0, 'red');
gradient.addColorStop(0.5, 'yellow');
gradient.addColorStop(1, 'blue');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, wd2, height);
}
reset();
function getCanvasRelativePosition(e, canvas) {
const rect = canvas.getBoundingClientRect();
return {
x: (e.clientX - rect.left) / rect.width * canvas.width,
y: (e.clientY - rect.top ) / rect.height * canvas.height,
};
}
function lerp(a, b, t) {
return a + (b - a) * t;
}
function setupLine(x, y, targetX, targetY) {
const deltaX = targetX - x;
const deltaY = targetY - y;
const deltaRow = Math.abs(deltaX);
const deltaCol = Math.abs(deltaY);
const counter = Math.max(deltaCol, deltaRow);
const axis = counter == deltaCol ? 1 : 0;
// setup a line draw.
return {
position: [x, y],
delta: [deltaX, deltaY],
deltaPerp: [deltaRow, deltaCol],
inc: [Math.sign(deltaX), Math.sign(deltaY)],
accum: Math.floor(counter / 2),
counter: counter,
endPnt: counter,
axis: axis,
u: 0,
};
};
function advanceLine(line) {
--line.counter;
line.u = 1 - line.counter / line.endPnt;
if (line.counter <= 0) {
return false;
}
const axis = line.axis;
const perp = 1 - axis;
line.accum += line.deltaPerp[perp];
if (line.accum >= line.endPnt) {
line.accum -= line.endPnt;
line.position[perp] += line.inc[perp];
}
line.position[axis] += line.inc[axis];
return true;
}
let lastX;
let lastY;
let lastForce;
let drawing = false;
let alpha = 0.9;
const brushCtx = document.createElement('canvas').getContext('2d');
let featherGradient;
function...