Почему рисование линии толщиной менее 1,5 пикселей в два раза медленнее, чем рисование линии толщиной 10 пикселей? - PullRequest
32 голосов
/ 23 января 2012

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

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

Рисование тонких линий (<1,5 пикселя в ширину) кажется очень медленным по сравнению с более толстыми линиями: </em> Performance

  • Вертикальная ось: процессор тикает, чтобы нарисовать 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;

Обновление

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

Difference between Diagonal, Horitonzal and Vertical lines Диагональные линии: синий, Горизонтальные линии: зеленый, Вертикальные линии: красный

В случае вертикальных линий наблюдается резкая разница между линиями шириной менее 1 пикселя. С диагональными линиями есть наклон между 1,0 и 1,5.

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

Ответы [ 2 ]

28 голосов
/ 24 января 2012

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

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

Поскольку видеосигналы работают с горизонтальной разверткой (например, ЭЛТ, а не с ЖК-дисплеем), графические операции традиционно сосредоточены на обработке всего одной горизонтальной линии сканирования за раз.

Вот мое предположение:

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

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

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

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

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

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

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

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

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

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

6 голосов
/ 26 января 2012

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

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

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

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

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

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

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

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

...