Changeset 521 for GraphicTest/Packages/bgrabitmap/geometrytypes.inc
- Timestamp:
- Apr 17, 2019, 12:58:41 AM (5 years ago)
- Location:
- GraphicTest
- Files:
-
- 2 edited
Legend:
- Unmodified
- Added
- Removed
-
GraphicTest
- Property svn:ignore
-
old new 8 8 GraphicTest.lps 9 9 GraphicTest.dbg 10 heaptrclog.trc
-
- Property svn:ignore
-
GraphicTest/Packages/bgrabitmap/geometrytypes.inc
r494 r521 19 19 {$endif} 20 20 21 {* Contains an array of points with single-precision floating point coordinates } 22 ArrayOfTPointF = array of TPointF; 23 24 {* An affine matrix contains three 2D vectors: the image of x, the image of y and the translation } 21 25 TAffineMatrix = array[1..2,1..3] of single; 22 26 … … 24 28 TRectF = Types.TRectF; 25 29 {$else} 30 {$define BGRA_DEFINE_TRECTF} 31 { TRectF } 32 26 33 TRectF = 27 34 {$ifndef FPC_REQUIRES_PROPER_ALIGNMENT} … … 29 36 {$endif FPC_REQUIRES_PROPER_ALIGNMENT} 30 37 record 38 private 39 function GetHeight: single; 40 function GetWidth: Single; 41 public 42 property Width: Single read GetWidth; 43 property Height: single read GetHeight; 44 procedure Offset (const dx,dy : Single); 31 45 case Integer of 32 46 0: (Left, Top, Right, Bottom: Single); 33 47 1: (TopLeft, BottomRight: TPointF); 34 48 end; 49 50 { TRectHelper } 51 52 TRectHelper = record helper for TRect 53 private 54 function GetHeight: integer; 55 function GetIsEmpty: boolean; 56 function GetWidth: integer; 57 procedure SetHeight(AValue: integer); 58 procedure SetWidth(AValue: integer); 59 public 60 constructor Create(Origin: TPoint; AWidth, AHeight: Longint); overload; 61 constructor Create(ALeft, ATop, ARight, ABottom: Longint); overload; 62 procedure Intersect(const ARect: TRect); 63 procedure Offset(DX, DY: Longint); 64 procedure Inflate(DX, DY: Longint); 65 function Contains(const APoint: TPoint): boolean; overload; 66 function Contains(const ARect: TRect): boolean; overload; 67 property Width: integer read GetWidth write SetWidth; 68 property Height: integer read GetHeight write SetHeight; 69 property IsEmpty: boolean read GetIsEmpty; 70 end; 71 72 operator=(const ARect1,ARect2: TRect): boolean; 73 74 type 75 { TSizeHelper } 76 77 TSizeHelper = record helper for TSize 78 private 79 function GetHeight: integer; 80 function GetWidth: integer; 81 public 82 property Width: integer read GetWidth; 83 property Height: integer read GetHeight; 84 end; 85 35 86 {$endif} 87 88 const 89 EmptyPoint : TPoint = (X: -2147483648; Y: -2147483648); 90 91 function IsEmptyPoint(const APoint: TPoint): boolean; 92 93 type 94 TPointFHelper = record helper for TPointF 95 function Ceiling: TPoint; 96 function Truncate: TPoint; 97 function Floor: TPoint; 98 function Round: TPoint; 99 function Length: Single; 100 end; 101 102 type 103 PRectF = ^TRectF; 104 105 { TRectFHelper } 106 107 TRectFHelper = record helper for TRectF 108 class function Intersect(const R1: TRectF; const R2: TRectF): TRectF; overload; static; 109 class function Union(const R1: TRectF; const R2: TRectF): TRectF; overload; static; 110 class function Union(const R1: TRectF; const R2: TRectF; ADiscardEmpty: boolean): TRectF; overload; static; 111 function Union(const r: TRectF):TRectF; 112 function Union(const r: TRectF; ADiscardEmpty: boolean):TRectF; 113 function IntersectsWith(const r: TRectF): boolean; 114 function IsEmpty: boolean; 115 end; 116 117 const 118 {* A value for an empty rectangle } 119 EmptyRectF : TRectF = (left:0; top:0; right:0; bottom: 0); 120 36 121 function RectF(Left, Top, Right, Bottom: Single): TRectF; 122 function RectF(const ATopLeft,ABottomRight: TPointF): TRectF; 123 function RectWithSizeF(left,top,width,height: Single): TRectF; 124 function IsEmptyRectF(const ARect:TRectF): boolean; 125 126 type 127 { TAffineBox } 128 129 TAffineBox = object 130 private 131 function GetAsPolygon: ArrayOfTPointF; 132 function GetBottomRight: TPointF; 133 function GetHeight: single; 134 function GetIsEmpty: boolean; 135 function GetRectBounds: TRect; 136 function GetRectBoundsF: TRectF; 137 function GetSurface: single; 138 function GetWidth: single; 139 public 140 TopLeft, TopRight, 141 BottomLeft: TPointF; 142 class function EmptyBox: TAffineBox; static; 143 class function AffineBox(ATopLeft, ATopRight, ABottomLeft: TPointF): TAffineBox; static; overload; 144 class function AffineBox(ARectF: TRectF): TAffineBox; static; overload; 145 function Contains(APoint: TPointF): boolean; 146 property RectBounds: TRect read GetRectBounds; 147 property RectBoundsF: TRectF read GetRectBoundsF; 148 property BottomRight: TPointF read GetBottomRight; 149 property IsEmpty: boolean read GetIsEmpty; 150 property AsPolygon: ArrayOfTPointF read GetAsPolygon; 151 property Width: single read GetWidth; 152 property Height: single read GetHeight; 153 property Surface: single read GetSurface; 154 end; 37 155 38 156 const … … 43 161 {----------------- Operators for TPointF --------------------} 44 162 {** Creates a new structure with values ''x'' and ''y'' } 45 function PointF(x, y: single): TPointF; 163 function PointF(x, y: single): TPointF; overload; 164 function PointF(pt: TPoint): TPointF; overload; 46 165 {** Checks if the structure is empty (equal to ''EmptyPointF'') } 47 166 function isEmptyPointF(const pt: TPointF): boolean; … … 68 187 type 69 188 TFaceCulling = (fcNone, fcKeepCW, fcKeepCCW); 70 {* Contains an array of points with single-precision floating point coordinates }71 ArrayOfTPointF = array of TPointF;72 189 73 190 {** Creates an array of ''TPointF'' } … … 109 226 ssRoundOutside, 110 227 {** The curve is outside the polygonal envelope and there is a tangeant at vertices (starting and ending points are reached) } 111 ssVertexToSide); 112 113 { TCubicBezierCurve } 114 {* Definition of a Bézier curve of order 3. It has two control points ''c1'' and ''c2''. Those are not reached by the curve } 115 TCubicBezierCurve = object 116 private 117 function SimpleComputePoints(AAcceptedDeviation: single = 0.1; AIncludeFirstPoint: boolean = true): ArrayOfTPointF; 118 public 119 {** Starting point (reached) } 120 p1: TPointF; 121 {** First control point (not reached by the curve) } 122 c1: TPointF; 123 {** Second control point (not reached by the curve) } 124 c2: TPointF; 125 {** Ending point (reached) } 126 p2: TPointF; 127 {** Computes the point at time ''t'', varying from 0 to 1 } 128 function ComputePointAt(t: single): TPointF; 129 {** Split the curve in two such that ''ALeft.p2'' = ''ARight.p1'' } 130 procedure Split(out ALeft, ARight: TCubicBezierCurve); 131 {** Compute an approximation of the length of the curve. ''AAcceptedDeviation'' indicates the 132 maximum orthogonal distance that is ignored and approximated by a straight line. } 133 function ComputeLength(AAcceptedDeviation: single = 0.1): single; 134 {** Computes a polygonal approximation of the curve. ''AAcceptedDeviation'' indicates the 135 maximum orthogonal distance that is ignored and approximated by a straight line. 136 ''AIncludeFirstPoint'' indicates if the first point must be included in the array } 137 function ToPoints(AAcceptedDeviation: single = 0.1; AIncludeFirstPoint: boolean = true): ArrayOfTPointF; 138 function GetBounds: TRectF; 139 end; 140 141 {** Creates a structure for a cubic Bézier curve } 142 function BezierCurve(origin, control1, control2, destination: TPointF) : TCubicBezierCurve; overload; 143 144 type 145 { TQuadraticBezierCurve } 146 {* Definition of a Bézier curve of order 2. It has one control point } 147 TQuadraticBezierCurve = object 148 private 149 function SimpleComputePoints(AAcceptedDeviation: single = 0.1; AIncludeFirstPoint: boolean = true): ArrayOfTPointF; 150 function ComputeExtremumPositionOutsideSegment: single; 151 public 152 {** Starting point (reached) } 153 p1: TPointF; 154 {** Control point (not reached by the curve) } 155 c: TPointF; 156 {** Ending point (reached) } 157 p2: TPointF; 158 {** Computes the point at time ''t'', varying from 0 to 1 } 159 function ComputePointAt(t: single): TPointF; 160 {** Split the curve in two such that ''ALeft.p2'' = ''ARight.p1'' } 161 procedure Split(out ALeft, ARight: TQuadraticBezierCurve); 162 {** Compute the '''exact''' length of the curve } 163 function ComputeLength: single; 164 {** Computes a polygonal approximation of the curve. ''AAcceptedDeviation'' indicates the 165 maximum orthogonal distance that is ignored and approximated by a straight line. 166 ''AIncludeFirstPoint'' indicates if the first point must be included in the array } 167 function ToPoints(AAcceptedDeviation: single = 0.1; AIncludeFirstPoint: boolean = true): ArrayOfTPointF; 168 function GetBounds: TRectF; 169 end; 170 171 {** Creates a structure for a quadratic Bézier curve } 172 function BezierCurve(origin, control, destination: TPointF) : TQuadraticBezierCurve; overload; 173 {** Creates a structure for a quadratic Bézier curve without curvature } 174 function BezierCurve(origin, destination: TPointF) : TQuadraticBezierCurve; overload; 228 ssVertexToSide, 229 {** The curve is rounded using Bezier curves when the angle is less than or equal to 45° } 230 ssEasyBezier); 175 231 176 232 type … … 271 327 procedure SetStrokeMatrix(const AValue: TAffineMatrix); virtual; abstract; 272 328 public 273 function ComputePolyline(const APoints: array of TPointF; AWidth: single; AClosedCap: boolean = true): ArrayOfTPointF; virtual; abstract;274 function ComputePolyline(const APoints: array of TPointF; AWidth: single; APenColor: TBGRAPixel; AClosedCap: boolean = true): ArrayOfTPointF; virtual; abstract;329 function ComputePolyline(const APoints: array of TPointF; AWidth: single; AClosedCap: boolean = true): ArrayOfTPointF; overload; virtual; abstract; 330 function ComputePolyline(const APoints: array of TPointF; AWidth: single; APenColor: TBGRAPixel; AClosedCap: boolean = true): ArrayOfTPointF; overload; virtual; abstract; 275 331 function ComputePolylineAutoCycle(const APoints: array of TPointF; AWidth: single): ArrayOfTPointF; virtual; abstract; 276 332 function ComputePolygon(const APoints: array of TPointF; AWidth: single): ArrayOfTPointF; virtual; abstract; … … 325 381 {** Computes the intersection of two lines. If they are parallel, returns 326 382 the middle of the segment between the two origins } 327 function IntersectLine(line1, line2: TLineDef): TPointF; 383 function IntersectLine(line1, line2: TLineDef): TPointF; overload; 328 384 {** Computes the intersection of two lines. If they are parallel, returns 329 385 the middle of the segment between the two origins. The value ''parallel'' 330 386 is set to indicate if the lines were parallel } 331 function IntersectLine(line1, line2: TLineDef; out parallel: boolean): TPointF; 387 function IntersectLine(line1, line2: TLineDef; out parallel: boolean): TPointF; overload; 332 388 {** Checks if the polygon formed by the given points is convex. ''IgnoreAlign'' 333 389 specifies that if the points are aligned, it should still be considered as convex } … … 348 404 procedure closePath; 349 405 {** Moves to a location, disconnected from previous points } 350 procedure moveTo(const pt: TPointF);406 procedure moveTo(constref pt: TPointF); 351 407 {** Adds a line from the current point } 352 procedure lineTo(const pt: TPointF);408 procedure lineTo(constref pt: TPointF); 353 409 {** Adds a polyline from the current point } 354 410 procedure polylineTo(const pts: array of TPointF); 355 411 {** Adds a quadratic Bézier curve from the current point } 356 procedure quadraticCurveTo(const cp,pt: TPointF);412 procedure quadraticCurveTo(constref cp,pt: TPointF); 357 413 {** Adds a cubic Bézier curve from the current point } 358 procedure bezierCurveTo(const cp1,cp2,pt: TPointF);414 procedure bezierCurveTo(constref cp1,cp2,pt: TPointF); 359 415 {** Adds an arc. If there is a current point, it is connected to the beginning of the arc } 360 procedure arc(const arcDef: TArcDef);416 procedure arc(constref arcDef: TArcDef); 361 417 {** Adds an opened spline. If there is a current point, it is connected to the beginning of the spline } 362 418 procedure openedSpline(const pts: array of TPointF; style: TSplineStyle); … … 366 422 procedure copyTo(dest: IBGRAPath); 367 423 {** Returns the content of the path as an array of points } 368 function getPoints: ArrayOfTPointF; 424 function getPoints: ArrayOfTPointF; overload; 369 425 {** Returns the content of the path as an array of points with the transformation specified by ''AMatrix'' } 370 function getPoints(AMatrix: TAffineMatrix): ArrayOfTPointF; 426 function getPoints(AMatrix: TAffineMatrix): ArrayOfTPointF; overload; 371 427 {** Returns a cursor to go through the path. The cursor must be freed by calling ''Free''. } 372 428 function getCursor: TBGRACustomPathCursor; 373 429 end; 430 431 { TBGRACustomPath } 432 433 TBGRACustomPath = class(IBGRAPath) 434 constructor Create; virtual; abstract; 435 procedure beginPath; virtual; abstract; 436 procedure closePath; virtual; abstract; 437 procedure moveTo(constref pt: TPointF); virtual; abstract; 438 procedure lineTo(constref pt: TPointF); virtual; abstract; 439 procedure polylineTo(const pts: array of TPointF); virtual; abstract; 440 procedure quadraticCurveTo(constref cp,pt: TPointF); virtual; abstract; 441 procedure bezierCurveTo(constref cp1,cp2,pt: TPointF); virtual; abstract; 442 procedure arc(constref arcDef: TArcDef); virtual; abstract; 443 procedure openedSpline(const pts: array of TPointF; style: TSplineStyle); virtual; abstract; 444 procedure closedSpline(const pts: array of TPointF; style: TSplineStyle); virtual; abstract; 445 procedure copyTo(dest: IBGRAPath); virtual; abstract; 446 protected 447 function getPoints: ArrayOfTPointF; virtual; abstract; 448 function getPoints(AMatrix: TAffineMatrix): ArrayOfTPointF; virtual; abstract; 449 function getLength: single; virtual; abstract; 450 function getCursor: TBGRACustomPathCursor; virtual; abstract; 451 protected 452 function QueryInterface({$IFDEF FPC_HAS_CONSTREF}constref{$ELSE}const{$ENDIF} IID: TGUID; out Obj): HResult; {$IF (not defined(WINDOWS)) AND (FPC_FULLVERSION>=20501)}cdecl{$ELSE}stdcall{$IFEND}; 453 function _AddRef: Integer; {$IF (not defined(WINDOWS)) AND (FPC_FULLVERSION>=20501)}cdecl{$ELSE}stdcall{$IFEND}; 454 function _Release: Integer; {$IF (not defined(WINDOWS)) AND (FPC_FULLVERSION>=20501)}cdecl{$ELSE}stdcall{$IFEND}; 455 end; 456 457 TBGRAPathAny = class of TBGRACustomPath; 374 458 375 459 { TBGRACustomPathCursor } … … 420 504 end; 421 505 506 var 507 BGRAPathFactory: TBGRAPathAny; 508 422 509 const 423 510 {* A value for an empty rectangle } … … 428 515 {* Creates a rectangle with the specified ''width'' and ''height'' } 429 516 function RectWithSize(left,top,width,height: integer): TRect; 517 518 {$DEFINE INCLUDE_INTERFACE} 519 {$I bezier.inc} 430 520 431 521 type … … 501 591 gtDiamond, 502 592 {** The color changes in a radial way from a given center } 503 gtRadial); 593 gtRadial, 594 {** The color changes according to the angle relative to a given center } 595 gtAngular); 504 596 const 505 597 {** List of string to represent gradient types } 506 598 GradientTypeStr : array[TGradientType] of string 507 = ('Linear','Reflected','Diamond','Radial' );599 = ('Linear','Reflected','Diamond','Radial','Angular'); 508 600 {** Returns the gradient type represented by the given string } 509 601 function StrToGradientType(str: string): TGradientType; … … 539 631 {$UNDEF INCLUDE_IMPLEMENTATION} 540 632 633 {$IFDEF BGRA_DEFINE_TRECTF} 634 { TRectF } 635 636 function TRectF.GetHeight: single; 637 begin 638 result := Bottom-Top; 639 end; 640 641 function TRectF.GetWidth: Single; 642 begin 643 result := Right-Left; 644 end; 645 646 procedure TRectF.Offset(const dx, dy: Single); 647 begin 648 left:=left+dx; right:=right+dx; 649 bottom:=bottom+dy; top:=top+dy; 650 end; 651 652 { TRectHelper } 653 654 function TRectHelper.GetHeight: integer; 655 begin 656 result := Bottom-Top; 657 end; 658 659 function TRectHelper.GetIsEmpty: boolean; 660 begin 661 result := (Width = 0) and (Height = 0) 662 end; 663 664 function TRectHelper.GetWidth: integer; 665 begin 666 result := Right-Left; 667 end; 668 669 procedure TRectHelper.SetHeight(AValue: integer); 670 begin 671 Bottom := Top+AValue; 672 end; 673 674 procedure TRectHelper.SetWidth(AValue: integer); 675 begin 676 Right := Left+AValue; 677 end; 678 679 constructor TRectHelper.Create(Origin: TPoint; AWidth, AHeight: Longint); 680 begin 681 self.Left := Origin.X; 682 self.Top := Origin.Y; 683 self.Right := Origin.X+AWidth; 684 self.Bottom := Origin.Y+AHeight; 685 end; 686 687 constructor TRectHelper.Create(ALeft, ATop, ARight, ABottom: Longint); 688 begin 689 self.Left := ALeft; 690 self.Top := ATop; 691 self.Right := ARight; 692 self.Bottom := ABottom; 693 end; 694 695 procedure TRectHelper.Intersect(const ARect: TRect); 696 begin 697 IntersectRect(self, self, ARect); 698 end; 699 700 procedure TRectHelper.Offset(DX, DY: Longint); 701 begin 702 OffsetRect(self, DX,DY); 703 end; 704 705 procedure TRectHelper.Inflate(DX, DY: Longint); 706 begin 707 InflateRect(self, DX,DY); 708 end; 709 710 function TRectHelper.Contains(const APoint: TPoint): boolean; 711 begin 712 result := (APoint.X >= Left) and (APoint.X <= Right) and 713 (APoint.Y >= Top) and (APoint.Y <= Bottom); 714 end; 715 716 function TRectHelper.Contains(const ARect: TRect): boolean; 717 begin 718 Result := (Left <= ARect.Left) and (ARect.Right <= Right) and (Top <= ARect.Top) and (ARect.Bottom <= Bottom); 719 end; 720 721 operator =(const ARect1, ARect2: TRect): boolean; 722 begin 723 result:= (ARect1.Left = ARect2.Left) and (ARect1.Top = ARect2.Top) and 724 (ARect1.Right = ARect2.Right) and (ARect1.Bottom = ARect2.Bottom); 725 end; 726 727 { TSizeHelper } 728 729 function TSizeHelper.GetHeight: integer; 730 begin 731 result := cy; 732 end; 733 734 function TSizeHelper.GetWidth: integer; 735 begin 736 result := cx; 737 end; 738 739 {$ENDIF} 740 741 function IsEmptyPoint(const APoint: TPoint): boolean; 742 begin 743 result := (APoint.x = -2147483648) or (APoint.y = -2147483648); 744 end; 745 746 function TPointFHelper.Ceiling: TPoint; 747 begin 748 if isEmptyPointF(self) then 749 result := EmptyPoint 750 else 751 begin 752 result.x:=ceil(x); 753 result.y:=ceil(y); 754 end; 755 end; 756 757 function TPointFHelper.Truncate: TPoint; 758 begin 759 if isEmptyPointF(self) then 760 result := EmptyPoint 761 else 762 begin 763 result.x:=trunc(x); 764 result.y:=trunc(y); 765 end; 766 end; 767 768 function TPointFHelper.Floor: TPoint; 769 begin 770 if isEmptyPointF(self) then 771 result := EmptyPoint 772 else 773 begin 774 result.x:=Math.floor(x); 775 result.y:=Math.floor(y); 776 end; 777 end; 778 779 function TPointFHelper.Round: TPoint; 780 begin 781 if isEmptyPointF(self) then 782 result := EmptyPoint 783 else 784 begin 785 result.x:=System.round(x); 786 result.y:=System.round(y); 787 end; 788 end; 789 790 function TPointFHelper.Length: Single; 791 begin 792 result:= VectLen(self); 793 end; 794 795 class function TRectFHelper.Intersect(const R1: TRectF; const R2: TRectF): TRectF; 796 begin 797 result.left:=max(R1.left,R2.left); 798 result.top:=max(R1.top,R2.top); 799 result.right:=min(R1.right,R2.right); 800 result.bottom:=min(R1.bottom,R2.bottom); 801 if (result.left >= result.right) or (result.top >= result.bottom) then 802 result := EmptyRectF; 803 end; 804 805 class function TRectFHelper.Union(const R1: TRectF; const R2: TRectF): TRectF; 806 begin 807 result.left:=min(R1.left,R2.left); 808 result.top:=min(R1.top,R2.top); 809 result.right:=max(R1.right,R2.right); 810 result.bottom:=max(R1.bottom,R2.bottom); 811 end; 812 813 class function TRectFHelper.Union(const R1: TRectF; const R2: TRectF; ADiscardEmpty: boolean): TRectF; 814 begin 815 if ADiscardEmpty and IsEmptyRectF(R1) then result:= R2 else 816 if ADiscardEmpty and IsEmptyRectF(R2) then result:= R1 else 817 result := Union(R1,R2); 818 end; 819 820 function TRectFHelper.Union(const r: TRectF): TRectF; 821 begin 822 result := TRectF.Union(self, r); 823 end; 824 825 function TRectFHelper.Union(const r: TRectF; ADiscardEmpty: boolean): TRectF; 826 begin 827 result := TRectF.Union(self, r, ADiscardEmpty); 828 end; 829 830 function TRectFHelper.IntersectsWith(const r: TRectF): boolean; 831 begin 832 result:= not TRectF.Intersect(self, r).IsEmpty; 833 end; 834 835 function TRectFHelper.IsEmpty: boolean; 836 begin 837 result:= IsEmptyRectF(self); 838 end; 839 840 { TAffineBox } 841 842 function TAffineBox.GetAsPolygon: ArrayOfTPointF; 843 begin 844 result := PointsF([TopLeft,TopRight,BottomRight,BottomLeft]); 845 end; 846 847 function TAffineBox.GetBottomRight: TPointF; 848 begin 849 if IsEmpty then 850 result := EmptyPointF 851 else 852 result := TopRight + (BottomLeft-TopLeft); 853 end; 854 855 function TAffineBox.GetHeight: single; 856 begin 857 if isEmptyPointF(TopLeft) or isEmptyPointF(BottomLeft) then 858 result := 0 859 else 860 result := VectLen(BottomLeft-TopLeft); 861 end; 862 863 function TAffineBox.GetIsEmpty: boolean; 864 begin 865 result := isEmptyPointF(TopRight) or isEmptyPointF(BottomLeft) or isEmptyPointF(TopLeft); 866 end; 867 868 function TAffineBox.GetRectBounds: TRect; 869 begin 870 with GetRectBoundsF do 871 result := Rect(floor(Left),floor(Top),ceil(Right),ceil(Bottom)); 872 end; 873 874 function TAffineBox.GetRectBoundsF: TRectF; 875 var 876 x1,y1,x2,y2: single; 877 begin 878 x1 := TopLeft.x; x2 := x1; 879 y1 := TopLeft.y; y2 := y1; 880 if TopRight.x > x2 then x2 := TopRight.x; 881 if TopRight.x < x1 then x1 := TopRight.x; 882 if TopRight.y > y2 then y2 := TopRight.y; 883 if TopRight.y < y1 then y1 := TopRight.y; 884 if BottomLeft.x > x2 then x2 := BottomLeft.x; 885 if BottomLeft.x < x1 then x1 := BottomLeft.x; 886 if BottomLeft.y > y2 then y2 := BottomLeft.y; 887 if BottomLeft.y < y1 then y1 := BottomLeft.y; 888 if BottomRight.x > x2 then x2 := BottomRight.x; 889 if BottomRight.x < x1 then x1 := BottomRight.x; 890 if BottomRight.y > y2 then y2 := BottomRight.y; 891 if BottomRight.y < y1 then y1 := BottomRight.y; 892 result := RectF(x1,y1,x2,y2); 893 end; 894 895 function TAffineBox.GetSurface: single; 896 var 897 u, v: TPointF; 898 lenU, lenH: Single; 899 begin 900 u := TopRight-TopLeft; 901 lenU := VectLen(u); 902 if lenU = 0 then exit(0); 903 u *= 1/lenU; 904 v := BottomLeft-TopLeft; 905 lenH := PointF(-u.y,u.x)*v; 906 result := abs(lenU*lenH); 907 end; 908 909 function TAffineBox.GetWidth: single; 910 begin 911 if isEmptyPointF(TopLeft) or isEmptyPointF(TopRight) then 912 result := 0 913 else 914 result := VectLen(TopRight-TopLeft); 915 end; 916 917 class function TAffineBox.EmptyBox: TAffineBox; 918 begin 919 result.TopLeft := EmptyPointF; 920 result.TopRight := EmptyPointF; 921 result.BottomLeft := EmptyPointF; 922 end; 923 924 class function TAffineBox.AffineBox(ATopLeft, ATopRight, ABottomLeft: TPointF): TAffineBox; 925 begin 926 result.TopLeft := ATopLeft; 927 result.TopRight := ATopRight; 928 result.BottomLeft := ABottomLeft; 929 end; 930 931 class function TAffineBox.AffineBox(ARectF: TRectF): TAffineBox; 932 begin 933 result.TopLeft := ARectF.TopLeft; 934 result.TopRight := PointF(ARectF.Right, ARectF.Top); 935 result.BottomLeft := PointF(ARectF.Left, ARectF.Bottom); 936 end; 937 938 function TAffineBox.Contains(APoint: TPointF): boolean; 939 var 940 u,v,perpU,perpV: TPointF; 941 posV1, posV2, posU1, posU2: single; 942 begin 943 if IsEmpty then exit(false); 944 945 u := TopRight-TopLeft; 946 perpU := PointF(-u.y,u.x); 947 v := BottomLeft-TopLeft; 948 perpV := PointF(v.y,-v.x); 949 950 //reverse normal if not in the same direction as other side 951 if perpU*v < 0 then 952 begin 953 perpU := -perpU; 954 perpV := -perpV; 955 end; 956 957 //determine position along normals 958 posU1 := (APoint-TopLeft)*perpU; 959 posU2 := (APoint-BottomLeft)*perpU; 960 posV1 := (APoint-TopLeft)*perpV; 961 posV2 := (APoint-TopRight)*perpV; 962 963 result := (posU1 >= 0) and (posU2 < 0) and (posV1 >= 0) and (posV2 < 0); 964 end; 965 541 966 function StrToGradientType(str: string): TGradientType; 542 967 var gt: TGradientType; … … 667 1092 end; 668 1093 669 //-------------- Bézier curves definitions ---------------- 670 // See : http://en.wikipedia.org/wiki/B%C3%A9zier_curve 671 672 // Define a Bézier curve with two control points. 673 function BezierCurve(origin, control1, control2, destination: TPointF): TCubicBezierCurve; 674 begin 675 result.p1 := origin; 676 result.c1 := control1; 677 result.c2 := control2; 678 result.p2 := destination; 679 end; 680 681 // Define a Bézier curve with one control point. 682 function BezierCurve(origin, control, destination: TPointF 683 ): TQuadraticBezierCurve; 684 begin 685 result.p1 := origin; 686 result.c := control; 687 result.p2 := destination; 688 end; 689 690 //straight line 691 function BezierCurve(origin, destination: TPointF): TQuadraticBezierCurve; 692 begin 693 result.p1 := origin; 694 result.c := (origin+destination)*0.5; 695 result.p2 := destination; 1094 { TBGRACustomPath } 1095 1096 function TBGRACustomPath.QueryInterface({$IFDEF FPC_HAS_CONSTREF}constref{$ELSE}const{$ENDIF} IID: TGUID; out Obj): HResult; {$IF (not defined(WINDOWS)) AND (FPC_FULLVERSION>=20501)}cdecl{$ELSE}stdcall{$IFEND}; 1097 begin 1098 if GetInterface(iid, obj) then 1099 Result := S_OK 1100 else 1101 Result := longint(E_NOINTERFACE); 1102 end; 1103 1104 { There is no automatic reference counting, but it is compulsory to define these functions } 1105 function TBGRACustomPath._AddRef: Integer; {$IF (not defined(WINDOWS)) AND (FPC_FULLVERSION>=20501)}cdecl{$ELSE}stdcall{$IFEND}; 1106 begin 1107 result := 0; 1108 end; 1109 1110 function TBGRACustomPath._Release: Integer; {$IF (not defined(WINDOWS)) AND (FPC_FULLVERSION>=20501)}cdecl{$ELSE}stdcall{$IFEND}; 1111 begin 1112 result := 0; 696 1113 end; 697 1114 … … 832 1249 end; 833 1250 1251 function RectF(const ATopLeft, ABottomRight: TPointF): TRectF; 1252 begin 1253 result.TopLeft:= ATopLeft; 1254 result.BottomRight:= ABottomRight; 1255 end; 1256 1257 function RectWithSizeF(left, top, width, height: Single): TRectF; 1258 begin 1259 result.Left:= Left; 1260 result.Top:= Top; 1261 result.Right:= left+width; 1262 result.Bottom:= top+height; 1263 end; 1264 1265 function IsEmptyRectF(const ARect: TRectF): boolean; 1266 begin 1267 result:= (ARect.Width = 0) and (ARect.Height = 0); 1268 end; 1269 834 1270 function PointF(x, y: single): TPointF; 835 1271 begin 836 1272 Result.x := x; 837 1273 Result.y := y; 1274 end; 1275 1276 function PointF(pt: TPoint): TPointF; 1277 begin 1278 if IsEmptyPoint(pt) then 1279 result:= EmptyPointF 1280 else 1281 begin 1282 Result.x := pt.x; 1283 Result.y := pt.y; 1284 end; 838 1285 end; 839 1286 … … 867 1314 function VectLen(v: TPointF): single; 868 1315 begin 869 result := sqrt(v*v); 1316 if isEmptyPointF(v) then 1317 result := EmptySingle 1318 else 1319 result := sqrt(v*v); 870 1320 end; 871 1321 … … 1069 1519 end; 1070 1520 1071 {------------------ Bezier curves ------------------------} 1072 1073 function ComputeBezierCurvePrecision(pt1, pt2, pt3, pt4: TPointF; AAcceptedDeviation: single = 0.1): integer; 1074 var 1075 len: single; 1076 begin 1077 len := sqr(pt1.x - pt2.x) + sqr(pt1.y - pt2.y); 1078 len := max(len, sqr(pt3.x - pt2.x) + sqr(pt3.y - pt2.y)); 1079 len := max(len, sqr(pt3.x - pt4.x) + sqr(pt3.y - pt4.y)); 1080 Result := round(sqrt(sqrt(len)/ AAcceptedDeviation) * 1); 1081 if Result<=0 then Result:=1; 1082 end; 1083 1084 { TCubicBezierCurve } 1085 1086 function TCubicBezierCurve.SimpleComputePoints(AAcceptedDeviation: single; 1087 AIncludeFirstPoint: boolean = true): ArrayOfTPointF; 1088 var 1089 t,step: single; 1090 i,nb: Integer; 1091 begin 1092 nb := ComputeBezierCurvePrecision(p1,c1,c2,p2, AAcceptedDeviation/2); 1093 if nb <= 1 then nb := 2; 1094 if AIncludeFirstPoint then 1095 begin 1096 setlength(result,nb); 1097 result[0] := p1; 1098 result[nb-1] := p2; 1099 step := 1/(nb-1); 1100 t := 0; 1101 for i := 1 to nb-2 do 1102 begin 1103 t += step; 1104 result[i] := ComputePointAt(t); 1105 end; 1106 end else 1107 begin 1108 setlength(result,nb-1); 1109 result[nb-2] := p2; 1110 step := 1/(nb-1); 1111 t := 0; 1112 for i := 0 to nb-3 do 1113 begin 1114 t += step; 1115 result[i] := ComputePointAt(t); 1116 end; 1117 end; 1118 end; 1119 1120 function TCubicBezierCurve.ComputePointAt(t: single): TPointF; 1121 var 1122 f1,f2,f3,f4: single; 1123 begin 1124 f1 := (1-t); 1125 f2 := f1*f1; 1126 f1 *= f2; 1127 f2 *= t*3; 1128 f4 := t*t; 1129 f3 := f4*(1-t)*3; 1130 f4 *= t; 1131 1132 result.x := f1*p1.x + f2*c1.x + 1133 f3*c2.x + f4*p2.x; 1134 result.y := f1*p1.y + f2*c1.y + 1135 f3*c2.y + f4*p2.y; 1136 end; 1137 1138 procedure TCubicBezierCurve.Split(out ALeft, ARight: TCubicBezierCurve); 1139 var midc: TPointF; 1140 begin 1141 ALeft.p1 := p1; 1142 ALeft.c1 := 0.5*(p1+c1); 1143 ARight.p2 := p2; 1144 ARight.c2 := 0.5*(p2+c2); 1145 midc := 0.5*(c1+c2); 1146 ALeft.c2 := 0.5*(ALeft.c1+midc); 1147 ARight.c1 := 0.5*(ARight.c2+midc); 1148 ALeft.p2 := 0.5*(ALeft.c2+ARight.c1); 1149 ARight.p1 := ALeft.p2; 1150 end; 1151 1152 function TCubicBezierCurve.ComputeLength(AAcceptedDeviation: single): single; 1153 var 1154 t,step: single; 1155 i,nb: Integer; 1156 curCoord,nextCoord: TPointF; 1157 begin 1158 nb := ComputeBezierCurvePrecision(p1,c1,c2,p2, AAcceptedDeviation); 1159 if nb <= 1 then nb := 2; 1160 result := 0; 1161 curCoord := p1; 1162 step := 1/(nb-1); 1163 t := 0; 1164 for i := 1 to nb-2 do 1165 begin 1166 t += step; 1167 nextCoord := ComputePointAt(t); 1168 result += VectLen(nextCoord-curCoord); 1169 curCoord := nextCoord; 1170 end; 1171 result += VectLen(p2-curCoord); 1172 end; 1173 1174 function TCubicBezierCurve.ToPoints(AAcceptedDeviation: single; 1175 AIncludeFirstPoint: boolean = true): ArrayOfTPointF; 1176 begin 1177 result := SimpleComputePoints(AAcceptedDeviation, AIncludeFirstPoint); 1178 end; 1179 1180 {//The following function computes by splitting the curve. It is slower than the simple function. 1181 function TCubicBezierCurve.ToPoints(AAcceptedDeviation: single; 1182 ARelativeDeviation: boolean): ArrayOfTPointF; 1183 function ToPointsRec(const ACurve: TCubicBezierCurve): ArrayOfTPointF; 1184 var simpleLen2: single; 1185 v: TPointF; 1186 left,right: TCubicBezierCurve; 1187 subLeft,subRight: ArrayOfTPointF; 1188 maxDev,dev1,dev2: single; 1189 subLeftLen: integer; 1190 1191 procedure ComputeExtremum; 1192 begin 1193 raise Exception.Create('Not implemented'); 1194 result := nil; 1195 end; 1196 1197 begin 1198 v := ACurve.p2-ACurve.p1; 1199 simpleLen2 := v*v; 1200 if simpleLen2 = 0 then 1201 begin 1202 if (ACurve.c1.x = ACurve.p1.x) and (ACurve.c1.y = ACurve.p1.y) and 1203 (ACurve.c2.x = ACurve.p2.x) and (ACurve.c2.y = ACurve.p2.y) then 1204 begin 1205 result := nil; 1206 exit; 1207 end; 1208 ACurve.Split(left,right); 1209 end else 1210 begin 1211 ACurve.Split(left,right); 1212 if not ARelativeDeviation then simpleLen2:= sqrt(simpleLen2); 1213 maxDev := AAcceptedDeviation*simpleLen2; 1214 if abs(PointF(v.y,-v.x) * (left.p2-ACurve.p1)) <= maxDev then 1215 begin 1216 dev1 := PointF(v.y,-v.x) * (ACurve.c1-ACurve.p1); 1217 dev2 := PointF(v.y,-v.x) * (ACurve.c2-ACurve.p2); 1218 if not ((Sign(dev1)<>Sign(dev2)) and ((abs(dev1) > maxDev) or (abs(dev2) > maxDev))) then 1219 begin 1220 result := nil; 1221 if ((ACurve.c1-ACurve.p1)*v < -maxDev) or 1222 ((ACurve.c1-ACurve.p2)*v > maxDev) or 1223 ((ACurve.c2-ACurve.p1)*v < -maxDev) or 1224 ((ACurve.c2-ACurve.p2)*v > maxDev) then 1225 ComputeExtremum; 1226 exit; 1227 end; 1228 end; 1229 end; 1230 subRight := ToPointsRec(right); 1231 subLeft := ToPointsRec(left); 1232 subLeftLen := length(subLeft); 1233 1234 //avoid leaving a gap in memory 1235 result := subLeft; 1236 subLeft := nil; 1237 setlength(result, subLeftLen+1+length(subRight)); 1238 result[subLeftLen] := left.p2; 1239 move(subRight[0], result[subLeftLen+1], length(subRight)*sizeof(TPointF)); 1240 end; 1241 1242 var 1243 subLen: integer; 1244 1245 begin 1246 if (c1.x = p1.x) and (c1.y = p1.y) and 1247 (c1.x = c2.x) and (c1.y = c2.y) and 1248 (c1.x = p2.x) and (c1.y = p2.y) then 1249 begin 1250 setlength(result,1); 1251 result[0] := c1; 1252 exit; 1253 end else 1254 begin 1255 result := ToPointsRec(self); 1256 subLen := length(result); 1257 setlength(result, length(result)+2); 1258 move(result[0], result[1], subLen*sizeof(TPointF)); 1259 result[0] := p1; 1260 result[high(result)] := p2; 1261 end; 1262 end;} 1263 1264 function TCubicBezierCurve.GetBounds: TRectF; 1265 const precision = 1e-5; 1266 1267 procedure Include(pt: TPointF); 1268 begin 1269 if pt.x < result.Left then result.Left := pt.x 1270 else if pt.x > result.Right then result.Right := pt.x; 1271 if pt.y < result.Top then result.Top := pt.y 1272 else if pt.y > result.Bottom then result.Bottom := pt.y; 1273 end; 1274 1275 procedure IncludeT(t: single); 1276 begin 1277 if (t > 0) and (t < 1) then 1278 Include(ComputePointAt(t)); 1279 end; 1280 1281 procedure IncludeABC(a,b,c: single); 1282 var b2ac, sqrtb2ac: single; 1283 begin 1284 if abs(a) < precision then 1285 begin 1286 if abs(b) < precision then exit; 1287 IncludeT(-c/b); 1288 end else 1289 begin 1290 b2ac := sqr(b) - 4 * a * c; 1291 if b2ac >= 0 then 1292 begin 1293 sqrtb2ac := sqrt(b2ac); 1294 IncludeT((-b + sqrtb2ac) / (2 * a)); 1295 IncludeT((-b - sqrtb2ac) / (2 * a)); 1296 end; 1297 end; 1298 end; 1299 1300 var 1301 va, vb, vc: TPointF; 1302 1303 begin 1304 result.TopLeft := p1; 1305 result.BottomRight := p1; 1306 Include(p2); 1307 1308 vb := 6 * p1 - 12 * c1 + 6 * c2; 1309 va := -3 * p1 + 9 * c1 - 9 * c2 + 3 * p2; 1310 vc := 3 * c1 - 3 * p1; 1311 1312 IncludeABC(va.x,vb.x,vc.x); 1313 IncludeABC(va.y,vb.y,vc.y); 1314 end; 1315 1316 { TQuadraticBezierCurve } 1317 1318 function TQuadraticBezierCurve.SimpleComputePoints(AAcceptedDeviation: single; 1319 AIncludeFirstPoint: boolean = true): ArrayOfTPointF; 1320 var 1321 t,step: single; 1322 i,nb: Integer; 1323 begin 1324 nb := ComputeBezierCurvePrecision(p1,c,c,p2, AAcceptedDeviation); 1325 if nb <= 1 then nb := 2; 1326 if AIncludeFirstPoint then 1327 begin 1328 setlength(result,nb); 1329 result[0] := p1; 1330 result[nb-1] := p2; 1331 step := 1/(nb-1); 1332 t := 0; 1333 for i := 1 to nb-2 do 1334 begin 1335 t += step; 1336 result[i] := ComputePointAt(t); 1337 end; 1338 end else 1339 begin 1340 setlength(result,nb-1); 1341 result[nb-2] := p2; 1342 step := 1/(nb-1); 1343 t := 0; 1344 for i := 0 to nb-3 do 1345 begin 1346 t += step; 1347 result[i] := ComputePointAt(t); 1348 end; 1349 end; 1350 end; 1351 1352 function TQuadraticBezierCurve.ComputeExtremumPositionOutsideSegment: single; 1353 var a,b: single; 1354 v: TPointF; 1355 begin 1356 v := self.p2-self.p1; 1357 a := (self.p1-2*self.c+self.p2)*v; 1358 if a = 0 then //no solution 1359 begin 1360 result := -1; 1361 exit; 1362 end; 1363 b := (self.c-self.p1)*v; 1364 result := -b/a; 1365 end; 1366 1367 function TQuadraticBezierCurve.ComputePointAt(t: single): TPointF; 1368 var 1369 rev_t,f2,t2: single; 1370 begin 1371 rev_t := (1-t); 1372 f2 := rev_t*t*2; 1373 rev_t *= rev_t; 1374 t2 := t*t; 1375 result.x := rev_t*p1.x + f2*c.x + t2*p2.x; 1376 result.y := rev_t*p1.y + f2*c.y + t2*p2.y; 1377 end; 1378 1379 procedure TQuadraticBezierCurve.Split(out ALeft, ARight: TQuadraticBezierCurve); 1380 begin 1381 ALeft.p1 := p1; 1382 ALeft.c := 0.5*(p1+c); 1383 ARight.p2 := p2; 1384 ARight.c := 0.5*(p2+c); 1385 ALeft.p2 := 0.5*(ALeft.c+ARight.c); 1386 ARight.p1 := ALeft.p2; 1387 end; 1388 1389 function TQuadraticBezierCurve.ComputeLength: single; 1390 var a,b: TPointF; 1391 A_,AB_,B_,Sabc,A_2,A_32,B_2,BA, 1392 divisor: single; 1393 extremumPos: single; 1394 extremum: TPointF; 1395 begin 1396 a := p1 - 2*c + p2; 1397 b := 2*(c - p1); 1398 A_ := 4*(a*a); 1399 B_ := b*b; 1400 if (A_ = 0) or (B_ = 0) then 1401 begin 1402 result := VectLen(p2-p1); 1403 exit; 1404 end; 1405 AB_ := 4*(a*b); 1406 1407 A_2 := sqrt(A_); 1408 B_2 := 2*sqrt(B_); 1409 BA := AB_/A_2; 1410 divisor := BA+B_2; 1411 if divisor <= 0 then 1412 begin 1413 extremumPos:= ComputeExtremumPositionOutsideSegment; 1414 if (extremumPos <= 0) or (extremumPos >= 1) then 1415 result := VectLen(p2-p1) 1416 else 1417 begin 1418 extremum := ComputePointAt(extremumPos); 1419 result := VectLen(extremum-p1)+VectLen(p2-extremum); 1420 end; 1421 exit; 1422 end; 1423 1424 Sabc := 2*sqrt(A_+AB_+B_); 1425 A_32 := 2*A_*A_2; 1426 result := ( A_32*Sabc + 1427 A_2*AB_*(Sabc-B_2) + 1428 (4*B_*A_-AB_*AB_)*ln( (2*A_2+BA+Sabc)/divisor ) 1429 )/(4*A_32); 1430 end; 1431 1432 function TQuadraticBezierCurve.ToPoints(AAcceptedDeviation: single; 1433 AIncludeFirstPoint: boolean = true): ArrayOfTPointF; 1434 begin 1435 result := SimpleComputePoints(AAcceptedDeviation, AIncludeFirstPoint); 1436 end; 1437 1438 function TQuadraticBezierCurve.GetBounds: TRectF; 1439 const precision = 1e-5; 1440 1441 procedure Include(pt: TPointF); 1442 begin 1443 if pt.x < result.Left then result.Left := pt.x 1444 else if pt.x > result.Right then result.Right := pt.x; 1445 if pt.y < result.Top then result.Top := pt.y 1446 else if pt.y > result.Bottom then result.Bottom := pt.y; 1447 end; 1448 1449 procedure IncludeT(t: single); 1450 begin 1451 if (t > 0) and (t < 1) then 1452 Include(ComputePointAt(t)); 1453 end; 1454 1455 procedure IncludeABC(a,b,c: single); 1456 var denom: single; 1457 begin 1458 denom := a-2*b+c; 1459 if abs(denom) < precision then exit; 1460 IncludeT((a-b)/denom); 1461 end; 1462 1463 begin 1464 result.TopLeft := p1; 1465 result.BottomRight := p1; 1466 Include(p2); 1467 1468 IncludeABC(p1.x,c.x,p2.x); 1469 IncludeABC(p1.y,c.y,p2.y); 1470 end; 1471 1472 {//The following function computes by splitting the curve. It is slower than the simple function 1473 function TQuadraticBezierCurve.ToPoints(AAcceptedDeviation: single; ARelativeDeviation: boolean): ArrayOfTPointF; 1474 1475 function ToPointsRec(const ACurve: TQuadraticBezierCurve): ArrayOfTPointF; 1476 var simpleLen2: single; 1477 v: TPointF; 1478 left,right: TQuadraticBezierCurve; 1479 subLeft,subRight: ArrayOfTPointF; 1480 subLeftLen: Integer; 1481 1482 procedure ComputeExtremum; 1483 var 1484 t: single; 1485 begin 1486 t := ACurve.ComputeExtremumPositionOutsideSegment; 1487 if (t <= 0) or (t >= 1) then 1488 result := nil 1489 else 1490 begin 1491 setlength(result,1); 1492 result[0] := ACurve.ComputePointAt(t); 1493 end; 1494 end; 1495 1496 begin 1497 v := ACurve.p2-ACurve.p1; 1498 simpleLen2 := v*v; 1499 if simpleLen2 = 0 then 1500 begin 1501 if (ACurve.c.x = ACurve.p1.x) and (ACurve.c.y = ACurve.p1.y) then 1502 begin 1503 result := nil; 1504 exit; 1505 end; 1506 ACurve.Split(left,right); 1507 end else 1508 begin 1509 ACurve.Split(left,right); 1510 if not ARelativeDeviation then simpleLen2:= sqrt(simpleLen2); 1511 if abs(PointF(v.y,-v.x) * (left.p2-ACurve.p1)) 1512 <= AAcceptedDeviation*simpleLen2 then 1513 begin 1514 result := nil; 1515 if ((ACurve.c-ACurve.p1)*v < -AAcceptedDeviation*simpleLen2) or 1516 ((ACurve.c-ACurve.p2)*v > AAcceptedDeviation*simpleLen2) then 1517 ComputeExtremum; 1518 exit; 1519 end; 1520 end; 1521 subRight := ToPointsRec(right); 1522 subLeft := ToPointsRec(left); 1523 subLeftLen := length(subLeft); 1524 1525 //avoid leaving a gap in memory 1526 result := subLeft; 1527 subLeft := nil; 1528 setlength(result, subLeftLen+1+length(subRight)); 1529 result[subLeftLen] := left.p2; 1530 move(subRight[0], result[subLeftLen+1], length(subRight)*sizeof(TPointF)); 1531 end; 1532 1533 var 1534 subLen: integer; 1535 1536 begin 1537 if (c.x = p1.x) and (c.y = p1.y) and 1538 (c.x = p2.x) and (c.y = p2.y) then 1539 begin 1540 setlength(result,1); 1541 result[0] := c; 1542 exit; 1543 end else 1544 begin 1545 result := ToPointsRec(self); 1546 subLen := length(result); 1547 setlength(result, length(result)+2); 1548 move(result[0], result[1], subLen*sizeof(TPointF)); 1549 result[0] := p1; 1550 result[high(result)] := p2; 1551 end; 1552 end;} 1521 {$DEFINE INCLUDE_IMPLEMENTATION} 1522 {$I bezier.inc} 1523 1553 1524 {$ENDIF}
Note:
See TracChangeset
for help on using the changeset viewer.