Exploring the Canvas Series: Creative Brushes Part 1

Exploring the Canvas Series: Creative Brushes Part 1

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.

Link: https://songlh.top/paint-board/

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 first one, and I’ll upload all the source code to my Github.

Github Source Code Demo

Rainbow Brush

The rainbow brush changes colour as it is being drawn, the effect is as follows.

This effect is just an extra colour shift compared to a normal brush, which as we know is implemented by connecting line segments one by one, whether they are curves or straight lines. So in order to achieve the rainbow effect, we need to change the colour of each line segment, which is done by modifying strokeStyle.
Then to change the strokeStyle colour, we can’t simply express it as a regular colour, we need to know about HSL.

HSL is a colour expression that describes colours by means of a cylindrical coordinate system, divided into Hue (H), Saturation (S), Luminance (L).
Hue (H): Indicates the position of the colour on the colour ring.
Saturation (S): It indicates the purity or degree of greyness of a colour. A saturation of 100% indicates a fully saturated colour, while 0% indicates a greyish colour.
Luminance (L): It indicates the brightness of the colour. Adjusting the Luminance changes the lightness or darkness of the colour.

MDN Detailed description
HSL Online Showcase Website: https://mothereffinghsl.com/

Instead, we just need to keep adjusting the hue in the HSL expression to achieve the effect of constantly changing colours.

let hue = 0 // Record the current hue
let isMouseDown = false
let movePoint: { x: number, y: number } | null = null // Record mouse point

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.lineCap = round
context2D.lineJoin = round
context2D.lineWidth = 10
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
if (movePoint) {
/**
* Gradually increases by 1 in the range 0 to 360, but resets to 0 when greater than or equal to 360.
*/

hue = hue < 360 ? hue + 1 : 0
context2D.beginPath()
// Change colours via HSL
context2D.strokeStyle = `hsl(${hue}, 90%, 50%)`
context2D.moveTo(movePoint.x, movePoint.y)
context2D.lineTo(clientX, clientY)
context2D.stroke()
}
movePoint = {
x: clientX,
y: clientY
}
}
}

const onMouseUp = () => {
if (!canvasRef?.current || !context2D) {
return
}
isMouseDown = false
movePoint = null
}

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

Multi Shape Brush

Multi-shape brush will move with the mouse, in the path of the move to generate random points for shape drawing, the effect is as follows

Implementation method, through each mouse movement coordinates, in the coordinates of the surrounding range of a few randomly generated points, and then in these points through the new Path2D() for the generation of graphical path, and then draw the

let isMouseDown = false

// Music Symbol Shape Path
const musicPath = ***

/**
* 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 = #000
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
const points = generateRandomCoordinates(clientX, clientY, 30, 3)
points.map((curPoint) => {
createShape(curPoint.x, curPoint.y)
})
}
}

const createShape = (x: number, y: number) => {
if (!context2D) {
return
}
// Path creation
const path = new Path2D(musicPath);
context2D.beginPath();

context2D.save();
context2D.translate(x, y);

// Shape random scaling
const scale = Math.random() * 1.5 + 0.5
context2D.scale(scale, scale);

context2D.fill(path);
context2D.restore();
}

const onMouseUp = () => {
if (!canvasRef?.current || !context2D) {
return
}
isMouseDown = false
moveDate = 0
}

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

Material Brush

The effect of the material brush is as follows

The first thing you need is a transparent picture of the material, this picture is used as a base, if you use a picture made of crayon material, it will have a crayon effect, if you use a picture made of frosted material, it will have a frosted effect.
The strokeStyle property can then take a CanvasPattern object, MDN

We can create a new canvas, then draw an image on this canvas, then draw a colour you want, and finally create a pattern from this canvas and assign it to strokeStyle to get the effect of the material’s brush.

let isMouseDown = false
let movePoint: { x: number, y: number } | null = null

// Load the required material image
const materialImage = new Promise<HTMLImageElement>((resolve) => {
const image = new Image()
image.src = Material image URL
image.onload = () => {
resolve(image)
}
})

/**
* Get pattern object
* @param color background colour
*/

const getPattern = async (color: string) => {
const canvas = document.createElement(canvas)
const context = canvas.getContext(2d) as CanvasRenderingContext2D
canvas.width = 100
canvas.height = 100
context.fillStyle = color

// Draw a rectangle as the background colour
context.fillRect(0, 0, 100, 100)
const image = await materialImage

// Drawing of material
if (image) {
context.drawImage(image, 0, 0, 100, 100)
}
return context.createPattern(canvas, repeat)
}

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

useEffect(() => {
initDraw()
}, [canvasRef])

const initDraw = async () => {
if (canvasRef?.current) {
const context2D = canvasRef?.current.getContext(2d)
if (context2D) {
context2D.lineCap = round
context2D.lineJoin = round
context2D.lineWidth = 10
// get pattern material
const pattern = await getPattern(blue)
if (pattern) {
context2D.strokeStyle = pattern
}
setContext2D(context2D)
}
}
}

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

const onMouseMove = (event: MouseEvent) => {
if (!canvasRef?.current || !context2D) {
return
}
if (isMouseDown) {
const { clientX, clientY } = event
if (movePoint) {
// Brush Drawing
context2D.beginPath()
context2D.moveTo(movePoint.x, movePoint.y)
context2D.lineTo(clientX, clientY)
context2D.stroke()
}
movePoint = {
x: clientX,
y: clientY
}
}
}

const onMouseUp = () => {
if (!canvasRef?.current || !context2D) {
return
}
isMouseDown = false
movePoint = null
}

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

Pixel Brush

The pixel brush effect is as follows

Pixel Paintbrush is through the mouse to move, in the path of the mouse to move, randomly point according to the position of the rectangle drawing, multiple rectangles combined to have a similar effect to the pixel dot

let isMouseDown = false
const drawWidth = 15 // Pixel Brush Size
const step = 5 // Size of each pixel point

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;
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

/**
* Iterate over the current pixel brush size, and determine whether to draw based on a random number.
*/

for (let i = drawWidth; i < drawWidth; i += step) {
for (let j = drawWidth; j < drawWidth; j += step) {
if (Math.random() > 0.5) {
context2D.save();
context2D.fillRect(clientX + i, clientY + j, step, step);
context2D.fill();
context2D.restore();
}
}
}
}
}

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

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

Conclusion

Thank you for reading. This is the whole content of this article, I hope this article is helpful to you, welcome to like and favourite. If you have any questions, please feel free to discuss in the comment section!

Leave a Reply

Your email address will not be published. Required fields are marked *