function useDrawing(canvasRef) {
const [isDrawing, setIsDrawing]
= useState(false);
const getPos = (e) => {
const rect = canvasRef.current
.getBoundingClientRect();
return {
x: e.clientX - rect.left,
y: e.clientY - rect.top
};
};
const startStroke = (e) => {
const ctx = canvasRef.current
.getContext('2d');
const { x, y } = getPos(e);
ctx.beginPath();
ctx.moveTo(x, y);
setIsDrawing(true);
};
const draw = (e) => {
if (!isDrawing) return;
const ctx = canvasRef.current
.getContext('2d');
const { x, y } = getPos(e);
ctx.lineTo(x, y);
ctx.stroke();
};
const endStroke = () => {
setIsDrawing(false);
};
return {
startStroke, draw, endStroke
};
}
// Instead of lineTo (jagged),
// use quadraticCurveTo (smooth)
let lastPoint = null;
function drawSmooth(ctx, newPoint) {
if (!lastPoint) {
lastPoint = newPoint;
return;
}
// Midpoint between last and current
const mid = {
x: (lastPoint.x + newPoint.x) / 2,
y: (lastPoint.y + newPoint.y) / 2
};
// Curve from last point through
// midpoint to current
ctx.quadraticCurveTo(
lastPoint.x, lastPoint.y,
mid.x, mid.y
);
ctx.stroke();
lastPoint = newPoint;
}
function configureBrush(ctx, settings) {
ctx.lineWidth = settings.size;
ctx.strokeStyle = settings.color;
ctx.globalAlpha = settings.opacity;
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
}
// Eraser = draw with composite mode
function setEraser(ctx, size) {
ctx.globalCompositeOperation =
'destination-out';
ctx.lineWidth = size;
}
// Reset to normal drawing
function setBrush(ctx) {
ctx.globalCompositeOperation =
'source-over';
}
const history = [];
let historyIndex = -1;
function saveSnapshot(canvas) {
const ctx = canvas.getContext('2d');
const snapshot = ctx.getImageData(
0, 0, canvas.width, canvas.height);
// Remove any "future" states
history.splice(historyIndex + 1);
history.push(snapshot);
historyIndex++;
}
function undo(canvas) {
if (historyIndex <= 0) return;
historyIndex--;
const ctx = canvas.getContext('2d');
ctx.putImageData(
history[historyIndex], 0, 0);
}
function redo(canvas) {
if (historyIndex >= history.length - 1)
return;
historyIndex++;
const ctx = canvas.getContext('2d');
ctx.putImageData(
history[historyIndex], 0, 0);
}
git switch -c feature/PIXELCRAFT-062-drawing-tool
git add src/
git commit -m "Add freehand drawing with brush engine + undo (PIXELCRAFT-062)"
git push origin feature/PIXELCRAFT-062-drawing-tool
# PR → Review → Merge → Close ticket ✅
Bézier curves are everywhere in computing.
Invented by Pierre Bézier for car body design at Renault (1962). Now they're in every font, every vector graphic, every animation curve.