Winforms: Как ускорить Invalidate ()? - PullRequest
8 голосов
/ 05 июня 2009

Я разрабатываю приложение для рисования с сохранением режима в GDI +. Приложение может рисовать простые формы на холсте и выполнять базовое редактирование. Математика, которая делает это, оптимизирована до последнего байта и не является проблемой. Я рисую на панели, которая использует встроенный Controlstyles.DoubleBuffer.

Теперь моя проблема возникает, если я разверну свое приложение на большом мониторе (в моем случае это HD). Если я попытаюсь нарисовать линию от одного угла (большого) холста к диагонали, противоположной другой, он начнет отставать, а процессор поднимется высоко.

Каждый графический объект в моем приложении имеет ограничивающий прямоугольник. Таким образом, когда я аннулирую ограничивающий прямоугольник линии, идущей от одного угла развернутого приложения к противоположному диагональному, этот ограничивающий прямоугольник практически такой же большой, как холст. Когда пользователь рисует линию, это делает недействительной ограничивающую рамку при событии mousemove, и видна явная задержка. Эта задержка также существует, если линия является единственным объектом на холсте.

Я пытался оптимизировать это разными способами. Если я рисую более короткую линию, процессор и отставание снижаются. Если я удаляю Invalidate () и сохраняю весь другой код, приложение работает быстро. Если я использую Регион (который охватывает только фигуру) для аннулирования вместо ограничивающего прямоугольника, это так же медленно. Если я разделю ограничивающий прямоугольник на ряд меньших прямоугольников, лежащих вплотную, тем самым уменьшая область аннулирования, видимого прироста производительности не видно.

Таким образом, я в растерянности. Как я могу ускорить аннулирование?

Кстати, и Paint.Net, и Mspaint страдают от одних и тех же недостатков. Однако Word и PowerPoint, кажется, способны рисовать линии, как описано выше, без задержки и нагрузки на процессор вообще. Таким образом можно добиться желаемых результатов, вопрос в том, как?

Ответы [ 4 ]

8 голосов
/ 08 июня 2009

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

Причина этого в том, что GDI + (как и сам GDI) делает недействительными области в прямоугольных формах, как вы указываете с помощью ограничительной рамки. Вы можете убедиться в этом сами, протестировав некоторые горизонтальные и вертикальные линии по сравнению с линиями, где наклон подобен аспекту вашей области отображения.

Итак, допустим, ваш холст 640х480. Если вы рисуете линию от 0,0 до 639 479; Invalidate () сделает недействительным весь регион с 0,0 до 639,0 сверху вниз, с 0,479 до 639,479 снизу. Горизонтальная линия, скажем, от 0,100 до 639,100, дает прямоугольник высотой всего 1 пиксель.

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

Так что в качестве решения, если у вас очень большая линия, разбейте ее на четверти или восьмые, и производительность должна значительно возрасти. Пересматривая приведенный выше пример, если вы просто разделите пополам на две части, вы уменьшите общую недействительную область до 0,0 x 319 239 плюс 320 240 x 639 479.

Вот наглядный пример разделения на четверть. Розовая область - это то, что недействительно. К сожалению, ТАК не позволяет мне публиковать изображения или более 1 ссылки, но этого должно быть достаточно, чтобы все объяснить.

(линия разделена на четверти, общая недействительная область - 1/4 поверхности)

экстент 640x480 с четырьмя прямоугольниками одинакового размера, вырезанными за линией, проведенной по диагонали

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

1 голос
/ 11 июня 2009

Вы не можете ускорить Invalidate. Причина, по которой он медленный, заключается в том, что он отправляет событие WM_PAINT в очередь сообщений. Затем он фильтруется, и в конце концов ваш OnPaint даже вызывается. Что вам нужно сделать, это рисовать прямо в вашем элементе управления во время события MouseMove.

В любом выполняемом мной элементе управления, который требует некоторой плавной анимации, мое событие OnPaint обычно вызывает только функцию PaintMe. Таким образом, я могу использовать эту функцию для перерисовки элемента управления в любое время.

1 голос
/ 06 июня 2009

Как насчет другой ветки, которая "публикует обновления" на реальном холсте.

Image paintImage;
private readonly object paintObject = new object();
public _ctor() { paintImage = new Image(); }

override OnPaint(PaintEventArgs pea) {
    if (needUpdate) {
        new Thread(updateImage).Start();
    }
    lock(paintObject) {
        pea.DrawImage(image, 0, 0, Width, Height);
    }
}

public void updateImage() {
    // don't draw to paintImage directly (it might cause threading issues)
    Image img = new Image();
    using (Graphics g = img.GetGraphics()) {
        foreach (PaintableObject po in renderObjects) {
            g.DrawObject(po);
        }
    }
    lock(paintObject){
        using (Graphics g = paintImage.GetGraphics()) {
            g.DrawImage(img, 0, 0, g.Width, g.Height);
        }
    }
    needUpdate = false;
}

Просто идея, поэтому я не проверял код; -)

1 голос
/ 05 июня 2009

Чтобы уточнить: пользователь рисует прямую линию или ваша линия на самом деле представляет собой набор сегментов линии, соединяющих точки мыши? Если линия является прямой линией от начала координат до текущей точки мыши, не используйте Invalidate (), а вместо этого используйте кисть XOR, чтобы нарисовать не отменяемую линию, а затем отредактируйте предыдущую линию, только Invalidating, когда пользователь завершит рисование.

Если вы рисуете несколько маленьких отрезков, просто лишите законной силы ограничительный прямоугольник самого последнего отрезка.

...