SetPixel работает слишком медленно.Есть ли более быстрый способ рисования в растровое изображение? - PullRequest
11 голосов
/ 14 октября 2011

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

  • Я использую растровые изображения, чтобы я мог использовать слои, как в Photoshop или 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);
                }
            }
        }
    }

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

Ответы [ 5 ]

12 голосов
/ 15 октября 2011

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

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);
    }
5 голосов
/ 14 октября 2011

SetPixel делает это: блокирует все изображение, устанавливает пиксель и разблокирует его

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

lockbits

3 голосов
/ 14 октября 2011

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

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

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

Мой код можно найти по адресу:

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

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

1 голос
/ 14 октября 2011

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

0 голосов
/ 14 октября 2011

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

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

...