In a recent project, I want to implement a pixel-style drawing board, where small pixel grids can be erased, frame selections change color, and various graphics can be erased. Such a small project may seem simple, but it contains a lot of things.
Draw a pixel gridLet’s first define the pixel grid class
Pixel = function (option) { this.x = option.x; this.y = option.y; this.shape = option.shape; this.size = option.size || 8;}
x and y represent the center point coordinates. This is what I did at the beginning. Define the path first.
createPath: function (ctx) {if (this.shape === 'circle') {this.createCircle(ctx);} else if (this.shape === 'rect') {this.createRect(ctx);} else {this.createCircle(ctx);}},createCircle: function (ctx) {var radius = this.size / 2;ctx.arc(this.x,this.y,radius,0,Math.PI*2);},createRect: function (ctx) {var points = this.getPoints(); points.forEach(function (point , i) { ctx[i == 0 ? 'moveTo' : 'lineTo'](point.x, point.y); }) ctx.lineTo(points[0].x, points[0].y);},
The pixel grid supports circles and rectangles. After the path is defined, it is then drawn.
draw: function (ctx) {ctx.save();ctx.lineWidth=this.lineWidth;ctx.strokeStyle=this.strokeStyle;ctx.fillStyle=this.fillStyle;ctx.beginPath();this.createPath(ctx); ctx.stroke();if(this.isFill){ctx.fill();}ctx.restore();}
Then create the pixel grid in batches via a loop:
for (var i = stepX + .5; i < canvas.width; i+=stepX) {for (var j = stepY + .5; j < canvas.height; j+=stepY) {var pixel = new Pixel({x : i,y: j,shape: 'circle'})box.push(pixel);pixel.draw(ctx);}}
This seems to be perfect, but there is a huge disadvantage. Every time a pixel is drawn, it is drawn back to the context, and the state of the canvas is changed every time. This will lead to poor rendering performance because there are many pixels. If the canvas is relatively large, , the performance is very worrying, and there are some operations on the drawing board. It is inappropriate to change the state of the canvas so frequently.
Therefore, the correct approach is: we should define all paths, and it is best to draw them into the canvas in batches at once;
//Define the position of the pixel for (var i = stepX + .5; i < canvas.width; i+=stepX) {for (var j = stepY + .5; j < canvas.height; j+=stepY) {var pixel = new Pixel({x: i,y: j,shape: 'circle'})box.push(pixel);}}//Batch drawing console.time('time');ctx.beginPath();for (var c = 0; c < box.length; c++) {var circle = box[c];ctx.moveTo(circle.x + 3, circle.y);circle.createPath(ctx);}ctx.closePath();ctx.stroke();console.timeEnd('time');
You can see that this rendering efficiency is very fast, and the state of the canvas is changed as little as possible, because every time the state of the context is changed, the canvas will be redrawn, and this state is a global state.
Pixel grid interactionThe requirement of the project is that pixels can be erased by pressing and moving the mouse on the canvas. This contains two knowledge points, one is how to obtain the pixel grid on the mouse movement path, and the second is performance issues, because of our needs The requirement is to draw 80,000 points. Not to mention anything else, just looping will take dozens or hundreds of milliseconds, not to mention drawing and rendering. Let’s look at the first question first:
Get the grid under the mouse movement pathSeeing this problem, we can easily think of writing a function to get the position of the mouse through the position of the mouse, which contains the grid, and then re-update the position calculation every time it moves. In this way, the requirement can be fulfilled, but if the mouse moves over It is impossible to do it quickly. The position of each point can be calculated, and the effect will be inconsistent. Let's change our thinking. We can clearly know the starting and ending points of the path that the mouse passes. We imagine the entire drawing path as a line segment. Then the problem becomes an algorithm for intersecting the line segment with the original. The line segment is The thickness of the brush and the path that the line segment passes through are the paths of the mouse movement, and the circles that intersect with them are the grids that need to be changed. Converted into code is as follows:
function sqr(x) { return x * x } function dist2(p1, p2) { return sqr(p1.x - p2.x) + sqr(p1.y - p2.y) } function distToSegmentSquared(p, v, w ) { var l2 = dist2(v, w); if (l2 == 0) return dist2(p, v); var t = ((px - vx) * (wx - vx) + (py - vy) * (wy - vy)) / l2; if (t < 0) return dist2(p, v); if (t > 1) return dist2 (p, w); return dist2(p, { x: vx + t * (wx - vx), y: vy + t * (wy - vy) }); }/** * @description Calculate whether the line segment intersects the circle* @param {x: num, y: num} p circle center point* @param {x: num, y: num} v starting point of the line segment* @param {x: num, y: num } w end point of line segment*/ function distToSegment(p, v, w) { var offset = pathHeight; var minX = Math.min(vx, wx) - offset; var maxX = Math.max(vx, wx) + offset; var minY = Math.min(vy, wy) - offset; var maxY = Math.max(vy, wy) + offset; if ((px < minX || px > maxX) && (py < minY || py > maxY)) { return Number.MAX_VALUE; } return Math.sqrt(distToSegmentSquared(p, v, w)); }
The specific logic will not be elaborated. Readers can read the code by themselves. Then, by obtaining the intersecting grid, delete the data in the box, and render it again, you can see the effect.
In the same way, we can create a dyeing effect, and then we can implement a small demo of the canvas pixel drawing board. However, to create a dyeing effect, you must use the first drawing method. Each pixel must be an object, because the state of each object is independent. However, there is no need to worry about performance. There are not many pixels, and there will be basically no lag. feel. The effect is roughly as follows:
I've been a little lazy recently, so I'll leave it like this for now. I'll have time to add a function to upload pictures, pixelate pictures and export functions later.
The above is the entire content of this article. I hope it will be helpful to everyone’s study. I also hope everyone will support VeVb Wulin Network.