Содержание

Построение двумерных примитивов

Существует несколько способов построения примитивов. Процесс построения векторных фигур в виде пикселей называется растеризацией. Можно выделить 2 наиболее значимых направления в растеризации. Трассировка лучей и сканирование линией.
В данной статье будет рассмотрены методы на основе сканирования линией. Когда фигура получается путем перебора линий сверху вниз и пикселеи линии с лева на права.
Всего на всего у нас несколько примитивов: прямая линия, кривая линия, окружность, треугольник, прямоугольник, много угольник. А также закрашенные текстурированные или с переливами цвета. Так вот процесс растеризиции сложной векторной картины. Можно разделить на стадии, как в конвейерной сборке. Применяя преобразования к координатам фигуры она растягивается, вращается. Затем фигура разбивается на примитивы и передаётся в блок растеризации. Где эти примитивы бьются на ещё более мелкие. Таким образом сводя сложную задачу к более простым.

Кривая линия разбивается на сегменты с малой кривизной такие как кривые Безье. Кривые безье разбиваются на отрезки прямых линий. Линии разбиваются на горизонтальные участки. А те на пиксели. Сплошные фигуры разбиваются на многоугольники. Те на треугольники или квадраты.

В первой части рассмотрим как строятся основные примитивы, а во второй части уже будут приведены профессиональные решения с учётом требования гладкости линий(Anti-aliasing).

Сглаживание линий

Сглаживание краёв

Для сглаживания краёв применяют различные методы. Это построенные особым образом примитивы.

Используя алгоритм Бу или Брезенхема. Также вы можете просто нарисовать примитивы, а затем наложить фильтр Размывание или применив устранение контурных неровностей.

Прямая линия

Теория

Реализация

Procedure Line2D (x1, y1, x2, y2: Integer; Color: TColor);
    Var e,i,x,y,dx,dy,sx,sy:   Integer;
Begin
    x := x1;
    y := y1;
    dx := Abs(x2 - x1);
    dy := Abs(y2 - y1);
    sx := Sign(x2 - x1);
    sy := Sign(y2 - y1);
 
    If (dx = 0) And (dy = 0) Then
        Begin
            SetPixel(x1, y1, Color);
            Exit;
        End;
    If dy < dx Then
        Begin
            e := 2*dy - dx;
            i := 1;
            Repeat
                SetPixel(x, y, Color);
                While e >= 0 Do
                    Begin
                        inc(y, sy);
                        dec(e, 2*dx)
                    End;
                inc(x, sx);
                inc(e, 2*dy);
                inc(i)
            Until i > dx;
            SetPixel (x, y, Color);
        End
    Else
        Begin
            e := 2*dx - dy;
            i := 1;
            Repeat
                SetPixel(x, y, Color);
                While e >= 0 Do
                    Begin
                        inc(x, sx);
                        dec(e, 2*dy)
                    End;
                inc(y, sy);
                inc(e, 2*dx);
                inc(i)
            Until i > dy;
            SetPixel (x, y, Color);
        End;
End;

Горизонтальная Линия

Вспомогательная процедура удобна для рисования закрашенных примитивов.

{рисуем горизонтальную линию}
procedure HLine(x1,x2,y:Integer; Color:Byte);
var x:Integer;
 yy:Word;
begin
yy:=Word(y)*Width;
for x:=x1 to x2 do
 Buffer^[yy+x]:=Color;
end;

Треугольник

Прямоугольник

Теория

Реализация

Тут все просто, рисуем в два цикла:

{Рисуем квадрат}
procedure Rectangle(x1,y1,x2,y2:Integer; Color:TColor);
var i:Integer;
yy:Word;
w,h:Integer;
begin
w:=x2-x1;
h:=y2-y1;
if w<0 then
 begin
 i:=x1; x1:=x2; x2:=i;
 w:=-w;
 end;
if h<0 then
 begin
 i:=y1; y1:=y2; y2:=i;
 h:=-h;
 end;
 
for i:=0 to w do
 begin
 SetPixel(x1+i,y1,Color);
 SetPixel(x1+i,y2,Color);
 end;
 
for i:=1 to h-1 do
 begin
 SetPixel(x1,y1+i,Color);
 SetPixel(x2,y1+i,Color);
 end;
end;
 
{Рисуем закрашенный квадрат}
procedure FillRectangle(x1,y1,x2,y2:Integer; Color:TColor);
var i:Integer;
w,h:Integer;
begin
w:=x2-x1;
h:=y2-y1;
if w<0 then
 begin
 i:=x1; x1:=x2; x2:=i;
 w:=-w;
 end;
//Отсечение не видимых частей которые выходят за облость видимости(полотна, экрана, битмапа)
if h<0 then
 begin
 i:=y1; y1:=y2; y2:=i;
 h:=-h;
 end;
 
if y1<0 then
 begin
 h:=h+y1;
 y1:=0;
 end;
if (y1<Height) and (y1+h>=Height) then  h:=Height-y1;
 
if x1<0 then
 begin
 w:=w+x1;
 x1:=0;
 end;
if (x1<Width) and (x1+w>=Width) then  w:=Width-x1;
if x1>=Width then w:=-1;
if y1>=Height then h:=-1;
// Вывод
for i:=0 to h do
 HLine(x1,x1+w,i+y1,Color);
end;

Окружность

Теория

Реализация

Код на Delphi:

{Рисуем окружность}
procedure Circle(xc, yc,r:Integer; Color:TColor);
var y,x,d:Integer;
begin
x:=0;
y:=r;
d:=3-2*r;
while x <= y do
 begin
   SetPixel(xc+x, yc+y, Color);
   SetPixel(xc+y, yc+x, Color);
   SetPixel(xc+y, yc-x, Color);
   SetPixel(xc+x, yc-y, Color);
   SetPixel(xc-x, yc-y, Color);
   SetPixel(xc-y, yc-x, Color);
   SetPixel(xc-y, yc+x, Color);
   SetPixel(xc-x, yc+y, Color);
 if (d < 0) then
  begin
  d:= d + 4*x + 6
  end
  else begin
       d:= d + 4*(x-y) + 10;
       dec(y);
       end;
 inc(x);
 end;
end;

Второй вариант вывода окружности через сплайны.

procedure CalcBezierEllipse(CX, CY, A, B: Integer; var BezPts: array of TPoint);
const
  MP = 0.55228475;
var
  i, CX2,CY2: Integer;
 
begin
  Assert(Length(BezPts) = 13);
  CX2 := 2 * CX;  CY2 := 2 * CY;
  BezPts[0] := Point(CX+1*A,CY+0*B);
  BezPts[1] := Point(CX+1*A,CY+MP*B);
  BezPts[2] := Point(CX+MP*A,CY+1*B);
  BezPts[3] := Point(CX+0*A,CY+1*B);
  BezPts[4] := Point(CX-MP*A,CY+1*B);
  BezPts[5] := Point(CX-1*A,CY+MP*B);
  for i := 0 to 5 do
    BezPts[i + 6] := Point(CX2 - BezPts[i].X, CY2 - BezPts[i].Y);
  BezPts[12] := BezPts[0];
end;
 
procedure Ellips(CX, CY, A, B: Integer);
var 
 BezPts: array [0..12] of TPoint;
 i:Integer;
 p0,p1,p2,p3:TPoint;
begin
CalcBezierEllipse(CX, CY, A, B,BezPts);
for i:=0 to 3 do
 begin
 p0:=BezPts[i*3+0];
 p1:=BezPts[i*3+1];
 p2:=BezPts[i*3+2];
 p3:=BezPts[i*3+3]; // следующая кривая начинается с последний точки предыдущей.
 Bezier(p0,p1,p2,p3);
 end;
end;

Дополнительная литература

http://www.bsu.by/main.aspx?guid=168261
http://www.cse.iitb.ac.in/~paragc/teaching/2010/cs475/papers/bresenham_line.pdf