Существует несколько способов построения примитивов. Процесс построения векторных фигур в виде пикселей называется растеризацией. Можно выделить 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;