Подтвердить что ты не робот

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

У меня есть небольшая программа рисования, над которой я работаю. Я использую 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);
                }
            }
        }
    }

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

4b9b3361

Ответ 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);
    }

Ответ 2

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

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

lockbits

Ответ 3

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

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

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

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

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

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

Ответ 4

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

Ответ 5

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

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