Ignore:
Timestamp:
Apr 9, 2015, 9:58:36 PM (10 years ago)
Author:
chronos
Message:
  • Fixed: Use csOpaque control style also to Image, PaintBox and OpenGLControl.
  • Modified: Change size of test frame with SpinEdits as delayed using timer.
  • Updated: BRGABitmap package to version 8.1.
File:
1 edited

Legend:

Unmodified
Added
Removed
  • GraphicTest/Packages/bgrabitmap/bgrapath.pas

    r452 r472  
    55interface
    66
     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
    737uses
    8   Classes, BGRABitmapTypes;
     38  Classes, BGRABitmapTypes, BGRATransform;
     39
     40type
     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;
    9119
    10120{----------------------- Spline ------------------}
     
    21131function ComputeEllipse(x,y,rx,ry: single; quality: single = 1): ArrayOfTPointF;
    22132function ComputeArc65536(x, y, rx, ry: single; start65536,end65536: word; quality: single = 1): ArrayOfTPointF;
     133function ComputeArcRad(x, y, rx, ry: single; startRadCCW,endRadCCW: single; quality: single = 1): ArrayOfTPointF;
     134function ComputeArc(const arc: TArcDef; quality: single = 1): ArrayOfTPointF;
    23135function ComputeRoundRect(x1,y1,x2,y2,rx,ry: single; quality: single = 1): ArrayOfTPointF; overload;
    24136function ComputeRoundRect(x1,y1,x2,y2,rx,ry: single; options: TRoundRectangleOptions; quality: single = 1): ArrayOfTPointF; overload;
    25137
     138function Html5ArcTo(const p0, p1, p2: TPointF; radius: single): TArcDef;
     139function SvgArcTo(const p0: TPointF; rx, ry, xAngleRadCW: single; largeArc,
     140  anticlockwise: boolean; const p1: TPointF): TArcDef;
     141function ArcStartPoint(const arc: TArcDef): TPointF;
     142function ArcEndPoint(const arc: TArcDef): TPointF;
     143function IsLargeArc(const arc: TArcDef): boolean;
     144
    26145implementation
    27146
    28 uses Math, BGRAResample;
     147uses Math, BGRAResample, SysUtils;
    29148
    30149function SplineVertexToSide(y0, y1, y2, y3: single; t: single): single;
     
    330449end;
    331450
    332 {$PUSH}{$R-}
    333451function ComputeArc65536(x, y, rx, ry: single; start65536,end65536: word; quality: single): ArrayOfTPointF;
    334452var i,nb: integer;
     
    362480  for i := 0 to nb-1 do
    363481  begin
     482    {$PUSH}{$R-}
    364483    pos := start65536+int64(i)*arclen div (int64(nb)-1);
     484    {$POP}
    365485    result[i] := PointF(x+rx*(Cos65536(pos)-32768)/32768,
    366486                        y-ry*(Sin65536(pos)-32768)/32768);
    367487  end;
    368488end;
    369 {$R+}
    370489
    371490function ComputeEllipse(x, y, rx, ry: single; quality: single): ArrayOfTPointF;
    372491begin
    373492  result := ComputeArc65536(x,y,rx,ry,0,0,quality);
     493end;
     494
     495function ComputeArcRad(x, y, rx, ry: single; startRadCCW, endRadCCW: single;
     496  quality: single): ArrayOfTPointF;
     497begin
     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);
     501end;
     502
     503function ComputeArc(const arc: TArcDef; quality: single): ArrayOfTPointF;
     504var startAngle,endAngle: single;
     505    i,n: integer;
     506    temp: TPointF;
     507    m: TAffineMatrix;
     508begin
     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;
    374530end;
    375531
     
    436592end;
    437593
     594function Html5ArcTo(const p0, p1, p2: TPointF; radius: single
     595  ): TArcDef;
     596var p3,p4,an,bn,cn,c: TPointF;
     597    dir, a2, b2, c2, cosx, sinx, d: single;
     598    anticlockwise: boolean;
     599begin
     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;
     637end;
     638
     639function SvgArcTo(const p0: TPointF; rx, ry, xAngleRadCW: single; largeArc,
     640  anticlockwise: boolean; const p1: TPointF): TArcDef;
     641var
     642    p0p,cp: TPointF;
     643    cross1,cross2,lambda: single;
     644begin
     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);
     683end;
     684
     685function ArcStartPoint(const arc: TArcDef): TPointF;
     686begin
     687  result := AffineMatrixRotationRad(-arc.xAngleRadCW)*PointF(cos(arc.startAngleRadCW)*arc.radius.x,
     688                                                       sin(arc.startAngleRadCW)*arc.radius.y) + arc.center;
     689end;
     690
     691function ArcEndPoint(const arc: TArcDef): TPointF;
     692begin
     693  result := AffineMatrixRotationRad(-arc.xAngleRadCW)*PointF(cos(arc.endAngleRadCW)*arc.radius.x,
     694                                                       sin(arc.endAngleRadCW)*arc.radius.y) + arc.center;
     695end;
     696
     697function IsLargeArc(const arc: TArcDef): boolean;
     698var diff,a1,a2: single;
     699begin
     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);
     707end;
     708
     709{ TBGRAPath }
     710
     711function TBGRAPath.GetSvgString: string;
     712const RadToDeg = 180/Pi;
     713var 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
     747var param: string;
     748
     749begin
     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;
     793end;
     794
     795procedure TBGRAPath.SetSvgString(const AValue: string);
     796begin
     797  resetTransform;
     798  beginPath;
     799  addPath(AValue);
     800end;
     801
     802procedure TBGRAPath.addPath(const AValue: string);
     803var 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
     827var
     828  command,implicitCommand: char;
     829  relative: boolean;
     830  c1,c2,p1: TPointF;
     831  a: TArcDef;
     832  largeArc: boolean;
     833begin
     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;
     910end;
     911
     912procedure TBGRAPath.addPath(source: IBGRAPath);
     913begin
     914  source.copyTo(self);
     915end;
     916
     917procedure TBGRAPath.NeedSpace(count: integer);
     918begin
     919  if FDataPos + count > FDataSize then
     920  begin
     921    FDataSize := FDataSize*2+8;
     922    ReAllocMem(FData, FDataSize);
     923  end;
     924end;
     925
     926procedure TBGRAPath.StoreCoord(const pt: TPointF);
     927begin
     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;
     936end;
     937
     938function TBGRAPath.ReadCoord: TPointF;
     939begin
     940  result := PPointF(FData+FDataPos)^;
     941  inc(FDataPos,sizeof(TPointF));
     942end;
     943
     944procedure TBGRAPath.StoreElementType(value: TBGRAPathElementType);
     945begin
     946  NeedSpace(sizeof(TBGRAPathElementType));
     947  PBGRAPathElementType(FData+FDataPos)^ := value;
     948  Inc(FDataPos, sizeof(TBGRAPathElementType));
     949  FLastElementType:= value;
     950end;
     951
     952function TBGRAPath.ReadElementType: TBGRAPathElementType;
     953begin
     954  result := PBGRAPathElementType(FData+FDataPos)^;
     955  inc(FDataPos,sizeof(TBGRAPathElementType));
     956end;
     957
     958function TBGRAPath.ReadArcDef: TArcDef;
     959begin
     960  result := PArcDef(FData+FDataPos)^;
     961  inc(FDataPos,sizeof(TArcDef));
     962end;
     963
     964procedure TBGRAPath.RewindFloat;
     965begin
     966  if FDataPos >= sizeof(single) then dec(FDataPos, sizeof(Single));
     967end;
     968
     969procedure TBGRAPath.Init;
     970begin
     971  FData := nil;
     972  FDataSize := 0;
     973  FDataPos := 0;
     974  FLastElementType := peNone;
     975  FLastCoord := EmptyPointF;
     976  FStartCoord := EmptyPointF;
     977  FExpectedControlPoint := EmptyPointF;
     978  resetTransform;
     979end;
     980
     981constructor TBGRAPath.Create;
     982begin
     983  Init;
     984end;
     985
     986constructor TBGRAPath.Create(ASvgString: string);
     987begin
     988  Init;
     989  SvgString:= ASvgString;
     990end;
     991
     992destructor TBGRAPath.Destroy;
     993begin
     994  if Assigned(FData) then
     995  begin
     996    FreeMem(FData);
     997    FData := nil;
     998  end;
     999  inherited Destroy;
     1000end;
     1001
     1002procedure TBGRAPath.beginPath;
     1003begin
     1004  FDataPos := 0;
     1005end;
     1006
     1007procedure TBGRAPath.closePath;
     1008begin
     1009  if (FLastElementType <> peNone) and (FLastElementType <> peCloseSubPath) then
     1010  begin
     1011    StoreElementType(peCloseSubPath);
     1012    FLastCoord := FStartCoord;
     1013  end;
     1014end;
     1015
     1016procedure TBGRAPath.translate(x, y: single);
     1017begin
     1018  FMatrix *= AffineMatrixTranslation(x,y);
     1019end;
     1020
     1021procedure TBGRAPath.resetTransform;
     1022begin
     1023  FMatrix := AffineMatrixIdentity;
     1024  FAngleRadCW := 0;
     1025  FScale:= 1;
     1026end;
     1027
     1028procedure TBGRAPath.rotate(angleRadCW: single);
     1029begin
     1030  FMatrix *= AffineMatrixRotationRad(-angleRadCW);
     1031  FAngleRadCW += angleRadCW;
     1032end;
     1033
     1034procedure TBGRAPath.rotateDeg(angleDeg: single);
     1035const degToRad = Pi/180;
     1036begin
     1037  rotate(angleDeg*degToRad);
     1038end;
     1039
     1040procedure TBGRAPath.rotate(angleRadCW: single; center: TPointF);
     1041begin
     1042  translate(center.x,center.y);
     1043  rotate(angleRadCW);
     1044  translate(-center.x,-center.y);
     1045end;
     1046
     1047procedure TBGRAPath.rotateDeg(angleDeg: single; center: TPointF);
     1048begin
     1049  translate(center.x,center.y);
     1050  rotateDeg(angleDeg);
     1051  translate(-center.x,-center.y);
     1052end;
     1053
     1054procedure TBGRAPath.scale(factor: single);
     1055begin
     1056  FMatrix *= AffineMatrixScale(factor,factor);
     1057  FScale *= factor;
     1058end;
     1059
     1060procedure TBGRAPath.moveTo(x, y: single);
     1061begin
     1062  moveTo(PointF(x,y));
     1063end;
     1064
     1065procedure TBGRAPath.lineTo(x, y: single);
     1066begin
     1067  lineTo(PointF(x,y));
     1068end;
     1069
     1070procedure TBGRAPath.moveTo(const pt: TPointF);
     1071begin
     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;
     1084end;
     1085
     1086procedure TBGRAPath.lineTo(const pt: TPointF);
     1087begin
     1088  if not isEmptyPointF(FLastCoord) then
     1089  begin
     1090    StoreElementType(peLineTo);
     1091    StoreCoord(pt);
     1092    FLastCoord := pt;
     1093  end else
     1094    moveTo(pt);
     1095end;
     1096
     1097procedure TBGRAPath.polylineTo(const pts: array of TPointF);
     1098var i: integer;
     1099begin
     1100  NeedSpace((sizeof(TBGRAPathElementType)+2*sizeof(single))*length(pts));
     1101  for i := 0 to high(pts) do with pts[i] do lineTo(x,y);
     1102end;
     1103
     1104procedure TBGRAPath.quadraticCurveTo(cpx, cpy, x, y: single);
     1105begin
     1106  quadraticCurveTo(PointF(cpx,cpy),PointF(x,y));
     1107end;
     1108
     1109procedure TBGRAPath.quadraticCurveTo(const cp, pt: TPointF);
     1110begin
     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);
     1120end;
     1121
     1122procedure TBGRAPath.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y: single);
     1123begin
     1124  bezierCurveTo(PointF(cp1x,cp1y),PointF(cp2x,cp2y),PointF(x,y));
     1125end;
     1126
     1127procedure TBGRAPath.bezierCurveTo(const cp1, cp2, pt: TPointF);
     1128begin
     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);
     1136end;
     1137
     1138procedure TBGRAPath.bezierCurve(const curve: TCubicBezierCurve);
     1139begin
     1140  moveTo(curve.p1);
     1141  bezierCurveTo(curve.c1,curve.c2,curve.p2);
     1142end;
     1143
     1144procedure TBGRAPath.smoothBezierCurveTo(cp2x, cp2y, x, y: single);
     1145begin
     1146  smoothBezierCurveTo(PointF(cp2x,cp2y),PointF(x,y));
     1147end;
     1148
     1149procedure TBGRAPath.smoothBezierCurveTo(const cp2, pt: TPointF);
     1150begin
     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);
     1157end;
     1158
     1159procedure TBGRAPath.quadraticCurve(const curve: TQuadraticBezierCurve);
     1160begin
     1161  moveTo(curve.p1);
     1162  quadraticCurveTo(curve.c,curve.p2);
     1163end;
     1164
     1165procedure TBGRAPath.smoothQuadraticCurveTo(x, y: single);
     1166begin
     1167  smoothQuadraticCurveTo(PointF(x,y));
     1168end;
     1169
     1170procedure TBGRAPath.smoothQuadraticCurveTo(const pt: TPointF);
     1171begin
     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);
     1178end;
     1179
     1180procedure TBGRAPath.rect(x, y, w, h: single);
     1181begin
     1182  moveTo(x,y);
     1183  lineTo(x+w,y);
     1184  lineTo(x+w,y+h);
     1185  lineTo(x,y+h);
     1186  closePath;
     1187end;
     1188
     1189procedure TBGRAPath.roundRect(x, y, w, h, radius: single);
     1190begin
     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;
     1205end;
     1206
     1207procedure TBGRAPath.arc(cx, cy, radius, startAngleRadCW, endAngleRadCW: single;
     1208  anticlockwise: boolean);
     1209begin
     1210  arc(cx,cy,radius,radius,0,startAngleRadCW,endAngleRadCW,anticlockwise);
     1211end;
     1212
     1213procedure TBGRAPath.arc(cx, cy, radius, startAngleRadCW, endAngleRadCW: single);
     1214begin
     1215  arc(cx,cy,radius,startAngleRadCW,endAngleRadCW,false);
     1216end;
     1217
     1218procedure TBGRAPath.arcDeg(cx, cy, radius, startAngleDeg, endAngleDeg: single;
     1219  anticlockwise: boolean);
     1220const degToRad = Pi/180;
     1221begin
     1222  arc(cx,cy,radius,(startAngleDeg-90)*degToRad,(endAngleDeg-90)*degToRad,anticlockwise);
     1223end;
     1224
     1225procedure TBGRAPath.arcDeg(cx, cy, radius, startAngleDeg, endAngleDeg: single);
     1226const degToRad = Pi/180;
     1227begin
     1228  arc(cx,cy,radius,(startAngleDeg-90)*degToRad,(endAngleDeg-90)*degToRad);
     1229end;
     1230
     1231procedure TBGRAPath.arcTo(x1, y1, x2, y2, radius: single);
     1232begin
     1233  arcTo(PointF(x1,y1), PointF(x2,y2), radius);
     1234end;
     1235
     1236procedure TBGRAPath.arcTo(const p1, p2: TPointF; radius: single);
     1237var p0 : TPointF;
     1238begin
     1239  if isEmptyPointF(FLastCoord) then
     1240    p0 := p1 else p0 := FLastCoord;
     1241  arc(Html5ArcTo(p0,p1,p2,radius));
     1242end;
     1243
     1244procedure TBGRAPath.arc(const arcDef: TArcDef);
     1245var transformedArc: TArcDef;
     1246begin
     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;
     1265end;
     1266
     1267procedure TBGRAPath.arc(cx, cy, rx, ry, xAngleRadCW, startAngleRadCW,
     1268  endAngleRadCW: single);
     1269begin
     1270  arc(ArcDef(cx,cy,rx,ry,xAngleRadCW,startAngleRadCW,endAngleRadCW,false));
     1271end;
     1272
     1273procedure TBGRAPath.arc(cx, cy, rx, ry, xAngleRadCW, startAngleRadCW, endAngleRadCW: single;
     1274  anticlockwise: boolean);
     1275begin
     1276  arc(ArcDef(cx,cy,rx,ry,xAngleRadCW,startAngleRadCW,endAngleRadCW,anticlockwise));
     1277end;
     1278
     1279procedure TBGRAPath.arcTo(rx, ry, xAngleRadCW: single; largeArc,
     1280  anticlockwise: boolean; x, y: single);
     1281begin
     1282  if isEmptyPointF(FLastCoord) then
     1283    moveTo(x,y)
     1284  else
     1285    arc(SvgArcTo(FLastCoord, rx,ry, xAngleRadCW, largeArc, anticlockwise, PointF(x,y)));
     1286end;
     1287
     1288procedure TBGRAPath.copyTo(dest: IBGRAPath);
     1289var savedPos: integer;
     1290    cp1,cp2,p1: TPointF;
     1291begin
     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;
     1317end;
     1318
     1319function 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};
     1320begin
     1321  if GetInterface(iid, obj) then
     1322    Result := S_OK
     1323  else
     1324    Result := longint(E_NOINTERFACE);
     1325end;
     1326
     1327{ There is no automatic reference counting, but it is compulsory to define these functions }
     1328function TBGRAPath._AddRef: Integer; {$IF (not defined(WINDOWS)) AND (FPC_FULLVERSION>=20501)}cdecl{$ELSE}stdcall{$IFEND};
     1329begin
     1330  result := 0;
     1331end;
     1332
     1333function TBGRAPath._Release: Integer; {$IF (not defined(WINDOWS)) AND (FPC_FULLVERSION>=20501)}cdecl{$ELSE}stdcall{$IFEND};
     1334begin
     1335  result := 0;
     1336end;
    4381337
    4391338end.
Note: See TracChangeset for help on using the changeset viewer.