1 unit BGRAPen;
2 
3 {$mode objfpc}{$H+}
4 
5 interface
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 
13 uses
14   SysUtils, BGRAGraphics, BGRABitmapTypes, BGRATransform;
15 
16 var   //predefined pen styles
17   SolidPenStyle, DashPenStyle, DotPenStyle, DashDotPenStyle, DashDotDotPenStyle, ClearPenStyle: TBGRAPenStyle;
18 
19 type
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 
GetArrownull41       function GetArrow: TBGRACustomArrow; override;
GetArrowOwnednull42       function GetArrowOwned: boolean; override;
GetCustomPenStylenull43       function GetCustomPenStyle: TBGRAPenStyle; override;
GetJoinStylenull44       function GetJoinStyle: TPenJoinStyle; override;
GetLineCapnull45       function GetLineCap: TPenEndCap; override;
GetMiterLimitnull46       function GetMiterLimit: single; override;
GetPenStylenull47       function GetPenStyle: TPenStyle; override;
GetStrokeMatrixnull48       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;
ComputePolylinenull60       function ComputePolyline(const APoints: array of TPointF; AWidth: single; AClosedCap: boolean = true): ArrayOfTPointF; overload; override;
ComputePolylinenull61       function ComputePolyline(const APoints: array of TPointF; AWidth: single; APenColor: TBGRAPixel; AClosedCap: boolean = true): ArrayOfTPointF; overload; override;
ComputePolylineAutocyclenull62       function ComputePolylineAutocycle(const APoints: array of TPointF; AWidth: single): ArrayOfTPointF; override;
ComputePolygonnull63       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;
onstnull73   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 }
ComputeWidePolylinePointsnull76 function 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 }
ComputeWidePolyPolylinePointsnull81 function 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
90 procedure BGRADrawLineAliased(dest: TBGRACustomBitmap; x1, y1, x2, y2: integer; c: TBGRAPixel; DrawLastPixel: boolean; ADrawMode: TDrawMode = dmDrawWithTransparency); deprecated;
91 procedure BGRAEraseLineAliased(dest: TBGRACustomBitmap; x1, y1, x2, y2: integer; alpha: byte; DrawLastPixel: boolean); deprecated;
92 
93 //antialiased version
94 procedure BGRADrawLineAntialias({%H-}dest: TBGRACustomBitmap; x1, y1, x2, y2: integer;
95   c: TBGRAPixel; DrawLastPixel: boolean; LinearBlend : boolean = false); overload; deprecated;
96 procedure BGRAEraseLineAntialias(dest: TBGRACustomBitmap; x1, y1, x2, y2: integer;
97   calpha: byte; DrawLastPixel: boolean); overload; deprecated;
98 
99 //antialiased version with bicolor dashes (to draw a frame)
100 procedure BGRADrawLineAntialias(dest: TBGRACustomBitmap; x1, y1, x2, y2: integer;
101   c1, c2: TBGRAPixel; dashLen: integer; DrawLastPixel: boolean; var DashPos: integer; LinearBlend : boolean = false); overload; deprecated;
102 
103 //length added to ensure accepable alpha join (using TBGRAMultishapeFiller is still better)
GetAlphaJoinFactornull104 function GetAlphaJoinFactor(alpha: byte): single;
105 
106 //create standard brush texture
CreateBrushTexturenull107 function 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
IsSolidPenStylenull111 function IsSolidPenStyle(ACustomPenStyle: TBGRAPenStyle): boolean;
IsClearPenStylenull112 function IsClearPenStyle(ACustomPenStyle: TBGRAPenStyle): boolean;
DuplicatePenStylenull113 function DuplicatePenStyle(ACustomPenStyle: array of single): TBGRAPenStyle;
PenStyleEqualnull114 function PenStyleEqual(AStyle1, AStyle2: TBGRAPenStyle): boolean;
BGRAToPenStylenull115 function BGRAToPenStyle(ACustomPenStyle: TBGRAPenStyle): TPenStyle;
PenStyleToBGRAnull116 function PenStyleToBGRA(APenStyle: TPenStyle): TBGRAPenStyle;
117 
118 implementation
119 
120 uses math, BGRAClasses, BGRAPath;
121 
122 procedure BGRADrawLineAliased(dest: TBGRACustomBitmap; x1, y1, x2, y2: integer;
123   c: TBGRAPixel; DrawLastPixel: boolean; ADrawMode: TDrawMode);
124 begin
125   dest.DrawLine(x1,y1,x2,y2, c,DrawLastPixel, ADrawMode);
126 end;
127 
128 procedure BGRAEraseLineAliased(dest: TBGRACustomBitmap; x1, y1, x2,
129   y2: integer; alpha: byte; DrawLastPixel: boolean);
130 begin
131   dest.EraseLine(x1,y1,x2,y2,alpha,DrawLastPixel);
132 end;
133 
134 procedure BGRADrawLineAntialias(dest: TBGRACustomBitmap; x1, y1, x2, y2: integer;
135   c: TBGRAPixel; DrawLastPixel: boolean; LinearBlend : boolean);
136 var
137   b: TUniversalBrush;
138 begin
139   if c.alpha = 0 then exit;
140   if LinearBlend then
141     dest.SolidBrush(b, c,dmLinearBlend)
142   else
143     dest.SolidBrush(b, c,dmDrawWithTransparency);
144   dest.DrawLineAntialias(x1,y1,x2,y2, b,DrawLastPixel);
145 end;
146 
147 procedure BGRAEraseLineAntialias(dest: TBGRACustomBitmap; x1, y1, x2,
148   y2: integer; calpha: byte; DrawLastPixel: boolean);
149 begin
150   dest.EraseLineAntialias(x1,y1,x2,y2,calpha,DrawLastPixel);
151 end;
152 
153 procedure BGRADrawLineAntialias(dest: TBGRACustomBitmap; x1, y1, x2, y2: integer;
154   c1, c2: TBGRAPixel; dashLen: integer; DrawLastPixel: boolean; var DashPos: integer; LinearBlend : boolean);
155 var
156   b1, b2: TUniversalBrush;
157 begin
158   if (c1.alpha=0) and (c2.alpha=0) then exit;
159   if LinearBlend then
160   begin
161     dest.SolidBrush(b1, c1,dmLinearBlend);
162     dest.SolidBrush(b2, c2,dmLinearBlend);
163   end
164   else
165   begin
166     dest.SolidBrush(b1, c1,dmDrawWithTransparency);
167     dest.SolidBrush(b2, c2,dmDrawWithTransparency);
168   end;
169   dest.DrawLineAntialias(x1,y1,x2,y2, b1,b2, dashLen,dashPos,DrawLastPixel);
170 end;
171 
GetAlphaJoinFactornull172 function GetAlphaJoinFactor(alpha: byte): single;
173 var t: single;
174 begin
175   if alpha = 255 then result := 1 else
176   begin
177     result := (power(20,alpha/255)-1)/19*0.5;
178     t := power(alpha/255,40);
179     result := result*(1-t)+t*0.82;
180   end;
181 end;
182 
CreateBrushTexturenull183 function CreateBrushTexture(prototype: TBGRACustomBitmap; brushstyle: TBrushStyle;
184   PatternColor, BackgroundColor: TBGRAPixel; width: integer = 8; height: integer = 8; penwidth: single = 1): TBGRACustomBitmap;
185 begin
186   result := prototype.CreateBrushTexture(brushstyle, PatternColor, BackgroundColor, width,height, penwidth);
187 end;
188 
IsSolidPenStylenull189 function IsSolidPenStyle(ACustomPenStyle: TBGRAPenStyle): boolean;
190 begin
191   result := ACustomPenStyle = nil;
192 end;
193 
IsClearPenStylenull194 function IsClearPenStyle(ACustomPenStyle: TBGRAPenStyle): boolean;
195 begin
196   if (length(ACustomPenStyle)=1) and (ACustomPenStyle[0]=0) then
197     result := true
198   else
199     result := false;
200 end;
201 
DuplicatePenStylenull202 function DuplicatePenStyle(ACustomPenStyle: array of single): TBGRAPenStyle;
203 var
204   i: Integer;
205 begin
206   setlength(result,length(ACustomPenStyle));
207   for i := 0 to high(result) do
208     result[i]:= ACustomPenStyle[i];
209 end;
210 
BGRAToPenStylenull211 function BGRAToPenStyle(ACustomPenStyle: TBGRAPenStyle): TPenStyle;
212 begin
213   if IsSolidPenStyle(ACustomPenStyle) then exit(psSolid);
214   if IsClearPenStyle(ACustomPenStyle) then exit(psClear);
215   if PenStyleEqual(ACustomPenStyle, DashPenStyle) then exit(psDash);
216   if PenStyleEqual(ACustomPenStyle, DotPenStyle) then exit(psDot);
217   if PenStyleEqual(ACustomPenStyle, DashDotPenStyle) then exit(psDashDot);
218   if PenStyleEqual(ACustomPenStyle, DashDotDotPenStyle) then exit(psDashDotDot);
219   exit(psPattern);
220 end;
221 
PenStyleToBGRAnull222 function PenStyleToBGRA(APenStyle: TPenStyle): TBGRAPenStyle;
223 begin
224   Case APenStyle of
225   psSolid: result := SolidPenStyle;
226   psDash: result := DashPenStyle;
227   psDot: result := DotPenStyle;
228   psDashDot: result := DashDotPenStyle;
229   psDashDotDot: result := DashDotDotPenStyle;
230   else result := ClearPenStyle;
231   end;
232 end;
233 
PenStyleEqualnull234 function PenStyleEqual(AStyle1, AStyle2: TBGRAPenStyle): boolean;
235 var
236   i: Integer;
237 begin
238   if length(AStyle1)<>length(AStyle2) then exit(false);
239   for i := 0 to high(AStyle1) do
240     if AStyle1[i] <> AStyle2[i] then exit(false);
241   exit(true);
242 end;
243 
244 procedure ApplyPenStyle(const leftPts, rightPts: array of TPointF; const penstyle: TBGRAPenStyle;
245     width: single; var posstyle: single; out styledPts: ArrayOfTPointF);
246 var
247   styleIndex :integer;
248   remainingDash: single;
249 
250   procedure NextStyleIndex;
251   begin
252     inc(styleIndex);
253     if styleIndex = length(penstyle) then
254       styleIndex := 0;
255     IncF(remainingDash, penstyle[styleindex]);
256   end;
257 
258 var
259   dashStartIndex: integer;
260   dashLeftStartPos,dashRightStartPos : TPointF;
261   betweenDash: boolean;
262 
263   procedure StartDash(index: integer; t: single);
264   begin
265     dashStartIndex := index;
266     if t = 0 then
267     begin
268       dashLeftStartPos := leftPts[index];
269       dashRightStartPos := rightPts[index];
270     end else
271     begin
272       dashLeftStartPos := leftPts[index] + (leftPts[index+1]-leftPts[index])*t;
273       dashRightStartPos := rightPts[index] + (rightPts[index+1]-rightPts[index])*t;
274     end;
275     betweenDash := false;
276   end;
277 
278 var
279   nbStyled: integer;
280 
281   procedure AddPt(pt: TPointF);
282   begin
283     if (nbStyled = 0) or (pt <> styledPts[nbStyled-1]) then
284     begin
285       if nbStyled = length(styledPts) then
286         setlength(styledPts,nbStyled*2+4);
287       styledPts[nbStyled] := pt;
288       inc(nbStyled);
289     end;
290   end;
291 
292   procedure StartPolygon;
293   begin
294     if nbStyled > 0 then AddPt(EmptyPointF);
295   end;
296 
297   procedure EndDash(index: integer; t: single);
298   var dashLeftEndPos,dashRightEndPos: TPointF;
299     i: Integer;
300   begin
301     if t=0 then
302     begin
303       dashLeftEndPos := leftPts[index];
304       dashRightEndPos := rightPts[index];
305     end else
306     begin
307       dashLeftEndPos := leftPts[index] + (leftPts[index+1]-leftPts[index])*t;
308       dashRightEndPos := rightPts[index] + (rightPts[index+1]-rightPts[index])*t;
309     end;
310     StartPolygon;
311     AddPt(dashLeftStartPos);
312     for i := dashStartIndex+1 to index do
313       AddPt(leftPts[i]);
314     AddPt(dashLeftEndPos);
315     AddPt(dashRightEndPos);
316     for i := index downto dashStartIndex+1 do
317       AddPt(rightPts[i]);
318     AddPt(dashRightStartPos);
319     betweenDash := true;
320   end;
321 
322 var
323   i,nb: integer;
324   styleLength: single;
325   len,lenDone: single;
326 
327 begin
328   nbStyled := 0;
329   styledPts := nil;
330   if IsClearPenStyle(penstyle) then exit;
331   if IsSolidPenStyle(penstyle) then
332   begin
333     for i := 0 to high(leftPts) do AddPt(leftPts[i]);
334     for i := high(rightPts) downto 0 do AddPt(rightPts[i]);
335     setlength(styledPts,nbStyled);
336     exit;
337   end;
338   if length(leftPts) <> length(rightPts) then
339     raise Exception.Create('Dimension mismatch');
340   nb := length(leftPts);
341   if length(penstyle) mod 2 <> 0 then
342     raise Exception.Create('Pen style must contain an even number of values');
343   styleLength := 0;
344   styleIndex := -1;
345   remainingDash := 0;
346   betweenDash   := false;
347   for i := 0 to high(penstyle) do
348     if penstyle[i] <= 0 then
349       raise Exception.Create('Invalid pen dash length')
350     else
351     begin
352       IncF(styleLength, penstyle[i]);
353       if styleLength >= posstyle then
354       begin
355         styleIndex := i;
356         remainingDash := styleLength-posstyle;
357         break;
358       end;
359     end;
360   if styleIndex = -1 then
361   begin
362     styleIndex := 0;
363     remainingDash := penstyle[0];
364   end;
365 
366   if styleIndex mod 2 = 0 then
367     StartDash(0, 0) else
368       betweenDash := true;
369   for i := 0 to nb-2 do
370   begin
371     len := (sqrt(sqr(leftPts[i+1].x-leftPts[i].x) + sqr(leftPts[i+1].y-leftPts[i].y))+
372            sqrt(sqr(rightPts[i+1].x-rightPts[i].x) + sqr(rightPts[i+1].y-rightPts[i].y)))/(2*width);
373     lenDone := 0;
374     while lenDone < len do
375     begin
376       if len-lenDone < remainingDash then
377       begin
378         DecF(remainingDash, len-lenDone);
379         if remainingDash = 0 then NextStyleIndex;
380         lenDone := len;
381       end else
382       if betweenDash then
383       begin
384         IncF(lenDone, remainingDash);
385         StartDash(i, lenDone/len);
386         remainingDash := 0;
387         NextStyleIndex;
388       end else
389       begin
390         IncF(lenDone, remainingDash);
391         EndDash(i, lenDone/len);
392         remainingDash := 0;
393         NextStyleIndex;
394       end;
395     end;
396   end;
397   if not betweenDash then
398     EndDash(nb-1,0);
399   setlength(styledPts,nbStyled);
400 end;
401 
ComputeWidePolylinePointsnull402 function ComputeWidePolylinePoints(const linepts: array of TPointF; width: single;
403           pencolor: TBGRAPixel; linecap: TPenEndCap; joinstyle: TPenJoinStyle; const penstyle: TBGRAPenStyle;
404           options: TBGRAPolyLineOptions; miterLimit: single; arrow: TBGRACustomArrow): ArrayOfTPointF;
405 const oneOver512 = 1/512;
406 var
407   startArrowPos, startArrowDir, endArrowPos, endArrowDir: TPointF;
408   startArrowLinePos, endArrowLinePos: single;
409   borders : array of record
410               leftSide,rightSide: TLineDef;
411               len: single;
412               leftDir: TPointF;
413             end;
414   compPts: array of TPointF;
415   nbCompPts: integer;
416   revCompPts: array of TPointF;
417   nbRevCompPts: integer;
418   pts: array of TPointF;
419   roundPrecision: integer;
420   hw: single; //half-width
421 
422   procedure AddPt(normal,rev: TPointF); overload;
423   begin
424     if (nbCompPts > 0) and (compPts[nbCompPts-1]=normal) and
425        (nbRevCompPts > 0) and (revCompPts[nbRevCompPts-1]=rev) then exit;
426 
427     if nbCompPts = length(compPts) then
428      setlength(compPts, length(compPts)*2);
429     compPts[nbCompPts] := normal;
430     inc(nbCompPts);
431 
432     if nbRevCompPts = length(revCompPts) then
433      setlength(revCompPts, length(revCompPts)*2);
434     revCompPts[nbRevCompPts] := rev;
435     inc(nbRevCompPts);
436   end;
437 
438   procedure AddPt(xnormal,ynormal: single; xrev,yrev: single); overload;
439   begin
440     AddPt(PointF(xnormal,ynormal),PointF(xrev,yrev));
441   end;
442 
443   procedure AddRoundCap(origin: TPointF; dir: TPointF; fromCenter: boolean; flipped: boolean= false);
444   var i: integer;
445       a,s,c: single;
446       offset,flipvalue: single;
447   begin
448     if fromCenter then offset := 0 else offset := -Pi/2;
449     if flipped then flipvalue := -1 else flipvalue := 1;
450     for i := 1 to RoundPrecision do
451     begin
452       a := i/(RoundPrecision+1)*Pi/2 + offset;
453       s := sin(a)*hw*flipvalue;
454       c := cos(a)*hw;
455       AddPt( PointF(origin.x+ dir.x*c - dir.y*s, origin.y + dir.y*c + dir.x*s),
456              PointF(origin.x+ dir.x*c + dir.y*s, origin.y + dir.y*c - dir.x*s) );
457     end;
458   end;
459 
460   procedure AddRoundCapAlphaJoin(origin: TPointF; dir: TPointF; fromCenter: boolean; flipped: boolean= false);
461   var i: integer;
462       a,s,c: single;
463       offset,flipvalue: single;
464       t,alphaFactor: single; //antialiasing join
465   begin
466     if fromCenter then offset := 0 else offset := -Pi/2;
467     if flipped then flipvalue := -1 else flipvalue := 1;
468 
469     alphaFactor := GetAlphaJoinFactor(pencolor.alpha);
470 
471     for i := 1 to RoundPrecision do
472     begin
473       a := i/(RoundPrecision+1)*Pi/2 + offset;
474       s := sin(a)*hw*flipvalue;
475       c := cos(a);
476       t := (1 - c) * (0.2 + alphaFactor*0.3) + alphaFactor;
477       c := c * hw;
478       AddPt( PointF(origin.x+ dir.x*(c-t) - dir.y*s, origin.y + dir.y*(c-t) + dir.x*s),
479              PointF(origin.x+ dir.x*(c-t) + dir.y*s, origin.y + dir.y*(c-t) - dir.x*s) );
480     end;
481   end;
482 
ComputeRoundJoinnull483   function ComputeRoundJoin(origin, pt1,pt2: TPointF): ArrayOfTPointF;
484   var a1,a2: single;
485       da: single;
486       precision,i: integer;
487   begin
488     a1 := arctan2(pt1.y-origin.y,pt1.x-origin.x);
489     a2 := arctan2(pt2.y-origin.y,pt2.x-origin.x);
490     if a2-a1 > Pi then DecF(a2, 2*Pi);
491     if a1-a2 > Pi then DecF(a1, 2*Pi);
492     if a2=a1 then
493     begin
494       setlength(result,1);
495       result[0] := pt1;
496       exit;
497     end;
498     da := a2-a1;
499     precision := round( sqrt( sqr(pt2.x-pt1.x)+sqr(pt2.y-pt1.y) ) ) +2;
500     setlength(result,precision);
501     for i := 0 to precision-1 do
502       result[i] := origin + PointF( cos(a1+i/(precision-1)*da)*hw,
503                                     sin(a1+i/(precision-1)*da)*hw );
504   end;
505 
506 var
507   joinLeft,joinRight: array of TPointF;
508   nbJoinLeft,nbJoinRight: integer;
509 
510   procedure SetJoinLeft(joinpts: array of TPointF);
511   var i: integer;
512   begin
513     nbJoinLeft := length(joinpts);
514     if length(joinLeft) < nbJoinLeft then setlength(joinLeft,length(joinLeft)+nbJoinLeft+2);
515     for i := 0 to nbJoinLeft-1 do
516       joinLeft[i] := joinpts[i];
517   end;
518 
519   procedure SetJoinRight(joinpts: array of TPointF);
520   var i: integer;
521   begin
522     nbJoinRight := length(joinpts);
523     if length(joinRight) < nbJoinRight then setlength(joinRight,length(joinRight)+nbJoinRight+2);
524     for i := 0 to nbJoinRight-1 do
525       joinRight[i] := joinpts[i];
526   end;
527 
528   procedure AddJoin(index: integer);
529   var len,i: integer;
530   begin
531     len := nbJoinLeft;
532     if nbJoinRight > len then
533       len := nbJoinRight;
534     if len = 0 then exit;
535     if (len > 1) and (index <> -1) then
536     begin
537       if nbJoinLeft=1 then
538         AddPt(joinLeft[0], joinLeft[0] - 2*borders[Index].leftDir) else
539       if nbJoinRight=1 then
540         AddPt( joinRight[0] + 2* borders[index].leftDir, joinRight[0]);
541     end;
542     for i := 0 to len-1 do
543     begin
544       AddPt(joinLeft[i*nbJoinLeft div len],
545             joinRight[i*nbJoinRight div len]);
546     end;
547     if (len > 1) and (index <> -1) then
548     begin
549       if nbJoinLeft=1 then
550         AddPt(joinLeft[0], joinLeft[0] - 2*borders[index+1].leftDir) else
551       if nbJoinRight=1 then
552         AddPt(joinRight[0]+2*borders[index+1].leftDir, joinRight[0]);
553     end;
554   end;
555 
556 var
557   NbPolyAcc: integer;
558 
559   procedure FlushLine(lastPointIndex: integer);
560   var
561     enveloppe: arrayOfTPointF;
562     posstyle: single;
563     i,idxInsert: Integer;
564   begin
565     if lastPointIndex <> -1 then
566        AddPt( pts[lastPointIndex] + borders[lastPointIndex-1].leftDir,
567               pts[lastPointIndex] - borders[lastPointIndex-1].leftDir);
568 
569     if (lastPointIndex = high(pts)) and (linecap = pecRound) and not (plNoEndCap in options) then
570     begin
571       if not (plRoundCapOpen in options) then
572         AddRoundCap(pts[high(pts)],borders[high(pts)-1].leftSide.dir,false)
573       else
574        AddRoundCapAlphaJoin(pts[high(pts)],
575             -borders[high(pts)-1].leftSide.dir, false,true);
576     end;
577     posstyle := 0;
578     ApplyPenStyle(slice(compPts,nbCompPts),slice(revCompPts,nbRevCompPts),penstyle,width,posstyle,enveloppe);
579 
580     if Result=nil then
581     begin
582       Result := enveloppe;
583       NbPolyAcc := length(enveloppe);
584     end
585       else
586     if enveloppe <> nil then
587     begin
588       if NbPolyAcc +1+length(enveloppe) > length(Result) then
589         setlength(Result, length(Result)*2+1+length(enveloppe));
590 
591       idxInsert := NbPolyAcc+1;
592       Result[idxInsert-1] := EmptyPointF;
593       for i := 0 to high(enveloppe) do
594         Result[idxInsert+i]:= enveloppe[i];
595       inc(NbPolyAcc, length(enveloppe)+1);
596     end;
597 
598     nbCompPts := 0;
599     nbRevCompPts := 0;
600   end;
601 
602   procedure CycleFlush;
603   var idx: integer;
604   begin
605     if Result = nil then
606     begin
607       if (nbCompPts > 1) and (nbRevCompPts > 1) then
608       begin
609         compPts[0] := compPts[nbCompPts-1];
610         revCompPts[0] := revCompPts[nbRevCompPts-1];
611       end;
612       FlushLine(-1);
613     end else
614     begin
615       if (nbCompPts >= 1) and (nbRevCompPts >= 1) and (NbPolyAcc >= 2) then
616       begin
617         Result[0] := compPts[nbCompPts-1];
618         idx := 0;
619         while (idx < high(Result)) and (not isEmptyPointF(Result[idx+1])) do inc(idx);
620         Result[idx] := revCompPts[nbRevCompPts-1];
621       end;
622       FlushLine(-1);
623     end;
624   end;
625 
626   procedure FinalizeArray;
627   var arrowStartData, arrowEndData: ArrayOfTPointF;
628     finalNb,i,delta: integer;
629     hasStart,hasEnd: boolean;
630   begin
631     if assigned(arrow) and not isEmptyPointF(startArrowPos) then
632       arrowStartData := arrow.ComputeStartAt(startArrowPos, startArrowDir, width, startArrowLinePos)
633     else
634       arrowStartData := nil;
635     if assigned(arrow) and not isEmptyPointF(endArrowPos) then
636       arrowEndData := arrow.ComputeEndAt(endArrowPos, endArrowDir, width, endArrowLinePos)
637     else
638       arrowEndData := nil;
639     hasStart := length(arrowStartData)>0;
640     hasEnd := length(arrowEndData)>0;
641     finalNb := NbPolyAcc;
642     if hasStart then
643     begin
644       delta := length(arrowStartData)+1;
645       inc(finalNb, delta);
646     end else delta := 0;
647     if hasEnd then inc(finalNb, length(arrowEndData)+1);
648     SetLength(Result, finalNb);
649     if hasStart then
650     begin
651       for i := NbPolyAcc-1 downto 0 do
652         result[i+delta] := result[i];
653       result[delta-1] := EmptyPointF;
654       for i := 0 to high(arrowStartData) do
655         result[i] := arrowStartData[i];
656     end;
657     if hasEnd then
658     begin
659       inc(delta, NbPolyAcc+1);
660       result[delta-1] := EmptyPointF;
661       for i := 0 to high(arrowEndData) do
662         result[i+delta] := arrowEndData[i];
663     end;
664   end;
665 
666 var
667   i: integer;
668   dir: TPointF;
669   leftInter,rightInter,diff: TPointF;
670   len,maxMiter: single;
671   littleBorder: TLineDef;
672   turn,maxDiff: single;
673   nbPts: integer;
674   ShouldFlushLine, HasLittleBorder, NormalRestart: Boolean;
675   pt1,pt2,pt3,pt4: TPointF;
676   linePos: single;
677   startArrowDone,endArrowDone: boolean;
678   wantedStartArrowPos,wantedEndArrowPos: single;
679 
680 begin
681   Result := nil;
682 
683   if (length(linepts)=0) or (width = 0) then exit;
684   if IsClearPenStyle(penstyle) then exit;
685   for i := 0 to high(linepts) do
686     if isEmptyPointF(linepts[i]) then
687     begin
688       result := ComputeWidePolyPolylinePoints(linepts,width,pencolor,linecap,joinstyle,penstyle,options,miterLimit,arrow);
689       exit;
690     end;
691 
692   if (plAutoCycle in options) and (length(linepts) >= 2) and (linepts[0]=linepts[high(linepts)]) then
693     options := options + [plCycle];
694   if plNoEndCap in options then options := options - [plRoundCapOpen];
695 
696   hw := width / 2;
697   case joinstyle of
698   pjsBevel,pjsRound: maxMiter := hw*1.001;
699   pjsMiter: if miterLimit < 1.001 then maxMiter := hw*1.001 else
700                maxMiter := hw*miterLimit;
701   else
702     raise Exception.Create('Unknown join style');
703   end;
704 
705   roundPrecision := round(hw)+2;
706 
707   nbPts := 0;
708   setlength(pts, length(linepts)+2);
709   for i := 0 to high(linepts) do
710     if (nbPts = 0) or (abs(linepts[i].x-pts[nbPts-1].x)>oneOver512) or (abs(linepts[i].y-pts[nbPts-1].y)>oneOver512) then
711     begin
712       pts[nbPts]:= linePts[i];
713       inc(nbPts);
714     end;
715   if (nbPts > 1) and (plCycle in options) and
716       (abs(pts[0].x-pts[nbPts-1].x)<=oneOver512) and
717       (abs(pts[0].y-pts[nbPts-1].y)<=oneOver512) then dec(nbPts);
718   if (plCycle in options) and (nbPts > 2) then
719   begin
720     if (pts[nbPts-1] <> pts[0]) then
721     begin
722       pts[nbPts] := pts[0];
723       inc(nbPts);
724     end;
725     pts[nbPts] := pts[1];
726     inc(nbPts);
727   end else
728     exclude(options, plCycle);
729 
730   setlength(pts,nbPts);
731 
732   if nbPts = 1 then
733   begin
734     if (linecap <> pecFlat) and ((linecap <> pecRound) or not (plRoundCapOpen in options)) then
735       result := ComputeEllipse(pts[0].x,pts[0].y,hw,hw);
736     exit;
737   end;
738 
739   startArrowDir := EmptyPointF;
740   startArrowPos := EmptyPointF;
741   endArrowDir := EmptyPointF;
742   endArrowPos := EmptyPointF;
743   if Assigned(arrow) then
744   begin
745     wantedStartArrowPos:= arrow.StartOffsetX;
746     wantedEndArrowPos:= arrow.EndOffsetX;
747     startArrowDone := not arrow.IsStartDefined;
748     endArrowDone := not arrow.IsEndDefined;
749   end
750   else
751   begin
752     wantedStartArrowPos:= 0;
753     wantedEndArrowPos:= 0;
754     startArrowDone := true;
755     endArrowDone := true;
756   end;
757 
758   //init computed points arrays
759   setlength(compPts, length(pts)*2+4);
760   setlength(revCompPts, length(pts)*2+4); //reverse order array
761   nbCompPts := 0;
762   nbRevCompPts := 0;
763   NbPolyAcc := 0;
764 
765   if not endArrowDone then
766   begin
767     wantedEndArrowPos:= -wantedEndArrowPos*width;
768     linePos := 0;
769     for i := high(pts) downto 1 do
770     begin
771       dir := pts[i-1]-pts[i];
772       len := VectLen(dir);
773       dir.Scale(1/len);
774       if not endArrowDone and (linePos+len >= wantedEndArrowPos) then
775       begin
776         endArrowPos := pts[i];
777         endArrowDir := -dir;
778         endArrowLinePos := -linePos/width;
779         endArrowDone := true;
780         break;
781       end;
782       IncF(linePos, len);
783     end;
784   end;
785 
786   wantedStartArrowPos:= -wantedStartArrowPos*width;
787   linePos := 0;
788   //compute borders
789   setlength(borders, length(pts)-1);
790   for i := 0 to high(pts)-1 do
791   begin
792     dir := pts[i+1]-pts[i];
793     len := VectLen(dir);
794     dir.Scale(1/len);
795     if not startArrowDone and (linePos+len >= wantedStartArrowPos) then
796     begin
797       startArrowPos := pts[i];
798       startArrowDir := -dir;
799       startArrowLinePos := -linePos/width;
800       startArrowDone := true;
801     end;
802     if (linecap = pecSquare) and ((not (plNoStartCap in options) and (i=0)) or
803       (not (plNoEndCap in options) and (i=high(pts)-1))) then //for square cap, just start and end further
804     begin
805       if i=0 then
806         pts[0].Offset(dir*(-hw));
807 
808       if (i=high(pts)-1) then
809         pts[high(pts)].Offset(dir*hw);
810 
811       //length changed
812       dir := pts[i+1]-pts[i];
813       len := VectLen(dir);
814       dir.Scale(1/len);
815     end else
816     if not (plNoStartCap in options) and (linecap = pecRound) and (i=0) and not (plCycle in options) then
817       AddRoundCap(pts[0], -dir ,true);
818 
819     borders[i].len := len;
820     borders[i].leftDir := PointF(dir.y*hw,-dir.x*hw);
821     borders[i].leftSide.origin := pts[i] + borders[i].leftDir;
822     borders[i].leftSide.dir := dir;
823     borders[i].rightSide.origin := pts[i] - borders[i].leftDir;
824     borders[i].rightSide.dir := dir;
825     IncF(linePos, len);
826   end;
827 
828   //first points
829   AddPt( pts[0] + borders[0].leftDir,
830          pts[0] - borders[0].leftDir );
831 
832   setlength(joinLeft,1);
833   setlength(joinRight,1);
834   ShouldFlushLine := False;
835   //between first and last points
836   for i := 0 to high(pts)-2 do
837   begin
838     HasLittleBorder := false;
839 
840     //determine u-turn
841     turn := borders[i].leftSide.dir * borders[i+1].leftSide.dir;
842     if turn < -0.99999 then
843     begin
844       if joinstyle <> pjsRound then
845       begin
846         littleBorder.origin := pts[i+1] + borders[i].leftSide.dir*maxMiter;
847         littleBorder.dir := borders[i].leftDir;
848         HasLittleBorder := true;
849         nbJoinLeft := 0;
850         nbJoinRight:= 0;
851         ShouldFlushLine := True;
852       end else
853       begin
854         pt1 := pts[i+1] + borders[i].leftDir;
855         pt2 := pts[i+1] + borders[i].leftSide.dir*hw;
856         SetJoinLeft(ComputeRoundJoin(pts[i+1],pt1,pt2));
857         pt1 := pts[i+1] - borders[i].leftDir;
858         SetJoinRight(ComputeRoundJoin(pts[i+1],pt1,pt2));
859         AddJoin(-1);
860         FlushLine(-1);
861         nbJoinLeft := 0;
862         nbJoinRight:= 0;
863         AddPt(pts[i+1]+borders[i+1].leftDir,
864               pts[i+1]-borders[i+1].leftDir);
865       end;
866     end else
867     if turn > 0.99999 then //straight line
868     begin
869       pt1 := pts[i+1] + borders[i].leftDir;
870       pt2 := pts[i+2] + borders[i+1].leftDir;
871       SetJoinLeft([pt1, (pt1+pt2)*(1/2),pt2]);
872 
873       pt1 := pts[i+1] - borders[i].leftDir;
874       pt2 := pts[i+2] - borders[i+1].leftDir;
875       SetJoinRight([pt1,(pt1+pt2)*(1/2),pt2]);
876     end else
877     begin
878       //determine turning left or right
879       turn := borders[i].leftSide.dir.x*borders[i+1].leftSide.dir.y - borders[i].leftSide.dir.y*borders[i+1].leftSide.dir.x;
880 
881       maxDiff := borders[i].len;
882       if borders[i+1].len < maxDiff then
883         maxDiff := borders[i+1].len;
884       if penstyle <> nil then
885         if maxDiff > 2*width then maxDiff := 2*width;
886       maxDiff := sqrt(sqr(maxDiff)+sqr(hw));
887 
888       //leftside join
889       leftInter := IntersectLine( borders[i].leftSide, borders[i+1].leftSide );
890       diff := leftInter-pts[i+1];
891       len := sqrt(diff*diff);
892       if (len > maxMiter) and (turn >= 0) then //if miter too far
893       begin
894         diff.Scale(1/len);
895         if joinstyle <> pjsRound then
896         begin
897           //compute little border
898           littleBorder.origin := pts[i+1]+diff*maxMiter;
899           littleBorder.dir := PointF(diff.y,-diff.x);
900           HasLittleBorder := true;
901 
902           //intersect with each border
903           pt1 := IntersectLine(borders[i].leftSide, littleBorder);
904           pt2 := IntersectLine(borders[i+1].leftSide, littleBorder);
905           SetJoinLeft( [pt1, pt2] );
906         end else
907         begin
908           //perpendicular
909           pt1 := PointF(pts[i+1].x+borders[i].leftSide.dir.y*hw,
910                         pts[i+1].y-borders[i].leftSide.dir.x*hw);
911           pt2 := PointF(pts[i+1].x+borders[i+1].leftSide.dir.y*hw,
912                         pts[i+1].y-borders[i+1].leftSide.dir.x*hw);
913           SetJoinLeft(ComputeRoundJoin(pts[i+1],pt1,pt2));
914         end;
915       end else
916       if (len > maxDiff) and (turn <= 0) then //if inner intersection too far
917       begin
918         ShouldFlushLine := True;
919         nbJoinLeft := 0;
920       end else
921       begin
922         if (turn > 0) and (len > 1.0001*hw) then
923           SetJoinLeft([leftInter,leftInter]) else
924         begin
925           nbJoinLeft := 1;
926           joinLeft[0] := leftInter;
927         end;
928       end;
929 
930       //rightside join
931       rightInter := IntersectLine( borders[i].rightSide, borders[i+1].rightSide );
932       diff := rightInter-pts[i+1];
933       len := VectLen(diff);
934       if (len > maxMiter) and (turn <= 0) then //if miter too far
935       begin
936         diff.Scale(1/len);
937 
938         if joinstyle <> pjsRound then
939         begin
940           //compute little border
941           littleBorder.origin := pts[i+1] + diff*maxMiter;
942           littleBorder.dir := PointF(diff.y, -diff.x);
943           HasLittleBorder := true;
944 
945           //intersect with each border
946           pt1 := IntersectLine(borders[i].rightSide, littleBorder);
947           pt2 := IntersectLine(borders[i+1].rightSide, littleBorder);
948           SetJoinRight( [pt1, pt2] );
949         end else
950         begin
951           //perpendicular
952           pt1 := PointF(pts[i+1].x-borders[i].rightSide.dir.y*hw,
953                         pts[i+1].y+borders[i].rightSide.dir.x*hw);
954           pt2 := PointF(pts[i+1].x-borders[i+1].rightSide.dir.y*hw,
955                         pts[i+1].y+borders[i+1].rightSide.dir.x*hw);
956           SetJoinRight(ComputeRoundJoin(pts[i+1],pt1,pt2));
957         end;
958       end else
959       if (len > maxDiff) and (turn >= 0) then //if inner intersection too far
960       begin
961         ShouldFlushLine := True;
962         nbJoinRight := 0;
963       end else
964       begin
965         if (turn < 0) and (len > 1.0001*hw) then
966           SetJoinRight([rightInter,rightInter]) else
967         begin
968           nbJoinRight := 1;
969           joinRight[0] := rightInter;
970         end;
971       end;
972     end;
973 
974     if ShouldFlushLine then
975     begin
976       NormalRestart := True;
977       if HasLittleBorder then
978       begin
979         if turn >= 0 then
980         begin
981           //intersect with each border
982           pt1 := IntersectLine(borders[i].leftSide, littleBorder);
983           pt2 := IntersectLine(borders[i+1].leftSide, littleBorder);
984           pt3 := pts[i+1] - borders[i].leftDir;
985           pt4 := pts[i+1] + borders[i].leftDir;
986 
987           AddPt(pt4,pt3);
988           AddPt(pt1,pt2);
989         end else
990         begin
991           //intersect with each border
992           pt1 := IntersectLine(borders[i+1].rightSide, littleBorder);
993           pt2 := IntersectLine(borders[i].rightSide, littleBorder);
994           pt3 := pts[i+1] + borders[i].leftDir;
995           pt4 := pts[i+1] - borders[i].leftDir;
996 
997           AddPt(pt3,pt4);
998           AddPt(pt1,pt2);
999         end;
1000 
1001         FlushLine(-1);
1002 
1003         AddPt(pt2,pt1);
1004       end else
1005       if joinstyle = pjsRound then
1006       begin
1007 
1008         if {(penstyle= nil) and} (turn > 0) then
1009         begin
1010           pt1 := pts[i+1] + borders[i].leftDir;
1011           pt2 := pts[i+1] + borders[i+1].leftDir;
1012           pt3 := pts[i+1] - borders[i].leftDir;
1013           pt4 := pts[i+1];
1014 
1015           SetJoinLeft([pt1,pt1]);
1016           SetJoinRight([pt3,pt4]);
1017           AddJoin(-1);
1018 
1019           SetJoinLeft(ComputeRoundJoin(pts[i+1],pt1,pt2));
1020           nbJoinRight := 1;
1021           joinRight[0] := pt4;
1022           AddJoin(-1);
1023           FlushLine(-1);
1024         end else
1025         if {(penstyle= nil) and} (turn < 0) then
1026         begin
1027           pt1 := pts[i+1] - borders[i].leftDir;
1028           pt2 := pts[i+1] - borders[i+1].leftDir;
1029           pt3 := pts[i+1] + borders[i].leftDir;
1030           pt4 := pts[i+1];
1031 
1032           SetJoinRight([pt1,pt1]);
1033           SetJoinLeft([pt3,pt4]);
1034           AddJoin(-1);
1035 
1036           SetJoinRight(ComputeRoundJoin(pts[i+1],pt1,pt2));
1037           nbJoinLeft := 1;
1038           joinLeft[0] := pt4;
1039           AddJoin(-1);
1040           FlushLine(-1);
1041         end else
1042         if (nbCompPts > 1) and (nbRevCompPts > 1) then
1043         begin
1044           pt1 := pts[i+1]+borders[i].leftDir;
1045           pt2 := pts[i+1]-borders[i].leftDir;
1046           AddPt( pt1, pt2 );
1047           FlushLine(-1);
1048         end else
1049         begin
1050           FlushLine(i+1);
1051         end;
1052       end else
1053       begin
1054         FlushLine(i+1);
1055         if turn > 0 then
1056           AddPt( leftInter, pts[i+1]+borders[i].leftDir ) else
1057         if turn < 0 then
1058           AddPt( pts[i+1] - borders[i].leftDir, rightInter );
1059       end;
1060 
1061       If NormalRestart then
1062         AddPt(pts[i+1]+borders[i+1].leftDir,
1063               pts[i+1]-borders[i+1].leftDir);
1064 
1065       ShouldFlushLine := false;
1066     end else
1067       AddJoin(i);
1068   end;
1069 
1070   if plCycle in options then
1071     CycleFlush
1072   else
1073     FlushLine(high(pts));
1074 
1075   FinalizeArray;
1076 end;
1077 
ComputeWidePolyPolylinePointsnull1078 function ComputeWidePolyPolylinePoints(const linepts: array of TPointF;
1079   width: single; pencolor: TBGRAPixel; linecap: TPenEndCap;
1080   joinstyle: TPenJoinStyle; const penstyle: TBGRAPenStyle;
1081   options: TBGRAPolyLineOptions; miterLimit: single; arrow: TBGRACustomArrow): ArrayOfTPointF;
1082 
1083 var
1084   results: array of array of TPointF;
1085   nbResults,nbTotalPts: integer;
1086 
1087   procedure AddWidePolyline(startIndex,endIndexP1: integer);
1088   var
1089     tempWidePolyline: array of TPointF;
1090     subPts: array of TPointF;
1091     j : integer;
1092   begin
1093     if endIndexP1 > startIndex then
1094     begin
1095       setlength(subPts,endIndexP1-startIndex);
1096       for j := startIndex to endIndexP1-1 do
1097         subPts[j-startIndex] := linepts[j];
1098       tempWidePolyline := ComputeWidePolylinePoints(subPts,width,pencolor,linecap,joinstyle,penstyle,options,miterLimit,arrow);
1099       if length(results) = nbresults then
1100         setlength(results,(nbresults+1)*2);
1101       results[nbResults] := tempWidePolyline;
1102       if nbResults <> 0 then inc(nbTotalPts);
1103       inc(nbResults);
1104       inc(nbTotalPts,length(tempWidePolyline));
1105     end;
1106   end;
1107 
1108 var
1109   start,i,j: integer;
1110 
1111 begin
1112   start := 0;
1113   nbResults := 0;
1114   nbTotalPts := 0;
1115   for i := 0 to high(linepts) do
1116     if isEmptyPointF(linepts[i]) then
1117     begin
1118       AddWidePolyline(start,i);
1119       start := i+1;
1120     end;
1121   AddWidePolyline(start,length(linepts));
1122 
1123   setlength(result, nbTotalPts);
1124   start := 0;
1125   for i := 0 to nbResults-1 do
1126   begin
1127     if i <> 0 then
1128     begin
1129       result[start] := EmptyPointF;
1130       inc(start);
1131     end;
1132     for j := 0 to high(results[i]) do
1133     begin
1134       result[start] := results[i][j];
1135       inc(start);
1136     end;
1137   end;
1138 end;
1139 
1140 { TBGRAPenStroker }
1141 
TBGRAPenStroker.GetArrownull1142 function TBGRAPenStroker.GetArrow: TBGRACustomArrow;
1143 begin
1144   result := FArrow;
1145 end;
1146 
TBGRAPenStroker.GetArrowOwnednull1147 function TBGRAPenStroker.GetArrowOwned: boolean;
1148 begin
1149   result := FArrowOwned;
1150 end;
1151 
GetCustomPenStylenull1152 function TBGRAPenStroker.GetCustomPenStyle: TBGRAPenStyle;
1153 begin
1154   result := DuplicatePenStyle(FCustomPenStyle);
1155 end;
1156 
GetJoinStylenull1157 function TBGRAPenStroker.GetJoinStyle: TPenJoinStyle;
1158 begin
1159   result := FJoinStyle;
1160 end;
1161 
GetLineCapnull1162 function TBGRAPenStroker.GetLineCap: TPenEndCap;
1163 begin
1164   result := FLineCap;
1165 end;
1166 
GetMiterLimitnull1167 function TBGRAPenStroker.GetMiterLimit: single;
1168 begin
1169   result := FMiterLimit;
1170 end;
1171 
GetPenStylenull1172 function TBGRAPenStroker.GetPenStyle: TPenStyle;
1173 begin
1174   result := FPenStyle;
1175 end;
1176 
TBGRAPenStroker.GetStrokeMatrixnull1177 function TBGRAPenStroker.GetStrokeMatrix: TAffineMatrix;
1178 begin
1179   result := FOriginalStrokeMatrix;
1180 end;
1181 
1182 procedure TBGRAPenStroker.SetArrow(AValue: TBGRACustomArrow);
1183 begin
1184   FArrow := AValue;
1185 end;
1186 
1187 procedure TBGRAPenStroker.SetArrowOwned(AValue: boolean);
1188 begin
1189   FArrowOwned := AValue;
1190 end;
1191 
1192 procedure TBGRAPenStroker.SetCustomPenStyle(AValue: TBGRAPenStyle);
1193 begin
1194   if PenStyleEqual(FCustomPenStyle,AValue) then Exit;
1195   FCustomPenStyle:= DuplicatePenStyle(AValue);
1196   FPenStyle:= BGRAToPenStyle(AValue);
1197 end;
1198 
1199 procedure TBGRAPenStroker.SetJoinStyle(AValue: TPenJoinStyle);
1200 begin
1201   FJoinStyle:= AValue;
1202 end;
1203 
1204 procedure TBGRAPenStroker.SetLineCap(AValue: TPenEndCap);
1205 begin
1206   FLineCap:= AValue;
1207 end;
1208 
1209 procedure TBGRAPenStroker.SetMiterLimit(AValue: single);
1210 begin
1211   FMiterLimit := AValue;
1212 end;
1213 
1214 procedure TBGRAPenStroker.SetStrokeMatrix(const AValue: TAffineMatrix);
1215 begin
1216   if FOriginalStrokeMatrix=AValue then Exit;
1217   FOriginalStrokeMatrix:=AValue;
1218   FStrokeMatrix := AValue;
1219   FStrokeMatrix[1,3] := 0;
1220   FStrokeMatrix[2,3] := 0;
1221   FStrokeZoom := max(VectLen(PointF(FStrokeMatrix[1,1],FStrokeMatrix[2,1])),
1222           VectLen(PointF(FStrokeMatrix[1,2],FStrokeMatrix[2,2])));
1223   if FStrokeZoom > 0 then
1224     FStrokeMatrix := FStrokeMatrix * AffineMatrixScale(1/FStrokeZoom,1/FStrokeZoom);
1225   if IsAffineMatrixInversible(FStrokeMatrix) then
1226     FStrokeMatrixInverse := AffineMatrixInverse(FStrokeMatrix)
1227   else
1228   begin
1229     FStrokeMatrixInverse := AffineMatrixIdentity;
1230     FStrokeMatrix := AffineMatrixIdentity;
1231   end;
1232   FStrokeMatrixIdentity := IsAffineMatrixIdentity(FStrokeMatrix);
1233 end;
1234 
1235 procedure TBGRAPenStroker.SetPenStyle(AValue: TPenStyle);
1236 begin
1237   if (FPenStyle=AValue) or (AValue=psPattern) then Exit;
1238   FCustomPenStyle := PenStyleToBGRA(AValue);
1239   FPenStyle := AValue;
1240 end;
1241 
1242 constructor TBGRAPenStroker.Create;
1243 begin
1244   Style := psSolid;
1245   LineCap := pecRound;
1246   JoinStyle := pjsBevel;
1247   MiterLimit := 2;
1248   fillchar(FOriginalStrokeMatrix,sizeof(FOriginalStrokeMatrix),0);
1249   StrokeMatrix := AffineMatrixIdentity;
1250 end;
1251 
1252 destructor TBGRAPenStroker.Destroy;
1253 begin
1254   if ArrowOwned then FreeAndNil(FArrow);
1255   inherited Destroy;
1256 end;
1257 
TBGRAPenStroker.ComputePolylinenull1258 function TBGRAPenStroker.ComputePolyline(const APoints: array of TPointF;
1259   AWidth: single; AClosedCap: boolean): ArrayOfTPointF;
1260 var
1261   c: TBGRAPixel;
1262 begin
1263   if not AClosedCap then
1264     c := BGRAWhite //needed for alpha junction
1265   else
1266     c := BGRAPixelTransparent;
1267 
1268   if FStrokeMatrixIdentity then
1269     result := ComputePolyline(APoints,AWidth*FStrokeZoom,c,AClosedCap)
1270   else
1271     result := FStrokeMatrix*ComputePolyline(FStrokeMatrixInverse*APoints,AWidth*FStrokeZoom,c,AClosedCap);
1272 end;
1273 
TBGRAPenStroker.ComputePolylinenull1274 function TBGRAPenStroker.ComputePolyline(const APoints: array of TPointF;
1275   AWidth: single; APenColor: TBGRAPixel; AClosedCap: boolean): ArrayOfTPointF;
1276 var options: TBGRAPolyLineOptions;
1277 begin
1278   options := [];
1279   if Assigned(Arrow) and Arrow.IsStartDefined then include(options, plNoStartCap);
1280   if Assigned(Arrow) and Arrow.IsEndDefined then include(options, plNoEndCap);
1281   if not AClosedCap then include(options, plRoundCapOpen);
1282   if FStrokeMatrixIdentity then
1283     result := BGRAPen.ComputeWidePolylinePoints(APoints, AWidth*FStrokeZoom, APenColor, LineCap, JoinStyle, CustomPenStyle, options, MiterLimit, Arrow)
1284   else
1285     result := FStrokeMatrix*BGRAPen.ComputeWidePolylinePoints(FStrokeMatrixInverse*APoints, AWidth*FStrokeZoom, APenColor, LineCap, JoinStyle, CustomPenStyle, options, MiterLimit, Arrow);
1286 end;
1287 
TBGRAPenStroker.ComputePolylineAutocyclenull1288 function TBGRAPenStroker.ComputePolylineAutocycle(
1289   const APoints: array of TPointF; AWidth: single): ArrayOfTPointF;
1290 var options: TBGRAPolyLineOptions;
1291 begin
1292   options := [plAutoCycle];
1293   if Assigned(Arrow) and Arrow.IsStartDefined then include(options, plNoStartCap);
1294   if Assigned(Arrow) and Arrow.IsEndDefined then include(options, plNoEndCap);
1295   if FStrokeMatrixIdentity then
1296     result := BGRAPen.ComputeWidePolylinePoints(APoints, AWidth*FStrokeZoom, BGRAPixelTransparent, LineCap, JoinStyle, CustomPenStyle, options, MiterLimit, Arrow)
1297   else
1298     result := FStrokeMatrix*BGRAPen.ComputeWidePolylinePoints(FStrokeMatrixInverse*APoints, AWidth*FStrokeZoom, BGRAPixelTransparent, LineCap, JoinStyle, CustomPenStyle, options, MiterLimit, Arrow)
1299 end;
1300 
TBGRAPenStroker.ComputePolygonnull1301 function TBGRAPenStroker.ComputePolygon(const APoints: array of TPointF;
1302   AWidth: single): ArrayOfTPointF;
1303 begin
1304   if FStrokeMatrixIdentity then
1305     result := BGRAPen.ComputeWidePolylinePoints(APoints, AWidth*FStrokeZoom, BGRAPixelTransparent, LineCap, JoinStyle, CustomPenStyle, [plCycle], MiterLimit)
1306   else
1307     result := FStrokeMatrix*BGRAPen.ComputeWidePolylinePoints(FStrokeMatrixInverse*APoints, AWidth*FStrokeZoom, BGRAPixelTransparent, LineCap, JoinStyle, CustomPenStyle, [plCycle], MiterLimit);
1308 end;
1309 
1310 initialization
1311 
1312   //special pen styles
1313   SolidPenStyle := nil;
1314 
1315   setlength(ClearPenStyle,1);
1316   ClearPenStyle[0] := 0;
1317 
1318   DashPenStyle := BGRAPenStyle(3,1);
1319   DotPenStyle := BGRAPenStyle(1,1);
1320   DashDotPenStyle := BGRAPenStyle(3,1,1,1);
1321   DashDotDotPenStyle := BGRAPenStyle(3,1,1,1,1,1);
1322 
1323 end.
1324 
1325