Em um projeto recente, quero implementar uma prancheta no estilo pixel, onde pequenas grades de pixels podem ser apagadas, as seleções de quadros mudam de cor e vários gráficos podem ser apagados. Um projeto tão pequeno pode parecer simples, mas contém muitos. coisas.
Desenhe uma grade de pixelsVamos primeiro definir a classe da grade de pixels
Pixel = função (opção) { this.x = option.x; this.y = option.y; this.shape = option.shape;
x e y representam as coordenadas do ponto central. Foi isso que fiz no início. Defina o caminho primeiro.
createPath: função (ctx) {if (this.shape === 'círculo') {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 pontos = this.getPoints(); , i) { ctx[i == 0 ? 'moveTo' : 'lineTo'](ponto.x, ponto.y }) ctx.lineTo(pontos[0].x, pontos[0].y);},
A grade de pixels suporta círculos e retângulos. Depois que o caminho é definido, ele é desenhado.
desenhar: função (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();}
Em seguida, crie a grade de pixels em lotes por meio de um loop:
for (var i = stepX + 0,5; i < canvas.width; i+=stepX) {for (var j = stepY + 0,5; j < canvas.height; j+=stepY) {var pixel = new Pixel({x : i,y: j,forma: 'círculo'})box.push(pixel);pixel.draw(ctx);}}
Isso parece perfeito, mas há uma grande desvantagem. Cada vez que um pixel é desenhado, ele é trazido de volta ao contexto e o estado da tela é alterado a cada vez. Isso levará a um desempenho de renderização ruim porque há muitos deles. Se a tela for relativamente grande, o desempenho é muito preocupante e há algumas operações na prancheta. É inadequado alterar o estado da tela com tanta frequência.
Portanto, a abordagem correta é: devemos definir todos os caminhos e é melhor desenhá-los na tela em lotes de uma só vez;
//Define a posição do pixel for (var i = stepX + .5; i < canvas.width; i+=stepX) {for (var j = stepY + .5; j < canvas.height; j+=stepY) { var pixel = novo Pixel({x: i,y: j,forma: 'circle'})box.push(pixel);}}//Desenho em lote console.time('time');ctx.beginPath();for (var c = 0; c < box.length; c++) {var círculo = caixa[c];ctx.moveTo(círculo.x + 3, círculo.y);circle.createPath(ctx);}ctx.closePath();ctx.stroke();console.timeEnd('time');
Você pode ver que essa eficiência de renderização é muito rápida, e o estado do canvas é alterado o mínimo possível, pois toda vez que o estado do contexto é alterado, o canvas será redesenhado, e esse estado é um estado global.
Interação de grade de pixelsO requisito do projeto é que os pixels possam ser apagados pressionando e movendo o mouse na tela. Isso contém dois pontos de conhecimento, um é como obter a grade de pixels no caminho do movimento do mouse e o segundo são problemas de desempenho, por causa de. nossas necessidades O requisito é desenhar 80.000 pontos, sem falar em mais nada, apenas o loop levará dezenas ou centenas de milissegundos, sem falar no desenho e na renderização. Vejamos primeiro a primeira pergunta:
Coloque a grade sob o caminho do movimento do mouseVendo esse problema, podemos facilmente pensar em escrever uma função para obter a posição do mouse através da posição do mouse, que contém a grade, e então atualizar novamente o cálculo da posição toda vez que ele se mover. pode ser cumprido, mas se o mouse passar É impossível fazê-lo rapidamente. A posição de cada ponto pode ser calculada e o efeito será inconsistente. Vamos mudar nosso pensamento. Podemos saber claramente os pontos inicial e final do caminho que o mouse passa. Imaginamos todo o caminho do desenho como um segmento de linha. Então o problema se torna um algoritmo para cruzar o segmento de linha com o original. segmento é A espessura do pincel e o caminho pelo qual o segmento de linha passa são os caminhos do movimento do mouse, e os círculos que se cruzam com eles são as grades que precisam ser alteradas. A conversão em código é a seguinte:
função sqr (x) { retornar x * x } função dist2 (p1, p2) { retornar sqr (p1.x - p2.x) + sqr (p1.y - p2.y) } função distToSegmentSquared (p, v, w ) { var l2 = dist2(v, w); if (l2 == 0) retornar dist2(p, v); ((px - vx) * (wx - vx) + (py - vy) * (wy - vy)) / l2 if (t < 0) return dist2(p, v); (p, w); return dist2(p, { x: vx + t * (wx - vx), y: vy + t * (wy - vy) }); @description Calcule se o segmento de linha intercepta o círculo* @param {x: num, y: num} p ponto central do círculo* @param {x: num, y: num} v ponto inicial do segmento de linha* @param {x : num, y: num } w ponto final do segmento de linha */ function distToSegment(p, v, w) { var offset = pathHeight; deslocamento; || px > maxX) && (py < minY || py > maxY)) { return Number.MAX_VALUE } return Math.sqrt(distToSegmentSquared(p, v, w));
A lógica específica não será elaborada. Os leitores poderão ler o código sozinhos. Então, ao obter a grade de interseção, exclua os dados da caixa e renderize-os novamente, você poderá ver o efeito.
Da mesma forma, podemos criar um efeito de tingimento e, em seguida, implementar uma pequena demonstração da prancheta de pixel da tela. Porém, para criar um efeito de tingimento, você deve usar o primeiro método de desenho. Cada pixel deve ser um objeto, pois o estado de cada objeto é independente, porém, não há necessidade de se preocupar com o desempenho. basicamente não haverá sensação de atraso. O efeito é aproximadamente o seguinte:
Tenho estado um pouco preguiçoso recentemente, então vou deixar assim por enquanto, terei tempo para adicionar uma função para fazer upload de imagens, pixelizar imagens e exportar funções mais tarde.
O texto acima é todo o conteúdo deste artigo. Espero que seja útil para o estudo de todos. Também espero que todos apoiem a Rede VeVb Wulin.