source: trunk/Packages/bgrabitmap/bezier.inc

Last change on this file was 2, checked in by chronos, 5 years ago
File size: 39.7 KB
Line 
1{$IFDEF INCLUDE_INTERFACE}
2{$UNDEF INCLUDE_INTERFACE}
3
4type
5 { TCubicBezierCurve }
6 {* Definition of a Bézier curve of order 3. It has two control points ''c1'' and ''c2''. Those are not reached by the curve }
7 TCubicBezierCurve = object
8 private
9 function SimpleComputePoints(AAcceptedDeviation: single = 0.1; AIncludeFirstPoint: boolean = true): ArrayOfTPointF;
10 public
11 {** Starting point (reached) }
12 p1: TPointF;
13 {** First control point (not reached by the curve) }
14 c1: TPointF;
15 {** Second control point (not reached by the curve) }
16 c2: TPointF;
17 {** Ending point (reached) }
18 p2: TPointF;
19 {** Computes the point at time ''t'', varying from 0 to 1 }
20 function ComputePointAt(t: single): TPointF;
21 {** Split the curve in two such that ''ALeft.p2'' = ''ARight.p1'' }
22 procedure Split(out ALeft, ARight: TCubicBezierCurve);
23 {** Compute an approximation of the length of the curve. ''AAcceptedDeviation'' indicates the
24 maximum orthogonal distance that is ignored and approximated by a straight line. }
25 function ComputeLength(AAcceptedDeviation: single = 0.1): single;
26 {** Computes a polygonal approximation of the curve. ''AAcceptedDeviation'' indicates the
27 maximum orthogonal distance that is ignored and approximated by a straight line.
28 ''AIncludeFirstPoint'' indicates if the first point must be included in the array }
29 function ToPoints(AAcceptedDeviation: single = 0.1; AIncludeFirstPoint: boolean = true): ArrayOfTPointF;
30 procedure CopyToPath(ADest: IBGRAPath);
31 function GetBounds: TRectF;
32 end;
33
34 {** Creates a structure for a cubic Bézier curve }
35 function BezierCurve(origin, control1, control2, destination: TPointF) : TCubicBezierCurve; overload;
36
37type
38 { TQuadraticBezierCurve }
39 {* Definition of a Bézier curve of order 2. It has one control point }
40 TQuadraticBezierCurve = object
41 private
42 function SimpleComputePoints(AAcceptedDeviation: single = 0.1; AIncludeFirstPoint: boolean = true): ArrayOfTPointF;
43 function ComputeExtremumPositionOutsideSegment: single;
44 public
45 {** Starting point (reached) }
46 p1: TPointF;
47 {** Control point (not reached by the curve) }
48 c: TPointF;
49 {** Ending point (reached) }
50 p2: TPointF;
51 {** Computes the point at time ''t'', varying from 0 to 1 }
52 function ComputePointAt(t: single): TPointF;
53 {** Split the curve in two such that ''ALeft.p2'' = ''ARight.p1'' }
54 procedure Split(out ALeft, ARight: TQuadraticBezierCurve);
55 {** Compute the '''exact''' length of the curve }
56 function ComputeLength: single;
57 {** Computes a polygonal approximation of the curve. ''AAcceptedDeviation'' indicates the
58 maximum orthogonal distance that is ignored and approximated by a straight line.
59 ''AIncludeFirstPoint'' indicates if the first point must be included in the array }
60 function ToPoints(AAcceptedDeviation: single = 0.1; AIncludeFirstPoint: boolean = true): ArrayOfTPointF;
61 procedure CopyToPath(ADest: IBGRAPath);
62 function GetBounds: TRectF;
63 end;
64
65 {** Creates a structure for a quadratic Bézier curve }
66 function BezierCurve(origin, control, destination: TPointF) : TQuadraticBezierCurve; overload;
67 {** Creates a structure for a quadratic Bézier curve without curvature }
68 function BezierCurve(origin, destination: TPointF) : TQuadraticBezierCurve; overload;
69
70type
71 { A quasi-standard rational quadratic Bezier curve is defined by three points and a number:
72 p1 = starting point
73 c = control point
74 p2 = ending point
75 weight = weight for the control point
76
77 The curve is defined with the function (t in [0;1]):
78 f: t -> ((1-t)^2*p1 + 2*t*(1-t)*weight*c + t^2*p2) / (1-t)^2 + 2*t*(1-t)*weight + t^2)
79
80 The curve is an arc of:
81 - ellipse when weight in ]-1;1[
82 - parabola when weight = 1 (classical quadratic Bezier curve)
83 - hyperbola when weight > 1
84
85 A negative weight give the complementary curve for its positive counterpart.
86 So when weight <= -1 the curve is discontinuous:
87 - infinite branches of parabola when weight = -1
88 - infinite branches of hyperbola and symetric hyperbola when weight < -1
89
90 To transform a rational quadratic Bezier curve with an affin transformation, you
91 only have to transform the three points and leave the weight as it is. }
92
93 ArrayOfSingle = array of single;
94
95 { TRationalQuadraticBezierCurve }
96 {* Definition of a quasi-standard rational Bézier curve of order 2. It has one weighted control point }
97 TRationalQuadraticBezierCurve = object
98 //** Starting, control and ending points
99 p1, c, p2 : TPointF;
100 //** Weight of control point
101 weight : single;
102 private
103 function GetIsInfinite: boolean;
104 function InternalComputePoints(AInfiniteBounds: TRectF; AAcceptedDeviation: single = 0.1; AIncludeFirstPoint: boolean = true): ArrayOfTPointF;
105 function GetBoundingPositions(AIncludeFirstAndLast: boolean; ASorted: boolean): ArrayOfSingle;
106 public
107 function ComputePointAt(t: single): TPointF;
108 function ComputeLength(AAcceptedDeviation: single = 0.1): single;
109 function ToPoints(AAcceptedDeviation: single = 0.1; AIncludeFirstPoint: boolean = true): ArrayOfTPointF; overload;
110 function ToPoints(AInfiniteBounds: TRectF; AAcceptedDeviation: single = 0.1; AIncludeFirstPoint: boolean = true): ArrayOfTPointF; overload;
111 function GetBounds: TRectF;
112 procedure Split(out ALeft, ARight: TRationalQuadraticBezierCurve);
113 property IsInfinite: boolean read GetIsInfinite;
114 end;
115
116 function BezierCurve(origin, control, destination: TPointF; Aweight:single) : TRationalQuadraticBezierCurve; overload;
117
118type
119 TEasyBezierCurveMode= (cmAuto, cmCurve, cmAngle);
120 TEasyBezierPointTransformFunc = function(APoint: PPointF; AData: Pointer): TPointF of object;
121
122 { TEasyBezierCurve }
123
124 TEasyBezierCurve = object
125 private
126 function GetCurveMode(AIndex: integer): TEasyBezierCurveMode;
127 function GetCurveStartPoint: TPointF;
128 function GetPoint(AIndex: integer): TPointF;
129 function GetPointCount: integer;
130 procedure SetClosed(AValue: boolean);
131 procedure SetCurveMode(AIndex: integer; AValue: TEasyBezierCurveMode);
132 procedure SetMinimumDotProduct(AValue: single);
133 procedure SetPoint(AIndex: integer; AValue: TPointF);
134 protected
135 FCurves: array of record
136 isCurvedToNext,isCurvedToPrevious: boolean;
137 Center,ControlPoint,NextCenter: TPointF;
138 end;
139 FInvalidated: boolean;
140 FPoints: array of record
141 Coord: TPointF;
142 CurveMode: TEasyBezierCurveMode;
143 end;
144 FMinimumDotProduct: single;
145 FClosed: boolean;
146 function MaybeCurve(start1, end1, start2, end2: integer): boolean;
147 procedure ComputeQuadraticCurves;
148 function PointTransformNone(APoint: PPointF; {%H-}AData: Pointer): TPointF;
149 function PointTransformOffset(APoint: PPointF; AData: Pointer): TPointF;
150 public
151 procedure Init;
152 procedure Clear;
153 procedure SetPoints(APoints: array of TPointF; ACurveMode: TEasyBezierCurveMode); overload;
154 procedure SetPoints(APoints: array of TPointF; ACurveMode: array of TEasyBezierCurveMode); overload;
155 procedure CopyToPath(ADest: IBGRAPath); overload;
156 procedure CopyToPath(ADest: IBGRAPath; AOffset: TPointF); overload;
157 procedure CopyToPath(ADest: IBGRAPath; ATransformFunc: TEasyBezierPointTransformFunc; ATransformData: Pointer); overload;
158 property Point[AIndex: integer]: TPointF read GetPoint write SetPoint;
159 property CurveMode[AIndex: integer]: TEasyBezierCurveMode read GetCurveMode write SetCurveMode;
160 property PointCount: integer read GetPointCount;
161 property MinimumDotProduct: single read FMinimumDotProduct write SetMinimumDotProduct;
162 property Closed: boolean read FClosed write SetClosed;
163 property CurveStartPoint: TPointF read GetCurveStartPoint;
164 function ToPoints: ArrayOfTPointF;
165 function ComputeLength: single;
166 end;
167
168const
169 EasyBezierDefaultMinimumDotProduct = 0.707;
170
171 function EasyBezierCurve(APoints: array of TPointF; AClosed: boolean; ACurveMode: TEasyBezierCurveMode;
172 AMinimumDotProduct: single = EasyBezierDefaultMinimumDotProduct): TEasyBezierCurve; overload;
173
174 function EasyBezierCurve(APoints: array of TPointF; AClosed: boolean; ACurveMode: array of TEasyBezierCurveMode;
175 AMinimumDotProduct: single = EasyBezierDefaultMinimumDotProduct): TEasyBezierCurve; overload;
176
177{$ENDIF}
178
179{$IFDEF INCLUDE_IMPLEMENTATION}
180{$UNDEF INCLUDE_IMPLEMENTATION}
181//-------------- Bézier curves definitions ----------------
182// See : http://en.wikipedia.org/wiki/B%C3%A9zier_curve
183
184// Define a Bézier curve with two control points.
185function BezierCurve(origin, control1, control2, destination: TPointF): TCubicBezierCurve;
186begin
187 result.p1 := origin;
188 result.c1 := control1;
189 result.c2 := control2;
190 result.p2 := destination;
191end;
192
193// Define a Bézier curve with one control point.
194function BezierCurve(origin, control, destination: TPointF
195 ): TQuadraticBezierCurve;
196begin
197 result.p1 := origin;
198 result.c := control;
199 result.p2 := destination;
200end;
201
202//straight line
203function BezierCurve(origin, destination: TPointF): TQuadraticBezierCurve;
204begin
205 result.p1 := origin;
206 result.c := (origin+destination)*0.5;
207 result.p2 := destination;
208end;
209
210// rational Bezier curve
211function BezierCurve(origin, control, destination: TPointF; Aweight:single) : TRationalQuadraticBezierCurve;
212begin
213 result.p1 := origin;
214 result.c := control;
215 result.p2 := destination;
216 result.weight := Aweight;
217end;
218
219function ComputeBezierCurvePrecision(pt1, pt2, pt3, pt4: TPointF; AAcceptedDeviation: single = 0.1): integer;
220var
221 len: single;
222begin
223 len := sqr(pt1.x - pt2.x) + sqr(pt1.y - pt2.y);
224 len := max(len, sqr(pt3.x - pt2.x) + sqr(pt3.y - pt2.y));
225 len := max(len, sqr(pt3.x - pt4.x) + sqr(pt3.y - pt4.y));
226 Result := round(sqrt(sqrt(len)/ AAcceptedDeviation) * 1);
227 if Result<=0 then Result:=1;
228end;
229
230{ TCubicBezierCurve }
231
232function TCubicBezierCurve.SimpleComputePoints(AAcceptedDeviation: single;
233 AIncludeFirstPoint: boolean = true): ArrayOfTPointF;
234var
235 t,step: single;
236 i,nb: Integer;
237 a,b,c: TpointF;
238begin
239 nb := ComputeBezierCurvePrecision(p1,c1,c2,p2, AAcceptedDeviation/2);
240 if nb <= 1 then nb := 2;
241 a:=p2-p1+3*(c1-c2);
242 b:=3*(p1+c2)-6*c1;
243 c:=3*(c1-p1);
244 if AIncludeFirstPoint then
245 begin
246 setlength(result,nb);
247 result[0] := p1;
248 result[nb-1] := p2;
249 step := 1/(nb-1);
250 t := 0;
251 for i := 1 to nb-2 do
252 begin
253 t += step;
254 result[i] := p1+t*(c+t*(b+t*a))
255 end;
256 end else
257 begin
258 setlength(result,nb-1);
259 result[nb-2] := p2;
260 step := 1/(nb-1);
261 t := 0;
262 for i := 0 to nb-3 do
263 begin
264 t += step;
265 result[i] := p1+t*(c+t*(b+t*a))
266 end;
267 end;
268end;
269
270function TCubicBezierCurve.ComputePointAt(t: single): TPointF;
271var
272 f1,f2,f3,f4: single;
273begin
274 f1 := (1-t);
275 f2 := f1*f1;
276 f1 *= f2;
277 f2 *= t*3;
278 f4 := t*t;
279 f3 := f4*(1-t)*3;
280 f4 *= t;
281
282 result.x := f1*p1.x + f2*c1.x +
283 f3*c2.x + f4*p2.x;
284 result.y := f1*p1.y + f2*c1.y +
285 f3*c2.y + f4*p2.y;
286end;
287
288procedure TCubicBezierCurve.Split(out ALeft, ARight: TCubicBezierCurve);
289var midc: TPointF;
290begin
291 ALeft.p1 := p1;
292 ALeft.c1 := 0.5*(p1+c1);
293 ARight.p2 := p2;
294 ARight.c2 := 0.5*(p2+c2);
295 midc := 0.5*(c1+c2);
296 ALeft.c2 := 0.5*(ALeft.c1+midc);
297 ARight.c1 := 0.5*(ARight.c2+midc);
298 ALeft.p2 := 0.5*(ALeft.c2+ARight.c1);
299 ARight.p1 := ALeft.p2;
300end;
301
302function TCubicBezierCurve.ComputeLength(AAcceptedDeviation: single): single;
303var
304 t,step: single;
305 i,nb: Integer;
306 curCoord,nextCoord: TPointF;
307begin
308 nb := ComputeBezierCurvePrecision(p1,c1,c2,p2, AAcceptedDeviation);
309 if nb <= 1 then nb := 2;
310 result := 0;
311 curCoord := p1;
312 step := 1/(nb-1);
313 t := 0;
314 for i := 1 to nb-2 do
315 begin
316 t += step;
317 nextCoord := ComputePointAt(t);
318 result += VectLen(nextCoord-curCoord);
319 curCoord := nextCoord;
320 end;
321 result += VectLen(p2-curCoord);
322end;
323
324function TCubicBezierCurve.ToPoints(AAcceptedDeviation: single;
325 AIncludeFirstPoint: boolean = true): ArrayOfTPointF;
326begin
327 result := SimpleComputePoints(AAcceptedDeviation, AIncludeFirstPoint);
328end;
329
330procedure TCubicBezierCurve.CopyToPath(ADest: IBGRAPath);
331begin
332 ADest.lineTo(p1);
333 ADest.bezierCurveTo(c1,c2,p2);
334end;
335
336{//The following function computes by splitting the curve. It is slower than the simple function.
337function TCubicBezierCurve.ToPoints(AAcceptedDeviation: single;
338 ARelativeDeviation: boolean): ArrayOfTPointF;
339 function ToPointsRec(const ACurve: TCubicBezierCurve): ArrayOfTPointF;
340 var simpleLen2: single;
341 v: TPointF;
342 left,right: TCubicBezierCurve;
343 subLeft,subRight: ArrayOfTPointF;
344 maxDev,dev1,dev2: single;
345 subLeftLen: integer;
346
347 procedure ComputeExtremum;
348 begin
349 raise Exception.Create('Not implemented');
350 result := nil;
351 end;
352
353 begin
354 v := ACurve.p2-ACurve.p1;
355 simpleLen2 := v*v;
356 if simpleLen2 = 0 then
357 begin
358 if (ACurve.c1.x = ACurve.p1.x) and (ACurve.c1.y = ACurve.p1.y) and
359 (ACurve.c2.x = ACurve.p2.x) and (ACurve.c2.y = ACurve.p2.y) then
360 begin
361 result := nil;
362 exit;
363 end;
364 ACurve.Split(left,right);
365 end else
366 begin
367 ACurve.Split(left,right);
368 if not ARelativeDeviation then simpleLen2:= sqrt(simpleLen2);
369 maxDev := AAcceptedDeviation*simpleLen2;
370 if abs(PointF(v.y,-v.x) * (left.p2-ACurve.p1)) <= maxDev then
371 begin
372 dev1 := PointF(v.y,-v.x) * (ACurve.c1-ACurve.p1);
373 dev2 := PointF(v.y,-v.x) * (ACurve.c2-ACurve.p2);
374 if not ((Sign(dev1)<>Sign(dev2)) and ((abs(dev1) > maxDev) or (abs(dev2) > maxDev))) then
375 begin
376 result := nil;
377 if ((ACurve.c1-ACurve.p1)*v < -maxDev) or
378 ((ACurve.c1-ACurve.p2)*v > maxDev) or
379 ((ACurve.c2-ACurve.p1)*v < -maxDev) or
380 ((ACurve.c2-ACurve.p2)*v > maxDev) then
381 ComputeExtremum;
382 exit;
383 end;
384 end;
385 end;
386 subRight := ToPointsRec(right);
387 subLeft := ToPointsRec(left);
388 subLeftLen := length(subLeft);
389
390 //avoid leaving a gap in memory
391 result := subLeft;
392 subLeft := nil;
393 setlength(result, subLeftLen+1+length(subRight));
394 result[subLeftLen] := left.p2;
395 move(subRight[0], result[subLeftLen+1], length(subRight)*sizeof(TPointF));
396 end;
397
398var
399 subLen: integer;
400
401begin
402 if (c1.x = p1.x) and (c1.y = p1.y) and
403 (c1.x = c2.x) and (c1.y = c2.y) and
404 (c1.x = p2.x) and (c1.y = p2.y) then
405 begin
406 setlength(result,1);
407 result[0] := c1;
408 exit;
409 end else
410 begin
411 result := ToPointsRec(self);
412 subLen := length(result);
413 setlength(result, length(result)+2);
414 move(result[0], result[1], subLen*sizeof(TPointF));
415 result[0] := p1;
416 result[high(result)] := p2;
417 end;
418end;}
419
420function TCubicBezierCurve.GetBounds: TRectF;
421const precision = 1e-5;
422
423 procedure Include(pt: TPointF);
424 begin
425 if pt.x < result.Left then result.Left := pt.x
426 else if pt.x > result.Right then result.Right := pt.x;
427 if pt.y < result.Top then result.Top := pt.y
428 else if pt.y > result.Bottom then result.Bottom := pt.y;
429 end;
430
431 procedure IncludeT(t: single);
432 begin
433 if (t > 0) and (t < 1) then
434 Include(ComputePointAt(t));
435 end;
436
437 procedure IncludeABC(a,b,c: single);
438 var b2ac, sqrtb2ac: single;
439 begin
440 if abs(a) < precision then
441 begin
442 if abs(b) < precision then exit;
443 IncludeT(-c/b);
444 end else
445 begin
446 b2ac := sqr(b) - 4 * a * c;
447 if b2ac >= 0 then
448 begin
449 sqrtb2ac := sqrt(b2ac);
450 IncludeT((-b + sqrtb2ac) / (2 * a));
451 IncludeT((-b - sqrtb2ac) / (2 * a));
452 end;
453 end;
454 end;
455
456var
457 va, vb, vc: TPointF;
458
459begin
460 result.TopLeft := p1;
461 result.BottomRight := p1;
462 Include(p2);
463
464 vb := 6 * p1 - 12 * c1 + 6 * c2;
465 va := -3 * p1 + 9 * c1 - 9 * c2 + 3 * p2;
466 vc := 3 * c1 - 3 * p1;
467
468 IncludeABC(va.x,vb.x,vc.x);
469 IncludeABC(va.y,vb.y,vc.y);
470end;
471
472{ TQuadraticBezierCurve }
473
474function TQuadraticBezierCurve.SimpleComputePoints(AAcceptedDeviation: single;
475 AIncludeFirstPoint: boolean = true): ArrayOfTPointF;
476var
477 t,step: single;
478 i,nb: Integer;
479 pA,pB : TpointF;
480begin
481 nb := ComputeBezierCurvePrecision(p1,c,c,p2, AAcceptedDeviation);
482 if nb <= 1 then nb := 2;
483 pA := p2+p1-2*c; pB := 2*(c-p1);
484 if AIncludeFirstPoint then
485 begin
486 setlength(result,nb);
487 result[0] := p1;
488 result[nb-1] := p2;
489 step := 1/(nb-1);
490 t := 0;
491 for i := 1 to nb-2 do
492 begin
493 t += step;
494 result[i] := p1+t*(pB+t*pA);
495 end;
496 end else
497 begin
498 setlength(result,nb-1);
499 result[nb-2] := p2;
500 step := 1/(nb-1);
501 t := 0;
502 for i := 0 to nb-3 do
503 begin
504 t += step;
505 result[i] := p1+t*(pB+t*pA);
506 end;
507 end;
508end;
509
510function TQuadraticBezierCurve.ComputeExtremumPositionOutsideSegment: single;
511var a,b: single;
512 v: TPointF;
513begin
514 v := self.p2-self.p1;
515 a := (self.p1-2*self.c+self.p2)*v;
516 if a = 0 then //no solution
517 begin
518 result := -1;
519 exit;
520 end;
521 b := (self.c-self.p1)*v;
522 result := -b/a;
523end;
524
525function TQuadraticBezierCurve.ComputePointAt(t: single): TPointF;
526var
527 rev_t,f2,t2: single;
528begin
529 rev_t := (1-t);
530 f2 := rev_t*t*2;
531 rev_t *= rev_t;
532 t2 := t*t;
533 result.x := rev_t*p1.x + f2*c.x + t2*p2.x;
534 result.y := rev_t*p1.y + f2*c.y + t2*p2.y;
535end;
536
537procedure TQuadraticBezierCurve.Split(out ALeft, ARight: TQuadraticBezierCurve);
538begin
539 ALeft.p1 := p1;
540 ALeft.c := 0.5*(p1+c);
541 ARight.p2 := p2;
542 ARight.c := 0.5*(p2+c);
543 ALeft.p2 := 0.5*(ALeft.c+ARight.c);
544 ARight.p1 := ALeft.p2;
545end;
546
547function TQuadraticBezierCurve.ComputeLength: single;
548var a,b: TPointF;
549 A_,AB_,B_,Sabc,A_2,A_32,B_2,BA,
550 divisor: single;
551 extremumPos: single;
552 extremum: TPointF;
553begin
554 a := p1 - 2*c + p2;
555 b := 2*(c - p1);
556 A_ := 4*(a*a);
557 B_ := b*b;
558 if (A_ = 0) or (B_ = 0) then
559 begin
560 result := VectLen(p2-p1);
561 exit;
562 end;
563 AB_ := 4*(a*b);
564
565 A_2 := sqrt(A_);
566 B_2 := 2*sqrt(B_);
567 BA := AB_/A_2;
568 divisor := BA+B_2;
569 if divisor <= 0 then
570 begin
571 extremumPos:= ComputeExtremumPositionOutsideSegment;
572 if (extremumPos <= 0) or (extremumPos >= 1) then
573 result := VectLen(p2-p1)
574 else
575 begin
576 extremum := ComputePointAt(extremumPos);
577 result := VectLen(extremum-p1)+VectLen(p2-extremum);
578 end;
579 exit;
580 end;
581
582 Sabc := 2*sqrt(A_+AB_+B_);
583 A_32 := 2*A_*A_2;
584 result := ( A_32*Sabc +
585 A_2*AB_*(Sabc-B_2) +
586 (4*B_*A_-AB_*AB_)*ln( (2*A_2+BA+Sabc)/divisor )
587 )/(4*A_32);
588end;
589
590function TQuadraticBezierCurve.ToPoints(AAcceptedDeviation: single;
591 AIncludeFirstPoint: boolean = true): ArrayOfTPointF;
592begin
593 result := SimpleComputePoints(AAcceptedDeviation, AIncludeFirstPoint);
594end;
595
596procedure TQuadraticBezierCurve.CopyToPath(ADest: IBGRAPath);
597begin
598 ADest.lineTo(p1);
599 ADest.quadraticCurveTo(c,p2);
600end;
601
602function TQuadraticBezierCurve.GetBounds: TRectF;
603const precision = 1e-5;
604
605 procedure Include(pt: TPointF);
606 begin
607 if pt.x < result.Left then result.Left := pt.x
608 else if pt.x > result.Right then result.Right := pt.x;
609 if pt.y < result.Top then result.Top := pt.y
610 else if pt.y > result.Bottom then result.Bottom := pt.y;
611 end;
612
613 procedure IncludeT(t: single);
614 begin
615 if (t > 0) and (t < 1) then
616 Include(ComputePointAt(t));
617 end;
618
619 procedure IncludeABC(a,b,c: single);
620 var denom: single;
621 begin
622 denom := a-2*b+c;
623 if abs(denom) < precision then exit;
624 IncludeT((a-b)/denom);
625 end;
626
627begin
628 result.TopLeft := p1;
629 result.BottomRight := p1;
630 Include(p2);
631
632 IncludeABC(p1.x,c.x,p2.x);
633 IncludeABC(p1.y,c.y,p2.y);
634end;
635
636{//The following function computes by splitting the curve. It is slower than the simple function
637function TQuadraticBezierCurve.ToPoints(AAcceptedDeviation: single; ARelativeDeviation: boolean): ArrayOfTPointF;
638
639 function ToPointsRec(const ACurve: TQuadraticBezierCurve): ArrayOfTPointF;
640 var simpleLen2: single;
641 v: TPointF;
642 left,right: TQuadraticBezierCurve;
643 subLeft,subRight: ArrayOfTPointF;
644 subLeftLen: Integer;
645
646 procedure ComputeExtremum;
647 var
648 t: single;
649 begin
650 t := ACurve.ComputeExtremumPositionOutsideSegment;
651 if (t <= 0) or (t >= 1) then
652 result := nil
653 else
654 begin
655 setlength(result,1);
656 result[0] := ACurve.ComputePointAt(t);
657 end;
658 end;
659
660 begin
661 v := ACurve.p2-ACurve.p1;
662 simpleLen2 := v*v;
663 if simpleLen2 = 0 then
664 begin
665 if (ACurve.c.x = ACurve.p1.x) and (ACurve.c.y = ACurve.p1.y) then
666 begin
667 result := nil;
668 exit;
669 end;
670 ACurve.Split(left,right);
671 end else
672 begin
673 ACurve.Split(left,right);
674 if not ARelativeDeviation then simpleLen2:= sqrt(simpleLen2);
675 if abs(PointF(v.y,-v.x) * (left.p2-ACurve.p1))
676 <= AAcceptedDeviation*simpleLen2 then
677 begin
678 result := nil;
679 if ((ACurve.c-ACurve.p1)*v < -AAcceptedDeviation*simpleLen2) or
680 ((ACurve.c-ACurve.p2)*v > AAcceptedDeviation*simpleLen2) then
681 ComputeExtremum;
682 exit;
683 end;
684 end;
685 subRight := ToPointsRec(right);
686 subLeft := ToPointsRec(left);
687 subLeftLen := length(subLeft);
688
689 //avoid leaving a gap in memory
690 result := subLeft;
691 subLeft := nil;
692 setlength(result, subLeftLen+1+length(subRight));
693 result[subLeftLen] := left.p2;
694 move(subRight[0], result[subLeftLen+1], length(subRight)*sizeof(TPointF));
695 end;
696
697var
698 subLen: integer;
699
700begin
701 if (c.x = p1.x) and (c.y = p1.y) and
702 (c.x = p2.x) and (c.y = p2.y) then
703 begin
704 setlength(result,1);
705 result[0] := c;
706 exit;
707 end else
708 begin
709 result := ToPointsRec(self);
710 subLen := length(result);
711 setlength(result, length(result)+2);
712 move(result[0], result[1], subLen*sizeof(TPointF));
713 result[0] := p1;
714 result[high(result)] := p2;
715 end;
716end;}
717
718{ TRationalQuadraticBezierCurve }
719
720function TRationalQuadraticBezierCurve.GetIsInfinite: boolean;
721begin
722 result:= (weight <= -1);
723end;
724
725function TRationalQuadraticBezierCurve.InternalComputePoints(AInfiniteBounds: TRectF; AAcceptedDeviation: single;
726 AIncludeFirstPoint: boolean = true): ArrayOfTPointF;
727var
728 pA,pB : TpointF;
729 a1,b1: single;
730
731 function InternalComputeAt(t: single): TPointF;
732 var
733 den: single;
734 begin
735 den := (1+t*(b1+t*a1));
736 if den <> 0 then
737 result := (p1+t*(pB+t*pA))*(1/den)
738 else
739 result := EmptyPointF
740 end;
741
742 procedure ComputeFactors;
743 var
744 c2 : TpointF;
745 c1: single;
746 begin
747 c1 := 2*weight; c2 := c1*c;
748 pA := p2+p1-c2; pB := -2*p1+c2;
749 a1 := 2-c1; b1 := -a1;
750 end;
751
752 function ComputeContinuous(t1,t2: single; AIncludeFirstPoint: boolean): ArrayOfTPointF;
753 var
754 pointCount: integer;
755
756 procedure AddPoint(APoint: TPointF);
757 begin
758 if isEmptyPointF(APoint) then exit;
759 if pointCount >= length(result) then
760 setlength(result, pointCount*2+4);
761 result[pointCount] := APoint;
762 inc(pointCount);
763 end;
764
765 procedure ComputeRec(left: single; constref leftPoint: TPointF; right: single; constref rightPoint: TPointF);
766 var
767 middlePoint, u: TPointF;
768 middle, lenU, deviation: Single;
769 begin
770 if rightPoint<>leftPoint then
771 begin
772 middle := (left+right)*0.5;
773 middlePoint := InternalComputeAt(middle);
774 u := rightPoint-leftPoint;
775 lenU := VectLen(u);
776 if lenU>0 then u *= (1/lenU);
777 deviation := abs((middlePoint-leftPoint)*PointF(u.y,-u.x));
778 if deviation > AAcceptedDeviation then
779 begin
780 ComputeRec(left, leftPoint, middle, middlePoint);
781 AddPoint(middlePoint);
782 ComputeRec(middle, middlePoint, right, rightPoint);
783 end else
784 if deviation > AAcceptedDeviation*0.6 then
785 AddPoint(middlePoint);
786 end;
787 end;
788
789 var
790 startPoint, endPoint: TPointF;
791 begin
792 pointCount := 0;
793 result:= nil;
794 startPoint := InternalComputeAt(t1);
795 endPoint := InternalComputeAt(t2);
796 if AIncludeFirstPoint then AddPoint(startPoint);
797 if endPoint <> startPoint then
798 begin
799 ComputeRec(t1,startPoint,t2,endPoint);
800 AddPoint(endPoint);
801 end;
802 setlength(result,PointCount);
803 end;
804
805var
806 tSplitA, tSplitB, tSplit1, tSplit2, delta: single;
807 leftPart,middlePart,rightPart: array of TPointF;
808 tList: ArrayOfSingle;
809 parts: array of ArrayOfTPointF;
810 i: Integer;
811
812 function PointWithinInifiniteBounds(APoint: TPointF): boolean;
813 begin
814 result := not isEmptyPointF(APoint) and
815 (APoint.x > AInfiniteBounds.Left) and (APoint.x < AInfiniteBounds.Right) and
816 (APoint.y > AInfiniteBounds.Top) and (APoint.y < AInfiniteBounds.Bottom);
817 end;
818
819begin
820 if weight = 0 then exit(PointsF([p1,p2]));
821 ComputeFactors;
822
823 if weight > -1 then
824 begin
825 tList := GetBoundingPositions(true,true);
826 setlength(parts, length(tList)-1);
827 for i := 0 to high(parts) do
828 parts[i] := ComputeContinuous(tList[i],tList[i+1], AIncludeFirstPoint and (i=0));
829 result := ConcatPointsF(parts);
830 end
831 else
832 if weight = -1 then
833 begin
834 tSplit1 := 0.5;
835 tSplitA := 0;
836 while PointWithinInifiniteBounds(InternalComputeAt(tSplitA)) do tSplitA := (tSplitA+tSplit1)*0.5;
837 tSplitB := 1;
838 while PointWithinInifiniteBounds(InternalComputeAt(tSplitB)) do tSplitB := (tSplitB+tSplit1)*0.5;
839
840 tList := GetBoundingPositions(true,true);
841 setlength(parts, length(tList)-1);
842 for i := 0 to high(parts) do
843 begin
844 if (tList[i] > tSplitA) and (tList[i+1] <= tSplitB) then parts[i] := nil
845 else
846 if (tList[i] <= tSplitA) and (tList[i+1] >= tSplitA) then
847 begin
848 parts[i] := ComputeContinuous(tList[i],tSplitA, AIncludeFirstPoint or (i>0));
849 setlength(parts[i], length(parts[i])+1);
850 parts[i][high(parts[i])] := EmptyPointF;
851
852 if tList[i+1] > tSplitB then
853 parts[i] := ConcatPointsF([parts[i], ComputeContinuous(tSplitB,tList[i+1], true)])
854 else
855 tList[i+1] := tSplitB;
856 end
857 else
858 if (tList[i] < tSplitB) and (tList[i+1] >= tSplitB) then
859 parts[i] := ComputeContinuous(tSplitB,tList[i+1], AIncludeFirstPoint or (i>0))
860 else
861 parts[i] := ComputeContinuous(tList[i],tList[i+1], AIncludeFirstPoint or (i>0));
862 end;
863 result := ConcatPointsF(parts);
864 end else
865 begin
866 delta:= 1 - 2/(1-weight);
867 tSplit1 := (1 - sqrt(delta))/2;
868 tSplit2 := 1-tSplit1;
869
870 tSplitA := 0;
871 while PointWithinInifiniteBounds(InternalComputeAt(tSplitA)) do tSplitA := (tSplitA+tSplit1)*0.5;
872 leftPart := ComputeContinuous(0, tSplitA, AIncludeFirstPoint);
873
874 tSplitA := (tSplit1+tSplit2)*0.5;
875 tSplitB := tSplitA;
876 while PointWithinInifiniteBounds(InternalComputeAt(tSplitA)) do tSplitA := (tSplitA+tSplit1)*0.5;
877 while PointWithinInifiniteBounds(InternalComputeAt(tSplitB)) do tSplitB := (tSplitB+tSplit2)*0.5;
878 middlePart := ComputeContinuous(tSplitA, tSplitB, true);
879
880 tSplitB := 1;
881 while PointWithinInifiniteBounds(InternalComputeAt(tSplitB)) do tSplitB := (tSplitB+tSplit2)*0.5;
882 rightPart:= ComputeContinuous(tSplitB, 1, true);
883 result := ConcatPointsF([leftPart, PointsF([EmptyPointF]), middlePart, PointsF([EmptyPointF]), rightPart]);
884 end;
885end;
886
887function TRationalQuadraticBezierCurve.GetBoundingPositions(
888 AIncludeFirstAndLast: boolean; ASorted: boolean): ArrayOfSingle;
889const precision = 1e-6;
890var a,delta,sqrtDelta,den,invDen: single;
891 A_,B_,p2_,c_: TPointF;
892 posCount : integer;
893
894 procedure Include(t: single);
895 var
896 i: Integer;
897 begin
898 if (t < 0) or (t > 1) then exit;
899 for i := 0 to PosCount-1 do
900 if result[i] = t then exit;
901 result[posCount] := t;
902 inc(posCount);
903 end;
904
905 procedure SortList;
906 var i,j,k: integer;
907 temp: single;
908 begin
909 for i := 1 to high(result) do
910 begin
911 j := i;
912 while (j > 0) and (result[j-1] > result[i]) do dec(j);
913 if j <> i then
914 begin
915 temp := result[i];
916 for k := i downto j+1 do
917 result[k] := result[k-1];
918 result[j] := temp;
919 end;
920 end;
921 end;
922
923begin
924 setlength(result, 6);
925 posCount := 0;
926
927 if AIncludeFirstAndLast then
928 begin
929 Include(0);
930 Include(1);
931 end;
932
933 p2_ := p2-p1; c_ := c-p1; //translation with -p1
934 B_ := 2*weight*c_; A_ := p2_-B_;
935 a := 2*(1-weight);
936
937 //on Ox
938 den := a*p2_.x;
939 if abs(den) >= precision then
940 begin
941 delta := sqr(A_.x)+den*B_.x;
942 if delta >= 0 then
943 begin
944 invDen := 1/den;
945 sqrtDelta := sqrt(delta);
946 Include( (A_.x-sqrtDelta)*invDen );
947 Include( (A_.x+sqrtDelta)*invDen );
948 end;
949 end else //den=0
950 if abs(A_.x) >= precision then
951 Include( -B_.x/A_.x*0.5 );
952
953 //on Oy
954 den := a*p2_.y;
955 if abs(den) >= precision then
956 begin
957 delta := sqr(A_.y)+den*B_.y;
958 if delta >= 0 then
959 begin
960 invDen := 1/den;
961 sqrtDelta := sqrt(delta);
962 Include( (A_.y-sqrtDelta)*invDen );
963 Include( (A_.y+sqrtDelta)*invDen );
964 end;
965 end else //den=0
966 if abs(A_.y) >= precision then
967 Include( -B_.y/A_.y*0.5 );
968
969 setlength(result, posCount);
970 if ASorted then SortList;
971end;
972
973function TRationalQuadraticBezierCurve.ComputePointAt(t: single): TPointF;
974var
975 rev_t,f2,t2,den: single;
976begin
977 rev_t := (1-t);
978 t2 := t*t;
979 f2 := weight*rev_t*t*2;
980 rev_t *= rev_t;
981 den := rev_t+f2+t2;
982 if den <> 0 then
983 begin
984 result.x := (rev_t*p1.x + f2*c.x + t2*p2.x)/den;
985 result.y := (rev_t*p1.y + f2*c.y + t2*p2.y)/den;
986 end
987 else
988 result := EmptyPointF
989end;
990
991function TRationalQuadraticBezierCurve.ToPoints(AInfiniteBounds: TRectF; AAcceptedDeviation: single;
992 AIncludeFirstPoint: boolean = true): ArrayOfTPointF;
993begin
994 if weight=1 then
995 result := BezierCurve(p1,c,p2).ToPoints(AAcceptedDeviation, AIncludeFirstPoint)
996 else
997 result := InternalComputePoints(AInfiniteBounds, AAcceptedDeviation, AIncludeFirstPoint)
998end;
999
1000function TRationalQuadraticBezierCurve.GetBounds: TRectF;
1001var a: single;
1002 A_,B_,p2_,c_: TPointF;
1003 t: single;
1004 tList: array of Single;
1005 i: Integer;
1006
1007 procedure Include(pt: TPointF);
1008 begin
1009 if pt.x < result.Left then result.Left := pt.x
1010 else if pt.x > result.Right then result.Right := pt.x;
1011 if pt.y < result.Top then result.Top := pt.y
1012 else if pt.y > result.Bottom then result.Bottom := pt.y;
1013 end;
1014
1015begin
1016 if weight=1 then exit(BezierCurve(p1,c,p2).GetBounds);
1017 if IsInfinite then exit(EmptyRectF);
1018 tList:= GetBoundingPositions(false,false);
1019
1020 result.TopLeft := p1;
1021 result.BottomRight := p1;
1022 Include(p2);
1023
1024 p2_ := p2-p1; c_ := c-p1; //translation with -p1
1025 B_ := 2*weight*c_; A_ := p2_-B_;
1026 a := 2*(1-weight);
1027
1028 for i := 0 to high(tList) do
1029 begin
1030 t := tList[i];
1031 Include( p1+t*(B_+t*A_)*(1/(1+t*(-a+t*a))) );
1032 end;
1033end;
1034
1035function TRationalQuadraticBezierCurve.ComputeLength(AAcceptedDeviation: single): single;
1036var i: Integer;
1037 curCoord,nextCoord: TPointF;
1038 pts: ArrayOfTPointF;
1039begin
1040 if weight = 1 then exit(BezierCurve(p1,c,p2).ComputeLength);
1041 if weight <= -1 then exit(EmptySingle); // no bounds in this case
1042 pts := InternalComputePoints(EmptyRectF, AAcceptedDeviation, true);
1043 curCoord := p1; result:=0;
1044 for i := 1 to high(pts) do
1045 begin
1046 nextCoord := pts[i];
1047 if (nextCoord <> EmptyPointF) and (curCoord <> EmptyPointF) then
1048 result += VectLen(nextCoord-curCoord);
1049 curCoord := nextCoord;
1050 end;
1051 finalize(pts)
1052end;
1053
1054function TRationalQuadraticBezierCurve.ToPoints(AAcceptedDeviation: single;
1055 AIncludeFirstPoint: boolean): ArrayOfTPointF;
1056begin
1057 result := ToPoints(RectF(-64,-64, 16384, 16384), AAcceptedDeviation, AIncludeFirstPoint);
1058end;
1059
1060procedure TRationalQuadraticBezierCurve.Split(out ALeft, ARight: TRationalQuadraticBezierCurve);
1061const precision=1E-6;
1062var M, D, E, H, c1, c2: TPointF;
1063 alpha, sg, w: single;
1064
1065 function Intersec(): TPointF; //dichotomie
1066 var t, t1, t2: single;
1067 U, V: TPointF;
1068 begin
1069 t1 := 0; t2 := 0.5; U := E-c1;
1070 if VectDet(U,p1-c1)>0 then sg := 1 else sg := -1;
1071 while (t2-t1) > precision do //19 iterations
1072 begin
1073 t := (t1+t2)/2;
1074 V := ComputePointAt(t)-c1;
1075 if VectDet(U,V)*sg>0 then t1 := t else t2 := t;
1076 end;
1077 result := ComputePointAt((t1+t2)/2)
1078 end;
1079
1080begin
1081 if IsInfinite then raise exception.Create('Cannot split an infinite curve');
1082
1083 M := ComputePointAt(0.5);
1084 ALeft.p1 := p1;
1085 ALeft.p2 := M;
1086 ARight.p1 := M;
1087 ARight.p2 := p2;
1088 ALeft.weight := 1;
1089 ARight.weight := 1;
1090 D := 0.5*(p1+p2);
1091 if (weight = 1) or (D = c) then
1092 begin
1093 ALeft.c := 0.5*(p1+c);
1094 ARight.c := 0.5*(p2+c);
1095 exit;
1096 end;
1097 if weight > 0 then
1098 alpha := VectLen(D-M)/VectLen(D-c)
1099 else
1100 alpha := -VectLen(D-M)/VectLen(D-c);
1101 c1 := p1 + alpha*(c-p1);
1102 c2 := p2 + alpha*(c-p2);
1103 ALeft.c := c1;
1104 ARight.c := c2;
1105 E := 0.5*(p1+M);
1106 H := Intersec(); //between [c1;E] and the curve
1107 w := VectLen(E-c1)/VectLen(H-c1)-1; // new weight
1108 ALeft.weight := w;
1109 ARight.weight := w;
1110end;
1111
1112{ TEasyBezierCurve }
1113
1114function EasyBezierCurve(APoints: array of TPointF; AClosed: boolean;
1115 ACurveMode: TEasyBezierCurveMode; AMinimumDotProduct: single): TEasyBezierCurve;
1116begin
1117 result.Init;
1118 result.SetPoints(APoints, ACurveMode);
1119 result.Closed:= AClosed;
1120 result.MinimumDotProduct:= AMinimumDotProduct;
1121end;
1122
1123function EasyBezierCurve(APoints: array of TPointF; AClosed: boolean;
1124 ACurveMode: array of TEasyBezierCurveMode;
1125 AMinimumDotProduct: single = EasyBezierDefaultMinimumDotProduct): TEasyBezierCurve;
1126begin
1127 result.Init;
1128 result.SetPoints(APoints, ACurveMode);
1129 result.Closed:= AClosed;
1130 result.MinimumDotProduct:= AMinimumDotProduct;
1131end;
1132
1133procedure TEasyBezierCurve.CopyToPath(ADest: IBGRAPath; ATransformFunc: TEasyBezierPointTransformFunc; ATransformData: Pointer);
1134var i: integer;
1135 nextMove: boolean;
1136 pt,startCoord: TPointF;
1137begin
1138 if PointCount = 0 then exit;
1139 if (FCurves = nil) or FInvalidated then ComputeQuadraticCurves;
1140 nextMove := true;
1141
1142 for i := 0 to PointCount-1 do
1143 begin
1144 pt := Point[i];
1145 if isEmptyPointF(pt) then
1146 begin
1147 if not nextMove and FClosed then ADest.closePath;
1148 nextMove := true;
1149 end else
1150 begin
1151 with FCurves[i] do
1152 begin
1153 if nextMove then
1154 begin
1155 if not isCurvedToPrevious then
1156 startCoord := pt
1157 else
1158 startCoord := Center;
1159 ADest.moveTo(ATransformFunc(@startCoord,ATransformData));
1160 nextMove := false;
1161 end else
1162 if not isCurvedToPrevious then
1163 ADest.lineTo(ATransformFunc(@pt,ATransformData));
1164
1165 if isCurvedToNext then
1166 begin
1167 if not isCurvedToPrevious then ADest.lineTo(ATransformFunc(@Center,ATransformData));
1168 ADest.quadraticCurveTo(ATransformFunc(@ControlPoint,ATransformData),ATransformFunc(@NextCenter,ATransformData));
1169 end;
1170 end;
1171 end;
1172 end;
1173 if not nextmove and FClosed then ADest.closePath;
1174end;
1175
1176function TEasyBezierCurve.ToPoints: ArrayOfTPointF;
1177var p: TBGRACustomPath;
1178begin
1179 if not Assigned(BGRAPathFactory) then raise exception.Create('BGRAPath unit needed');
1180 p := BGRAPathFactory.Create;
1181 CopyToPath(p);
1182 result := p.getPoints;
1183 p.Free;
1184end;
1185
1186function TEasyBezierCurve.ComputeLength: single;
1187var p: TBGRACustomPath;
1188begin
1189 if not Assigned(BGRAPathFactory) then raise exception.Create('BGRAPath unit needed');
1190 p := BGRAPathFactory.Create;
1191 CopyToPath(p);
1192 result := p.getLength;
1193 p.Free;
1194end;
1195
1196procedure TEasyBezierCurve.CopyToPath(ADest: IBGRAPath);
1197begin
1198 CopyToPath(ADest, @PointTransformNone, nil);
1199end;
1200
1201procedure TEasyBezierCurve.CopyToPath(ADest: IBGRAPath; AOffset: TPointF);
1202begin
1203 CopyToPath(ADest, @PointTransformOffset, @AOffset);
1204end;
1205
1206procedure TEasyBezierCurve.ComputeQuadraticCurves;
1207var
1208 i,FirstPointIndex,NextPt,NextPt2: integer;
1209begin
1210 setlength(FCurves, PointCount);
1211 FirstPointIndex := 0;
1212 for i := 0 to PointCount-1 do
1213 FCurves[i].isCurvedToPrevious := false;
1214 for i := 0 to PointCount-1 do
1215 begin
1216 FCurves[i].isCurvedToNext := false;
1217 FCurves[i].Center := EmptyPointF;
1218 FCurves[i].ControlPoint := EmptyPointF;
1219 FCurves[i].NextCenter := EmptyPointF;
1220
1221 if IsEmptyPointF(Point[i]) then
1222 begin
1223 FirstPointIndex := i+1;
1224 end else
1225 begin
1226 NextPt := i+1;
1227 if (NextPt = PointCount) or isEmptyPointF(Point[NextPt]) then NextPt := FirstPointIndex;
1228 NextPt2 := NextPt+1;
1229 if (NextPt2 = PointCount) or isEmptyPointF(Point[NextPt2]) then NextPt2 := FirstPointIndex;
1230
1231 FCurves[i].Center := (Point[i]+Point[NextPt])*0.5;
1232 FCurves[i].NextCenter := (Point[NextPt]+Point[NextPt2])*0.5;
1233 FCurves[i].ControlPoint := Point[NextPt];
1234
1235 if (i < PointCount-2) or FClosed then
1236 begin
1237 case CurveMode[nextPt] of
1238 cmAuto: FCurves[i].isCurvedToNext:= MaybeCurve(i,NextPt,NextPt,NextPt2);
1239 cmCurve: FCurves[i].isCurvedToNext:= true;
1240 else FCurves[i].isCurvedToNext:= false;
1241 end;
1242 FCurves[NextPt].isCurvedToPrevious := FCurves[i].isCurvedToNext;
1243 end;
1244 end;
1245 end;
1246 FInvalidated:= false;
1247end;
1248
1249function TEasyBezierCurve.PointTransformNone(APoint: PPointF; AData: Pointer): TPointF;
1250begin
1251 result := APoint^;
1252end;
1253
1254function TEasyBezierCurve.PointTransformOffset(APoint: PPointF; AData: Pointer): TPointF;
1255begin
1256 result := APoint^ + PPointF(AData)^;
1257end;
1258
1259procedure TEasyBezierCurve.Init;
1260begin
1261 FClosed := false;
1262 FMinimumDotProduct:= EasyBezierDefaultMinimumDotProduct;
1263 FPoints := nil;
1264 FInvalidated := true;
1265end;
1266
1267procedure TEasyBezierCurve.Clear;
1268begin
1269 FPoints := nil;
1270end;
1271
1272procedure TEasyBezierCurve.SetPoints(APoints: array of TPointF;
1273 ACurveMode: TEasyBezierCurveMode);
1274var
1275 i: Integer;
1276begin
1277 setlength(FPoints, length(APoints));
1278 for i := 0 to high(APoints) do
1279 begin
1280 FPoints[i].Coord := APoints[i];
1281 FPoints[i].CurveMode:= ACurveMode;
1282 end;
1283 FInvalidated:= true;
1284end;
1285
1286procedure TEasyBezierCurve.SetPoints(APoints: array of TPointF;
1287 ACurveMode: array of TEasyBezierCurveMode);
1288var
1289 i,j: Integer;
1290begin
1291 setlength(FPoints, length(APoints));
1292 j := 0;
1293 for i := 0 to high(APoints) do
1294 begin
1295 FPoints[i].Coord := APoints[i];
1296 if length(ACurveMode) = 0 then
1297 FPoints[i].CurveMode:= cmAuto
1298 else
1299 begin
1300 FPoints[i].CurveMode:= ACurveMode[j];
1301 inc(j);
1302 if j = length(ACurveMode) then j := 0;
1303 end;
1304 end;
1305 FInvalidated:= true;
1306end;
1307
1308function TEasyBezierCurve.GetCurveMode(AIndex: integer): TEasyBezierCurveMode;
1309begin
1310 if (AIndex < 0) or (AIndex >= PointCount) then raise exception.Create('Index out of bounds');
1311 result:= FPoints[AIndex].CurveMode;
1312end;
1313
1314function TEasyBezierCurve.GetCurveStartPoint: TPointF;
1315begin
1316 if (PointCount=0) or isEmptyPointF(Point[0]) then exit(EmptyPointF);
1317 if FInvalidated or (FCurves = nil) then ComputeQuadraticCurves;
1318 if not FCurves[0].isCurvedToPrevious then
1319 result := Point[0]
1320 else
1321 result := FCurves[0].Center;
1322end;
1323
1324function TEasyBezierCurve.GetPoint(AIndex: integer): TPointF;
1325begin
1326 if (AIndex < 0) or (AIndex >= PointCount) then raise exception.Create('Index out of bounds');
1327 result:= FPoints[AIndex].Coord;
1328end;
1329
1330function TEasyBezierCurve.GetPointCount: integer;
1331begin
1332 result:= length(FPoints);
1333end;
1334
1335procedure TEasyBezierCurve.SetClosed(AValue: boolean);
1336begin
1337 if FClosed=AValue then Exit;
1338 FClosed:=AValue;
1339 FInvalidated:= true;
1340end;
1341
1342procedure TEasyBezierCurve.SetCurveMode(AIndex: integer;
1343 AValue: TEasyBezierCurveMode);
1344begin
1345 if (AIndex < 0) or (AIndex >= PointCount) then raise exception.Create('Index out of bounds');
1346 if FPoints[AIndex].CurveMode = AValue then exit;
1347 FPoints[AIndex].CurveMode := AValue;
1348 FInvalidated:= true;
1349end;
1350
1351procedure TEasyBezierCurve.SetMinimumDotProduct(AValue: single);
1352begin
1353 if FMinimumDotProduct=AValue then Exit;
1354 FMinimumDotProduct:=AValue;
1355 FInvalidated:= true;
1356end;
1357
1358procedure TEasyBezierCurve.SetPoint(AIndex: integer; AValue: TPointF);
1359begin
1360 if (AIndex < 0) or (AIndex >= PointCount) then raise exception.Create('Index out of bounds');
1361 if FPoints[AIndex].Coord = AValue then exit;
1362 FPoints[AIndex].Coord := AValue;
1363 FInvalidated:= true;
1364end;
1365
1366function TEasyBezierCurve.MaybeCurve(start1,end1,start2,end2: integer): boolean;
1367var
1368 u,v: TPointF;
1369 lu,lv: single;
1370begin
1371 if (start1=-1) or (end1=-1) or (start2=-1) or (end2=-1) then
1372 begin
1373 result := false;
1374 exit;
1375 end;
1376 u := pointF(Point[end1].x - Point[start1].x, Point[end1].y - Point[start1].y);
1377 lu := sqrt(u*u);
1378 if lu <> 0 then u *= 1/lu;
1379 v := pointF(Point[end2].x - Point[start2].x, Point[end2].y - Point[start2].y);
1380 lv := sqrt(v*v);
1381 if lv <> 0 then v *= 1/lv;
1382
1383 result := u*v > FMinimumDotProduct;
1384end;
1385
1386{$ENDIF}
Note: See TracBrowser for help on using the repository browser.