Leo Song

Posted on

# Exploring the Canvas Series: Creative Brushes Part 3

## Introduction

I am currently developing a powerful open source creative drawing board. This drawing board contains a variety of creative brushes, which allows users to experience a new drawing effect. Whether on mobile or PC , you can enjoy a better interactive experience and effect display . And this project has many powerful auxiliary painting functions, including but not limited to forward and backward, copy and delete, upload and download, multiple boards and layers and so on. I'm not going to list all the detailed features, looking forward to your exploration.

Github: https://github.com/LHRUN/paint-board Welcome to Star ⭐️

In the gradual development of the project, I plan to write some articles, on the one hand, to record the technical details, which is my habit all the time. On the other hand, I'd like to promote the project, and I hope to get your use and feedback, and of course, a Star would be my greatest support.

I'm going to explain the implementation of the Creative Brush in 3 articles, this is the third one, and I'll upload all the source code to my Github.

Github Source Code Demo

## Multi Point Connection

• The effect of multipoint connection is as follows

• Multi-point connection is through the mouse in the coordinates of the movement of the randomly generated nearby points, and then the random points of the circle drawing, and each time will record the last movement of the point, and the two points of the line segment connection, you will have the above effect.
``````interface Point {
x: number
y: number
}

let isMouseDown = false
let lastPoints: Point[] = [] // Previous Array of random circular points

/**
* Generate random points within a rectangle
*/
const generateRandomCoordinates = (
centerX: number, // Rectangle Centre Point X
centerY: number, // Rectangle Centre Point Y
size: number, // Rectangle Size
count: number // Number of generation
) => {
const halfSize = size / 2
const points = []

for (let i = 0; i < count; i++) {
const randomX = Math.floor(centerX - halfSize + Math.random() * size)
const randomY = Math.floor(centerY - halfSize + Math.random() * size)
points.push({ x: randomX, y: randomY })
}

return points
}

function PaintBoard() {
const canvasRef = useRef<HTMLCanvasElement | null>(null)
const [context2D, setContext2D] = useState<CanvasRenderingContext2D | null>(null)

useEffect(() => {
if (canvasRef?.current) {
const context2D = canvasRef?.current.getContext('2d')
if (context2D) {
context2D.fillStyle = '#000000';
context2D.strokeStyle = '#000000';
context2D.lineWidth = 1;
setContext2D(context2D)
}
}
}, [canvasRef])

const onMouseDown = () => {
if (!canvasRef?.current || !context2D) {
return
}
isMouseDown = true
}

const onMouseMove = (event: MouseEvent) => {
if (!canvasRef?.current || !context2D) {
return
}

if (isMouseDown) {
const { clientX, clientY } = event
// Generate random points
const points = generateRandomCoordinates(clientX, clientY, 50, 3)
draw(points)
lastPoints = points
}
}

const draw = (points: Point[]) => {
if (!context2D) {
return
}

// If there is a previous random point, connect it.
if (lastPoints.length) {
lastPoints.forEach(({ x, y }, index) => {
context2D.beginPath();
context2D.save();
context2D.moveTo(x, y);
context2D.lineTo(points[index].x, points[index].y);
context2D.stroke();
context2D.restore();
})
}

// Circular drawing of the current point
points.map((curPoint) => {
context2D.beginPath();
context2D.save();
context2D.arc(curPoint.x, curPoint.y, 7, 0, 2 * Math.PI, false);
context2D.fill();
context2D.restore();
})
}

const onMouseUp = () => {
if (!canvasRef?.current || !context2D) {
return
}
isMouseDown = false
lastPoints = []
}

return (
<div>
<canvas
ref={canvasRef}
onMouseDown={onMouseDown}
onMouseMove={onMouseMove}
onMouseUp={onMouseUp}
/>
</div>
)
}
``````

## Wave Brush

• The wave curve effect is as follows

• Wave curve is in the mouse movement, according to the mouse to move the distance and angle of the two points for the semicircle drawing, and then the semicircle is every time will be flipped, so there will be a kind of wave effect
``````interface Point {
x: number
y: number
}

let isMouseDown = false
let movePoint: Point = { x: 0, y: 0 } // Moving Coordinate Recording
let flip = 1 // flip flag

/**
* Get the distance between two points
*/
const getDistance = (start: Point, end: Point) => {
return Math.sqrt(Math.pow(start.x - end.x, 2) + Math.pow(start.y - end.y, 2))
}

function PaintBoard() {
const canvasRef = useRef<HTMLCanvasElement | null>(null)
const [context2D, setContext2D] = useState<CanvasRenderingContext2D | null>(null)

useEffect(() => {
if (canvasRef?.current) {
const context2D = canvasRef?.current.getContext('2d')
if (context2D) {
context2D.strokeStyle = '#000'
context2D.lineJoin = 'round'
context2D.lineCap = 'round'
setContext2D(context2D)
}
}
}, [canvasRef])

const onMouseDown = (event: MouseEvent) => {
if (!canvasRef?.current || !context2D) {
return
}
isMouseDown = true
const { clientX, clientY } = event
movePoint = {
x: clientX,
y: clientY
}
}

const onMouseMove = (event: MouseEvent) => {
if (!canvasRef?.current || !context2D) {
return
}

if (isMouseDown) {
const { clientX, clientY } = event

// Get the distance between two points
const distance = getDistance(movePoint, { x: clientX, y: clientY })

// Get the middle point of the two points, which is also the centre of the semicircle.
const midX = (movePoint.x + clientX) / 2
const midY = (movePoint.y + clientY) / 2

context2D.beginPath();
context2D.save();

// Calculate the angle between two points
const angle = Math.atan2(clientY - movePoint.y, clientX - movePoint.x)

// Calculating whether to flip up or down
const flipAngle = (flip % 2) * Math.PI

// Drawing a semicircle
context2D.arc(
midX,
midY,
distance / 2,
angle + flipAngle,
angle + flipAngle + Math.PI
);
context2D.stroke();
context2D.restore();

// update data
flip++;
movePoint = {
x: clientX,
y: clientY
}
}
}

const onMouseUp = () => {
if (!canvasRef?.current || !context2D) {
return
}
isMouseDown = false
movePoint = { x: 0, y: 0 }
}

return (
<div>
<canvas
ref={canvasRef}
onMouseDown={onMouseDown}
onMouseMove={onMouseMove}
onMouseUp={onMouseUp}
/>
</div>
)
}
``````

## Thorn Brush

• The thorn brush effect is as follows

• Thorn brush is in the mouse movement, through the movement of the two points of the distance and angle of the ellipse drawing, because the height of the ellipse is relatively small, coupled with the width is calculated by the distance of movement, so there will be a kind of sharp effect
``````interface Point {
x: number
y: number
}

let isMouseDown = false
let movePoint: Point = { x: 0, y: 0 } // Moving Coordinate Recording
const minSize = 3 // Minimum width and height of ellipse

/**
* Get the distance between two points
*/
const getDistance = (start: Point, end: Point) => {
return Math.sqrt(Math.pow(start.x - end.x, 2) + Math.pow(start.y - end.y, 2))
}

function PaintBoard() {
const canvasRef = useRef<HTMLCanvasElement | null>(null)
const [context2D, setContext2D] = useState<CanvasRenderingContext2D | null>(null)

useEffect(() => {
if (canvasRef?.current) {
const context2D = canvasRef?.current.getContext('2d')
if (context2D) {
context2D.fillStyle = '#000'
context2D.lineJoin = 'round'
context2D.lineCap = 'round'
setContext2D(context2D)
}
}
}, [canvasRef])

const onMouseDown = (event: MouseEvent) => {
if (!canvasRef?.current || !context2D) {
return
}
isMouseDown = true
const { clientX, clientY } = event
movePoint = {
x: clientX,
y: clientY
}
}

const onMouseMove = (event: MouseEvent) => {
if (!canvasRef?.current || !context2D) {
return
}

if (isMouseDown) {
const { clientX, clientY } = event
// Get the distance between two points
const distance = getDistance(movePoint, point)

// Get the middle point of the two points, which is also the centre of the ellipse.
const midX = (movePoint.x + clientX) / 2
const midY = (movePoint.y + clientY) / 2

context2D.beginPath();
context2D.save();

// Calculate the angle between two points
const angle = Math.atan2(clientY - movePoint.y, clientX - movePoint.x)

/**
* Drawing ellipse
* height:  minSize
* width: distance * 5 + minSize
*/
context2D.ellipse(
midX,
midY,
distance * 5 + minSize,
minSize,
angle,
0,
2 * Math.PI
);
context2D.fill();
context2D.restore();

movePoint = {
x: clientX,
y: clientY
}
}
}

const onMouseUp = () => {
if (!canvasRef?.current || !context2D) {
return
}
isMouseDown = false
movePoint = { x: 0, y: 0 }
}

return (
<div>
<canvas
ref={canvasRef}
onMouseDown={onMouseDown}
onMouseMove={onMouseMove}
onMouseUp={onMouseUp}
/>
</div>
)
}
``````