source: trunk/Packages/bgrabitmap/bgrapen.pas

Last change on this file was 2, checked in by chronos, 5 years ago
File size: 48.1 KB
Line 
1unit BGRAPen;
2
3{$mode objfpc}{$H+}
4
5interface
6
7{ This unit handles pen style and width, as well as line caps and join styles.
8
9 A line consists in two points.
10 A polyline consists in one or more lines, defined by two points or more than two points
11 A poly-polyline consists in a series of polylines, defined by polyline points separated by empty points (see EmptyPointF) }
12
13uses
14 SysUtils, BGRAGraphics, BGRABitmapTypes, BGRATransform;
15
16var //predefined pen styles
17 SolidPenStyle, DashPenStyle, DotPenStyle, DashDotPenStyle, DashDotDotPenStyle, ClearPenStyle: TBGRAPenStyle;
18
19type
20 TPenJoinStyle = BGRAGraphics.TPenJoinStyle;
21 TPenEndCap = BGRAGraphics.TPenEndCap;
22
23 { TBGRAPenStroker }
24
25 TBGRAPenStroker = class(TBGRACustomPenStroker)
26 protected
27 { Pen style can be defined by PenStyle property of by CustomPenStyle property.
28 When PenStyle property is assigned, CustomPenStyle property is assigned the actual
29 pen pattern. }
30 FCustomPenStyle: TBGRAPenStyle;
31 FPenStyle: TPenStyle;
32 FArrow: TBGRACustomArrow;
33 FArrowOwned: boolean;
34 FOriginalStrokeMatrix,FStrokeMatrix,FStrokeMatrixInverse: TAffineMatrix;
35 FStrokeZoom: single;
36 FStrokeMatrixIdentity: boolean;
37 FLineCap: TPenEndCap;
38 FJoinStyle: TPenJoinStyle;
39 FMiterLimit: single;
40
41 function GetArrow: TBGRACustomArrow; override;
42 function GetArrowOwned: boolean; override;
43 function GetCustomPenStyle: TBGRAPenStyle; override;
44 function GetJoinStyle: TPenJoinStyle; override;
45 function GetLineCap: TPenEndCap; override;
46 function GetMiterLimit: single; override;
47 function GetPenStyle: TPenStyle; override;
48 function GetStrokeMatrix: TAffineMatrix; override;
49 procedure SetArrow(AValue: TBGRACustomArrow); override;
50 procedure SetArrowOwned(AValue: boolean); override;
51 procedure SetCustomPenStyle(AValue: TBGRAPenStyle); override;
52 procedure SetJoinStyle(AValue: TPenJoinStyle); override;
53 procedure SetLineCap(AValue: TPenEndCap); override;
54 procedure SetMiterLimit(AValue: single); override;
55 procedure SetPenStyle(AValue: TPenStyle); override;
56 procedure SetStrokeMatrix(const AValue: TAffineMatrix); override;
57 public
58 constructor Create;
59 destructor Destroy; override;
60 function ComputePolyline(const APoints: array of TPointF; AWidth: single; AClosedCap: boolean = true): ArrayOfTPointF; overload; override;
61 function ComputePolyline(const APoints: array of TPointF; AWidth: single; APenColor: TBGRAPixel; AClosedCap: boolean = true): ArrayOfTPointF; overload; override;
62 function ComputePolylineAutocycle(const APoints: array of TPointF; AWidth: single): ArrayOfTPointF; override;
63 function ComputePolygon(const APoints: array of TPointF; AWidth: single): ArrayOfTPointF; override;
64
65 end;
66
67 TBGRAPolyLineOption = (plRoundCapOpen, //specifies that the line ending is opened
68 plCycle, //specifies that it is a polygon
69 plAutoCycle, //specifies that a cycle must be used if the last point is the first point
70 plNoStartCap,
71 plNoEndCap);
72 TBGRAPolyLineOptions = set of TBGRAPolyLineOption;
73 TComputeArrowHeadProc = function(const APosition: TPointF; const ADirection: TPointF; const AWidth: single; const ACurrentPos: single): ArrayOfTPointF of object;
74
75{ Compute the path for a polyline }
76function ComputeWidePolylinePoints(const linepts: array of TPointF; width: single;
77 pencolor: TBGRAPixel; linecap: TPenEndCap; joinstyle: TPenJoinStyle; const penstyle: TBGRAPenStyle;
78 options: TBGRAPolyLineOptions; miterLimit: single = 2; arrow: TBGRACustomArrow = nil): ArrayOfTPointF;
79
80{ Compute the path for a poly-polyline }
81function ComputeWidePolyPolylinePoints(const linepts: array of TPointF; width: single;
82 pencolor: TBGRAPixel; linecap: TPenEndCap; joinstyle: TPenJoinStyle; const penstyle: TBGRAPenStyle;
83 options: TBGRAPolyLineOptions; miterLimit: single = 2; arrow: TBGRACustomArrow = nil): ArrayOfTPointF;
84
85{--------------------- Pixel line procedures --------------------------}
86{ These procedures take integer coordinates as parameters and do not handle pen styles and width.
87 They are faster and can be useful for drawing a simple frame }
88
89//aliased version
90procedure BGRADrawLineAliased(dest: TBGRACustomBitmap; x1, y1, x2, y2: integer; c: TBGRAPixel; DrawLastPixel: boolean; ADrawMode: TDrawMode = dmDrawWithTransparency);
91procedure BGRAEraseLineAliased(dest: TBGRACustomBitmap; x1, y1, x2, y2: integer; alpha: byte; DrawLastPixel: boolean);
92
93//antialiased version
94procedure BGRADrawLineAntialias({%H-}dest: TBGRACustomBitmap; x1, y1, x2, y2: integer;
95 c: TBGRAPixel; DrawLastPixel: boolean; LinearBlend : boolean = false); overload;
96procedure BGRAEraseLineAntialias(dest: TBGRACustomBitmap; x1, y1, x2, y2: integer;
97 calpha: byte; DrawLastPixel: boolean); overload;
98
99//antialiased version with bicolor dashes (to draw a frame)
100procedure BGRADrawLineAntialias(dest: TBGRACustomBitmap; x1, y1, x2, y2: integer;
101 c1, c2: TBGRAPixel; dashLen: integer; DrawLastPixel: boolean; var DashPos: integer; LinearBlend : boolean = false); overload;
102
103//length added to ensure accepable alpha join (using TBGRAMultishapeFiller is still better)
104function GetAlphaJoinFactor(alpha: byte): single;
105
106//create standard brush texture
107function CreateBrushTexture(prototype: TBGRACustomBitmap; brushstyle: TBrushStyle; PatternColor, BackgroundColor: TBGRAPixel;
108 width: integer = 8; height: integer = 8; penwidth: single = 1): TBGRACustomBitmap;
109
110//check special pen styles
111function IsSolidPenStyle(ACustomPenStyle: TBGRAPenStyle): boolean;
112function IsClearPenStyle(ACustomPenStyle: TBGRAPenStyle): boolean;
113function DuplicatePenStyle(ACustomPenStyle: array of single): TBGRAPenStyle;
114function PenStyleEqual(AStyle1, AStyle2: TBGRAPenStyle): boolean;
115function BGRAToPenStyle(ACustomPenStyle: TBGRAPenStyle): TPenStyle;
116
117implementation
118
119uses math, BGRAPath;
120
121procedure BGRADrawLineAliased(dest: TBGRACustomBitmap; x1, y1, x2, y2: integer;
122 c: TBGRAPixel; DrawLastPixel: boolean; ADrawMode: TDrawMode);
123var
124 Y, X: integer;
125 DX, DY, SX, SY, E: integer;
126 PixelProc: procedure (x, y: int32or64; c: TBGRAPixel) of object;
127begin
128 if (Y1 = Y2) then
129 begin
130 if (X1 = X2) then
131 begin
132 if DrawLastPixel then
133 dest.DrawPixel(X1, Y1, c, ADrawMode);
134 end else
135 begin
136 if not DrawLastPixel then
137 begin
138 if X2 > X1 then dec(X2) else inc(X2);
139 end;
140 dest.HorizLine(X1,Y1,X2,c, ADrawMode);
141 end;
142 Exit;
143 end else
144 if (X1 = X2) then
145 begin
146 if not DrawLastPixel then
147 begin
148 if Y2 > Y1 then dec(Y2) else inc(Y2);
149 end;
150 dest.VertLine(X1,Y1,Y2,c, ADrawMode);
151 Exit;
152 end;
153
154 DX := X2 - X1;
155 DY := Y2 - Y1;
156
157 if (ADrawMode = dmSetExceptTransparent) and (c.alpha <> 255) then exit else
158 if c.alpha = 0 then
159 begin
160 if ADrawMode in[dmDrawWithTransparency,dmLinearBlend] then exit;
161 if (ADrawMode = dmXor) and (DWord(c)=0) then exit;
162 end;
163 case ADrawMode of
164 dmDrawWithTransparency: PixelProc := @dest.DrawPixel;
165 dmXor: PixelProc := @dest.XorPixel;
166 dmLinearBlend: PixelProc := @dest.FastBlendPixel;
167 else
168 PixelProc := @dest.SetPixel;
169 end;
170
171 if DX < 0 then
172 begin
173 SX := -1;
174 DX := -DX;
175 end
176 else
177 SX := 1;
178
179 if DY < 0 then
180 begin
181 SY := -1;
182 DY := -DY;
183 end
184 else
185 SY := 1;
186
187 DX := DX shl 1;
188 DY := DY shl 1;
189
190 X := X1;
191 Y := Y1;
192 if DX > DY then
193 begin
194 E := DY - DX shr 1;
195
196 while X <> X2 do
197 begin
198 PixelProc(X, Y, c);
199 if E >= 0 then
200 begin
201 Inc(Y, SY);
202 Dec(E, DX);
203 end;
204 Inc(X, SX);
205 Inc(E, DY);
206 end;
207 end
208 else
209 begin
210 E := DX - DY shr 1;
211
212 while Y <> Y2 do
213 begin
214 PixelProc(X, Y, c);
215 if E >= 0 then
216 begin
217 Inc(X, SX);
218 Dec(E, DY);
219 end;
220 Inc(Y, SY);
221 Inc(E, DX);
222 end;
223 end;
224
225 if DrawLastPixel then
226 PixelProc(X2, Y2, c);
227end;
228
229procedure BGRAEraseLineAliased(dest: TBGRACustomBitmap; x1, y1, x2,
230 y2: integer; alpha: byte; DrawLastPixel: boolean);
231var
232 Y, X: integer;
233 DX, DY, SX, SY, E: integer;
234begin
235
236 if (Y1 = Y2) and (X1 = X2) then
237 begin
238 if DrawLastPixel then
239 dest.ErasePixel(X1, Y1, alpha);
240 Exit;
241 end;
242
243 DX := X2 - X1;
244 DY := Y2 - Y1;
245
246 if DX < 0 then
247 begin
248 SX := -1;
249 DX := -DX;
250 end
251 else
252 SX := 1;
253
254 if DY < 0 then
255 begin
256 SY := -1;
257 DY := -DY;
258 end
259 else
260 SY := 1;
261
262 DX := DX shl 1;
263 DY := DY shl 1;
264
265 X := X1;
266 Y := Y1;
267 if DX > DY then
268 begin
269 E := DY - DX shr 1;
270
271 while X <> X2 do
272 begin
273 dest.ErasePixel(X, Y, alpha);
274 if E >= 0 then
275 begin
276 Inc(Y, SY);
277 Dec(E, DX);
278 end;
279 Inc(X, SX);
280 Inc(E, DY);
281 end;
282 end
283 else
284 begin
285 E := DX - DY shr 1;
286
287 while Y <> Y2 do
288 begin
289 dest.ErasePixel(X, Y, alpha);
290 if E >= 0 then
291 begin
292 Inc(X, SX);
293 Dec(E, DY);
294 end;
295 Inc(Y, SY);
296 Inc(E, DX);
297 end;
298 end;
299
300 if DrawLastPixel then
301 dest.ErasePixel(X2, Y2, alpha);
302end;
303
304procedure BGRADrawLineAntialias(dest: TBGRACustomBitmap; x1, y1, x2, y2: integer;
305 c: TBGRAPixel; DrawLastPixel: boolean; LinearBlend : boolean);
306var
307 Y, X: integer;
308 DX, DY, SX, SY, E: integer;
309 alpha: NativeUInt;
310 pixelproc: procedure(x,y: int32or64; c: TBGRAPixel) of object;
311begin
312 if LinearBlend then
313 pixelproc := @dest.FastBlendPixel
314 else
315 pixelproc := @dest.DrawPixel;
316
317 if (Y1 = Y2) and (X1 = X2) then
318 begin
319 if DrawLastPixel then
320 pixelproc(X1, Y1, c);
321 Exit;
322 end;
323
324 DX := X2 - X1;
325 DY := Y2 - Y1;
326
327 if DX < 0 then
328 begin
329 SX := -1;
330 DX := -DX;
331 end
332 else
333 SX := 1;
334
335 if DY < 0 then
336 begin
337 SY := -1;
338 DY := -DY;
339 end
340 else
341 SY := 1;
342
343 DX := DX shl 1;
344 DY := DY shl 1;
345
346 X := X1;
347 Y := Y1;
348
349 if DX > DY then
350 begin
351 E := 0;
352
353 while X <> X2 do
354 begin
355 alpha := c.alpha * E div DX;
356 pixelproc(X, Y, BGRA(c.red, c.green, c.blue, c.alpha - alpha));
357 pixelproc(X, Y + SY, BGRA(c.red, c.green, c.blue, alpha));
358 Inc(E, DY);
359 if E >= DX then
360 begin
361 Inc(Y, SY);
362 Dec(E, DX);
363 end;
364 Inc(X, SX);
365 end;
366 end
367 else
368 begin
369 E := 0;
370
371 while Y <> Y2 do
372 begin
373 alpha := c.alpha * E div DY;
374 pixelproc(X, Y, BGRA(c.red, c.green, c.blue, c.alpha - alpha));
375 pixelproc(X + SX, Y, BGRA(c.red, c.green, c.blue, alpha));
376 Inc(E, DX);
377 if E >= DY then
378 begin
379 Inc(X, SX);
380 Dec(E, DY);
381 end;
382 Inc(Y, SY);
383 end;
384 end;
385 if DrawLastPixel then
386 pixelproc(X2, Y2, c);
387end;
388
389procedure BGRAEraseLineAntialias(dest: TBGRACustomBitmap; x1, y1, x2,
390 y2: integer; calpha: byte; DrawLastPixel: boolean);
391var
392 Y, X: integer;
393 DX, DY, SX, SY, E: integer;
394 alpha: NativeUInt;
395begin
396
397 if (Y1 = Y2) and (X1 = X2) then
398 begin
399 if DrawLastPixel then
400 dest.ErasePixel(X1, Y1, calpha);
401 Exit;
402 end;
403
404 DX := X2 - X1;
405 DY := Y2 - Y1;
406
407 if DX < 0 then
408 begin
409 SX := -1;
410 DX := -DX;
411 end
412 else
413 SX := 1;
414
415 if DY < 0 then
416 begin
417 SY := -1;
418 DY := -DY;
419 end
420 else
421 SY := 1;
422
423 DX := DX shl 1;
424 DY := DY shl 1;
425
426 X := X1;
427 Y := Y1;
428
429 if DX > DY then
430 begin
431 E := 0;
432
433 while X <> X2 do
434 begin
435 alpha := calpha * E div DX;
436 dest.ErasePixel(X, Y, calpha - alpha);
437 dest.ErasePixel(X, Y + SY, alpha);
438 Inc(E, DY);
439 if E >= DX then
440 begin
441 Inc(Y, SY);
442 Dec(E, DX);
443 end;
444 Inc(X, SX);
445 end;
446 end
447 else
448 begin
449 E := 0;
450
451 while Y <> Y2 do
452 begin
453 alpha := calpha * E div DY;
454 dest.ErasePixel(X, Y, calpha - alpha);
455 dest.ErasePixel(X + SX, Y, alpha);
456 Inc(E, DX);
457 if E >= DY then
458 begin
459 Inc(X, SX);
460 Dec(E, DY);
461 end;
462 Inc(Y, SY);
463 end;
464 end;
465 if DrawLastPixel then
466 dest.ErasePixel(X2, Y2, calpha);
467end;
468
469procedure BGRADrawLineAntialias(dest: TBGRACustomBitmap; x1, y1, x2, y2: integer;
470 c1, c2: TBGRAPixel; dashLen: integer; DrawLastPixel: boolean; var DashPos: integer; LinearBlend : boolean);
471var
472 Y, X: integer;
473 DX, DY, SX, SY, E: integer;
474 alpha: NativeUInt;
475 c: TBGRAPixel;
476begin
477 if (c1.alpha=0) and (c2.alpha=0) then exit;
478 if DashLen <= 0 then
479 begin
480 BGRADrawLineAntialias(dest,x1,y1,x2,y2,MergeBGRA(c1,c2),DrawLastPixel,LinearBlend);
481 exit;
482 end;
483
484 DashPos := PositiveMod(DashPos,DashLen+DashLen);
485 if DashPos < DashLen then c := c1 else c := c2;
486
487 if (Y1 = Y2) and (X1 = X2) then
488 begin
489 if DrawLastPixel then
490 dest.DrawPixel(X1, Y1, c);
491 Exit;
492 end;
493
494 DX := X2 - X1;
495 DY := Y2 - Y1;
496
497 if DX < 0 then
498 begin
499 SX := -1;
500 DX := -DX;
501 end
502 else
503 SX := 1;
504
505 if DY < 0 then
506 begin
507 SY := -1;
508 DY := -DY;
509 end
510 else
511 SY := 1;
512
513 DX := DX shl 1;
514 DY := DY shl 1;
515
516 X := X1;
517 Y := Y1;
518
519 if DX > DY then
520 begin
521 E := 0;
522
523 while X <> X2 do
524 begin
525 alpha := c.alpha * E div DX;
526 dest.DrawPixel(X, Y, BGRA(c.red, c.green, c.blue, c.alpha - alpha));
527 dest.DrawPixel(X, Y + SY, BGRA(c.red, c.green, c.blue, alpha));
528 Inc(E, DY);
529 if E >= DX then
530 begin
531 Inc(Y, SY);
532 Dec(E, DX);
533 end;
534 Inc(X, SX);
535
536 Inc(DashPos);
537 if DashPos = DashLen then
538 c := c2
539 else
540 if DashPos = DashLen + DashLen then
541 begin
542 c := c1;
543 DashPos := 0;
544 end;
545 end;
546 end
547 else
548 begin
549 E := 0;
550
551 while Y <> Y2 do
552 begin
553 alpha := c.alpha * E div DY;
554 dest.DrawPixel(X, Y, BGRA(c.red, c.green, c.blue, c.alpha - alpha));
555 dest.DrawPixel(X + SX, Y, BGRA(c.red, c.green, c.blue, alpha));
556 Inc(E, DX);
557 if E >= DY then
558 begin
559 Inc(X, SX);
560 Dec(E, DY);
561 end;
562 Inc(Y, SY);
563
564 Inc(DashPos);
565 if DashPos = DashLen then
566 c := c2
567 else
568 if DashPos = DashLen + DashLen then
569 begin
570 c := c1;
571 DashPos := 0;
572 end;
573 end;
574 end;
575 if DrawLastPixel then
576 begin
577 dest.DrawPixel(X2, Y2, c);
578 inc(DashPos);
579 if DashPos = DashLen + DashLen then DashPos := 0;
580 end;
581end;
582
583function GetAlphaJoinFactor(alpha: byte): single;
584var t: single;
585begin
586 if alpha = 255 then result := 1 else
587 begin
588 result := (power(20,alpha/255)-1)/19*0.5;
589 t := power(alpha/255,40);
590 result := result*(1-t)+t*0.82;
591 end;
592end;
593
594function CreateBrushTexture(prototype: TBGRACustomBitmap; brushstyle: TBrushStyle;
595 PatternColor, BackgroundColor: TBGRAPixel; width: integer = 8; height: integer = 8; penwidth: single = 1): TBGRACustomBitmap;
596begin
597 result := prototype.NewBitmap(width,height);
598 if brushstyle <> bsClear then
599 begin
600 result.Fill(BackgroundColor);
601 if brushstyle in[bsDiagCross,bsBDiagonal] then
602 begin
603 result.DrawLineAntialias(-1,height,width,-1,PatternColor,penwidth);
604 result.DrawLineAntialias(-1-penwidth,0+penwidth,0+penwidth,-1-penwidth,PatternColor,penwidth);
605 result.DrawLineAntialias(width-1-penwidth,height+penwidth,width+penwidth,height-1-penwidth,PatternColor,penwidth);
606 end;
607 if brushstyle in[bsDiagCross,bsFDiagonal] then
608 begin
609 result.DrawLineAntialias(-1,-1,width,height,PatternColor,penwidth);
610 result.DrawLineAntialias(width-1-penwidth,-1-penwidth,width+penwidth,0+penwidth,PatternColor,penwidth);
611 result.DrawLineAntialias(-1-penwidth,height-1-penwidth,0+penwidth,height+penwidth,PatternColor,penwidth);
612 end;
613 if brushstyle in[bsHorizontal,bsCross] then
614 result.DrawLineAntialias(-1,height div 2,width,height div 2,PatternColor,penwidth);
615 if brushstyle in[bsVertical,bsCross] then
616 result.DrawLineAntialias(width div 2,-1,width div 2,height,PatternColor,penwidth);
617 end;
618end;
619
620function IsSolidPenStyle(ACustomPenStyle: TBGRAPenStyle): boolean;
621begin
622 result := ACustomPenStyle = nil;
623end;
624
625function IsClearPenStyle(ACustomPenStyle: TBGRAPenStyle): boolean;
626begin
627 if (length(ACustomPenStyle)=1) and (ACustomPenStyle[0]=0) then
628 result := true
629 else
630 result := false;
631end;
632
633function DuplicatePenStyle(ACustomPenStyle: array of single): TBGRAPenStyle;
634var
635 i: Integer;
636begin
637 setlength(result,length(ACustomPenStyle));
638 for i := 0 to high(result) do
639 result[i]:= ACustomPenStyle[i];
640end;
641
642function BGRAToPenStyle(ACustomPenStyle: TBGRAPenStyle): TPenStyle;
643begin
644 if IsSolidPenStyle(ACustomPenStyle) then exit(psSolid);
645 if IsClearPenStyle(ACustomPenStyle) then exit(psClear);
646 if PenStyleEqual(ACustomPenStyle, DashPenStyle) then exit(psDash);
647 if PenStyleEqual(ACustomPenStyle, DotPenStyle) then exit(psDot);
648 if PenStyleEqual(ACustomPenStyle, DashDotPenStyle) then exit(psDashDot);
649 if PenStyleEqual(ACustomPenStyle, DashDotDotPenStyle) then exit(psDashDotDot);
650 exit(psPattern);
651end;
652
653function PenStyleEqual(AStyle1, AStyle2: TBGRAPenStyle): boolean;
654var
655 i: Integer;
656begin
657 if length(AStyle1)<>length(AStyle2) then exit(false);
658 for i := 0 to high(AStyle1) do
659 if AStyle1[i] <> AStyle2[i] then exit(false);
660 exit(true);
661end;
662
663procedure ApplyPenStyle(const leftPts, rightPts: array of TPointF; const penstyle: TBGRAPenStyle;
664 width: single; var posstyle: single; out styledPts: ArrayOfTPointF);
665var
666 styleIndex :integer;
667 remainingDash: single;
668
669 procedure NextStyleIndex;
670 begin
671 inc(styleIndex);
672 if styleIndex = length(penstyle) then
673 styleIndex := 0;
674 remainingDash += penstyle[styleindex];
675 end;
676
677var
678 dashStartIndex: integer;
679 dashLeftStartPos,dashRightStartPos : TPointF;
680 betweenDash: boolean;
681
682 procedure StartDash(index: integer; t: single);
683 begin
684 dashStartIndex := index;
685 if t = 0 then
686 begin
687 dashLeftStartPos := leftPts[index];
688 dashRightStartPos := rightPts[index];
689 end else
690 begin
691 dashLeftStartPos := leftPts[index] + (leftPts[index+1]-leftPts[index])*t;
692 dashRightStartPos := rightPts[index] + (rightPts[index+1]-rightPts[index])*t;
693 end;
694 betweenDash := false;
695 end;
696
697var
698 nbStyled: integer;
699
700 procedure AddPt(pt: TPointF);
701 begin
702 if (nbStyled = 0) or (pt <> styledPts[nbStyled-1]) then
703 begin
704 if nbStyled = length(styledPts) then
705 setlength(styledPts,nbStyled*2+4);
706 styledPts[nbStyled] := pt;
707 inc(nbStyled);
708 end;
709 end;
710
711 procedure StartPolygon;
712 begin
713 if nbStyled > 0 then AddPt(EmptyPointF);
714 end;
715
716 procedure EndDash(index: integer; t: single);
717 var dashLeftEndPos,dashRightEndPos: TPointF;
718 i: Integer;
719 begin
720 if t=0 then
721 begin
722 dashLeftEndPos := leftPts[index];
723 dashRightEndPos := rightPts[index];
724 end else
725 begin
726 dashLeftEndPos := leftPts[index] + (leftPts[index+1]-leftPts[index])*t;
727 dashRightEndPos := rightPts[index] + (rightPts[index+1]-rightPts[index])*t;
728 end;
729 StartPolygon;
730 AddPt(dashLeftStartPos);
731 for i := dashStartIndex+1 to index do
732 AddPt(leftPts[i]);
733 AddPt(dashLeftEndPos);
734 AddPt(dashRightEndPos);
735 for i := index downto dashStartIndex+1 do
736 AddPt(rightPts[i]);
737 AddPt(dashRightStartPos);
738 betweenDash := true;
739 end;
740
741var
742 i,nb: integer;
743 styleLength: single;
744 len,lenDone: single;
745
746begin
747 nbStyled := 0;
748 styledPts := nil;
749 if IsClearPenStyle(penstyle) then exit;
750 if IsSolidPenStyle(penstyle) then
751 begin
752 for i := 0 to high(leftPts) do AddPt(leftPts[i]);
753 for i := high(rightPts) downto 0 do AddPt(rightPts[i]);
754 setlength(styledPts,nbStyled);
755 exit;
756 end;
757 if length(leftPts) <> length(rightPts) then
758 raise Exception.Create('Dimension mismatch');
759 nb := length(leftPts);
760 if length(penstyle) mod 2 <> 0 then
761 raise Exception.Create('Pen style must contain an even number of values');
762 styleLength := 0;
763 styleIndex := -1;
764 remainingDash := 0;
765 betweenDash := false;
766 for i := 0 to high(penstyle) do
767 if penstyle[i] <= 0 then
768 raise Exception.Create('Invalid pen dash length')
769 else
770 begin
771 styleLength += penstyle[i];
772 if styleLength >= posstyle then
773 begin
774 styleIndex := i;
775 remainingDash := styleLength-posstyle;
776 break;
777 end;
778 end;
779 if styleIndex = -1 then
780 begin
781 styleIndex := 0;
782 remainingDash := penstyle[0];
783 end;
784
785 if styleIndex mod 2 = 0 then
786 StartDash(0, 0) else
787 betweenDash := true;
788 for i := 0 to nb-2 do
789 begin
790 len := (sqrt(sqr(leftPts[i+1].x-leftPts[i].x) + sqr(leftPts[i+1].y-leftPts[i].y))+
791 sqrt(sqr(rightPts[i+1].x-rightPts[i].x) + sqr(rightPts[i+1].y-rightPts[i].y)))/(2*width);
792 lenDone := 0;
793 while lenDone < len do
794 begin
795 if len-lenDone < remainingDash then
796 begin
797 remainingDash -= len-lenDone;
798 if remainingDash = 0 then NextStyleIndex;
799 lenDone := len;
800 end else
801 if betweenDash then
802 begin
803 lenDone += remainingDash;
804 StartDash(i, lenDone/len);
805 remainingDash := 0;
806 NextStyleIndex;
807 end else
808 begin
809 lenDone += remainingDash;
810 EndDash(i, lenDone/len);
811 remainingDash := 0;
812 NextStyleIndex;
813 end;
814 end;
815 end;
816 if not betweenDash then
817 EndDash(nb-1,0);
818 setlength(styledPts,nbStyled);
819end;
820
821function ComputeWidePolylinePoints(const linepts: array of TPointF; width: single;
822 pencolor: TBGRAPixel; linecap: TPenEndCap; joinstyle: TPenJoinStyle; const penstyle: TBGRAPenStyle;
823 options: TBGRAPolyLineOptions; miterLimit: single; arrow: TBGRACustomArrow): ArrayOfTPointF;
824const oneOver512 = 1/512;
825var
826 startArrowPos, startArrowDir, endArrowPos, endArrowDir: TPointF;
827 startArrowLinePos, endArrowLinePos: single;
828 borders : array of record
829 leftSide,rightSide: TLineDef;
830 len: single;
831 leftDir: TPointF;
832 end;
833 compPts: array of TPointF;
834 nbCompPts: integer;
835 revCompPts: array of TPointF;
836 nbRevCompPts: integer;
837 pts: array of TPointF;
838 roundPrecision: integer;
839 hw: single; //half-width
840
841 procedure AddPt(normal,rev: TPointF); overload;
842 begin
843 if (nbCompPts > 0) and (compPts[nbCompPts-1]=normal) and
844 (nbRevCompPts > 0) and (revCompPts[nbRevCompPts-1]=rev) then exit;
845
846 if nbCompPts = length(compPts) then
847 setlength(compPts, length(compPts)*2);
848 compPts[nbCompPts] := normal;
849 inc(nbCompPts);
850
851 if nbRevCompPts = length(revCompPts) then
852 setlength(revCompPts, length(revCompPts)*2);
853 revCompPts[nbRevCompPts] := rev;
854 inc(nbRevCompPts);
855 end;
856
857 procedure AddPt(xnormal,ynormal: single; xrev,yrev: single); overload;
858 begin
859 AddPt(PointF(xnormal,ynormal),PointF(xrev,yrev));
860 end;
861
862 procedure AddRoundCap(origin: TPointF; dir: TPointF; fromCenter: boolean; flipped: boolean= false);
863 var i: integer;
864 a,s,c: single;
865 offset,flipvalue: single;
866 begin
867 if fromCenter then offset := 0 else offset := -Pi/2;
868 if flipped then flipvalue := -1 else flipvalue := 1;
869 for i := 1 to RoundPrecision do
870 begin
871 a := i/(RoundPrecision+1)*Pi/2 + offset;
872 s := sin(a)*hw*flipvalue;
873 c := cos(a)*hw;
874 AddPt( PointF(origin.x+ dir.x*c - dir.y*s, origin.y + dir.y*c + dir.x*s),
875 PointF(origin.x+ dir.x*c + dir.y*s, origin.y + dir.y*c - dir.x*s) );
876 end;
877 end;
878
879 procedure AddRoundCapAlphaJoin(origin: TPointF; dir: TPointF; fromCenter: boolean; flipped: boolean= false);
880 var i: integer;
881 a,s,c: single;
882 offset,flipvalue: single;
883 t,alphaFactor: single; //antialiasing join
884 begin
885 if fromCenter then offset := 0 else offset := -Pi/2;
886 if flipped then flipvalue := -1 else flipvalue := 1;
887
888 alphaFactor := GetAlphaJoinFactor(pencolor.alpha);
889
890 for i := 1 to RoundPrecision do
891 begin
892 a := i/(RoundPrecision+1)*Pi/2 + offset;
893 s := sin(a)*hw*flipvalue;
894 c := cos(a);
895 t := (1 - c) * (0.2 + alphaFactor*0.3) + alphaFactor;
896 c *= hw;
897 AddPt( PointF(origin.x+ dir.x*(c-t) - dir.y*s, origin.y + dir.y*(c-t) + dir.x*s),
898 PointF(origin.x+ dir.x*(c-t) + dir.y*s, origin.y + dir.y*(c-t) - dir.x*s) );
899 end;
900 end;
901
902 function ComputeRoundJoin(origin, pt1,pt2: TPointF): ArrayOfTPointF;
903 var a1,a2: single;
904 da: single;
905 precision,i: integer;
906 begin
907 a1 := arctan2(pt1.y-origin.y,pt1.x-origin.x);
908 a2 := arctan2(pt2.y-origin.y,pt2.x-origin.x);
909 if a2-a1 > Pi then a2 -= 2*Pi;
910 if a1-a2 > Pi then a1 -= 2*Pi;
911 if a2=a1 then
912 begin
913 setlength(result,1);
914 result[0] := pt1;
915 exit;
916 end;
917 da := a2-a1;
918 precision := round( sqrt( sqr(pt2.x-pt1.x)+sqr(pt2.y-pt1.y) ) ) +2;
919 setlength(result,precision);
920 for i := 0 to precision-1 do
921 result[i] := origin + PointF( cos(a1+i/(precision-1)*da)*hw,
922 sin(a1+i/(precision-1)*da)*hw );
923 end;
924
925var
926 joinLeft,joinRight: array of TPointF;
927 nbJoinLeft,nbJoinRight: integer;
928
929 procedure SetJoinLeft(joinpts: array of TPointF);
930 var i: integer;
931 begin
932 nbJoinLeft := length(joinpts);
933 if length(joinLeft) < nbJoinLeft then setlength(joinLeft,length(joinLeft)+nbJoinLeft+2);
934 for i := 0 to nbJoinLeft-1 do
935 joinLeft[i] := joinpts[i];
936 end;
937
938 procedure SetJoinRight(joinpts: array of TPointF);
939 var i: integer;
940 begin
941 nbJoinRight := length(joinpts);
942 if length(joinRight) < nbJoinRight then setlength(joinRight,length(joinRight)+nbJoinRight+2);
943 for i := 0 to nbJoinRight-1 do
944 joinRight[i] := joinpts[i];
945 end;
946
947 procedure AddJoin(index: integer);
948 var len,i: integer;
949 begin
950 len := nbJoinLeft;
951 if nbJoinRight > len then
952 len := nbJoinRight;
953 if len = 0 then exit;
954 if (len > 1) and (index <> -1) then
955 begin
956 if nbJoinLeft=1 then
957 AddPt(joinLeft[0], joinLeft[0] - 2*borders[Index].leftDir) else
958 if nbJoinRight=1 then
959 AddPt( joinRight[0] + 2* borders[index].leftDir, joinRight[0]);
960 end;
961 for i := 0 to len-1 do
962 begin
963 AddPt(joinLeft[i*nbJoinLeft div len],
964 joinRight[i*nbJoinRight div len]);
965 end;
966 if (len > 1) and (index <> -1) then
967 begin
968 if nbJoinLeft=1 then
969 AddPt(joinLeft[0], joinLeft[0] - 2*borders[index+1].leftDir) else
970 if nbJoinRight=1 then
971 AddPt(joinRight[0]+2*borders[index+1].leftDir, joinRight[0]);
972 end;
973 end;
974
975var
976 NbPolyAcc: integer;
977
978 procedure FlushLine(lastPointIndex: integer);
979 var
980 enveloppe: arrayOfTPointF;
981 posstyle: single;
982 i,idxInsert: Integer;
983 begin
984 if lastPointIndex <> -1 then
985 AddPt( pts[lastPointIndex] + borders[lastPointIndex-1].leftDir,
986 pts[lastPointIndex] - borders[lastPointIndex-1].leftDir);
987
988 if (lastPointIndex = high(pts)) and (linecap = pecRound) and not (plNoEndCap in options) then
989 begin
990 if not (plRoundCapOpen in options) then
991 AddRoundCap(pts[high(pts)],borders[high(pts)-1].leftSide.dir,false)
992 else
993 AddRoundCapAlphaJoin(pts[high(pts)],
994 -borders[high(pts)-1].leftSide.dir, false,true);
995 end;
996 posstyle := 0;
997 ApplyPenStyle(slice(compPts,nbCompPts),slice(revCompPts,nbRevCompPts),penstyle,width,posstyle,enveloppe);
998
999 if Result=nil then
1000 begin
1001 Result := enveloppe;
1002 NbPolyAcc := length(enveloppe);
1003 end
1004 else
1005 if enveloppe <> nil then
1006 begin
1007 if NbPolyAcc +1+length(enveloppe) > length(Result) then
1008 setlength(Result, length(Result)*2+1+length(enveloppe));
1009
1010 idxInsert := NbPolyAcc+1;
1011 Result[idxInsert-1] := EmptyPointF;
1012 for i := 0 to high(enveloppe) do
1013 Result[idxInsert+i]:= enveloppe[i];
1014 inc(NbPolyAcc, length(enveloppe)+1);
1015 end;
1016
1017 nbCompPts := 0;
1018 nbRevCompPts := 0;
1019 end;
1020
1021 procedure CycleFlush;
1022 var idx: integer;
1023 begin
1024 if Result = nil then
1025 begin
1026 if (nbCompPts > 1) and (nbRevCompPts > 1) then
1027 begin
1028 compPts[0] := compPts[nbCompPts-1];
1029 revCompPts[0] := revCompPts[nbRevCompPts-1];
1030 end;
1031 FlushLine(-1);
1032 end else
1033 begin
1034 if (nbCompPts >= 1) and (nbRevCompPts >= 1) and (NbPolyAcc >= 2) then
1035 begin
1036 Result[0] := compPts[nbCompPts-1];
1037 idx := 0;
1038 while (idx < high(Result)) and (not isEmptyPointF(Result[idx+1])) do inc(idx);
1039 Result[idx] := revCompPts[nbRevCompPts-1];
1040 end;
1041 FlushLine(-1);
1042 end;
1043 end;
1044
1045 procedure FinalizeArray;
1046 var arrowStartData, arrowEndData: ArrayOfTPointF;
1047 finalNb,i,delta: integer;
1048 hasStart,hasEnd: boolean;
1049 begin
1050 if assigned(arrow) and not isEmptyPointF(startArrowPos) then
1051 arrowStartData := arrow.ComputeStartAt(startArrowPos, startArrowDir, width, startArrowLinePos)
1052 else
1053 arrowStartData := nil;
1054 if assigned(arrow) and not isEmptyPointF(endArrowPos) then
1055 arrowEndData := arrow.ComputeEndAt(endArrowPos, endArrowDir, width, endArrowLinePos)
1056 else
1057 arrowEndData := nil;
1058 hasStart := length(arrowStartData)>0;
1059 hasEnd := length(arrowEndData)>0;
1060 finalNb := NbPolyAcc;
1061 if hasStart then
1062 begin
1063 delta := length(arrowStartData)+1;
1064 finalNb += delta;
1065 end else delta := 0;
1066 if hasEnd then finalNb += length(arrowEndData)+1;
1067 SetLength(Result, finalNb);
1068 if hasStart then
1069 begin
1070 for i := NbPolyAcc-1 downto 0 do
1071 result[i+delta] := result[i];
1072 result[delta-1] := EmptyPointF;
1073 for i := 0 to high(arrowStartData) do
1074 result[i] := arrowStartData[i];
1075 end;
1076 if hasEnd then
1077 begin
1078 delta += NbPolyAcc+1;
1079 result[delta-1] := EmptyPointF;
1080 for i := 0 to high(arrowEndData) do
1081 result[i+delta] := arrowEndData[i];
1082 end;
1083 end;
1084
1085var
1086 i: integer;
1087 dir: TPointF;
1088 leftInter,rightInter,diff: TPointF;
1089 len,maxMiter: single;
1090 littleBorder: TLineDef;
1091 turn,maxDiff: single;
1092 nbPts: integer;
1093 ShouldFlushLine, HasLittleBorder, NormalRestart: Boolean;
1094 pt1,pt2,pt3,pt4: TPointF;
1095 linePos: single;
1096 startArrowDone,endArrowDone: boolean;
1097 wantedStartArrowPos,wantedEndArrowPos: single;
1098
1099begin
1100 Result := nil;
1101
1102 if (length(linepts)=0) or (width = 0) then exit;
1103 if IsClearPenStyle(penstyle) then exit;
1104 for i := 0 to high(linepts) do
1105 if isEmptyPointF(linepts[i]) then
1106 begin
1107 result := ComputeWidePolyPolylinePoints(linepts,width,pencolor,linecap,joinstyle,penstyle,options,miterLimit,arrow);
1108 exit;
1109 end;
1110
1111 if (plAutoCycle in options) and (length(linepts) >= 2) and (linepts[0]=linepts[high(linepts)]) then
1112 options := options + [plCycle];
1113 if plNoEndCap in options then options := options - [plRoundCapOpen];
1114
1115 hw := width / 2;
1116 case joinstyle of
1117 pjsBevel,pjsRound: maxMiter := hw*1.001;
1118 pjsMiter: if miterLimit < 1.001 then maxMiter := hw*1.001 else
1119 maxMiter := hw*miterLimit;
1120 else
1121 raise Exception.Create('Unknown join style');
1122 end;
1123
1124 roundPrecision := round(hw)+2;
1125
1126 nbPts := 0;
1127 setlength(pts, length(linepts)+2);
1128 for i := 0 to high(linepts) do
1129 if (nbPts = 0) or (abs(linepts[i].x-pts[nbPts-1].x)>oneOver512) or (abs(linepts[i].y-pts[nbPts-1].y)>oneOver512) then
1130 begin
1131 pts[nbPts]:= linePts[i];
1132 inc(nbPts);
1133 end;
1134 if (nbPts > 1) and (plCycle in options) and
1135 (abs(pts[0].x-pts[nbPts-1].x)<=oneOver512) and
1136 (abs(pts[0].y-pts[nbPts-1].y)<=oneOver512) then dec(nbPts);
1137 if (plCycle in options) and (nbPts > 2) then
1138 begin
1139 if (pts[nbPts-1] <> pts[0]) then
1140 begin
1141 pts[nbPts] := pts[0];
1142 inc(nbPts);
1143 end;
1144 pts[nbPts] := pts[1];
1145 inc(nbPts);
1146 end else
1147 options -= [plCycle];
1148
1149 setlength(pts,nbPts);
1150
1151 if nbPts = 1 then
1152 begin
1153 if (linecap <> pecFlat) and ((linecap <> pecRound) or not (plRoundCapOpen in options)) then
1154 result := ComputeEllipse(pts[0].x,pts[0].y,hw,hw);
1155 exit;
1156 end;
1157
1158 startArrowDir := EmptyPointF;
1159 startArrowPos := EmptyPointF;
1160 endArrowDir := EmptyPointF;
1161 endArrowPos := EmptyPointF;
1162 if Assigned(arrow) then
1163 begin
1164 wantedStartArrowPos:= arrow.StartOffsetX;
1165 wantedEndArrowPos:= arrow.EndOffsetX;
1166 startArrowDone := not arrow.IsStartDefined;
1167 endArrowDone := not arrow.IsEndDefined;
1168 end
1169 else
1170 begin
1171 wantedStartArrowPos:= 0;
1172 wantedEndArrowPos:= 0;
1173 startArrowDone := true;
1174 endArrowDone := true;
1175 end;
1176
1177 //init computed points arrays
1178 setlength(compPts, length(pts)*2+4);
1179 setlength(revCompPts, length(pts)*2+4); //reverse order array
1180 nbCompPts := 0;
1181 nbRevCompPts := 0;
1182 NbPolyAcc := 0;
1183
1184 if not endArrowDone then
1185 begin
1186 wantedEndArrowPos:= -wantedEndArrowPos*width;
1187 linePos := 0;
1188 for i := high(pts) downto 1 do
1189 begin
1190 dir := pts[i-1]-pts[i];
1191 len := sqrt(dir*dir);
1192 dir *= 1/len;
1193 if not endArrowDone and (linePos+len >= wantedEndArrowPos) then
1194 begin
1195 endArrowPos := pts[i];
1196 endArrowDir := -dir;
1197 endArrowLinePos := -linePos/width;
1198 endArrowDone := true;
1199 break;
1200 end;
1201 linePos += len;
1202 end;
1203 end;
1204
1205 wantedStartArrowPos:= -wantedStartArrowPos*width;
1206 linePos := 0;
1207 //compute borders
1208 setlength(borders, length(pts)-1);
1209 for i := 0 to high(pts)-1 do
1210 begin
1211 dir := pts[i+1]-pts[i];
1212 len := sqrt(dir*dir);
1213 dir *= 1/len;
1214 if not startArrowDone and (linePos+len >= wantedStartArrowPos) then
1215 begin
1216 startArrowPos := pts[i];
1217 startArrowDir := -dir;
1218 startArrowLinePos := -linePos/width;
1219 startArrowDone := true;
1220 end;
1221 if (linecap = pecSquare) and ((not (plNoStartCap in options) and (i=0)) or
1222 (not (plNoEndCap in options) and (i=high(pts)-1))) then //for square cap, just start and end further
1223 begin
1224 if i=0 then
1225 pts[0] -= dir*hw;
1226
1227 if (i=high(pts)-1) then
1228 pts[high(pts)] += dir*hw;
1229
1230 //length changed
1231 dir := pts[i+1]-pts[i];
1232 len := sqrt(dir*dir);
1233 dir *= 1/len;
1234 end else
1235 if not (plNoStartCap in options) and (linecap = pecRound) and (i=0) and not (plCycle in options) then
1236 AddRoundCap(pts[0], -dir ,true);
1237
1238 borders[i].len := len;
1239 borders[i].leftDir := PointF(dir.y*hw,-dir.x*hw);
1240 borders[i].leftSide.origin := pts[i] + borders[i].leftDir;
1241 borders[i].leftSide.dir := dir;
1242 borders[i].rightSide.origin := pts[i] - borders[i].leftDir;
1243 borders[i].rightSide.dir := dir;
1244 linePos += len;
1245 end;
1246
1247 //first points
1248 AddPt( pts[0] + borders[0].leftDir,
1249 pts[0] - borders[0].leftDir );
1250
1251 setlength(joinLeft,1);
1252 setlength(joinRight,1);
1253 ShouldFlushLine := False;
1254 //between first and last points
1255 for i := 0 to high(pts)-2 do
1256 begin
1257 HasLittleBorder := false;
1258
1259 //determine u-turn
1260 turn := borders[i].leftSide.dir * borders[i+1].leftSide.dir;
1261 if turn < -0.99999 then
1262 begin
1263 if joinstyle <> pjsRound then
1264 begin
1265 littleBorder.origin := pts[i+1] + borders[i].leftSide.dir*maxMiter;
1266 littleBorder.dir := borders[i].leftDir;
1267 HasLittleBorder := true;
1268 end;
1269
1270 nbJoinLeft := 0;
1271 nbJoinRight:= 0;
1272
1273 ShouldFlushLine := True;
1274 end else
1275 if turn > 0.99999 then //straight line
1276 begin
1277 pt1 := pts[i+1] + borders[i].leftDir;
1278 pt2 := pts[i+2] + borders[i+1].leftDir;
1279 SetJoinLeft([pt1, (pt1+pt2)*(1/2),pt2]);
1280
1281 pt1 := pts[i+1] - borders[i].leftDir;
1282 pt2 := pts[i+2] - borders[i+1].leftDir;
1283 SetJoinRight([pt1,(pt1+pt2)*(1/2),pt2]);
1284 end else
1285 begin
1286 //determine turning left or right
1287 turn := borders[i].leftSide.dir.x*borders[i+1].leftSide.dir.y - borders[i].leftSide.dir.y*borders[i+1].leftSide.dir.x;
1288
1289 maxDiff := borders[i].len;
1290 if borders[i+1].len < maxDiff then
1291 maxDiff := borders[i+1].len;
1292 if penstyle <> nil then
1293 if maxDiff > 2*width then maxDiff := 2*width;
1294 maxDiff := sqrt(sqr(maxDiff)+sqr(hw));
1295
1296 //leftside join
1297 leftInter := IntersectLine( borders[i].leftSide, borders[i+1].leftSide );
1298 diff := leftInter-pts[i+1];
1299 len := sqrt(diff*diff);
1300 if (len > maxMiter) and (turn >= 0) then //if miter too far
1301 begin
1302 diff.x /= len;
1303 diff.y /= len;
1304 if joinstyle <> pjsRound then
1305 begin
1306 //compute little border
1307 littleBorder.origin := pts[i+1]+diff*maxMiter;
1308 littleBorder.dir := PointF(diff.y,-diff.x);
1309 HasLittleBorder := true;
1310
1311 //intersect with each border
1312 pt1 := IntersectLine(borders[i].leftSide, littleBorder);
1313 pt2 := IntersectLine(borders[i+1].leftSide, littleBorder);
1314 SetJoinLeft( [pt1, pt2] );
1315 end else
1316 begin
1317 //perpendicular
1318 pt1 := PointF(pts[i+1].x+borders[i].leftSide.dir.y*hw,
1319 pts[i+1].y-borders[i].leftSide.dir.x*hw);
1320 pt2 := PointF(pts[i+1].x+borders[i+1].leftSide.dir.y*hw,
1321 pts[i+1].y-borders[i+1].leftSide.dir.x*hw);
1322 SetJoinLeft(ComputeRoundJoin(pts[i+1],pt1,pt2));
1323 end;
1324 end else
1325 if (len > maxDiff) and (turn <= 0) then //if inner intersection too far
1326 begin
1327 ShouldFlushLine := True;
1328 nbJoinLeft := 0;
1329 end else
1330 begin
1331 if (turn > 0) and (len > 1.0001*hw) then
1332 SetJoinLeft([leftInter,leftInter]) else
1333 begin
1334 nbJoinLeft := 1;
1335 joinLeft[0] := leftInter;
1336 end;
1337 end;
1338
1339 //rightside join
1340 rightInter := IntersectLine( borders[i].rightSide, borders[i+1].rightSide );
1341 diff := rightInter-pts[i+1];
1342 len := sqrt(diff*diff);
1343 if (len > maxMiter) and (turn <= 0) then //if miter too far
1344 begin
1345 diff *= 1/len;
1346
1347 if joinstyle <> pjsRound then
1348 begin
1349 //compute little border
1350 littleBorder.origin := pts[i+1] + diff*maxMiter;
1351 littleBorder.dir := PointF(diff.y, -diff.x);
1352 HasLittleBorder := true;
1353
1354 //intersect with each border
1355 pt1 := IntersectLine(borders[i].rightSide, littleBorder);
1356 pt2 := IntersectLine(borders[i+1].rightSide, littleBorder);
1357 SetJoinRight( [pt1, pt2] );
1358 end else
1359 begin
1360 //perpendicular
1361 pt1 := PointF(pts[i+1].x-borders[i].rightSide.dir.y*hw,
1362 pts[i+1].y+borders[i].rightSide.dir.x*hw);
1363 pt2 := PointF(pts[i+1].x-borders[i+1].rightSide.dir.y*hw,
1364 pts[i+1].y+borders[i+1].rightSide.dir.x*hw);
1365 SetJoinRight(ComputeRoundJoin(pts[i+1],pt1,pt2));
1366 end;
1367 end else
1368 if (len > maxDiff) and (turn >= 0) then //if inner intersection too far
1369 begin
1370 ShouldFlushLine := True;
1371 nbJoinRight := 0;
1372 end else
1373 begin
1374 if (turn < 0) and (len > 1.0001*hw) then
1375 SetJoinRight([rightInter,rightInter]) else
1376 begin
1377 nbJoinRight := 1;
1378 joinRight[0] := rightInter;
1379 end;
1380 end;
1381 end;
1382
1383 if ShouldFlushLine then
1384 begin
1385 NormalRestart := True;
1386 if HasLittleBorder then
1387 begin
1388 if turn >= 0 then
1389 begin
1390 //intersect with each border
1391 pt1 := IntersectLine(borders[i].leftSide, littleBorder);
1392 pt2 := IntersectLine(borders[i+1].leftSide, littleBorder);
1393 pt3 := pts[i+1] - borders[i].leftDir;
1394 pt4 := pts[i+1] + borders[i].leftDir;
1395
1396 AddPt(pt4,pt3);
1397 AddPt(pt1,pt2);
1398 end else
1399 begin
1400 //intersect with each border
1401 pt1 := IntersectLine(borders[i+1].rightSide, littleBorder);
1402 pt2 := IntersectLine(borders[i].rightSide, littleBorder);
1403 pt3 := pts[i+1] + borders[i].leftDir;
1404 pt4 := pts[i+1] - borders[i].leftDir;
1405
1406 AddPt(pt3,pt4);
1407 AddPt(pt1,pt2);
1408 end;
1409
1410 FlushLine(-1);
1411
1412 AddPt(pt2,pt1);
1413 end else
1414 if joinstyle = pjsRound then
1415 begin
1416
1417 if {(penstyle= nil) and} (turn > 0) then
1418 begin
1419 pt1 := pts[i+1] + borders[i].leftDir;
1420 pt2 := pts[i+1] + borders[i+1].leftDir;
1421 pt3 := pts[i+1] - borders[i].leftDir;
1422 pt4 := pts[i+1];
1423
1424 SetJoinLeft([pt1,pt1]);
1425 SetJoinRight([pt3,pt4]);
1426 AddJoin(-1);
1427
1428 SetJoinLeft(ComputeRoundJoin(pts[i+1],pt1,pt2));
1429 nbJoinRight := 1;
1430 joinRight[0] := pt4;
1431 AddJoin(-1);
1432 FlushLine(-1);
1433 end else
1434 if {(penstyle= nil) and} (turn < 0) then
1435 begin
1436 pt1 := pts[i+1] - borders[i].leftDir;
1437 pt2 := pts[i+1] - borders[i+1].leftDir;
1438 pt3 := pts[i+1] + borders[i].leftDir;
1439 pt4 := pts[i+1];
1440
1441 SetJoinRight([pt1,pt1]);
1442 SetJoinLeft([pt3,pt4]);
1443 AddJoin(-1);
1444
1445 SetJoinRight(ComputeRoundJoin(pts[i+1],pt1,pt2));
1446 nbJoinLeft := 1;
1447 joinLeft[0] := pt4;
1448 AddJoin(-1);
1449 FlushLine(-1);
1450 end else
1451 if (nbCompPts > 1) and (nbRevCompPts > 1) then
1452 begin
1453 pt1 := pts[i+1]+borders[i].leftDir;
1454 pt2 := pts[i+1]-borders[i].leftDir;
1455 AddPt( pt1, pt2 );
1456 FlushLine(-1);
1457 end else
1458 begin
1459 FlushLine(i+1);
1460 end;
1461 end else
1462 begin
1463 FlushLine(i+1);
1464 if turn > 0 then
1465 AddPt( leftInter, pts[i+1]+borders[i].leftDir ) else
1466 if turn < 0 then
1467 AddPt( pts[i+1] - borders[i].leftDir, rightInter );
1468 end;
1469
1470 If NormalRestart then
1471 AddPt(pts[i+1]+borders[i+1].leftDir,
1472 pts[i+1]-borders[i+1].leftDir);
1473
1474 ShouldFlushLine := false;
1475 end else
1476 AddJoin(i);
1477 end;
1478
1479 if plCycle in options then
1480 CycleFlush
1481 else
1482 FlushLine(high(pts));
1483
1484 FinalizeArray;
1485end;
1486
1487function ComputeWidePolyPolylinePoints(const linepts: array of TPointF;
1488 width: single; pencolor: TBGRAPixel; linecap: TPenEndCap;
1489 joinstyle: TPenJoinStyle; const penstyle: TBGRAPenStyle;
1490 options: TBGRAPolyLineOptions; miterLimit: single; arrow: TBGRACustomArrow): ArrayOfTPointF;
1491
1492var
1493 results: array of array of TPointF;
1494 nbResults,nbTotalPts: integer;
1495
1496 procedure AddWidePolyline(startIndex,endIndexP1: integer);
1497 var
1498 tempWidePolyline: array of TPointF;
1499 subPts: array of TPointF;
1500 j : integer;
1501 begin
1502 if endIndexP1 > startIndex then
1503 begin
1504 setlength(subPts,endIndexP1-startIndex);
1505 for j := startIndex to endIndexP1-1 do
1506 subPts[j-startIndex] := linepts[j];
1507 tempWidePolyline := ComputeWidePolylinePoints(subPts,width,pencolor,linecap,joinstyle,penstyle,options,miterLimit,arrow);
1508 if length(results) = nbresults then
1509 setlength(results,(nbresults+1)*2);
1510 results[nbResults] := tempWidePolyline;
1511 if nbResults <> 0 then inc(nbTotalPts);
1512 inc(nbResults);
1513 inc(nbTotalPts,length(tempWidePolyline));
1514 end;
1515 end;
1516
1517var
1518 start,i,j: integer;
1519
1520begin
1521 start := 0;
1522 nbResults := 0;
1523 nbTotalPts := 0;
1524 for i := 0 to high(linepts) do
1525 if isEmptyPointF(linepts[i]) then
1526 begin
1527 AddWidePolyline(start,i);
1528 start := i+1;
1529 end;
1530 AddWidePolyline(start,length(linepts));
1531
1532 setlength(result, nbTotalPts);
1533 start := 0;
1534 for i := 0 to nbResults-1 do
1535 begin
1536 if i <> 0 then
1537 begin
1538 result[start] := EmptyPointF;
1539 inc(start);
1540 end;
1541 for j := 0 to high(results[i]) do
1542 begin
1543 result[start] := results[i][j];
1544 inc(start);
1545 end;
1546 end;
1547end;
1548
1549{ TBGRAPenStroker }
1550
1551function TBGRAPenStroker.GetArrow: TBGRACustomArrow;
1552begin
1553 result := FArrow;
1554end;
1555
1556function TBGRAPenStroker.GetArrowOwned: boolean;
1557begin
1558 result := FArrowOwned;
1559end;
1560
1561function TBGRAPenStroker.GetCustomPenStyle: TBGRAPenStyle;
1562begin
1563 result := FCustomPenStyle;
1564end;
1565
1566function TBGRAPenStroker.GetJoinStyle: TPenJoinStyle;
1567begin
1568 result := FJoinStyle;
1569end;
1570
1571function TBGRAPenStroker.GetLineCap: TPenEndCap;
1572begin
1573 result := FLineCap;
1574end;
1575
1576function TBGRAPenStroker.GetMiterLimit: single;
1577begin
1578 result := FMiterLimit;
1579end;
1580
1581function TBGRAPenStroker.GetPenStyle: TPenStyle;
1582begin
1583 result := FPenStyle;
1584end;
1585
1586function TBGRAPenStroker.GetStrokeMatrix: TAffineMatrix;
1587begin
1588 result := FOriginalStrokeMatrix;
1589end;
1590
1591procedure TBGRAPenStroker.SetArrow(AValue: TBGRACustomArrow);
1592begin
1593 FArrow := AValue;
1594end;
1595
1596procedure TBGRAPenStroker.SetArrowOwned(AValue: boolean);
1597begin
1598 FArrowOwned := AValue;
1599end;
1600
1601procedure TBGRAPenStroker.SetCustomPenStyle(AValue: TBGRAPenStyle);
1602begin
1603 if FCustomPenStyle=AValue then Exit;
1604 FCustomPenStyle:=AValue;
1605 if AValue = SolidPenStyle then FPenStyle := psSolid
1606 else if AValue = ClearPenStyle then FPenStyle:= psClear
1607 else if AValue = DashPenStyle then FPenStyle:= psDash
1608 else if AValue = DotPenStyle then FPenStyle := psDot
1609 else if AValue = DashDotPenStyle then FPenStyle:= psDashDot
1610 else if AValue = DashDotDotPenStyle then FPenStyle:= psDashDotDot
1611 else
1612 begin
1613 FPenStyle := psPattern;
1614 FCustomPenStyle:= DuplicatePenStyle(AValue);
1615 end;
1616end;
1617
1618procedure TBGRAPenStroker.SetJoinStyle(AValue: TPenJoinStyle);
1619begin
1620 FJoinStyle:= AValue;
1621end;
1622
1623procedure TBGRAPenStroker.SetLineCap(AValue: TPenEndCap);
1624begin
1625 FLineCap:= AValue;
1626end;
1627
1628procedure TBGRAPenStroker.SetMiterLimit(AValue: single);
1629begin
1630 FMiterLimit := AValue;
1631end;
1632
1633procedure TBGRAPenStroker.SetStrokeMatrix(const AValue: TAffineMatrix);
1634begin
1635 if FOriginalStrokeMatrix=AValue then Exit;
1636 FOriginalStrokeMatrix:=AValue;
1637 FStrokeMatrix := AValue;
1638 FStrokeMatrix[1,3] := 0;
1639 FStrokeMatrix[2,3] := 0;
1640 FStrokeZoom := max(VectLen(PointF(FStrokeMatrix[1,1],FStrokeMatrix[2,1])),
1641 VectLen(PointF(FStrokeMatrix[1,2],FStrokeMatrix[2,2])));
1642 if FStrokeZoom > 0 then
1643 FStrokeMatrix *= AffineMatrixScale(1/FStrokeZoom,1/FStrokeZoom);
1644 FStrokeMatrixIdentity := IsAffineMatrixIdentity(FStrokeMatrix);
1645 FStrokeMatrixInverse := AffineMatrixInverse(FStrokeMatrix);
1646end;
1647
1648procedure TBGRAPenStroker.SetPenStyle(AValue: TPenStyle);
1649begin
1650 if FPenStyle=AValue then Exit;
1651 Case AValue of
1652 psSolid: FCustomPenStyle := SolidPenStyle;
1653 psDash: FCustomPenStyle := DashPenStyle;
1654 psDot: FCustomPenStyle := DotPenStyle;
1655 psDashDot: FCustomPenStyle := DashDotPenStyle;
1656 psDashDotDot: FCustomPenStyle := DashDotDotPenStyle;
1657 else FCustomPenStyle := ClearPenStyle;
1658 end;
1659 FPenStyle := AValue;
1660end;
1661
1662constructor TBGRAPenStroker.Create;
1663begin
1664 Style := psSolid;
1665 LineCap := pecRound;
1666 JoinStyle := pjsBevel;
1667 MiterLimit := 2;
1668 fillchar(FOriginalStrokeMatrix,sizeof(FOriginalStrokeMatrix),0);
1669 StrokeMatrix := AffineMatrixIdentity;
1670end;
1671
1672destructor TBGRAPenStroker.Destroy;
1673begin
1674 if ArrowOwned then FreeAndNil(FArrow);
1675 inherited Destroy;
1676end;
1677
1678function TBGRAPenStroker.ComputePolyline(const APoints: array of TPointF;
1679 AWidth: single; AClosedCap: boolean): ArrayOfTPointF;
1680var
1681 c: TBGRAPixel;
1682begin
1683 if not AClosedCap then
1684 c := BGRAWhite //needed for alpha junction
1685 else
1686 c := BGRAPixelTransparent;
1687
1688 if FStrokeMatrixIdentity then
1689 result := ComputePolyline(APoints,AWidth*FStrokeZoom,c,AClosedCap)
1690 else
1691 result := FStrokeMatrix*ComputePolyline(FStrokeMatrixInverse*APoints,AWidth*FStrokeZoom,c,AClosedCap);
1692end;
1693
1694function TBGRAPenStroker.ComputePolyline(const APoints: array of TPointF;
1695 AWidth: single; APenColor: TBGRAPixel; AClosedCap: boolean): ArrayOfTPointF;
1696var options: TBGRAPolyLineOptions;
1697begin
1698 options := [];
1699 if Assigned(Arrow) and Arrow.IsStartDefined then options += [plNoStartCap];
1700 if Assigned(Arrow) and Arrow.IsEndDefined then options += [plNoEndCap];
1701 if not AClosedCap then options += [plRoundCapOpen];
1702 if FStrokeMatrixIdentity then
1703 result := BGRAPen.ComputeWidePolylinePoints(APoints, AWidth*FStrokeZoom, APenColor, LineCap, JoinStyle, CustomPenStyle, options, MiterLimit, Arrow)
1704 else
1705 result := FStrokeMatrix*BGRAPen.ComputeWidePolylinePoints(FStrokeMatrixInverse*APoints, AWidth*FStrokeZoom, APenColor, LineCap, JoinStyle, CustomPenStyle, options, MiterLimit, Arrow);
1706end;
1707
1708function TBGRAPenStroker.ComputePolylineAutocycle(
1709 const APoints: array of TPointF; AWidth: single): ArrayOfTPointF;
1710var options: TBGRAPolyLineOptions;
1711begin
1712 options := [plAutoCycle];
1713 if Assigned(Arrow) and Arrow.IsStartDefined then options += [plNoStartCap];
1714 if Assigned(Arrow) and Arrow.IsEndDefined then options += [plNoEndCap];
1715 if FStrokeMatrixIdentity then
1716 result := BGRAPen.ComputeWidePolylinePoints(APoints, AWidth*FStrokeZoom, BGRAPixelTransparent, LineCap, JoinStyle, CustomPenStyle, options, MiterLimit, Arrow)
1717 else
1718 result := FStrokeMatrix*BGRAPen.ComputeWidePolylinePoints(FStrokeMatrixInverse*APoints, AWidth*FStrokeZoom, BGRAPixelTransparent, LineCap, JoinStyle, CustomPenStyle, options, MiterLimit, Arrow)
1719end;
1720
1721function TBGRAPenStroker.ComputePolygon(const APoints: array of TPointF;
1722 AWidth: single): ArrayOfTPointF;
1723begin
1724 if FStrokeMatrixIdentity then
1725 result := BGRAPen.ComputeWidePolylinePoints(APoints, AWidth*FStrokeZoom, BGRAPixelTransparent, LineCap, JoinStyle, CustomPenStyle, [plCycle], MiterLimit)
1726 else
1727 result := FStrokeMatrix*BGRAPen.ComputeWidePolylinePoints(FStrokeMatrixInverse*APoints, AWidth*FStrokeZoom, BGRAPixelTransparent, LineCap, JoinStyle, CustomPenStyle, [plCycle], MiterLimit);
1728end;
1729
1730initialization
1731
1732 //special pen styles
1733 SolidPenStyle := nil;
1734
1735 setlength(ClearPenStyle,1);
1736 ClearPenStyle[0] := 0;
1737
1738 DashPenStyle := BGRAPenStyle(3,1);
1739 DotPenStyle := BGRAPenStyle(1,1);
1740 DashDotPenStyle := BGRAPenStyle(3,1,1,1);
1741 DashDotDotPenStyle := BGRAPenStyle(3,1,1,1,1,1);
1742
1743end.
1744
Note: See TracBrowser for help on using the repository browser.