2
0
Эх сурвалжийг харах

add vector polyline/curve, miter limit 2, fix arrow end choice

Johann 6 жил өмнө
parent
commit
f162caf88e

+ 0 - 28
lazpaint/lazpaintmainform.lfm

@@ -688,20 +688,6 @@ object FMain: TFMain
       Width = 45
       DropDownCount = 12
       ItemHeight = 17
-      Items.Strings = (
-        'None'
-        'Tail'
-        'Tip'
-        'Normal'
-        'Cut'
-        'Flipped'
-        'FlippedCut'
-        'Triangle'
-        'TriangleBack1'
-        'TriangleBack2'
-        'HollowTriangle'
-        'HollowTriangleBack1'
-      )
       Style = csOwnerDrawFixed
       TabOrder = 1
     end
@@ -712,20 +698,6 @@ object FMain: TFMain
       Width = 45
       DropDownCount = 12
       ItemHeight = 17
-      Items.Strings = (
-        'None'
-        'Tail'
-        'Tip'
-        'Normal'
-        'Cut'
-        'Flipped'
-        'FlippedCut'
-        'Triangle'
-        'TriangleBack1'
-        'TriangleBack2'
-        'HollowTriangle'
-        'HollowTriangleBack1'
-      )
       Style = csOwnerDrawFixed
       TabOrder = 2
     end

+ 1 - 1
lazpaint/lazpaintmainform.pas

@@ -820,7 +820,7 @@ implementation
 uses LCLIntf, BGRAUTF8, ugraph, math, umac, uclipboard, ucursors,
    ufilters, ULoadImage, ULoading, UFileExtensions, UBrushType,
    ugeometricbrush, UPreviewDialog, UQuestion, BGRALayerOriginal,
-   BGRATransform;
+   BGRATransform, LCVectorPolyShapes;
 
 const PenWidthFactor = 10;
 

+ 46 - 17
lazpaint/maintoolbar.inc

@@ -282,6 +282,7 @@ end;
 procedure TFMain.InitToolbarElements;
 var
   i: Integer;
+  ak: TArrowKind;
 begin
   Panel_Embedded.Visible := LazPaintInstance.Embedded;
   Panel_File.Visible := not LazPaintInstance.Embedded;
@@ -292,6 +293,13 @@ begin
   Shape_BackColor.Brush.Color := BGRAToColor(ToolManager.ToolBackColor);
   SpinEdit_BackOpacity.Value := ToolManager.ToolBackColor.alpha;
   SpinEdit_PenWidth.Value := Round(ToolManager.ToolPenWidth*PenWidthFactor);
+  for ak := low(TArrowKind) to high(TArrowKind) do
+  begin
+    ComboBox_ArrowStart.Items.Add(ArrowKindToStr[ak]);
+    ComboBox_ArrowEnd.Items.Add(ArrowKindToStr[ak]);
+  end;
+  ComboBox_ArrowStart.ItemIndex := 0;
+  ComboBox_ArrowEnd.ItemIndex := 0;
   SpinEdit_ArrowSizeX.Value := round(ToolManager.ToolArrowSize.x*PenWidthFactor);
   SpinEdit_ArrowSizeY.Value := round(ToolManager.ToolArrowSize.y*PenWidthFactor);
   Tool_DrawShapeBorder.Down := ToolManager.ToolOptionDrawShape;
@@ -305,10 +313,7 @@ begin
   Tool_GridMoveWithoutDeformation.Down := ToolManager.ToolDeformationGridMoveWithoutDeformation;
   SpinEdit_GridNbX.Value := ToolManager.ToolDeformationGridNbX-1;
   SpinEdit_GridNbY.Value := ToolManager.ToolDeformationGridNbY-1;
-  if ToolManager.ToolSplineEasyBezier then
-    Combo_SplineStyle.ItemIndex := Combo_SplineStyle.Items.IndexOf('Easy Bézier')
-  else
-    Combo_SplineStyle.ItemIndex:= ord(ToolManager.ToolSplineStyle);
+  Combo_SplineStyle.ItemIndex:= ord(ToolManager.ToolSplineStyle);
   UpdateCurveMode;
   Tool_TextOutline.Down := ToolManager.ToolTextOutline;
   SpinEdit_TextOutlineWidth.Value := round(ToolManager.ToolTextOutlineWidth*PenWidthFactor);
@@ -821,20 +826,30 @@ end;
 
 procedure TFMain.ComboBox_ArrowStartChange(Sender: TObject);
 begin
-  if initialized then UpdateEditPicture;
+  if initialized then
+  begin
+    ToolManager.ToolArrowStart:= ComboBox_ArrowStart.Text;
+    UpdateEditPicture;
+  end;
 end;
 
 procedure TFMain.ComboBox_ArrowStartDrawItem(Control: TWinControl;
   Index: Integer; ARect: TRect; State: TOwnerDrawState);
+var
+  kind: String;
 begin
   if Index = -1 then exit;
-  ToolManager.ToolArrowStart := ComboBox_ArrowStart.Items[Index];
-  DrawArrow(ComboBox_ArrowStart.Canvas,ARect,True,ToolManager.ToolArrowStart,ToolManager.ToolLineCap,State);
+  kind := ComboBox_ArrowStart.Items[Index];
+  DrawArrow(ComboBox_ArrowStart.Canvas,ARect,True,kind,ToolManager.ToolLineCap,State);
 end;
 
 procedure TFMain.ComboBox_ArrowEndChange(Sender: TObject);
 begin
-  if initialized then UpdateEditPicture;
+  if initialized then
+  begin
+    ToolManager.ToolArrowEnd:= ComboBox_ArrowEnd.Text;
+    UpdateEditPicture;
+  end;
 end;
 
 procedure TFMain.BrushLoadFromFileExecute(Sender: TObject);
@@ -860,10 +875,12 @@ end;
 
 procedure TFMain.ComboBox_ArrowEndDrawItem(Control: TWinControl;
   Index: Integer; ARect: TRect; State: TOwnerDrawState);
+var
+  kind: String;
 begin
   if Index = -1 then exit;
-  ToolManager.ToolArrowEnd := ComboBox_ArrowEnd.Items[Index];
-  DrawArrow(ComboBox_ArrowEnd.Canvas,ARect,False,ToolManager.ToolArrowEnd,ToolManager.ToolLineCap,State);
+  kind := ComboBox_ArrowEnd.Items[Index];
+  DrawArrow(ComboBox_ArrowEnd.Canvas,ARect,False,kind,ToolManager.ToolLineCap,State);
 end;
 
 procedure TFMain.SpinEdit_ArrowSizeChange(Sender: TObject);
@@ -974,9 +991,9 @@ end;
 procedure TFMain.SetCurveMode(AMode: TToolSplineMode);
 begin
   if (ToolManager.CurrentTool <> nil) and
-     (ToolManager.CurrentTool is TToolGenericSpline) then
+     (ToolManager.CurrentTool is TToolSpline) then
   begin
-    (ToolManager.CurrentTool as TToolGenericSpline).CurrentMode := AMode;
+    (ToolManager.CurrentTool as TToolSpline).CurrentMode := AMode;
     UpdateCurveMode;
   end;
 end;
@@ -984,11 +1001,11 @@ end;
 procedure TFMain.UpdateCurveMode;
 var
   cm: TToolSplineMode;
-  splineTool: TToolGenericSpline;
+  splineTool: TToolSpline;
 begin
-  if (ToolManager.CurrentTool <> nil) and (ToolManager.CurrentTool is TToolGenericSpline) then
+  if (ToolManager.CurrentTool <> nil) and (ToolManager.CurrentTool is TToolSpline) then
   begin
-    splineTool := ToolManager.CurrentTool as TToolGenericSpline;
+    splineTool := ToolManager.CurrentTool as TToolSpline;
     Tool_CurveMovePoint.Enabled := not splineTool.IsHandDrawing and not splineTool.IsIdle;
     cm := splineTool.CurrentMode;
     if Tool_CurveMovePoint.Down <> (cm = tsmMovePoint) then
@@ -1008,7 +1025,6 @@ begin
   if initialized then
   begin
     v := Combo_SplineStyle.Text;
-    ToolManager.ToolSplineEasyBezier := false;
     if v = 'Inside' then ToolManager.ToolSplineStyle := ssInside else
     if v = 'Inside + ends' then ToolManager.ToolSplineStyle := ssInsideWithEnds else
     if v = 'Crossing' then ToolManager.ToolSplineStyle := ssCrossing else
@@ -1016,7 +1032,20 @@ begin
     if v = 'Outside' then ToolManager.ToolSplineStyle := ssOutside else
     if v = 'Round outside' then ToolManager.ToolSplineStyle:= ssRoundOutside else
     if v = 'Vertex to side' then ToolManager.ToolSplineStyle:= ssVertexToSide else
-    if v = 'Easy Bézier' then ToolManager.ToolSplineEasyBezier := true;
+    if v = 'Easy Bézier' then ToolManager.ToolSplineStyle := ssEasyBezier;
+    if ToolManager.ToolSplineStyle<>ssEasyBezier then
+    begin
+      Tool_CurveMovePoint.Down := true;
+      SetCurveMode(tsmMovePoint);
+      Tool_CurveModeAuto.Enabled := false;
+      Tool_CurveModeAngle.Enabled := false;
+      Tool_CurveModeCurve.Enabled := false;
+    end else
+    begin
+      Tool_CurveModeAuto.Enabled := true;
+      Tool_CurveModeAngle.Enabled := true;
+      Tool_CurveModeCurve.Enabled := true;
+    end;
     UpdateEditPicture(True);
     UpdateCurveMode;
   end;

+ 9 - 29
lazpaint/ugraph.pas

@@ -51,15 +51,14 @@ function ClearTypeFilter(source: TBGRACustomBitmap): TBGRACustomBitmap;
 function ClearTypeInverseFilter(source: TBGRACustomBitmap): TBGRACustomBitmap;
 
 function DoResample(source :TBGRABitmap; newWidth, newHeight: integer; StretchMode: TResampleMode): TBGRABitmap;
-procedure DrawArrow(ACanvas: TCanvas; ARect: TRect; AStart: boolean; AKind: string; ALineCap: TPenEndCap; State: TOwnerDrawState);
-procedure ApplyArrowStyle(AStart: boolean; AKind: string; ABmp: TBGRABitmap; ASize: TPointF);
+procedure DrawArrow(ACanvas: TCanvas; ARect: TRect; AStart: boolean; AKindStr: string; ALineCap: TPenEndCap; State: TOwnerDrawState);
 procedure BCAssignSystemStyle(AButton: TBCButton);
 
 implementation
 
 uses GraphType, math, Types, BGRAUTF8, FileUtil, dialogs, BGRAAnimatedGif,
   BGRAGradients, BGRATextFX, uresourcestrings, LCScaleDPI, BCTypes,
-  BGRAThumbnail;
+  BGRAThumbnail, LCVectorPolyShapes;
 
 procedure BCAssignSystemState(AState: TBCButtonState; AFontColor, ATopColor, AMiddleTopColor, AMiddleBottomColor, ABottomColor, ABorderColor: TColor);
 begin
@@ -733,11 +732,13 @@ begin
   result := source.Resample(newWidth,newHeight,StretchMode) as TBGRABitmap;
 end;
 
-procedure DrawArrow(ACanvas: TCanvas; ARect: TRect; AStart: boolean; AKind: string; ALineCap: TPenEndCap; State: TOwnerDrawState);
+procedure DrawArrow(ACanvas: TCanvas; ARect: TRect; AStart: boolean; AKindStr: string; ALineCap: TPenEndCap; State: TOwnerDrawState);
 var bmp : TBGRABitmap;
   c,c2: TBGRAPixel;
   x1,x2,xm1,xm2,y,w,temp: single;
+  kind: TArrowKind;
 begin
+  kind := StrToArrowKind(AKindStr);
   if odSelected in State then
   begin
     c2 := ColorToBGRA(ColorToRGB(clHighlight));
@@ -748,7 +749,7 @@ begin
     c := ColorToBGRA(ColorToRGB(clWindowText));
   end;
   with Size(ARect) do bmp:= TBGRABitmap.Create(cx,cy,c2);
-  ApplyArrowStyle(AStart,AKind,bmp,PointF(1.5,1.5));
+  ApplyArrowStyle(bmp.Arrow,AStart,kind,PointF(1.5,1.5));
   bmp.LineCap := ALineCap;
   w := bmp.Height/5;
   if w > 0 then
@@ -757,8 +758,8 @@ begin
     x2 := 0;
     xm1 := 0;
     xm2 := w*2.5;
-    if (AKind = 'Normal') or (AKind = 'Cut') then x1 -= w*0.7 else
-    if (AKind = 'Flipped') or (AKind = 'FlippedCut') then x1 += w*0.7;
+    if kind in[akNone,akCut] then x1 -= w*0.7 else
+    if kind in[akFlipped,akFlippedCut] then x1 += w*0.7;
     if not AStart then
     begin
       temp := x1;
@@ -772,7 +773,7 @@ begin
     x1 -= 0.5;
     x2 += bmp.Width-0.5;
     y := (bmp.Height-1)/2;
-    if (AKind='Tail') or (AKind='None') or (AKind = 'Tip') then w *= 2;
+    if kind in[akTail,akNone,akTip] then w *= 2;
     bmp.DrawLineAntialias(x1,y,x2,y,c,w);
     if bmp.Width > bmp.Height*2 then
       bmp.GradientFill(0,0,bmp.width,bmp.height,c2,BGRAPixelTransparent,gtLinear,PointF(xm1,0),PointF(xm2,0),dmDrawWithTransparency);
@@ -781,27 +782,6 @@ begin
   bmp.Free;
 end;
 
-procedure ApplyArrowStyle(AStart: boolean; AKind: string; ABmp: TBGRABitmap; ASize: TPointF);
-var backOfs: single;
-begin
-  backOfs := 0;
-  if (ASize.x = 0) or (ASize.y = 0) then AKind := 'None';
-  if (length(AKind)>0) and (AKind[length(AKind)] in['1'..'9']) then backOfs := (ord(AKind[length(AKind)])-ord('0'))*0.25;
-  case AKind of
-  'Tail': if AStart then ABmp.ArrowStartAsTail else ABmp.ArrowEndAsTail;
-  'Tip': if AStart then ABmp.ArrowStartAsTriangle else ABmp.ArrowEndAsTriangle;
-  'Normal','Cut','Flipped','FlippedCut': if AStart then ABmp.ArrowStartAsClassic((AKind='Flipped') or (AKind='FlippedCut'),(AKind='Cut') or (AKind='FlippedCut'))
-    else ABmp.ArrowEndAsClassic((AKind='Flipped') or (AKind='FlippedCut'),(AKind='Cut') or (AKind='FlippedCut'));
-  'Triangle','TriangleBack1','TriangleBack2': if AStart then ABmp.ArrowStartAsTriangle(backOfs) else ABmp.ArrowEndAsTriangle(backOfs);
-  'HollowTriangle','HollowTriangleBack1','HollowTriangleBack2': if AStart then ABmp.ArrowStartAsTriangle(backOfs,False,True) else ABmp.ArrowEndAsTriangle(backOfs,False,True);
-  else if AStart then ABmp.ArrowStartAsNone else ABmp.ArrowEndAsNone;
-  end;
-  if (AKind = 'Tip') and not ((ASize.x = 0) or (ASize.y = 0)) then
-    ASize := ASize*(0.5/ASize.y);
-  if AStart then ABmp.ArrowStartSize := ASize
-  else ABmp.ArrowEndSize := ASize;
-end;
-
 function CreateMarbleTexture(tx,ty: integer): TBGRABitmap;
 var
   colorOscillation: integer;

+ 3 - 5
lazpaint/utool.pas

@@ -187,7 +187,6 @@ type
     ToolArrowSize: TPointF;
     ToolJoinStyle: TPenJoinStyle;
     ToolSplineStyle: TSplineStyle;
-    ToolSplineEasyBezier: boolean;
     ToolPenStyle: TPenStyle;
     ToolPerspectiveRepeat,ToolPerspectiveTwoPlanes: boolean;
     ToolDeformationGridMoveWithoutDeformation: boolean;
@@ -917,13 +916,12 @@ begin
   ToolFloodFillOptionProgressive := true;
   ToolLineCap := pecRound;
   ToolJoinStyle := pjsRound;
-  ToolArrowStart := 'None';
-  ToolArrowEnd := 'None';
+  ToolArrowStart := 'none';
+  ToolArrowEnd := 'none';
   ToolArrowSize := PointF(2,2);
   ToolPenStyle := psSolid;
   ToolEraserAlpha := 255;
-  ToolSplineStyle := ssRoundOutside;
-  ToolSplineEasyBezier := true;
+  ToolSplineStyle := ssEasyBezier;
   ToolTextOutline := False;
   ToolTextShadow := false;
   ToolTextFont := TFont.Create;

+ 22 - 1
lazpaint/utoolbasic.pas

@@ -73,6 +73,9 @@ type
   { TVectorialTool }
 
   TVectorialTool = class(TGenericTool)
+  private
+    function GetIsHandDrawing: boolean;
+    function GetIsIdle: boolean;
   protected
     FShape: TVectorShape;
     FSwapColor: boolean;
@@ -116,6 +119,8 @@ type
     function ToolKeyPress(var key: TUTF8Char): TRect; override;
     function ToolKeyUp(var key: Word): TRect; override;
     function Render(VirtualScreen: TBGRABitmap; {%H-}VirtualScreenWidth, {%H-}VirtualScreenHeight: integer; BitmapToVirtualScreen: TBitmapToVirtualScreenFunction):TRect; override;
+    property IsIdle: boolean read GetIsIdle;
+    property IsHandDrawing: boolean read GetIsHandDrawing;
     destructor Destroy; override;
   end;
 
@@ -229,6 +234,7 @@ begin
     if not AlwaysRasterizeShape and Manager.Image.SelectionMaskEmpty then
     begin
       CancelAction;
+      if FShape.Usermode = vsuCreate then FShape.Usermode:= vsuEdit;
       rF := FShape.GetRenderBounds(rect(0,0,Manager.Image.Width,Manager.Image.Height), VectorTransform);
       if rF.IntersectsWith(rectF(0,0,Manager.Image.Width,Manager.Image.Height)) then
       begin
@@ -276,6 +282,16 @@ begin
   result := OnlyRenderChange;
 end;
 
+function TVectorialTool.GetIsHandDrawing: boolean;
+begin
+  result := Assigned(FShape) and (FQuickDefine or (FShape.Usermode = vsuCreate));
+end;
+
+function TVectorialTool.GetIsIdle: boolean;
+begin
+  result := FShape = nil;
+end;
+
 function TVectorialTool.AlwaysRasterizeShape: boolean;
 begin
   result := false;
@@ -505,7 +521,12 @@ end;
 
 function TVectorialTool.DoToolUpdate(toolDest: TBGRABitmap): TRect;
 begin
-  if Assigned(FShape) then AssignShapeStyle(FLastShapeTransform);
+  if Assigned(FShape) then
+  begin
+    FShape.BeginUpdate;
+    AssignShapeStyle(FLastShapeTransform);
+    FShape.EndUpdate;
+  end;
   result := EmptyRect;
 end;
 

+ 146 - 8
lazpaint/utoolpolygon.pas

@@ -5,13 +5,44 @@ unit UToolPolygon;
 interface
 
 uses
-  Classes, SysUtils, utool, BGRABitmap, BGRABitmapTypes, ULayerAction;
+  Classes, SysUtils, utool, utoolbasic, BGRABitmap, BGRABitmapTypes,
+  LCVectorOriginal, LCLType;
 
 const
   EasyBezierMinimumDotProduct = 0.5;
 
 type
+  TToolSplineMode = (tsmMovePoint, tsmCurveModeAuto, tsmCurveModeAngle, tsmCurveModeSpline);
+
+  { TToolPolygon }
+
+  TToolPolygon = class(TVectorialTool)
+  protected
+    function CreateShape: TVectorShape; override;
+    procedure AssignShapeStyle(AMatrix: TAffineMatrix); override;
+    procedure UpdateUserMode; virtual;
+  end;
+
+  { TToolSpline }
+
+  TToolSpline = class(TToolPolygon)
+  private
+    FCurrentMode: TToolSplineMode;
+    FNextCurveMode: TEasyBezierCurveMode;
+    FCurveModeHintShown: Boolean;
+    function GetCurrentMode: TToolSplineMode;
+    procedure SetCurrentMode(AValue: TToolSplineMode);
+    procedure UpdateUserMode; override;
+  protected
+    function CreateShape: TVectorShape; override;
+    procedure AssignShapeStyle(AMatrix: TAffineMatrix); override;
+  public
+    constructor Create(AManager: TToolManager); override;
+    function ToolKeyPress(var key: TUTF8Char): TRect; override;
+    property CurrentMode: TToolSplineMode read GetCurrentMode write SetCurrentMode;
+  end;
 
+{
   { TToolGenericPolygon }
 
   TToolGenericPolygon = class(TGenericTool)
@@ -80,8 +111,6 @@ type
     function FinalPolygonView(toolDest: TBGRABitmap): TRect; override;
   end;
 
-  TToolSplineMode = (tsmMovePoint, tsmCurveModeAuto, tsmCurveModeAngle, tsmCurveModeSpline);
-
   { TToolGenericSpline }
 
   TToolGenericSpline = class(TToolGenericPolygon)
@@ -125,14 +154,123 @@ type
     function GetIsSelectingTool: boolean; override;
     function HandDrawingPolygonView(toolDest: TBGRABitmap): TRect; override;
     function FinalPolygonView({%H-}toolDest: TBGRABitmap):TRect; override;
-  end;
+  end;}
 
 implementation
 
-uses Types, Graphics, LCLType, ugraph, Dialogs, Controls, LazPaintType,
-  BGRAFillInfo, BGRAPath;
+uses LazPaintType, LCVectorPolyShapes;
+
+{ TToolSpline }
+
+function TToolSpline.GetCurrentMode: TToolSplineMode;
+var
+  c: TCurveShape;
+begin
+  if Assigned(FShape) then
+  begin
+    c := TCurveShape(FShape);
+    case c.Usermode of
+    vsuEdit: FCurrentMode := tsmMovePoint;
+    vsuCreate: if c.PointCount > 1 then
+               begin
+                 case c.CurveMode[c.PointCount-2] of
+                   cmAuto: FCurrentMode := tsmCurveModeAuto;
+                   cmAngle: FCurrentMode := tsmCurveModeAngle;
+                   cmCurve: FCurrentMode := tsmCurveModeSpline;
+                 end;
+               end else
+                 result := tsmCurveModeAuto;
+    vsuCurveSetAuto: FCurrentMode := tsmCurveModeAuto;
+    vsuCurveSetAngle: FCurrentMode := tsmCurveModeAngle;
+    vsuCurveSetCurve: FCurrentMode := tsmCurveModeSpline;
+    end;
+  end;
+  result := FCurrentMode;
+end;
+
+procedure TToolSpline.SetCurrentMode(AValue: TToolSplineMode);
+begin
+  if FCurrentMode = AValue then exit;
+  FCurrentMode := AValue;
+  UpdateUserMode;
+end;
+
+procedure TToolSpline.UpdateUserMode;
+var
+  c: TCurveShape;
+begin
+  if FShape = nil then exit;
+  if FQuickDefine then
+  begin
+    FShape.Usermode := vsuCreate;
+    exit;
+  end;
+  c := TCurveShape(FShape);
+  case FCurrentMode of
+  tsmMovePoint: if not (c.Usermode in [vsuEdit,vsuCreate]) then c.Usermode := vsuEdit;
+  tsmCurveModeAuto: if c.Usermode <> vsuCreate then c.Usermode := vsuCurveSetAuto else
+                    if c.PointCount > 1 then c.CurveMode[c.PointCount-2] := cmAuto;
+  tsmCurveModeAngle: if c.Usermode <> vsuCreate then c.Usermode := vsuCurveSetAngle else
+                     if c.PointCount > 1 then c.CurveMode[c.PointCount-2] := cmAngle;
+  tsmCurveModeSpline: if c.Usermode <> vsuCreate then c.Usermode := vsuCurveSetCurve else
+                      if c.PointCount > 1 then c.CurveMode[c.PointCount-2] := cmCurve;
+  end;
+end;
+
+function TToolSpline.CreateShape: TVectorShape;
+begin
+  result := TCurveShape.Create(nil);
+  result.Usermode := vsuCreate;
+  TCurveShape(result).CosineAngle:= EasyBezierMinimumDotProduct;
+  if not FCurveModeHintShown then
+  begin
+    Manager.ToolPopup(tpmCurveModeHint);
+    FCurveModeHintShown := true;
+  end;
+end;
+
+procedure TToolSpline.AssignShapeStyle(AMatrix: TAffineMatrix);
+begin
+  inherited AssignShapeStyle(AMatrix);
+  TCurveShape(FShape).SplineStyle:= Manager.ToolSplineStyle;
+end;
+
+constructor TToolSpline.Create(AManager: TToolManager);
+begin
+  inherited Create(AManager);
+  FNextCurveMode := cmAuto;
+end;
+
+function TToolSpline.ToolKeyPress(var key: TUTF8Char): TRect;
+begin
+  Result:=inherited ToolKeyPress(key);
+  if Key='x' then Key := #0;
+end;
 
-{ TToolGenericSpline }
+{ TToolPolygon }
+
+function TToolPolygon.CreateShape: TVectorShape;
+begin
+  result := TPolylineShape.Create(nil);
+end;
+
+procedure TToolPolygon.AssignShapeStyle(AMatrix: TAffineMatrix);
+begin
+  inherited AssignShapeStyle(AMatrix);
+  TCustomPolypointShape(FShape).Closed := Manager.ToolOptionCloseShape;
+  TCustomPolypointShape(FShape).ArrowStartKind := StrToArrowKind(Manager.ToolArrowStart);
+  TCustomPolypointShape(FShape).ArrowEndKind := StrToArrowKind(Manager.ToolArrowEnd);
+  TCustomPolypointShape(FShape).ArrowSize := Manager.ToolArrowSize;
+  UpdateUserMode;
+end;
+
+procedure TToolPolygon.UpdateUserMode;
+begin
+  if FShape = nil then exit;
+  if FQuickDefine then FShape.Usermode := vsuCreate;
+end;
+
+{{ TToolGenericSpline }
 
 function TToolGenericSpline.GetCurrentMode: TToolSplineMode;
 begin
@@ -962,7 +1100,7 @@ begin
   if length(polygonPoints)<>0 then
     ValidatePolygon(GetToolDrawingLayer);
   inherited Destroy;
-end;
+end;}
 
 initialization
 

+ 51 - 99
lazpaint/utoolselect.pas

@@ -41,22 +41,18 @@ type
 
   { TToolSelectPoly }
 
-  TToolSelectPoly = class(TToolGenericPolygon)
+  TToolSelectPoly = class(TToolPolygon)
   protected
-    function HandDrawingPolygonView({%H-}toolDest: TBGRABitmap): TRect; override;
-    function FinalPolygonView(toolDest: TBGRABitmap): TRect; override;
+    procedure AssignShapeStyle(AMatrix: TAffineMatrix); override;
     function GetIsSelectingTool: boolean; override;
-    function GetFillColor: TBGRAPixel; override;
   end;
 
   { TToolSelectSpline }
 
-  TToolSelectSpline = class(TToolGenericSpline)
+  TToolSelectSpline = class(TToolSpline)
   protected
-    function HandDrawingPolygonView(toolDest: TBGRABitmap): TRect; override;
-    function FinalPolygonView({%H-}toolDest: TBGRABitmap): TRect; override;
+    procedure AssignShapeStyle(AMatrix: TAffineMatrix); override;
     function GetIsSelectingTool: boolean; override;
-    function GetFillColor: TBGRAPixel; override;
   end;
 
   { TToolMagicWand }
@@ -133,6 +129,52 @@ implementation
 uses types, ugraph, LCLType, LazPaintType, Math, BGRATransform, BGRAPath,
   BGRAPen, LCVectorRectShapes;
 
+procedure AssignSelectShapeStyle(AShape: TVectorShape; ASwapColor: boolean);
+var
+  f: TVectorShapeFields;
+begin
+  f:= AShape.Fields;
+  if vsfPenFill in f then AShape.PenFill.Clear;
+  if vsfPenStyle in f Then AShape.PenStyle := ClearPenStyle;
+  if vsfBackFill in f then
+  begin
+    if ASwapColor then
+      AShape.BackFill.SetSolid(BGRABlack)
+    else
+      AShape.BackFill.SetSolid(BGRAWhite);
+  end;
+end;
+
+{ TToolSelectSpline }
+
+procedure TToolSelectSpline.AssignShapeStyle(AMatrix: TAffineMatrix);
+begin
+  FShape.BeginUpdate;
+  inherited AssignShapeStyle(AMatrix);
+  AssignSelectShapeStyle(FShape, FSwapColor);
+  FShape.EndUpdate;
+end;
+
+function TToolSelectSpline.GetIsSelectingTool: boolean;
+begin
+  Result:= true;
+end;
+
+{ TToolSelectPoly }
+
+procedure TToolSelectPoly.AssignShapeStyle(AMatrix: TAffineMatrix);
+begin
+  FShape.BeginUpdate;
+  inherited AssignShapeStyle(AMatrix);
+  AssignSelectShapeStyle(FShape, FSwapColor);
+  FShape.EndUpdate;
+end;
+
+function TToolSelectPoly.GetIsSelectingTool: boolean;
+begin
+  Result:= true;
+end;
+
 { TVectorialSelectTool }
 
 function TVectorialSelectTool.GetIsSelectingTool: boolean;
@@ -141,19 +183,8 @@ begin
 end;
 
 procedure TVectorialSelectTool.AssignShapeStyle(AMatrix: TAffineMatrix);
-var
-  f: TVectorShapeFields;
 begin
-  f:= FShape.Fields;
-  if vsfPenFill in f then FShape.PenFill.Clear;
-  if vsfPenStyle in f Then FShape.PenStyle := ClearPenStyle;
-  if vsfBackFill in f then
-  begin
-    if FSwapColor then
-      FShape.BackFill.SetSolid(BGRABlack)
-    else
-      FShape.BackFill.SetSolid(BGRAWhite);
-  end;
+  AssignSelectShapeStyle(FShape, FSwapColor);
   if FShape is TCustomRectShape then
   begin
     if Manager.ToolRatio = 0 then
@@ -470,49 +501,6 @@ begin
   inherited Destroy;
 end;
 
-{ TToolSelectSpline }
-
-function TToolSelectSpline.HandDrawingPolygonView(toolDest: TBGRABitmap): TRect;
-var
-  splinePoints: ArrayOfTPointF;
-begin
-  if Manager.ToolSplineEasyBezier then
-  begin
-    NeedCurveMode;
-    splinePoints := EasyBezierCurve(polygonPoints,True,FCurveMode,EasyBezierMinimumDotProduct).ToPoints;
-  end else
-    splinePoints := toolDest.ComputeClosedSpline(polygonPoints,Manager.ToolSplineStyle);
-  FRenderedPolygonPoints := splinePoints;
-
-  if length(splinePoints) > 2 then
-  begin
-    toolDest.FillPolyAntialias(splinePoints, fillColor);
-    result := GetShapeBounds(splinePoints,1);
-  end else
-    result := EmptyRect;
-end;
-
-function TToolSelectSpline.FinalPolygonView(toolDest: TBGRABitmap): TRect;
-begin
-  if FAfterHandDrawing then
-    result := HandDrawingPolygonView(toolDest)
-  else
-    result := EmptyRect;
-end;
-
-function TToolSelectSpline.GetIsSelectingTool: boolean;
-begin
-  Result:= true;
-end;
-
-function TToolSelectSpline.GetFillColor: TBGRAPixel;
-begin
-  if swapedColor then
-    result := BGRABlack
-  else
-    result := BGRAWhite;
-end;
-
 { TToolSelectionPen }
 
 function TToolSelectionPen.GetIsSelectingTool: boolean;
@@ -558,42 +546,6 @@ begin
   ValidateAction;
 end;
 
-{ TToolSelectPoly }
-
-function TToolSelectPoly.HandDrawingPolygonView(toolDest: TBGRABitmap): TRect;
-begin
-  result := EmptyRect;
-  //nothing
-end;
-
-function TToolSelectPoly.FinalPolygonView(toolDest: TBGRABitmap): TRect;
-var
-  i: Integer;
-begin
-  if length(polygonPoints) > 2 then
-  begin
-    toolDest.FillPolyAntialias(polygonPoints, fillColor);
-    result := GetShapeBounds(polygonPoints,1);
-  end else
-    result := EmptyRect;
-  setlength(FRenderedPolygonPoints, length(polygonPoints));
-  for i := 0 to high(polygonPoints) do
-    FRenderedPolygonPoints[i] := polygonPoints[i];
-end;
-
-function TToolSelectPoly.GetIsSelectingTool: boolean;
-begin
-  result := true;
-end;
-
-function TToolSelectPoly.GetFillColor: TBGRAPixel;
-begin
-  if swapedColor then
-    result := BGRABlack
-  else
-    result := BGRAWhite;
-end;
-
 initialization
 
   RegisterTool(ptMagicWand,TToolMagicWand);

+ 2 - 2
lazpaintcontrols/lcvectororiginal.pas

@@ -77,7 +77,7 @@ type
     procedure SetUsermode(AValue: TVectorShapeUsermode); virtual;
     procedure LoadFill(AStorage: TBGRACustomOriginalStorage; AObjectName: string; var AValue: TVectorialFill);
     procedure SaveFill(AStorage: TBGRACustomOriginalStorage; AObjectName: string; AValue: TVectorialFill);
-    function ComputeStroke(APoints: ArrayOfTPointF; AClosed: boolean; AStrokeMatrix: TAffineMatrix): ArrayOfTPointF;
+    function ComputeStroke(APoints: ArrayOfTPointF; AClosed: boolean; AStrokeMatrix: TAffineMatrix): ArrayOfTPointF; virtual;
     function GetStroker: TBGRAPenStroker;
     property Stroker: TBGRAPenStroker read GetStroker;
     procedure FillChange({%H-}ASender: TObject); virtual;
@@ -871,7 +871,7 @@ begin
   if FStroker = nil then
   begin
     FStroker := TBGRAPenStroker.Create;
-    FStroker.MiterLimit:= sqrt(2);
+    FStroker.MiterLimit:= 2;
   end;
   result := FStroker;
 end;

+ 158 - 10
lazpaintcontrols/lcvectorpolyshapes.pas

@@ -8,6 +8,19 @@ uses
   Classes, SysUtils, Types, LCVectorOriginal, BGRABitmapTypes, BGRALayerOriginal,
   BGRABitmap, BGRATransform, BGRAGradients;
 
+type
+  TArrowKind = (akNone, akTail, akTip, akNormal, akCut, akFlipped, akFlippedCut,
+                akTriangle, akTriangleBack1, akTriangleBack2,
+                akHollowTriangle, akHollowTriangleBack1, akHollowTriangleBack2);
+
+const
+  ArrowKindToStr: array[TArrowKind] of string =
+    ('none', 'tail', 'tip', 'normal', 'cut', 'flipped', 'flipped-cut',
+     'triangle', 'triangle-back1', 'triangle-back2',
+     'hollow-triangle', 'hollow-triangle-back1', 'hollow-triangle-back2');
+
+function StrToArrowKind(AStr: string): TArrowKind;
+
 type
   { TCustomPolypointShape }
 
@@ -16,6 +29,9 @@ type
     FClosed: boolean;
     function GetPoint(AIndex: integer): TPointF;
     function GetPointCount: integer;
+    procedure SetArrowEndKind(AValue: TArrowKind);
+    procedure SetArrowSize(AValue: TPointF);
+    procedure SetArrowStartKind(AValue: TArrowKind);
     procedure SetPoint(AIndex: integer; AValue: TPointF);
   protected
     FPoints: array of record
@@ -29,6 +45,8 @@ type
     FAddingPoint: boolean;
     FMousePos: TPointF;
     FHoverPoint: integer;
+    FArrowStartKind,FArrowEndKind: TArrowKind;
+    FArrowSize: TPointF;
     procedure OnMovePoint({%H-}ASender: TObject; {%H-}APrevCoord, ANewCoord: TPointF; {%H-}AShift: TShiftState);
     procedure OnMoveCenterPoint({%H-}ASender: TObject; {%H-}APrevCoord, ANewCoord: TPointF; {%H-}AShift: TShiftState);
     procedure OnStartMove({%H-}ASender: TObject; APointIndex: integer; {%H-}AShift: TShiftState);
@@ -42,11 +60,13 @@ type
     procedure DoClickPoint({%H-}APointIndex: integer; {%H-}AShift: TShiftState); virtual;
     function CanMovePoints: boolean; virtual;
     procedure InsertPointAuto;
+    function ComputeStroke(APoints: ArrayOfTPointF; AClosed: boolean;
+      AStrokeMatrix: TAffineMatrix): ArrayOfTPointF; override;
   public
     constructor Create(AContainer: TVectorOriginal); override;
     procedure Clear;
     destructor Destroy; override;
-    function AddPoint(const APoint: TPointF): integer;
+    function AddPoint(const APoint: TPointF): integer; virtual;
     function RemovePoint(AIndex: integer): boolean;
     procedure RemovePointRange(AFromIndex, AToIndexPlus1: integer);
     procedure InsertPoint(AIndex: integer; APoint: TPointF);
@@ -59,10 +79,14 @@ type
     procedure ConfigureCustomEditor(AEditor: TBGRAOriginalEditor); override;
     procedure Transform(AMatrix: TAffineMatrix); override;
     class function Usermodes: TVectorShapeUsermodes; override;
+    class function DefaultArrowSize: TPointF;
     property Points[AIndex:integer]: TPointF read GetPoint write SetPoint;
     property PointCount: integer read GetPointCount;
     property Closed: boolean read GetClosed write SetClosed;
     property HoverPoint: integer read FHoverPoint;
+    property ArrowStartKind: TArrowKind read FArrowStartKind write SetArrowStartKind;
+    property ArrowEndKind: TArrowKind read FArrowEndKind write SetArrowEndKind;
+    property ArrowSize: TPointF read FArrowSize write SetArrowSize;
   end;
 
   { TPolylineShape }
@@ -84,8 +108,10 @@ type
 
   TCurveShape = class(TPolylineShape)
   private
+    FCosineAngle: single;
     FSplineStyle: TSplineStyle;
     function GetCurveMode(AIndex: integer): TEasyBezierCurveMode;
+    procedure SetCosineAngle(AValue: single);
     procedure SetCurveMode(AIndex: integer; AValue: TEasyBezierCurveMode);
     procedure SetSplineStyle(AValue: TSplineStyle);
   protected
@@ -95,17 +121,55 @@ type
   public
     class function Usermodes: TVectorShapeUsermodes; override;
     constructor Create(AContainer: TVectorOriginal); override;
+    function AddPoint(const APoint: TPointF): integer; override;
     procedure KeyPress(UTF8Key: string; var AHandled: boolean); override;
     procedure LoadFromStorage(AStorage: TBGRACustomOriginalStorage); override;
     procedure SaveToStorage(AStorage: TBGRACustomOriginalStorage); override;
     class function StorageClassName: RawByteString; override;
     property SplineStyle: TSplineStyle read FSplineStyle write SetSplineStyle;
     property CurveMode[AIndex: integer]: TEasyBezierCurveMode read GetCurveMode write SetCurveMode;
+    property CosineAngle: single read FCosineAngle write SetCosineAngle;
   end;
 
+procedure ApplyArrowStyle(AArrow: TBGRACustomArrow; AStart: boolean; AKind: TArrowKind; ASize: TPointF);
+
 implementation
 
-uses BGRAPen, BGRAGraphics, BGRAFillInfo, BGRAPath, math, LCVectorialFill;
+uses BGRAPen, BGRAGraphics, BGRAFillInfo, BGRAPath, math, LCVectorialFill,
+  BGRAArrow;
+
+function StrToArrowKind(AStr: string): TArrowKind;
+var
+  ak: TArrowKind;
+begin
+  for ak := low(TArrowKind) to high(TArrowKind) do
+    if CompareText(AStr, ArrowKindToStr[ak])=0 then exit(ak);
+  result := akNone;
+end;
+
+procedure ApplyArrowStyle(AArrow: TBGRACustomArrow; AStart: boolean; AKind: TArrowKind; ASize: TPointF);
+var backOfs: single;
+begin
+  backOfs := 0;
+  if (ASize.x = 0) or (ASize.y = 0) then AKind := akNone;
+  if AKind in[akTriangleBack1,akHollowTriangleBack1] then backOfs := 0.25;
+  if AKind in[akTriangleBack2,akHollowTriangleBack2] then backOfs := 0.50;
+  case AKind of
+  akTail: if AStart then AArrow.StartAsTail else AArrow.EndAsTail;
+  akTip: if AStart then AArrow.StartAsTriangle else AArrow.EndAsTriangle;
+  akNormal,akCut,akFlipped,akFlippedCut:
+    if AStart then AArrow.StartAsClassic(AKind in[akFlipped,akFlippedCut], AKind in[akCut,akFlippedCut])
+    else AArrow.EndAsClassic(AKind in[akFlipped,akFlippedCut], AKind in[akCut,akFlippedCut]);
+  akTriangle,akTriangleBack1,akTriangleBack2:
+    if AStart then AArrow.StartAsTriangle(backOfs) else AArrow.EndAsTriangle(backOfs);
+  akHollowTriangle,akHollowTriangleBack1,akHollowTriangleBack2:
+    if AStart then AArrow.StartAsTriangle(backOfs,False,True) else AArrow.EndAsTriangle(backOfs,False,True);
+  else if AStart then AArrow.StartAsNone else AArrow.EndAsNone;
+  end;
+  if (AKind = akTip) and not ((ASize.x = 0) or (ASize.y = 0)) then
+    ASize := ASize*(0.5/ASize.y);
+  if AStart then AArrow.StartSize := ASize else AArrow.EndSize := ASize;
+end;
 
 procedure IncludePointF(var ARectF: TRectF; APointF: TPointF);
 begin
@@ -154,6 +218,30 @@ begin
   result:= length(FPoints);
 end;
 
+procedure TCustomPolypointShape.SetArrowEndKind(AValue: TArrowKind);
+begin
+  if FArrowEndKind=AValue then Exit;
+  BeginUpdate;
+  FArrowEndKind:=AValue;
+  EndUpdate;
+end;
+
+procedure TCustomPolypointShape.SetArrowSize(AValue: TPointF);
+begin
+  if FArrowSize=AValue then Exit;
+  BeginUpdate;
+  FArrowSize:=AValue;
+  EndUpdate;
+end;
+
+procedure TCustomPolypointShape.SetArrowStartKind(AValue: TArrowKind);
+begin
+  if FArrowStartKind=AValue then Exit;
+  BeginUpdate;
+  FArrowStartKind:=AValue;
+  EndUpdate;
+end;
+
 procedure TCustomPolypointShape.SetClosed(AValue: boolean);
 begin
   if AValue = FClosed then exit;
@@ -235,6 +323,11 @@ begin
   Result:= inherited Usermodes + [vsuCreate];
 end;
 
+class function TCustomPolypointShape.DefaultArrowSize: TPointF;
+begin
+  result := PointF(2,2);
+end;
+
 procedure TCustomPolypointShape.SetUsermode(AValue: TVectorShapeUsermode);
 var
   add: Boolean;
@@ -356,6 +449,17 @@ begin
   end;
 end;
 
+function TCustomPolypointShape.ComputeStroke(APoints: ArrayOfTPointF;
+  AClosed: boolean; AStrokeMatrix: TAffineMatrix): ArrayOfTPointF;
+begin
+  if Stroker.Arrow = nil then Stroker.Arrow := TBGRAArrow.Create;
+  ApplyArrowStyle(Stroker.Arrow, true, ArrowStartKind, ArrowSize);
+  ApplyArrowStyle(Stroker.Arrow, false, ArrowEndKind, ArrowSize);
+  Result:=inherited ComputeStroke(APoints, AClosed, AStrokeMatrix);
+  Stroker.Arrow.StartAsNone;
+  Stroker.Arrow.EndAsNone;
+end;
+
 constructor TCustomPolypointShape.Create(AContainer: TVectorOriginal);
 begin
   inherited Create(AContainer);
@@ -435,7 +539,10 @@ begin
   FMousePos := PointF(X,Y);
   if FAddingPoint then
   begin
-    Points[PointCount-1] := FMousePos;
+    if (PointCount = 1) and (FMousePos <> Points[PointCount-1]) then
+      Points[PointCount] := FMousePos
+    else
+      Points[PointCount-1] := FMousePos;
     AHandled:= true;
   end;
 end;
@@ -514,6 +621,11 @@ begin
     FPoints[i].data := nil;
   end;
   FClosed:= AStorage.Bool['closed'];
+  if AStorage.HasAttribute('arrow-size') then
+    FArrowSize := AStorage.PointF['arrow-size']
+  else FArrowSize := DefaultArrowSize;
+  FArrowStartKind:= StrToArrowKind(AStorage.RawString['arrow-start-kind']);
+  FArrowEndKind:= StrToArrowKind(AStorage.RawString['arrow-end-kind']);
   EndUpdate;
 end;
 
@@ -533,6 +645,12 @@ begin
   AStorage.FloatArray['x'] := x;
   AStorage.FloatArray['y'] := y;
   AStorage.Bool['closed'] := Closed;
+  if ArrowStartKind=akNone then AStorage.RemoveAttribute('arrow-start-kind')
+  else AStorage.RawString['arrow-start-kind'] := ArrowKindToStr[ArrowStartKind];
+  if ArrowEndKind=akNone then AStorage.RemoveAttribute('arrow-end-kind')
+  else AStorage.RawString['arrow-end-kind'] := ArrowKindToStr[ArrowEndKind];
+  if (ArrowStartKind=akNone) and (ArrowEndKind=akNone) then AStorage.RemoveAttribute('arrow-size')
+  else AStorage.PointF['arrow-size'] := FArrowSize;
 end;
 
 procedure TCustomPolypointShape.ConfigureCustomEditor(AEditor: TBGRAOriginalEditor);
@@ -547,7 +665,7 @@ begin
   for i:= 0 to PointCount-1 do
     if isEmptyPointF(Points[i]) then
       FPoints[i].editorIndex := -1
-    else if (FAddingPoint and ((i = 0) or (i = PointCount-1))) then
+    else if (FAddingPoint and (i = PointCount-1)) then
     begin
       FPoints[i].editorIndex := -1;
       FCenterPoint += Points[i];
@@ -660,7 +778,7 @@ begin
     pts := GetCurve(AMatrix);
     if PenVisible(rboAssumePenFill in AOptions) then
     begin
-      if JoinStyle = pjsRound then
+      if (JoinStyle = pjsRound) and (ArrowStartKind = akNone) and (ArrowEndKind = akNone) then
       begin
         xMargin := (abs(AMatrix[1,1])+abs(AMatrix[1,2]))*PenWidth*0.5;
         yMargin := (abs(AMatrix[2,1])+abs(AMatrix[2,2]))*PenWidth*0.5;
@@ -728,6 +846,14 @@ begin
     result := cmAuto;
 end;
 
+procedure TCurveShape.SetCosineAngle(AValue: single);
+begin
+  if FCosineAngle=AValue then Exit;
+  BeginUpdate;
+  FCosineAngle:=AValue;
+  EndUpdate;
+end;
+
 procedure TCurveShape.SetCurveMode(AIndex: integer; AValue: TEasyBezierCurveMode);
 begin
   if (AIndex < 0) or (AIndex >= PointCount) then exit;
@@ -751,7 +877,7 @@ begin
     setlength(cm, PointCount);
     for i := 0 to PointCount-1 do
       cm[i] := CurveMode[i];
-    eb := EasyBezierCurve(pts, Closed, cm);
+    eb := EasyBezierCurve(pts, Closed, cm, CosineAngle);
     result := eb.ToPoints;
   end else
   begin
@@ -787,23 +913,43 @@ begin
   FSplineStyle:= ssEasyBezier;
 end;
 
+function TCurveShape.AddPoint(const APoint: TPointF): integer;
+begin
+  if (PointCount > 1) and (APoint = Points[PointCount-1]) then
+  begin
+    BeginUpdate;
+    CurveMode[PointCount-1] := CurveMode[PointCount-2];
+    Result:=inherited AddPoint(APoint);
+    EndUpdate;
+  end
+  else Result:=inherited AddPoint(APoint);
+end;
+
 procedure TCurveShape.KeyPress(UTF8Key: string; var AHandled: boolean);
+var
+  targetPoint: Integer;
 begin
-  if (FHoverPoint >= 0) and (FHoverPoint < PointCount) then
+  if FHoverPoint<>-1 then
+    targetPoint := FHoverPoint
+  else if FAddingPoint and (PointCount > 1) then
+    targetPoint := PointCount-2
+  else
+    targetPoint := -1;
+  if (targetPoint >= 0) and (targetPoint < PointCount) then
   begin
     if (UTF8Key = 'A') or (UTF8Key = 'a') then
     begin
-      CurveMode[FHoverPoint] := cmAuto;
+      CurveMode[targetPoint] := cmAuto;
       AHandled := true;
     end else
     if (UTF8Key = 'S') or (UTF8Key = 's') then
     begin
-      CurveMode[FHoverPoint] := cmCurve;
+      CurveMode[targetPoint] := cmCurve;
       AHandled:= true;
     end else
     if (UTF8Key = 'X') or (UTF8Key = 'x') then
     begin
-      CurveMode[FHoverPoint] := cmAngle;
+      CurveMode[targetPoint] := cmAngle;
       AHandled:= true;
     end;
   end;
@@ -841,6 +987,7 @@ begin
       for i:= length(cm) to PointCount-1 do
         CurveMode[i] := cmCurve;
   end;
+  CosineAngle := AStorage.FloatDef['cosine-angle', EasyBezierDefaultMinimumDotProduct];
   EndUpdate;
 end;
 
@@ -869,6 +1016,7 @@ begin
       cm[i] := ord(CurveMode[i]);
     AStorage.FloatArray['curve-mode'] := cm;
   end;
+  AStorage.Float['cosine-angle'] := CosineAngle;
 end;
 
 class function TCurveShape.StorageClassName: RawByteString;