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.
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 aboutHSL
.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 theHSL
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 aCanvasPattern
object, MDN - We can create a new
canvas
, then draw an image on thiscanvas
, then draw a colour you want, and finally create apattern
from thiscanvas
and assign it tostrokeStyle
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!