Почему рисование линии толщиной менее 1,5 пикселей в два раза медленнее, чем рисование линии толщиной 10 пикселей?

32

Я просто играю с FireMonkey, чтобы увидеть, будет ли графическая картина быстрее, чем GDI или Graphics32 (сейчас моя библиотека выбора).

Чтобы узнать, насколько это быстро, я выполнил несколько тестов, но я столкнулся с каким-то нечетным поведением:

Чертежные тонкие линии (< 1,5 пикселя в ширину), по-видимому, чрезвычайно медленны по сравнению с более толстыми линиями: Изображение 684

  • Вертикальная ось: процессор печатает 1000 строк.
  • Горизонтальная ось: тиканье линии *

Результаты довольно стабильны; рисунок всегда становится намного быстрее, если ширина линии больше 1 пикселя.

В других библиотеках, кажется, есть быстрые алгоритмы для одиночных строк, а толстые линии медленнее, потому что сначала создается многоугольник, так почему же FireMonkey наоборот?

Мне в основном нужны однопиксельные линии, поэтому следует рисовать строки по-другому, возможно?

Тесты выполнялись с помощью этого кода:

// draw random lines, and copy result to clipboard, to paste in excel
procedure TForm5.PaintBox1Paint(Sender: TObject; Canvas: TCanvas);
var
  i,iWidth:Integer;
  p1,p2: TPointF;
  sw:TStopWatch;
const
  cLineCount=1000;
begin
  Memo1.Lines.Clear;
  // draw 1000 different widths, from tickness 0.01 to 10
  for iWidth := 1 to 1000 do
  begin
    Caption := IntToStr(iWidth);
    Canvas.BeginScene;
    Canvas.Clear(claLightgray);
    Canvas.Stroke.Kind := TBrushKind.bkSolid;
    Canvas.Stroke.Color := $55000000;
    Canvas.StrokeThickness :=iWidth/100;
    sw := sw.StartNew;
    // draw 1000 random lines
    for I := 1 to cLineCount do
    begin
      p1.Create(Random*Canvas.Width,Random*Canvas.Height);
      p2.Create(Random*Canvas.Width,Random*Canvas.Height);
      Canvas.DrawLine(p1,p2,0.5);
    end;
    Canvas.EndScene;
    sw.Stop;
    Memo1.Lines.Add(Format('%f'#9'%d', [Canvas.StrokeThickness,  Round(sw.ElapsedTicks / cLineCount)]));
  end;
  Clipboard.AsText := Memo1.Text;
end;

Обновление

@Steve Wellens: Действительно, вертикальные линии и горизонтальные линии намного быстрее. На самом деле разница между горизонтальными и вертикальными:

Изображение 685 Диагональные линии: синий, горизонтальные линии: зеленый, вертикальные линии: красный

С вертикальными линиями наблюдается резкое различие между линиями шириной менее 1 пикселя. С диагональными линиями есть наклон между 1.0 и 1.5.

Странная вещь заключается в том, что практически нет разницы между рисованием горизонтальной линии 1 пикселя и картированием одного из 20 пикселей. Я думаю, это то, где аппаратное ускорение начинает меняться?

  • 3
    Возможно, он принимает какие -то сглаживающие решения, например, решает, должен ли быть нарисован какой-либо пиксель линии?
  • 1
    Я согласен (возможно, он не использует алгоритм линии Брезенхэма). Вы можете проверить это, нарисовав чистую вертикальную или горизонтальную линию.
Показать ещё 2 комментария
Теги:
performance
graphics
line
firemonkey

2 ответа

28
Лучший ответ

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

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

Поскольку видеосигналы работают на горизонтальной развертке (думаю, CRT, а не LCD), графические операции традиционно фокусируются на обработке одной горизонтальной развертки за раз.

Вот моя догадка:

Чтобы решить некоторые проблемы с липкой, растеризаторы иногда "подталкивают" линии, так что больше их виртуальных пикселей выравниваются с пикселями устройства. Если горизонтальная линия толщиной 0,25 пикселя находится на полпути между сканирующей линией А и В устройства, эта линия может полностью исчезнуть, потому что она не регистрируется достаточно сильно, чтобы осветить любые пиксели в линии сканирования А или В. Таким образом, растеризатор может подтолкнуть строка "вниз" маленького бита в виртуальных координатах, чтобы он совпадал с пикселями устройства scanline B и создавал хорошую ярко освещенную горизонтальную линию.

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

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

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

Диагональная линия не имеет ярлыков. У этого есть jaggies везде, таким образом, есть много работы сглаживания, чтобы сделать повсюду. В вычислении антиалиасов должна быть рассмотрена (подвыбор) целая матрица точек (не менее 4, возможно, 8) вокруг целевой точки, чтобы решить, сколько частичного значения дать пиксель устройства. Матрица может быть упрощена или полностью исключена для вертикальных или горизонтальных линий, но не для диагоналей.

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

Два смежных пикселя устройства при яркости 40% в порядке. Если единственным выходом растеризатора для линии сканирования является два смежных пиксела с 5%, глаз будет воспринимать промежуток в линии. Не в порядке.

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

Почему 1,5 магическое число для толщины линии? Спросите Пифагора. Если пиксель вашего устройства равен 1 единице по ширине и высоте, длина диагонали пикселя квадратного устройства равна sqrt (1 ^ 2 + 1 ^ 2) = sqrt (2) = 1.41ish. Когда ваша толщина линии больше, чем длина диагонали пикселя устройства, вы всегда должны иметь по крайней мере один "хорошо освещенный" пиксель на выходе линии сканирования независимо от угла линии.

Что моя теория, во всяком случае.

6

В других библиотеках, кажется, есть быстрые алгоритмы для одиночных строк, а толстые линии медленнее, потому что сначала создается многоугольник, так почему же FireMonkey наоборот?

В Graphics32 алгоритм линии Bresenham используется для ускорения линий, которые рисуются с шириной 1px, и это определенно должно быть быстрым. FireMonkey не имеет собственного встроенного растеризатора, вместо этого он делегирует операции рисования другим API (в Windows он будет делегировать либо Direct2D, либо GDI +.)

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

http://www.graphics32.org/news/newsgroups.php?article_id=10249

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

Мне в основном нужны однопиксельные линии, так что я должен рисовать строки по-другому?

Я реализовал новый бэкэнд FireMonkey (новый потомок TCanvas), который опирается на мой собственный механизм Rasterizer VPR. Он должен быть быстрее Direct2D для тонких линий и для текста (даже при том, что он использует методы многоугольной растеризации). Возможно, все еще есть некоторые предостережения, которые необходимо устранить, чтобы заставить его работать на 100% без проблем в качестве бэкэнда Firemonkey. Подробнее здесь:

http://graphics32.org/news/newsgroups.php?article_id=11565

  • 0
    Я должен добавить, что я пересмотрел свою прежнюю теорию о том, что речь идет о методах выбраковки окклюзии. Я думаю, что более вероятно, что случай, когда большая часть геометрии покрывает один и тот же пиксель (т. Е. Более одного края), рассматривается по-разному.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню