Как нарисовать кривую Безье
Автор: Даниил Карапетян
Как нарисовать кривую
Безье. Именно она применяется для построения гладких кривых во всех графических
программах - от PaintBrush до CorelDraw и PhotoShop. Для задания кривой Безье n-ной степени (чем больше степень, тем более кривой
может быть линия; кривая первой степени - отрезок) нужно указать n+1 точку. Первая
и последняя точки будут началом и концом кривой, а остальные точки задаю ее поведение
на других участках. В частности, первая и n-ая точки задают касательные и кривизну
кривой на ее концах. В большинстве программ используются кривые Безье третьего
порядка. Начиная с Delphi5 такую кривую можно нарисовать при помощи функции PolyBezier. Кривая Безье задается параметрически (x=x(t), y=y(t)). Это позволяет ей вести
себя абсолютно произвольно. Если бы она задавалась, как y(x), она не смогла бы
даже сделать поворот на 180 градусов. Функции x(t) и y(t) выглядят так:
x(t)= Cn0 * t0 * (1-t)n * x0 + Cn1 * t1 * (1-t)n-1 * x1 + Cn2 * t2 * (1-t)n-2
* x2 + ... + Cnn * tn * (1-t)0 * xn
y(t)= Cn0 * t0 * (1-t)n * y0 + Cn1 * t1 * (1-t)n-1 * y1 + Cn2 * t2 * (1-t)n-2
* y2 + ... + Cnn * tn * (1-t)0 * yn
где n - порядок кривой, Cni - коэффициенты в разложении бинома Ньютона, t - параметр,
меняющийся от 0 до 1, xi, yi - координаты опорных точек. Эта программа строит кривую Безье n-ного порядка. n задается в SpinEdit1. Все
узлы можно перемещать по полю мышью. Для создания нового узла нужно нажать мышью
на пустое место на поле или увеличить порядок кривой.
uses Math;
const
RectSize = 5;
MaxN = 128;
var
n: integer = -1;
pt: array[0..MaxN] of TPoint;
C: array[0..MaxN] of single;
bm: TBitMap;
function GetBinomialCoefficient(m, i: integer): single;
function Factorial(x: integer): double;
var
i: integer;
begin
result := 1;
for i := 2 to x do
result := result * i;
end;
begin
result := Factorial(m) / (Factorial(i) * Factorial(m - i));
end;
procedure DrawBezier(Canvas: TCanvas; Count: integer);
type
TPointArray = array[word] of TPoint;
PPointArray = ^TPointArray;
var
p: PPointArray;
Step, qx, qy, t, q: single;
i, j: integer;
begin
GetMem(p, sizeof(TPoint) * (Count + 1));
Step := 1.0 / Count;
for i := 0 to Count do
begin
t := i * Step;
qx := 0;
qy := 0;
for j := 0 to n do
begin
q := C[j] * IntPower(1 - t, j) * IntPower(t, n - j);
qx := qx + q * pt[j].x;
qy := qy + q * pt[j].y;
end;
p[i] := Point(round(qx), round(qy));
end;
Canvas.Polyline(Slice(p^, Count + 1));
FreeMem(p);
end;
procedure DrawLines(canvas: TCanvas; const pt: array of TPoint);
var
i: integer;
begin
Canvas.Pen.Color := clGreen;
Canvas.Pen.Width := 1;
Canvas.MoveTo(pt[0].x, pt[0].y);
for i := 0 to n do
begin
Canvas.Rectangle(Bounds(pt[i].x - RectSize, pt[i].y - RectSize,
2 * RectSize, 2 * RectSize));
Canvas.LineTo(pt[i].x, pt[i].y);
end;
end;
procedure Redraw;
begin
with Form1.PaintBox1 do
begin
bm.Canvas.FillRect(Bounds(0, 0, Width, Height));
if Form1.CheckBox1.Checked then
DrawLines(bm.Canvas, pt);
bm.Canvas.PolyBezier(pt);
bm.Canvas.Pen.Color := clRed;
bm.Canvas.pen.Width := Form1.SpinEdit3.Value;
DrawBezier(bm.Canvas, Form1.SpinEdit2.Value);
Canvas.Draw(0, 0, bm);
end;
end;
var
moving: integer = -1;
oldr: TRect;
procedure FillRandom(NewN: integer);
var
i: integer;
begin
randomize;
for i := n + 1 to NewN do
pt[i] := Point(random(Form1.PaintBox1.Width - 20) + 10,
random(Form1.PaintBox1.Height - 20) + 10);
n := NewN;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
bm := TBitmap.Create;
bm.Width := PaintBox1.Width;
bm.Height := PaintBox1.height;
SpinEdit1.MinValue := 1;
SpinEdit1.MaxValue := MaxN;
SpinEdit1.Value := 3;
SpinEdit2.MinValue := 6;
SpinEdit2.MaxValue := MaxN * 4;
SpinEdit2.Value := 50;
SpinEdit2.OnChange := PaintBox1.OnPaint;
SpinEdit3.MinValue := 1;
SpinEdit3.MaxValue := 8;
SpinEdit3.Value := 3;
SpinEdit3.OnChange := PaintBox1.OnPaint;
CheckBox1.Checked := true;
CheckBox1.OnClick := PaintBox1.OnPaint;
end;
procedure TForm1.PaintBox1MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
var
i: integer;
r: TRect;
begin
if Button <> mbLeft then
Exit;
for i := 0 to n do
if (abs(X - pt[i].x) <= RectSize) and (abs(Y - pt[i].y) <= RectSize) then
begin
moving := i;
r.TopLeft := Form1.ClientToScreen(PaintBox1.BoundsRect.TopLeft);
r.BottomRight := Form1.ClientToScreen(PaintBox1.BoundsRect.BottomRight);
GetClipCursor(oldr);
ClipCursor(@r);
Exit;
end;
if moving < 0 then
begin
SpinEdit1.Value := SpinEdit1.Value + 1;
pt[n] := Point(X, Y);
Redraw;
end;
end;
procedure TForm1.PaintBox1MouseMove(Sender: TObject; Shift: TShiftState; X, Y:
Integer);
begin
if moving < 0 then
Exit;
pt[moving] := Point(X, Y);
Redraw;
end;
procedure TForm1.PaintBox1MouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
if (Button = mbLeft) and (moving >= 0) then
begin
moving := -1;
ClipCursor(@oldr);
end;
end;
procedure TForm1.SpinEdit1Change(Sender: TObject);
var
i: integer;
begin
FillRandom(SpinEdit1.Value);
SpinEdit2.MinValue := n * 2;
for i := 0 to n do
C[i] := GetBinomialCoefficient(n, i);
Redraw;
end;
procedure TForm1.PaintBox1Paint(Sender: TObject);
begin
Redraw;
end;
|