SetPixel слишком медленный. Есть ли более быстрый способ рисования в bitmap?

у меня есть небольшая программа рисования, над которой я работаю. Я использую SetPixel на растровом изображении, чтобы сделать этот рисунок линий. Когда размер кисти становится большим, например, 25 пикселей, наблюдается заметное снижение производительности. Мне интересно, есть ли более быстрый способ рисования растрового изображения. Вот немного фона проекта:

  • Я использую растровые изображения, чтобы я мог использовать слои, как в Photoshop или GIMP.
  • линии рисуются вручную, потому что это будет в конечном итоге используйте давление графического планшета, чтобы изменить размер линии по ее длине.
  • линий в конечном итоге должен быть анти-aliaced/сглаженные по краям.

я включу свой код рисования на всякий случай, если это медленный, а не бит набора пикселей.

это в окнах, где происходит картина:

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

большое спасибо за любую помощь вы можете предоставить.

5 ответов


вы можете заблокировать данные растрового изображения и использовать указатели для ручной установки значений. Это намного быстрее. Хотя вам придется использовать небезопасный код.

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 делает это: блокирует все изображение, устанавливает пиксель и разблокирует его

попробуйте сделать это: вы получаете блокировку для всего образа памяти с помощью lockbits, обрабатываете обновление и освобождаете блокировку после.

lockbits прямой


Я обычно использую массив для представления необработанных пиксельных данных. А затем скопируйте между этим массивом и растровым изображением с небезопасным кодом.

Создание массива Color - Это плохая идея, поскольку Color структура относительно большая (12 байт+). Таким образом, вы можете либо определить свою собственную 4-байтовую структуру(это тот, который я выбрал), либо просто использовать массив int или byte.

вы также должны повторно использовать свой массив, так как GC на LOH имеет тенденцию быть дорогим.

мой код может быть найден по ссылке:

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

альтернативой является запись всего кода с помощью указателей непосредственно в растровое изображение. Это немного быстрее, но может сделать код более уродливым и более подверженным ошибкам.


просто идея: заполните экранное растровое изображение пикселями кисти. Вам нужно только восстановить это растровое изображение при изменении кисти, размера или цвета. А затем просто нарисуйте это растровое изображение на существующем растровом изображении, где находится мышь. Если вы можете модулировать растровое изображение цветом, вы можете установить пиксели в оттенках серого и модулировать его текущим цветом кисти.


вы вызываете GetBitmap в своем вложенном цикле for. Похоже, что это не обязательно, вы должны GetBitmap вне циклов for, поскольку ссылка не изменится.

Также посмотрите на ответ @fantasticfix, Lockbits почти всегда сортирует проблемы с медленной производительностью при получении / настройке пикселей