⬆️ ⬇️

[Graphic editor on Canvas] Soft brush



If you are creating a graphic editor on canvas, you probably want to have a soft brush in your arsenal. So, this task is rather nontrivial and I will try to highlight the main difficulties and suggest solutions.

(in the picture an example of the work of a soft brush in GIMP)

Before reading, I recommend to read the previous article .







First of all, you need to understand the audience and the purpose of your editor. This should help you to prioritize, of which two can be distinguished for this problem:



The solution I propose is based on the built-in capability of the canvas api that is suitable for this task. Namely - the shadows. The idea is this: when a user draws along an outline, in fact, a line is drawn somewhere outside the outline with a certain offset, and the shadow is adjusted so that it hits exactly where the user draws.



Here is an example implementation:

<!DOCTYPE html>

<html>

<head>

<title></title>

<meta http-equiv= "Content-Type" content= "text/html; charset=UTF-8" >

<style type= "text/css" >

body {

margin: 0;

}

#cnvs {

outline: #000000 1px solid;

}

</style>

<script type= "text/javascript" >

var action = "up" ;

//: , , , ,

var canvas,ctx,offset,points,bufer;



//

function initcnvs(){

canvas = document .getElementById( 'cnvs' );

ctx = canvas.getContext( '2d' );

ctx.lineWidth = 10;

// ( )

offset = 1000;

//

ctx.shadowBlur = 10;

ctx.shadowColor = "#000000" ;

ctx.shadowOffsetX = -offset;

points = new Array();

//

bufer = ctx.getImageData(0,0,canvas.width,canvas.height);

};



//

function mDown(e){

action = "down" ;

points.push([e.pageX,e.pageY]);

};



// -

function mUp(e){

action = "up" ;

points = new Array();

bufer = ctx.getImageData(0,0,canvas.width,canvas.height);

};



// -

function mMove(e){

if (action == "down" ) {

ctx.putImageData(bufer,0,0);

points.push([e.pageX,e.pageY]);

ctx.beginPath();

ctx.moveTo(points[0][0]+offset, points[0][1]);

for (i = 1; i < points.length; i++){

ctx.lineTo(points[i][0]+offset,points[i][1]);

}

ctx.stroke();

}

};

</script>

</head>

<body onload= "initcnvs()" onmousedown= "mDown(event)" onmousemove= "mMove(event)" onmouseup= "mUp(event)" >

<canvas id= "cnvs" width= "800" height= "500" ></canvas>

</body>

</html>




* This source code was highlighted with Source Code Highlighter .


ctx.shadowBlur is responsible for blurring the shadow, ctx.shadowColor is the color, ctx.shadowOffsetX is the offset along the X axis.

The offset variable stores a safe offset so that the original line cannot reach the outline. Canvas raster is stored in the bufer variable. What for? Here it turns out the main disadvantage of the algorithm. After adding each new point to the line (and to the shadow, respectively), we have to redraw the entire line again. If this is not done, and a line is drawn from the segments connecting two adjacent points, then its discontinuity will be noticeable. (in the case of an opaque brush, this method would be suitable).

')

A natural question arises: if the number of points is large, will this affect the performance? Answer: it will. A noticeable slowdown begins after 5 seconds of drawing, but you can still draw without much difficulty for a long time.

In some cases, this option may be acceptable.

For example, in the deviantart editor, this is exactly the same or similar option.

Alternatively, you can set a fixed size of the array and save the canvas to the buffer after filling, and then continue to draw from this point. In the place of the gap will be a small artifact. Someone can make such an option.



Of course, there are more options. Here, let's say, is not the easiest.



In this figure I noted which pieces of the line would have to be drawn separately. Semicircles will need to be filled with a radial gradient from opaque to transparent. Other pieces - linear gradient.



I’ll say right away - in this algorithm, there are a lot of things that are not very complex, but I won’t give you any boring mathematics. If you really need it, it is better to bring it out yourself in order to fully understand all the points. Here are the highlights of the algorithm:





There is still a fairly simple way to implement - sprites .



That's all I wanted to talk about today. In the comments I will answer all your questions.



PS: thanks to Serator for prompting to use outline instead of border. This eliminated the error in the coordinates.

Thanks to the user TheShock for a couple of sensible comments.



Previous articles in the series:

Sketch Brush

Source: https://habr.com/ru/post/119891/



All Articles