Changeset 472 for GraphicTest/Packages/bgrabitmap/bgrapath.pas
- Timestamp:
- Apr 9, 2015, 9:58:36 PM (10 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
GraphicTest/Packages/bgrabitmap/bgrapath.pas
r452 r472 5 5 interface 6 6 7 { There are different conventions for angles. 8 9 First is about the unit. It can be one of the following: 10 - degrees (0..360) 11 - radian (0..2*Pi) 12 - tenth of degrees (0..3600) 13 - from 0 to 65536 14 15 Second is about the origin. It can be one of the following: 16 - right-most position (this is the default origin for radian and 65536) 17 - top-most position (this is the default origin for degrees) 18 19 Third is about the sign. It can be one of the following: 20 - positive is clockwise (this is the default for degrees) 21 - positive is counterclockwise (this is the default for radian and 65536) 22 23 TBGRAPath and TBGRACanvas2D follow HTML5 convention which is: 24 (radian, right-most, clockwise) that can be shortened to (radian, clockwise) 25 because right-most is the default for radian. This is abbreviated as "radCW". 26 27 When radian are CCW, it is also specified in order to make it clear, even 28 if it is the default convention in mathematics. 29 30 In order to make things easier, there are some functions that accept angles 31 in degrees. The convention used here is the usual degree convention: 32 (degrees, top-most, clockwise) that can be shortened to (degree) 33 because top-most and clockwise is the default for degrees. 34 35 } 36 7 37 uses 8 Classes, BGRABitmapTypes; 38 Classes, BGRABitmapTypes, BGRATransform; 39 40 type 41 TBGRAPathElementType = (peNone, peMoveTo, peLineTo, peCloseSubPath, peQuadraticBezierTo, peCubicBezierTo, peArc); 42 PBGRAPathElementType = ^TBGRAPathElementType; 43 44 { TBGRAPath } 45 46 TBGRAPath = class(IBGRAPath) 47 private 48 function GetSvgString: string; 49 procedure SetSvgString(const AValue: string); 50 protected 51 FData: pbyte; 52 FDataSize: integer; 53 FDataPos: integer; 54 FLastElementType: TBGRAPathElementType; 55 FLastCoord, 56 FStartCoord: TPointF; 57 FExpectedControlPoint: TPointF; 58 FMatrix: TAffineMatrix; //this matrix must have a base of vectors 59 //orthogonal, of same length and with positive 60 //orientation in order to preserve arcs 61 FScale,FAngleRadCW: single; 62 procedure NeedSpace(count: integer); 63 procedure StoreCoord(const pt: TPointF); 64 function ReadCoord: TPointF; 65 procedure StoreElementType(value: TBGRAPathElementType); 66 function ReadElementType: TBGRAPathElementType; 67 function ReadArcDef: TArcDef; 68 procedure RewindFloat; 69 procedure Init; 70 public 71 constructor Create; overload; 72 constructor Create(ASvgString: string); overload; 73 destructor Destroy; override; 74 procedure beginPath; 75 procedure closePath; 76 procedure translate(x,y: single); 77 procedure resetTransform; 78 procedure rotate(angleRadCW: single); overload; 79 procedure rotateDeg(angleDeg: single); overload; 80 procedure rotate(angleRadCW: single; center: TPointF); overload; 81 procedure rotateDeg(angleDeg: single; center: TPointF); overload; 82 procedure scale(factor: single); 83 procedure moveTo(x,y: single); overload; 84 procedure lineTo(x,y: single); overload; 85 procedure moveTo(const pt: TPointF); overload; 86 procedure lineTo(const pt: TPointF); overload; 87 procedure polylineTo(const pts: array of TPointF); 88 procedure quadraticCurveTo(cpx,cpy,x,y: single); overload; 89 procedure quadraticCurveTo(const cp,pt: TPointF); overload; 90 procedure quadraticCurve(const curve: TQuadraticBezierCurve); overload; 91 procedure smoothQuadraticCurveTo(x,y: single); overload; 92 procedure smoothQuadraticCurveTo(const pt: TPointF); overload; 93 procedure bezierCurveTo(cp1x,cp1y,cp2x,cp2y,x,y: single); overload; 94 procedure bezierCurveTo(const cp1,cp2,pt: TPointF); overload; 95 procedure bezierCurve(const curve: TCubicBezierCurve); overload; 96 procedure smoothBezierCurveTo(cp2x,cp2y,x,y: single); overload; 97 procedure smoothBezierCurveTo(const cp2,pt: TPointF); overload; 98 procedure rect(x,y,w,h: single); 99 procedure roundRect(x,y,w,h,radius: single); 100 procedure arc(cx, cy, radius, startAngleRadCW, endAngleRadCW: single; anticlockwise: boolean); overload; 101 procedure arc(cx, cy, radius, startAngleRadCW, endAngleRadCW: single); overload; 102 procedure arcDeg(cx, cy, radius, startAngleDeg, endAngleDeg: single; anticlockwise: boolean); overload; 103 procedure arcDeg(cx, cy, radius, startAngleDeg, endAngleDeg: single); overload; 104 procedure arcTo(x1, y1, x2, y2, radius: single); overload; 105 procedure arcTo(const p1,p2: TPointF; radius: single); overload; 106 procedure arc(const arcDef: TArcDef); overload; 107 procedure arc(cx, cy, rx,ry, xAngleRadCW, startAngleRadCW, endAngleRadCW: single); overload; 108 procedure arc(cx, cy, rx,ry, xAngleRadCW, startAngleRadCW, endAngleRadCW: single; anticlockwise: boolean); overload; 109 procedure arcTo(rx,ry, xAngleRadCW: single; largeArc, anticlockwise: boolean; x,y:single); 110 procedure copyTo(dest: IBGRAPath); 111 procedure addPath(const AValue: string); overload; 112 procedure addPath(source: IBGRAPath); overload; 113 property SvgString: string read GetSvgString write SetSvgString; 114 protected 115 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}; 116 function _AddRef: Integer; {$IF (not defined(WINDOWS)) AND (FPC_FULLVERSION>=20501)}cdecl{$ELSE}stdcall{$IFEND}; 117 function _Release: Integer; {$IF (not defined(WINDOWS)) AND (FPC_FULLVERSION>=20501)}cdecl{$ELSE}stdcall{$IFEND}; 118 end; 9 119 10 120 {----------------------- Spline ------------------} … … 21 131 function ComputeEllipse(x,y,rx,ry: single; quality: single = 1): ArrayOfTPointF; 22 132 function ComputeArc65536(x, y, rx, ry: single; start65536,end65536: word; quality: single = 1): ArrayOfTPointF; 133 function ComputeArcRad(x, y, rx, ry: single; startRadCCW,endRadCCW: single; quality: single = 1): ArrayOfTPointF; 134 function ComputeArc(const arc: TArcDef; quality: single = 1): ArrayOfTPointF; 23 135 function ComputeRoundRect(x1,y1,x2,y2,rx,ry: single; quality: single = 1): ArrayOfTPointF; overload; 24 136 function ComputeRoundRect(x1,y1,x2,y2,rx,ry: single; options: TRoundRectangleOptions; quality: single = 1): ArrayOfTPointF; overload; 25 137 138 function Html5ArcTo(const p0, p1, p2: TPointF; radius: single): TArcDef; 139 function SvgArcTo(const p0: TPointF; rx, ry, xAngleRadCW: single; largeArc, 140 anticlockwise: boolean; const p1: TPointF): TArcDef; 141 function ArcStartPoint(const arc: TArcDef): TPointF; 142 function ArcEndPoint(const arc: TArcDef): TPointF; 143 function IsLargeArc(const arc: TArcDef): boolean; 144 26 145 implementation 27 146 28 uses Math, BGRAResample ;147 uses Math, BGRAResample, SysUtils; 29 148 30 149 function SplineVertexToSide(y0, y1, y2, y3: single; t: single): single; … … 330 449 end; 331 450 332 {$PUSH}{$R-}333 451 function ComputeArc65536(x, y, rx, ry: single; start65536,end65536: word; quality: single): ArrayOfTPointF; 334 452 var i,nb: integer; … … 362 480 for i := 0 to nb-1 do 363 481 begin 482 {$PUSH}{$R-} 364 483 pos := start65536+int64(i)*arclen div (int64(nb)-1); 484 {$POP} 365 485 result[i] := PointF(x+rx*(Cos65536(pos)-32768)/32768, 366 486 y-ry*(Sin65536(pos)-32768)/32768); 367 487 end; 368 488 end; 369 {$R+}370 489 371 490 function ComputeEllipse(x, y, rx, ry: single; quality: single): ArrayOfTPointF; 372 491 begin 373 492 result := ComputeArc65536(x,y,rx,ry,0,0,quality); 493 end; 494 495 function ComputeArcRad(x, y, rx, ry: single; startRadCCW, endRadCCW: single; 496 quality: single): ArrayOfTPointF; 497 begin 498 result := ComputeArc65536(x,y,rx,ry,round(startRadCCW*32768/Pi) and $ffff,round(endRadCCW*32768/Pi) and $ffff,quality); 499 result[0] := PointF(x+cos(startRadCCW)*rx,y-sin(startRadCCW)*ry); 500 result[high(result)] := PointF(x+cos(endRadCCW)*rx,y-sin(endRadCCW)*ry); 501 end; 502 503 function ComputeArc(const arc: TArcDef; quality: single): ArrayOfTPointF; 504 var startAngle,endAngle: single; 505 i,n: integer; 506 temp: TPointF; 507 m: TAffineMatrix; 508 begin 509 startAngle := -arc.startAngleRadCW; 510 endAngle:= -arc.endAngleRadCW; 511 if not arc.anticlockwise then 512 begin 513 result := ComputeArcRad(arc.center.x,arc.center.y,arc.radius.x,arc.radius.y,endAngle,startAngle,quality); 514 n := length(result); 515 if n>1 then 516 for i := 0 to (n-2) div 2 do 517 begin 518 temp := result[i]; 519 result[i] := result[n-1-i]; 520 result[n-1-i] := temp; 521 end; 522 end else 523 result := ComputeArcRad(arc.center.x,arc.center.y,arc.radius.x,arc.radius.y,startAngle,endAngle,quality); 524 if arc.xAngleRadCW <> 0 then 525 begin 526 m := AffineMatrixTranslation(arc.center.x,arc.center.y)*AffineMatrixRotationRad(-arc.xAngleRadCW)*AffineMatrixTranslation(-arc.center.x,-arc.center.y); 527 for i := 0 to high(result) do 528 result[i] := m*result[i]; 529 end; 374 530 end; 375 531 … … 436 592 end; 437 593 594 function Html5ArcTo(const p0, p1, p2: TPointF; radius: single 595 ): TArcDef; 596 var p3,p4,an,bn,cn,c: TPointF; 597 dir, a2, b2, c2, cosx, sinx, d: single; 598 anticlockwise: boolean; 599 begin 600 result.center := p1; 601 result.radius := PointF(0,0); 602 result.xAngleRadCW:= 0; 603 result.startAngleRadCW := 0; 604 result.endAngleRadCW:= 0; 605 result.anticlockwise:= false; 606 607 radius := abs(radius); 608 if (p0 = p1) or (p1 = p2) or (radius = 0) then exit; 609 610 dir := (p2.x-p1.x)*(p0.y-p1.y) + (p2.y-p1.y)*(p1.x-p0.x); 611 if dir = 0 then exit; 612 613 a2 := (p0.x-p1.x)*(p0.x-p1.x) + (p0.y-p1.y)*(p0.y-p1.y); 614 b2 := (p1.x-p2.x)*(p1.x-p2.x) + (p1.y-p2.y)*(p1.y-p2.y); 615 c2 := (p0.x-p2.x)*(p0.x-p2.x) + (p0.y-p2.y)*(p0.y-p2.y); 616 cosx := (a2+b2-c2)/(2*sqrt(a2*b2)); 617 618 sinx := sqrt(1 - cosx*cosx); 619 if (sinx = 0) or (cosx = 1) then exit; 620 d := radius / ((1 - cosx) / sinx); 621 622 an := (p1-p0)*(1/sqrt(a2)); 623 bn := (p1-p2)*(1/sqrt(b2)); 624 p3 := p1 - an*d; 625 p4 := p1 - bn*d; 626 anticlockwise := (dir < 0); 627 628 cn := PointF(an.y,-an.x)*radius; 629 if not anticlockwise then cn := -cn; 630 c := p3 + cn; 631 632 result.center := c; 633 result.radius:= PointF(radius,radius); 634 result.startAngleRadCW := arctan2((p3.y-c.y), (p3.x-c.x)); 635 result.endAngleRadCW := arctan2((p4.y-c.y), (p4.x-c.x)); 636 result.anticlockwise:= anticlockwise; 637 end; 638 639 function SvgArcTo(const p0: TPointF; rx, ry, xAngleRadCW: single; largeArc, 640 anticlockwise: boolean; const p1: TPointF): TArcDef; 641 var 642 p0p,cp: TPointF; 643 cross1,cross2,lambda: single; 644 begin 645 if (rx=0) or (ry=0) or (p0 = p1) then 646 begin 647 result.radius := PointF(0,0); 648 result.xAngleRadCW:= 0; 649 result.anticlockwise := false; 650 result.endAngleRadCW := 0; 651 result.startAngleRadCW:= 0; 652 result.center := p1; 653 exit; 654 end; 655 result.xAngleRadCW := xAngleRadCW; 656 result.anticlockwise := anticlockwise; 657 p0p := AffineMatrixRotationRad(xAngleRadCW)*( (p0-p1)*0.5 ); 658 659 //ensure radius is big enough 660 lambda := sqr(p0p.x/rx) + sqr(p0p.y/ry); 661 if lambda > 1 then 662 begin 663 lambda := sqrt(lambda); 664 rx *= lambda; 665 ry *= lambda; 666 end; 667 result.radius := PointF(rx,ry); 668 669 //compute center 670 cross2 := sqr(rx*p0p.y) + sqr(ry*p0p.x); 671 cross1 := sqr(rx*ry); 672 if cross1 <= cross2 then 673 cp := PointF(0,0) 674 else 675 cp := sqrt((cross1-cross2)/cross2)* 676 PointF(rx*p0p.y/ry, -ry*p0p.x/rx); 677 if largeArc <> anticlockwise then cp := -cp; 678 679 result.center := AffineMatrixRotationRad(-xAngleRadCW)*cp + 680 (p0+p1)*0.5; 681 result.startAngleRadCW := arctan2((p0p.y-cp.y)/ry,(p0p.x-cp.x)/rx); 682 result.endAngleRadCW := arctan2((-p0p.y-cp.y)/ry,(-p0p.x-cp.x)/rx); 683 end; 684 685 function ArcStartPoint(const arc: TArcDef): TPointF; 686 begin 687 result := AffineMatrixRotationRad(-arc.xAngleRadCW)*PointF(cos(arc.startAngleRadCW)*arc.radius.x, 688 sin(arc.startAngleRadCW)*arc.radius.y) + arc.center; 689 end; 690 691 function ArcEndPoint(const arc: TArcDef): TPointF; 692 begin 693 result := AffineMatrixRotationRad(-arc.xAngleRadCW)*PointF(cos(arc.endAngleRadCW)*arc.radius.x, 694 sin(arc.endAngleRadCW)*arc.radius.y) + arc.center; 695 end; 696 697 function IsLargeArc(const arc: TArcDef): boolean; 698 var diff,a1,a2: single; 699 begin 700 a1 := arc.startAngleRadCW - floor(arc.startAngleRadCW/(2*Pi))*(2*Pi); 701 a2 := arc.endAngleRadCW - floor(arc.endAngleRadCW/(2*Pi))*(2*Pi); 702 if not arc.anticlockwise then 703 diff := a2 - a1 704 else 705 diff := a1 - a2; 706 result := (diff < 0) or (diff >= Pi); 707 end; 708 709 { TBGRAPath } 710 711 function TBGRAPath.GetSvgString: string; 712 const RadToDeg = 180/Pi; 713 var savedPos: integer; 714 a: TArcDef; 715 formats: TFormatSettings; 716 lastPos,p1: TPointF; 717 implicitCommand: char; 718 719 function FloatToString(value: single): string; 720 begin 721 result := FloatToStrF(value,ffGeneral,7,0,formats)+' '; 722 end; 723 724 function CoordToString(const pt: TPointF): string; 725 begin 726 lastPos := pt; 727 result := FloatToString(pt.x)+FloatToString(pt.y); 728 end; 729 730 function BoolToString(value: boolean): string; 731 begin 732 if value then 733 result := '1 ' else result := '0 '; 734 end; 735 736 procedure addCommand(command: char; parameters: string); 737 begin 738 if result <> '' then result += ' '; //optional whitespace 739 if command <> implicitCommand then result += command; 740 result += trim(parameters); 741 if command = 'M' then implicitCommand:= 'L' 742 else if command = 'm' then implicitCommand:= 'l' 743 else if command in['z','Z'] then implicitCommand:= #0 744 else implicitCommand := command; 745 end; 746 747 var param: string; 748 749 begin 750 formats := DefaultFormatSettings; 751 formats.DecimalSeparator := '.'; 752 753 result := ''; 754 savedPos:= FDataPos; 755 FDataPos := 0; 756 lastPos := EmptyPointF; 757 implicitCommand := #0; 758 while FDataPos < savedPos do 759 begin 760 case ReadElementType of 761 peMoveTo: addCommand('M',CoordToString(ReadCoord)); 762 peLineTo: addCommand('L',CoordToString(ReadCoord)); 763 peCloseSubPath: addCommand('z',''); 764 peQuadraticBezierTo: 765 begin 766 param := CoordToString(ReadCoord); 767 param += CoordToString(ReadCoord); 768 addCommand('Q',param); 769 end; 770 peCubicBezierTo: 771 begin 772 param := CoordToString(ReadCoord); 773 param += CoordToString(ReadCoord); 774 param += CoordToString(ReadCoord); 775 addCommand('C',param); 776 end; 777 peArc: 778 begin 779 a := ReadArcDef; 780 p1 := ArcStartPoint(a); 781 if isEmptyPointF(lastPos) or (p1 <> lastPos) then 782 addCommand('L',CoordToString(p1)); 783 param := CoordToString(a.radius); 784 param += FloatToString(a.xAngleRadCW*RadToDeg); 785 param += BoolToString(IsLargeArc(a)); 786 param += BoolToString(not a.anticlockwise); 787 param += CoordToString(ArcEndPoint(a)); 788 addCommand('A',param); 789 end; 790 end; 791 end; 792 FDataPos := savedPos; 793 end; 794 795 procedure TBGRAPath.SetSvgString(const AValue: string); 796 begin 797 resetTransform; 798 beginPath; 799 addPath(AValue); 800 end; 801 802 procedure TBGRAPath.addPath(const AValue: string); 803 var p: integer; 804 numberError: boolean; 805 806 function parseFloat: single; 807 var numberStart: integer; 808 errPos: integer; 809 begin 810 while (p <= length(AValue)) and (AValue[p] in[#0..#32,',']) do inc(p); 811 numberStart:= p; 812 if (p <= length(AValue)) and (AValue[p] in['+','-']) then inc(p); 813 while (p <= length(AValue)) and (AValue[p] in['0'..'9','.']) do inc(p); 814 if (p <= length(AValue)) and (AValue[p] in['e','E']) then inc(p); 815 if (p <= length(AValue)) and (AValue[p] in['+','-']) then inc(p); 816 while (p <= length(AValue)) and (AValue[p] in['0'..'9','.']) do inc(p); 817 val(copy(AValue,numberStart,p-numberStart),result,errPos); 818 if errPos <> 0 then numberError := true; 819 end; 820 821 function parseCoord(relative: boolean): TPointF; 822 begin 823 result := PointF(parseFloat,parseFloat); 824 if relative and not isEmptyPointF(FLastCoord) then result += FLastCoord; 825 end; 826 827 var 828 command,implicitCommand: char; 829 relative: boolean; 830 c1,c2,p1: TPointF; 831 a: TArcDef; 832 largeArc: boolean; 833 begin 834 FLastCoord := EmptyPointF; 835 FStartCoord := EmptyPointF; 836 p := 1; 837 implicitCommand:= #0; 838 while p <= length(AValue) do 839 begin 840 command := AValue[p]; 841 if (command in['0'..'9','.','+','-']) and (implicitCommand <> #0) then 842 command := implicitCommand 843 else 844 begin 845 inc(p); 846 end; 847 relative := (command = lowerCase(command)); 848 numberError := false; 849 if upcase(command) in ['L','H','V','C','S','Q','T','A'] then 850 implicitCommand:= command; //by default the command repeats 851 case upcase(command) of 852 'Z': begin 853 closePath; 854 implicitCommand:= #0; 855 end; 856 'M': begin 857 p1 := parseCoord(relative); 858 if not numberError then moveTo(p1); 859 if relative then implicitCommand:= 'l' else 860 implicitCommand:= 'L'; 861 end; 862 'L': begin 863 p1 := parseCoord(relative); 864 if not numberError then lineTo(p1); 865 end; 866 'H': begin 867 if not isEmptyPointF(FLastCoord) then p1 := FLastCoord 868 else p1 := PointF(0,0); 869 if relative then p1.x += parseFloat 870 else p1.x := parseFloat; 871 if not numberError then lineTo(p1); 872 end; 873 'V': begin 874 if not isEmptyPointF(FLastCoord) then p1 := FLastCoord 875 else p1 := PointF(0,0); 876 if relative then p1.y += parseFloat 877 else p1.y := parseFloat; 878 if not numberError then lineTo(p1); 879 end; 880 'C': begin 881 c1 := parseCoord(relative); 882 c2 := parseCoord(relative); 883 p1 := parseCoord(relative); 884 if not numberError then bezierCurveTo(c1,c2,p1); 885 end; 886 'S': begin 887 c2 := parseCoord(relative); 888 p1 := parseCoord(relative); 889 if not numberError then smoothBezierCurveTo(c2,p1); 890 end; 891 'Q': begin 892 c1 := parseCoord(relative); 893 p1 := parseCoord(relative); 894 if not numberError then quadraticCurveTo(c1,p1); 895 end; 896 'T': begin 897 p1 := parseCoord(relative); 898 if not numberError then smoothQuadraticCurveTo(p1); 899 end; 900 'A': begin 901 a.radius := parseCoord(false); 902 a.xAngleRadCW := parseFloat*Pi/180; 903 largeArc := parseFloat<>0; 904 a.anticlockwise:= parseFloat=0; 905 p1 := parseCoord(relative); 906 arcTo(a.radius.x,a.radius.y,a.xAngleRadCW,largeArc,a.anticlockwise,p1.x,p1.y); 907 end; 908 end; 909 end; 910 end; 911 912 procedure TBGRAPath.addPath(source: IBGRAPath); 913 begin 914 source.copyTo(self); 915 end; 916 917 procedure TBGRAPath.NeedSpace(count: integer); 918 begin 919 if FDataPos + count > FDataSize then 920 begin 921 FDataSize := FDataSize*2+8; 922 ReAllocMem(FData, FDataSize); 923 end; 924 end; 925 926 procedure TBGRAPath.StoreCoord(const pt: TPointF); 927 begin 928 NeedSpace(sizeof(single)*2); 929 with FMatrix*pt do 930 begin 931 PSingle(FData+FDataPos)^ := x; 932 PSingle(FData+FDataPos+sizeof(single))^ := y; 933 end; 934 Inc(FDataPos, sizeof(single)*2); 935 FLastCoord := pt; 936 end; 937 938 function TBGRAPath.ReadCoord: TPointF; 939 begin 940 result := PPointF(FData+FDataPos)^; 941 inc(FDataPos,sizeof(TPointF)); 942 end; 943 944 procedure TBGRAPath.StoreElementType(value: TBGRAPathElementType); 945 begin 946 NeedSpace(sizeof(TBGRAPathElementType)); 947 PBGRAPathElementType(FData+FDataPos)^ := value; 948 Inc(FDataPos, sizeof(TBGRAPathElementType)); 949 FLastElementType:= value; 950 end; 951 952 function TBGRAPath.ReadElementType: TBGRAPathElementType; 953 begin 954 result := PBGRAPathElementType(FData+FDataPos)^; 955 inc(FDataPos,sizeof(TBGRAPathElementType)); 956 end; 957 958 function TBGRAPath.ReadArcDef: TArcDef; 959 begin 960 result := PArcDef(FData+FDataPos)^; 961 inc(FDataPos,sizeof(TArcDef)); 962 end; 963 964 procedure TBGRAPath.RewindFloat; 965 begin 966 if FDataPos >= sizeof(single) then dec(FDataPos, sizeof(Single)); 967 end; 968 969 procedure TBGRAPath.Init; 970 begin 971 FData := nil; 972 FDataSize := 0; 973 FDataPos := 0; 974 FLastElementType := peNone; 975 FLastCoord := EmptyPointF; 976 FStartCoord := EmptyPointF; 977 FExpectedControlPoint := EmptyPointF; 978 resetTransform; 979 end; 980 981 constructor TBGRAPath.Create; 982 begin 983 Init; 984 end; 985 986 constructor TBGRAPath.Create(ASvgString: string); 987 begin 988 Init; 989 SvgString:= ASvgString; 990 end; 991 992 destructor TBGRAPath.Destroy; 993 begin 994 if Assigned(FData) then 995 begin 996 FreeMem(FData); 997 FData := nil; 998 end; 999 inherited Destroy; 1000 end; 1001 1002 procedure TBGRAPath.beginPath; 1003 begin 1004 FDataPos := 0; 1005 end; 1006 1007 procedure TBGRAPath.closePath; 1008 begin 1009 if (FLastElementType <> peNone) and (FLastElementType <> peCloseSubPath) then 1010 begin 1011 StoreElementType(peCloseSubPath); 1012 FLastCoord := FStartCoord; 1013 end; 1014 end; 1015 1016 procedure TBGRAPath.translate(x, y: single); 1017 begin 1018 FMatrix *= AffineMatrixTranslation(x,y); 1019 end; 1020 1021 procedure TBGRAPath.resetTransform; 1022 begin 1023 FMatrix := AffineMatrixIdentity; 1024 FAngleRadCW := 0; 1025 FScale:= 1; 1026 end; 1027 1028 procedure TBGRAPath.rotate(angleRadCW: single); 1029 begin 1030 FMatrix *= AffineMatrixRotationRad(-angleRadCW); 1031 FAngleRadCW += angleRadCW; 1032 end; 1033 1034 procedure TBGRAPath.rotateDeg(angleDeg: single); 1035 const degToRad = Pi/180; 1036 begin 1037 rotate(angleDeg*degToRad); 1038 end; 1039 1040 procedure TBGRAPath.rotate(angleRadCW: single; center: TPointF); 1041 begin 1042 translate(center.x,center.y); 1043 rotate(angleRadCW); 1044 translate(-center.x,-center.y); 1045 end; 1046 1047 procedure TBGRAPath.rotateDeg(angleDeg: single; center: TPointF); 1048 begin 1049 translate(center.x,center.y); 1050 rotateDeg(angleDeg); 1051 translate(-center.x,-center.y); 1052 end; 1053 1054 procedure TBGRAPath.scale(factor: single); 1055 begin 1056 FMatrix *= AffineMatrixScale(factor,factor); 1057 FScale *= factor; 1058 end; 1059 1060 procedure TBGRAPath.moveTo(x, y: single); 1061 begin 1062 moveTo(PointF(x,y)); 1063 end; 1064 1065 procedure TBGRAPath.lineTo(x, y: single); 1066 begin 1067 lineTo(PointF(x,y)); 1068 end; 1069 1070 procedure TBGRAPath.moveTo(const pt: TPointF); 1071 begin 1072 if FLastElementType <> peMoveTo then 1073 begin 1074 StoreElementType(peMoveTo); 1075 StoreCoord(pt); 1076 end else 1077 begin 1078 RewindFloat; 1079 RewindFloat; 1080 StoreCoord(pt); 1081 end; 1082 FLastCoord := pt; 1083 FStartCoord := FLastCoord; 1084 end; 1085 1086 procedure TBGRAPath.lineTo(const pt: TPointF); 1087 begin 1088 if not isEmptyPointF(FLastCoord) then 1089 begin 1090 StoreElementType(peLineTo); 1091 StoreCoord(pt); 1092 FLastCoord := pt; 1093 end else 1094 moveTo(pt); 1095 end; 1096 1097 procedure TBGRAPath.polylineTo(const pts: array of TPointF); 1098 var i: integer; 1099 begin 1100 NeedSpace((sizeof(TBGRAPathElementType)+2*sizeof(single))*length(pts)); 1101 for i := 0 to high(pts) do with pts[i] do lineTo(x,y); 1102 end; 1103 1104 procedure TBGRAPath.quadraticCurveTo(cpx, cpy, x, y: single); 1105 begin 1106 quadraticCurveTo(PointF(cpx,cpy),PointF(x,y)); 1107 end; 1108 1109 procedure TBGRAPath.quadraticCurveTo(const cp, pt: TPointF); 1110 begin 1111 if not isEmptyPointF(FLastCoord) then 1112 begin 1113 StoreElementType(peQuadraticBezierTo); 1114 StoreCoord(cp); 1115 StoreCoord(pt); 1116 FLastCoord := pt; 1117 end else 1118 lineTo(pt); 1119 FExpectedControlPoint := pt+(pt-cp); 1120 end; 1121 1122 procedure TBGRAPath.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y: single); 1123 begin 1124 bezierCurveTo(PointF(cp1x,cp1y),PointF(cp2x,cp2y),PointF(x,y)); 1125 end; 1126 1127 procedure TBGRAPath.bezierCurveTo(const cp1, cp2, pt: TPointF); 1128 begin 1129 if isEmptyPointF(FLastCoord) then moveTo(cp1); 1130 StoreElementType(peCubicBezierTo); 1131 StoreCoord(cp1); 1132 StoreCoord(cp2); 1133 StoreCoord(pt); 1134 FLastCoord := pt; 1135 FExpectedControlPoint := pt + (pt-cp2); 1136 end; 1137 1138 procedure TBGRAPath.bezierCurve(const curve: TCubicBezierCurve); 1139 begin 1140 moveTo(curve.p1); 1141 bezierCurveTo(curve.c1,curve.c2,curve.p2); 1142 end; 1143 1144 procedure TBGRAPath.smoothBezierCurveTo(cp2x, cp2y, x, y: single); 1145 begin 1146 smoothBezierCurveTo(PointF(cp2x,cp2y),PointF(x,y)); 1147 end; 1148 1149 procedure TBGRAPath.smoothBezierCurveTo(const cp2, pt: TPointF); 1150 begin 1151 if (FLastElementType = peCubicBezierTo) and not isEmptyPointF(FExpectedControlPoint) then 1152 bezierCurveTo(FExpectedControlPoint,cp2,pt) 1153 else if not isEmptyPointF(FLastCoord) then 1154 bezierCurveTo(FLastCoord,cp2,pt) 1155 else 1156 bezierCurveTo(cp2,cp2,pt); 1157 end; 1158 1159 procedure TBGRAPath.quadraticCurve(const curve: TQuadraticBezierCurve); 1160 begin 1161 moveTo(curve.p1); 1162 quadraticCurveTo(curve.c,curve.p2); 1163 end; 1164 1165 procedure TBGRAPath.smoothQuadraticCurveTo(x, y: single); 1166 begin 1167 smoothQuadraticCurveTo(PointF(x,y)); 1168 end; 1169 1170 procedure TBGRAPath.smoothQuadraticCurveTo(const pt: TPointF); 1171 begin 1172 if (FLastElementType = peQuadraticBezierTo) and not isEmptyPointF(FExpectedControlPoint) then 1173 quadraticCurveTo(FExpectedControlPoint,pt) 1174 else if not isEmptyPointF(FLastCoord) then 1175 quadraticCurveTo(FLastCoord,pt) 1176 else 1177 quadraticCurveTo(pt,pt); 1178 end; 1179 1180 procedure TBGRAPath.rect(x, y, w, h: single); 1181 begin 1182 moveTo(x,y); 1183 lineTo(x+w,y); 1184 lineTo(x+w,y+h); 1185 lineTo(x,y+h); 1186 closePath; 1187 end; 1188 1189 procedure TBGRAPath.roundRect(x, y, w, h, radius: single); 1190 begin 1191 if radius <= 0 then 1192 begin 1193 rect(x,y,w,h); 1194 exit; 1195 end; 1196 if (w <= 0) or (h <= 0) then exit; 1197 if radius*2 > w then radius := w/2; 1198 if radius*2 > h then radius := h/2; 1199 moveTo(x+radius,y); 1200 arcTo(PointF(x+w,y),PointF(x+w,y+h), radius); 1201 arcTo(PointF(x+w,y+h),PointF(x,y+h), radius); 1202 arcTo(PointF(x,y+h),PointF(x,y), radius); 1203 arcTo(PointF(x,y),PointF(x+w,y), radius); 1204 closePath; 1205 end; 1206 1207 procedure TBGRAPath.arc(cx, cy, radius, startAngleRadCW, endAngleRadCW: single; 1208 anticlockwise: boolean); 1209 begin 1210 arc(cx,cy,radius,radius,0,startAngleRadCW,endAngleRadCW,anticlockwise); 1211 end; 1212 1213 procedure TBGRAPath.arc(cx, cy, radius, startAngleRadCW, endAngleRadCW: single); 1214 begin 1215 arc(cx,cy,radius,startAngleRadCW,endAngleRadCW,false); 1216 end; 1217 1218 procedure TBGRAPath.arcDeg(cx, cy, radius, startAngleDeg, endAngleDeg: single; 1219 anticlockwise: boolean); 1220 const degToRad = Pi/180; 1221 begin 1222 arc(cx,cy,radius,(startAngleDeg-90)*degToRad,(endAngleDeg-90)*degToRad,anticlockwise); 1223 end; 1224 1225 procedure TBGRAPath.arcDeg(cx, cy, radius, startAngleDeg, endAngleDeg: single); 1226 const degToRad = Pi/180; 1227 begin 1228 arc(cx,cy,radius,(startAngleDeg-90)*degToRad,(endAngleDeg-90)*degToRad); 1229 end; 1230 1231 procedure TBGRAPath.arcTo(x1, y1, x2, y2, radius: single); 1232 begin 1233 arcTo(PointF(x1,y1), PointF(x2,y2), radius); 1234 end; 1235 1236 procedure TBGRAPath.arcTo(const p1, p2: TPointF; radius: single); 1237 var p0 : TPointF; 1238 begin 1239 if isEmptyPointF(FLastCoord) then 1240 p0 := p1 else p0 := FLastCoord; 1241 arc(Html5ArcTo(p0,p1,p2,radius)); 1242 end; 1243 1244 procedure TBGRAPath.arc(const arcDef: TArcDef); 1245 var transformedArc: TArcDef; 1246 begin 1247 if (arcDef.radius.x = 0) and (arcDef.radius.y = 0) then 1248 lineTo(arcDef.center) 1249 else 1250 begin 1251 if isEmptyPointF(FLastCoord) then 1252 moveTo(ArcStartPoint(arcDef)); 1253 StoreElementType(peArc); 1254 NeedSpace(sizeof(TArcDef)); 1255 transformedArc.anticlockwise := arcDef.anticlockwise; 1256 transformedArc.startAngleRadCW := arcDef.startAngleRadCW; 1257 transformedArc.endAngleRadCW := arcDef.endAngleRadCW; 1258 transformedArc.center := FMatrix*arcDef.center; 1259 transformedArc.radius := arcDef.radius*FScale; 1260 transformedArc.xAngleRadCW := arcDef.xAngleRadCW+FAngleRadCW; 1261 PArcDef(FData+FDataPos)^ := transformedArc; 1262 inc(FDataPos, sizeof(TArcDef)); 1263 FLastCoord := ArcEndPoint(arcDef); 1264 end; 1265 end; 1266 1267 procedure TBGRAPath.arc(cx, cy, rx, ry, xAngleRadCW, startAngleRadCW, 1268 endAngleRadCW: single); 1269 begin 1270 arc(ArcDef(cx,cy,rx,ry,xAngleRadCW,startAngleRadCW,endAngleRadCW,false)); 1271 end; 1272 1273 procedure TBGRAPath.arc(cx, cy, rx, ry, xAngleRadCW, startAngleRadCW, endAngleRadCW: single; 1274 anticlockwise: boolean); 1275 begin 1276 arc(ArcDef(cx,cy,rx,ry,xAngleRadCW,startAngleRadCW,endAngleRadCW,anticlockwise)); 1277 end; 1278 1279 procedure TBGRAPath.arcTo(rx, ry, xAngleRadCW: single; largeArc, 1280 anticlockwise: boolean; x, y: single); 1281 begin 1282 if isEmptyPointF(FLastCoord) then 1283 moveTo(x,y) 1284 else 1285 arc(SvgArcTo(FLastCoord, rx,ry, xAngleRadCW, largeArc, anticlockwise, PointF(x,y))); 1286 end; 1287 1288 procedure TBGRAPath.copyTo(dest: IBGRAPath); 1289 var savedPos: integer; 1290 cp1,cp2,p1: TPointF; 1291 begin 1292 savedPos:= FDataPos; 1293 FDataPos := 0; 1294 while FDataPos < savedPos do 1295 begin 1296 case ReadElementType of 1297 peMoveTo: dest.moveTo(ReadCoord); 1298 peLineTo: dest.lineTo(ReadCoord); 1299 peCloseSubPath: dest.closePath; 1300 peQuadraticBezierTo: 1301 begin 1302 cp1 := ReadCoord; 1303 p1 := ReadCoord; 1304 dest.quadraticCurveTo(cp1,p1); 1305 end; 1306 peCubicBezierTo: 1307 begin 1308 cp1 := ReadCoord; 1309 cp2 := ReadCoord; 1310 p1 := ReadCoord; 1311 dest.bezierCurveTo(cp1,cp2,p1); 1312 end; 1313 peArc: dest.arc(ReadArcDef); 1314 end; 1315 end; 1316 FDataPos := savedPos; 1317 end; 1318 1319 function TBGRAPath.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}; 1320 begin 1321 if GetInterface(iid, obj) then 1322 Result := S_OK 1323 else 1324 Result := longint(E_NOINTERFACE); 1325 end; 1326 1327 { There is no automatic reference counting, but it is compulsory to define these functions } 1328 function TBGRAPath._AddRef: Integer; {$IF (not defined(WINDOWS)) AND (FPC_FULLVERSION>=20501)}cdecl{$ELSE}stdcall{$IFEND}; 1329 begin 1330 result := 0; 1331 end; 1332 1333 function TBGRAPath._Release: Integer; {$IF (not defined(WINDOWS)) AND (FPC_FULLVERSION>=20501)}cdecl{$ELSE}stdcall{$IFEND}; 1334 begin 1335 result := 0; 1336 end; 438 1337 439 1338 end.
Note:
See TracChangeset
for help on using the changeset viewer.