【问题标题】:SetPixel is too slow. Is there a faster way to draw to bitmap?SetPixel 太慢了。有没有更快的方法来绘制位图?
【发布时间】:2011-10-14 13:57:00
【问题描述】:

我正在开发一个小型绘图程序。我在位图上使用 SetPixel 来绘制线条。当画笔尺寸变大时,比如 25 像素,性能会明显下降。我想知道是否有更快的方法来绘制位图。以下是该项目的一些背景:

  • 我正在使用位图,以便可以利用图层,例如在 Photoshop 或 The GIMP 中。
  • 正在手动绘制线条,因为这最终将使用图形输入板压力来改变线条在其长度上的大小。
  • 线条最终应沿边缘进行抗锯齿/平滑处理。

我将包含我的绘图代码,以防万一它很慢而不是 Set-Pixel 位。

这是在发生绘画的窗口中:

    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();
    }

MouseMove 的实现:

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

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;
        }
    }

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);
                }
            }
        }
    }

非常感谢您提供的任何帮助。

【问题讨论】:

    标签: c# graphics bitmap gdi layer


    【解决方案1】:

    您可以锁定位图数据并使用指针手动设置值。它要快得多。虽然你必须使用不安全的代码。

    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);
        }
    

    【讨论】:

    • 谢谢。我想我明白发生了什么; ptr 是包含红色、绿色和蓝色像素值的位图数据块,并且正在手动设置它们。我今天会试一试,看看效果如何。非常感谢,我觉得我的理解又提高了一个档次:P 不过有一件事,我认为 C# 中的位图也有一个 alpha 通道。我认为这将在像素颜色信息中与 RGB 一起处理。
    • 您需要指定 msdn.microsoft.com/en-us/library/… 以包含 alpha 组件。只需将PixelFormat.Format24bppRgb 更改为PixelFormat.Format32bppArgb。我相信它应该在 R 之后,所以只需将它抵消 +3。如果您确实使用每像素 32 位,则需要将 x * 3 更改为 x * 4,因为现在每像素 4 字节。
    • 我刚试过这个。现在在所有刷子尺寸上都非常可爱和快速!然而,现在出现了一些奇怪的行为:实际绘图发生在鼠标所在位置的偏移处。我想知道它是否与这里的这一行有关:BitmapData data = bmp.LockBits(new Rectangle(areaToPaint.Right, areaToPaint.Bottom), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);需要为矩形指定一个位置。我使用了提供的“位置”值。对吗?
    • 矩形的position 值是矩形在图片框或面板上的位置。您正在使用鼠标的位置。如果您的图像布局没有居中,您可以使用 (0, 0) 作为位置。如果它居中并且您的图片框比您的图像大,那么您需要调整偏移量。您还应该将new Rectangle(areaToPaint.Right, areaToPaint.Bottom) 更改为new Rectangle(0, 0, bmp.Width, bmp.Height)。我以为areaToPaint 是整个图像的尺寸。
    • 是否有必要使用不安全的代码来做到这一点?难道你不能使用Marshal.WriteByte 写入IntPtr 的内存地址而不是将其转换为byte* 来进行不安全的指针操作吗?还是会慢一些?
    【解决方案2】:

    SetPixel 这样做: is 锁定整个图像,设置像素并解锁它

    尝试这样做:您使用锁位获取整个内存映像的锁,然后处理您更新并释放锁。

    lockbits

    【讨论】:

    • 3年过去了,还是没有例子
    • @rr- 很长一段时间
    【解决方案3】:

    我通常使用一个数组来表示原始像素数据。然后使用不安全代码在该数组和位图之间复制。

    制作Color 的数组是个坏主意,因为Color 结构体相对较大(12 字节以上)。因此,您可以定义自己的 4 字节结构(这是我选择的),也可以简单地使用 intbyte 的数组。

    您还应该重用您的阵列,因为 LOH 上的 GC 往往很昂贵。

    我的代码可以在:

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

    另一种方法是使用直接将指针写入位图中的所有代码。这还是有点快,但会使代码更难看,更容易出错。

    【讨论】:

    • 您是使用目标位图大小的数组还是仅使用要修改的区域大小的数组?
    • 在我的应用程序中,我使用了一个与位图大小相同的数组,但我需要处理整个位图。但即使我只在位图的一部分上工作,我仍然会使用完整的数组,但可能只填写我需要的部分。这样,在处理图像的不同大小部分时,我不需要分配新数组。
    【解决方案4】:

    只是一个想法:用画笔像素填充屏幕外位图。您只需要在画笔、大小或颜色发生更改时重新生成此位图。然后只需将此位图绘制到鼠标所在的现有位图上。 如果您可以使用颜色调制位图,则可以将像素设置为灰度并使用当前画笔颜色对其进行调制。

    【讨论】:

    • 这听起来是个很酷的主意。谢谢。我想如果我使用透明度,那么在绘制现有线条时它会起作用。
    【解决方案5】:

    您在嵌套的 for 循环中调用 GetBitmap。看起来没有必要,您应该在 for 循环之外使用 GetBitmap,因为引用不会改变。

    还请查看@fantasticfix 的答案,Lockbits 几乎总是对获取/设置像素的性能缓慢问题进行分类

    【讨论】:

      猜你喜欢
      • 2014-10-04
      • 1970-01-01
      • 2015-05-30
      • 2022-10-15
      • 2022-01-15
      • 2023-04-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多