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