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