SetPixel é muito lento. Existe uma maneira mais rápida de desenhar em bitmap?

Eu tenho um pequeno programa de pintura em que estou trabalhando. Eu estou usando o SetPixel em um bitmap para fazer esse desenho de linhas. Quando o tamanho do pincel fica grande, como 25 pixels de largura, há uma queda de desempenho notável. Eu estou querendo saber se existe uma maneira mais rápida de desenhar em um bitmap. Aqui está um pouco do pano de fundo do projeto:

  • Eu estou usando bitmaps para que eu possa utilizar camadas, como no Photoshop ou no GIMP.
  • As linhas estão sendo desenhadas manualmente porque isso acabará usando a pressão da mesa gráfica para alterar o tamanho da linha ao longo de seu comprimento.
  • As linhas devem eventualmente ser anti-aliadas / suavizadas ao longo das bordas.

Vou include meu código de desenho apenas no caso de ser este que é lento e não o bit Set-Pixel.

Isto está nas janelas onde a pintura acontece:

private void canvas_MouseMove(object sender, MouseEventArgs e) { m_lastPosition = m_currentPosition; m_currentPosition = e.Location; if(m_penDown && m_pointInWindow) m_currentTool.MouseMove(m_lastPosition, m_currentPosition, m_layer); canvas.Invalidate(); } 

Implementação do MouseMove:

  public override void MouseMove(Point lastPos, Point currentPos, Layer currentLayer) { DrawLine(lastPos, currentPos, currentLayer); } 

Implementação do DrawLine:

  // The primary drawing code for most tools. A line is drawn from the last position to the current position public override void DrawLine(Point lastPos, Point currentPos, Layer currentLayer) { // Creat a line vector Vector2D vector = new Vector2D(currentPos.X - lastPos.X, currentPos.Y - lastPos.Y); // Create the point to draw at PointF drawPoint = new Point(lastPos.X, lastPos.Y); // Get the amount to step each time PointF step = vector.GetNormalisedVector(); // Find the length of the line double length = vector.GetMagnitude(); // For each step along the line... for (int i = 0; i < length; i++) { // Draw a pixel PaintPoint(currentLayer, new Point((int)drawPoint.X, (int)drawPoint.Y)); drawPoint.X += step.X; drawPoint.Y += step.Y; } } 

Implementação do PaintPoint:

  public override void PaintPoint(Layer layer, Point position) { // Rasterise the pencil tool // Assume it is square // Check the pixel to be set is witin the bounds of the layer // Set the tool size rect to the locate on of the point to be painted m_toolArea.Location = position; // Get the area to be painted Rectangle areaToPaint = new Rectangle(); areaToPaint = Rectangle.Intersect(layer.GetRectangle(), m_toolArea); // Check this is not a null area if (!areaToPaint.IsEmpty) { // Go through the draw area and set the pixels as they should be for (int y = areaToPaint.Top; y < areaToPaint.Bottom; y++) { for (int x = areaToPaint.Left; x < areaToPaint.Right; x++) { layer.GetBitmap().SetPixel(x, y, m_colour); } } } } 

Muito obrigado por qualquer ajuda que você possa fornecer.

Você pode bloquear os dados de bitmap e usar pointers para definir manualmente os valores. É muito mais rápido. Embora você tenha que usar código não seguro.

 public override void PaintPoint(Layer layer, Point position) { // Rasterise the pencil tool // Assume it is square // Check the pixel to be set is witin the bounds of the layer // Set the tool size rect to the locate on of the point to be painted m_toolArea.Location = position; // Get the area to be painted Rectangle areaToPaint = new Rectangle(); areaToPaint = Rectangle.Intersect(layer.GetRectangle(), m_toolArea); Bitmap bmp; BitmapData data = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); int stride = data.Stride; unsafe { byte* ptr = (byte*)data.Scan0; // Check this is not a null area if (!areaToPaint.IsEmpty) { // Go through the draw area and set the pixels as they should be for (int y = areaToPaint.Top; y < areaToPaint.Bottom; y++) { for (int x = areaToPaint.Left; x < areaToPaint.Right; x++) { // layer.GetBitmap().SetPixel(x, y, m_colour); ptr[(x * 3) + y * stride] = m_colour.B; ptr[(x * 3) + y * stride + 1] = m_colour.G; ptr[(x * 3) + y * stride + 2] = m_colour.R; } } } } bmp.UnlockBits(data); } 

SetPixel faz isso: bloqueia toda a imagem, define o pixel e desbloqueia

tente fazer isso: você adquire um bloqueio para toda a imagem da memory com lockbits, processa você atualiza e libera o bloqueio depois.

lockbits

Eu costumo usar uma matriz para representar os dados brutos de pixels. Em seguida, copie entre essa matriz e o bitmap com código não seguro.

Fazer a matriz de Color é uma má idéia, já que a estrutura Color é relativamente grande (12 bytes +). Assim, você pode definir sua própria estrutura de 4 bytes (essa é a que eu escolhi) ou simplesmente usar uma matriz de int ou byte .

Você também deve reutilizar seu array, pois o GC no LOH tende a ser caro.

Meu código pode ser encontrado em:

https://github.com/CodesInChaos/ChaosUtil/blob/master/Chaos.Image/

Uma alternativa é escrever todo o seu código usando pointers diretamente no bitmap. Ainda é um pouco mais rápido, mas pode tornar o código mais feio e propenso a erros.

Apenas uma ideia: preencha um bitmap fora da canvas com seus pixels de pincel. Você só precisa gerar novamente este bitmap quando o pincel, tamanho ou cor é alterado. E, em seguida, basta desenhar este bitmap em seu bitmap existente, onde o mouse está localizado. Se você puder modular um bitmap com uma cor, poderá definir os pixels em escala de cinza e modulá-los com a cor do pincel atual.

Você está chamando o GetBitmap dentro do seu loop for nested. Parece que não é necessário, você deve GetBitmap fora dos loops for, pois a referência não vai mudar.

Veja também a resposta do @fantasticfix, o Lockbits quase sempre classifica problemas de desempenho lentos com a obtenção / definição de pixels