Firemonkey扩展增强:iOS/Android使用贝塞尔曲线绘制签名(笔迹)

来源:互联网 发布:mac上常用的软件 编辑:程序博客网 时间:2024/06/15 19:31

Firemonkey使用TPathData类来存储一系列相连的曲线和直线。查看TPathData的曲线方法源码可知,SmoothCurveTo和QuadCurveTo方法最终还是转化为调用CurveTo方法。而CurveTo是绘制三阶贝塞尔曲线,也就是说TPataData不支持直接绘制二阶贝塞尔曲线。

Firemonkey自带有一个PaintBox控件,但是这个控件仅仅是公布了一个OnPaint事件,真正绘制内容代码还需自己实现。TMS里有个继承自TShape的SignatureCapture控件,查看源码发现其是在Mouse Down/Move/Up 三个事件中记录点并使用的是DrawLine方法进行连线,所以其绘制签名笔迹不够圆滑。OrangeUI里也有一个DrawPanel控件可以手绘笔迹,虽没有源码,但其测试效果也不圆滑,而且在iOS上测试还有一个能绘制到控件区域外的Bug。

这里给出一个使用原生手势识别和BezierPath绘制签名的控件实现源码:

unit FMX.TU2Signature;interfaceuses  System.Classes, System.Types, System.UITypes,  FMX.Types, FMX.Graphics, FMX.Controls, FMX.Objects, FMX.TU2;type  IBezierPath = interface    procedure MoveTo(const P: TPointF);    procedure LineTo(const P: TPointF);                              //一阶    procedure QuadTo(const ControlPoint, EndPoint: TPointF);         //二阶    procedure CurveTo(const Control1, Control2, EndPoint: TPointF);  //三阶    procedure DrawToBitmap(const ACanvas: TCanvas);    procedure Clear;    function IsEmpty: Boolean;    {Update}    procedure Resize(const AWidth, AHeight: Single);    procedure SetPenColor(const Value: TAlphaColor);    procedure SetPenThickness(const Value: Single);  end;  [ComponentPlatformsAttribute(TU2FMXPlatforms)]  TSignature = class(TRectangle)  private    FPrevPoint: TPointF;    FPath: IBezierPath;    FIndex: Integer;    FPenColor: TAlphaColor;    FPenThickness: Single;  private    procedure SetPenColor(const Value: TAlphaColor);    procedure SetPenThickness(const Value: Single);    function GetEmpty: Boolean;  protected    procedure CMGesture(var EventInfo: TGestureEventInfo); override;    procedure Paint; override;    procedure DoResized; override;  public    constructor Create(AOwner: TComponent); override;    procedure Clear;    property Empty: Boolean read GetEmpty;  published    property PenColor: TAlphaColor read FPenColor write SetPenColor;    property PenThickness: Single read FPenThickness write SetPenThickness;  end;implementationuses{$IFDEF IOS}  FMX.TU2Signature.iOS,{$ENDIF IOS}{$IFDEF ANDROID}  FMX.TU2Signature.Android,{$ENDIF}  System.SysUtils;{ TSignatureControl }constructor TSignature.Create(AOwner: TComponent);begin  inherited;  {$IFDEF IOS}  FPath := TiOSBezierPath.Create;  {$ENDIF}  {$IFDEF ANDROID}  FPath := TAndroidBezierPath.Create;  {$ENDIF}  FPenColor := TAlphaColorRec.Black;  FPenThickness := 2;  Touch.InteractiveGestures := [TInteractiveGesture.Pan];end;procedure TSignature.DoResized;begin  inherited;  {$IF Defined(IOS) OR Defined(ANDROID)}  FPath.Resize(Width, Height);  {$ENDIF}end;function TSignature.GetEmpty: Boolean;begin  {$IF Defined(IOS) OR Defined(ANDROID)}  Result := FPath.IsEmpty;  {$ELSE}  Result := True;  {$ENDIF}end;procedure TSignature.Paint;begin  inherited;  {$IF Defined(IOS) OR Defined(ANDROID)}  if not FPath.IsEmpty then    FPath.DrawToBitmap(Canvas);  {$ENDIF}end;procedure TSignature.SetPenColor(const Value: TAlphaColor);begin  {$IF Defined(IOS) OR Defined(ANDROID)}  FPath.SetPenColor(Value);  {$ENDIF}  FPenColor := Value;end;procedure TSignature.SetPenThickness(const Value: Single);begin  {$IF Defined(IOS) OR Defined(ANDROID)}  FPath.SetPenThickness(Value);  {$ENDIF}  FPenThickness := Value;end;procedure TSignature.Clear;begin  FPath.Clear;  Repaint;end;procedure TSignature.CMGesture(var EventInfo: TGestureEventInfo);var  LP: TPointF;begin  if EventInfo.GestureID=igiPan then  begin    LP := AbsoluteToLocal(EventInfo.Location);    if PointInObjectLocal(LP.X,LP.Y) then    begin      if TInteractiveGestureFlag.gfBegin in EventInfo.Flags then      begin        FPath.MoveTo(LP);        FIndex := 0;        FPrevPoint := LP;      end else if EventInfo.Flags=[] then      begin        if FIndex=0 then          Inc(FIndex)        else begin          FPath.QuadTo(FPrevPoint, LP.MidPoint(FPrevPoint));          Repaint;        end;        FPrevPoint := LP;      end else if TInteractiveGestureFlag.gfEnd in EventInfo.Flags then      begin        FPath.QuadTo(FPrevPoint, LP.MidPoint(FPrevPoint));        Repaint;      end;    end;  end else    inherited;end;end.

iOS平台实现代码:

unit FMX.TU2Signature.iOS;interfaceuses System.Types, System.UITypes, FMX.Graphics, FMX.TU2Signature, iOSapi.UIKit;type  TiOSBezierPath = class(TInterfacedObject, IBezierPath)  private    FData: UIBezierPath;    FPenColor: TAlphaColor;    FSize: TSize;  protected    procedure MoveTo(const P: TPointF);    procedure LineTo(const P: TPointF);                              //一阶    procedure QuadTo(const ControlPoint, EndPoint: TPointF);         //二阶    procedure CurveTo(const Control1, Control2, EndPoint: TPointF);  //三阶    procedure DrawToBitmap(const ACanvas: TCanvas);    procedure Clear;    function IsEmpty: Boolean;    {Update}    procedure Resize(const AWidth, AHeight: Single);    procedure SetPenColor(const Value: TAlphaColor);    procedure SetPenThickness(const Value: Single);  public    constructor Create;    destructor Destroy; override;  end;implementationuses iOSapi.Foundation, iOSapi.CoreGraphics, iOSapi.CocoaTypes, FMX.Helpers.iOS;{ TiOSBezierPath }constructor TiOSBezierPath.Create;begin  FData := TUIBezierPath.Wrap(TUIBezierPath.OCClass.bezierPath);  FData.setLineWidth(2);  FData.setLineCapStyle(kCGLineCapRound);  FData.setLineJoinStyle(kCGLineJoinRound);  FData.retain;  FPenColor := TAlphaColorRec.Black;end;destructor TiOSBezierPath.Destroy;begin  FData.release;  inherited;end;procedure TiOSBezierPath.Clear;begin  FData.removeAllPoints;end;function TiOSBezierPath.IsEmpty: Boolean;begin  Result := FData.isEmpty;end;procedure TiOSBezierPath.MoveTo(const P: TPointF);begin  FData.moveToPoint(NSPoint.Create(P));end;procedure TiOSBezierPath.LineTo(const P: TPointF);begin  FData.addLineToPoint(NSPoint.Create(P));end;procedure TiOSBezierPath.QuadTo(const ControlPoint, EndPoint: TPointF);begin  FData.addQuadCurveToPoint(NSPoint.Create(EndPoint),NSPoint.Create(ControlPoint));end;procedure TiOSBezierPath.CurveTo(const Control1, Control2, EndPoint: TPointF);begin  FData.addCurveToPoint(NSPoint.Create(EndPoint),NSPoint.Create(Control1),NSPoint.Create(Control2));end;procedure TiOSBezierPath.DrawToBitmap(const ACanvas: TCanvas);var  img: UIImage;  bm: TBitmap;begin  UIGraphicsBeginImageContextWithOptions(CGSizeMake(FSize.Width, FSize.Height), False, 0);  //绘制曲线  AlphaColorToUIColor(FPenColor).setStroke;  FData.stroke;  //GPU绘制缓存  img := TUIImage.Wrap(UIGraphicsGetImageFromCurrentImageContext);  bm := UIImageToBitmap(img, 0, FSize);  UIGraphicsEndImageContext;  ACanvas.DrawBitmap(bm, bm.BoundsF, TRectF.Create(0,0,FSize.cx, FSize.cy), 1);end;procedure TiOSBezierPath.SetPenColor(const Value: TAlphaColor);begin  FPenColor := Value;end;procedure TiOSBezierPath.SetPenThickness(const Value: Single);begin  FData.setLineWidth(Value);end;procedure TiOSBezierPath.Resize(const AWidth, AHeight: Single);begin  FSize.cx := Round(AWidth);  FSize.cy := Round(AHeight);end;end.

Android平台实现:

unit FMX.TU2Signature.Android;interfaceuses System.Types, System.UITypes, FMX.Graphics, FMX.TU2Signature,  Androidapi.JNI.GraphicsContentViewText;type  TAndroidBezierPath = class(TInterfacedObject, IBezierPath)  private    FData: JPath;    FCanvas: JCanvas;    FBitmap: JBitmap;    FPaint: JPaint;  protected    procedure MoveTo(const P: TPointF);    procedure LineTo(const P: TPointF);                              //一阶    procedure QuadTo(const ControlPoint, EndPoint: TPointF);         //二阶    procedure CurveTo(const Control1, Control2, EndPoint: TPointF);  //三阶    procedure DrawToBitmap(const ACanvas: TCanvas);    procedure Clear;    function IsEmpty: Boolean;    {Update}    procedure Resize(const AWidth, AHeight: Single);    procedure SetPenColor(const Value: TAlphaColor);    procedure SetPenThickness(const Value: Single);  public    constructor Create;    destructor Destroy; override;  end;implementationuses System.SysUtils, FMX.Surfaces, FMX.Helpers.Android;{ TAndroidBezierPath }constructor TAndroidBezierPath.Create;begin  FData := TJPath.JavaClass.Init;  FPaint := TJPaint.Wrap(TJPaint.JavaClass.init(TJPaint.JavaClass.ANTI_ALIAS_FLAG));  FPaint.setStyle(TJPaint_Style.Wrap(TJPaint_Style.JavaClass.STROKE));  FPaint.setStrokeCap(TJPaint_Cap.JavaClass.ROUND);  FPaint.setStrokeJoin(TJPaint_Join.JavaClass.ROUND);  FPaint.setStrokeWidth(2);  FPaint.setColor(TAlphaColorRec.Black);end;destructor TAndroidBezierPath.Destroy;begin  FData := nil;  FPaint := nil;  FCanvas := nil;  FBitmap := nil;  inherited;end;procedure TAndroidBezierPath.Clear;begin  FData.reset;end;function TAndroidBezierPath.IsEmpty: Boolean;begin  Result := FData.isEmpty;end;procedure TAndroidBezierPath.MoveTo(const P: TPointF);begin  FData.moveTo(P.X, P.Y);end;procedure TAndroidBezierPath.LineTo(const P: TPointF);begin  FData.lineTo(P.X, P.Y);end;procedure TAndroidBezierPath.QuadTo(const ControlPoint, EndPoint: TPointF);begin  FData.quadTo(ControlPoint.X, ControlPoint.Y, EndPoint.X, EndPoint.Y);end;procedure TAndroidBezierPath.CurveTo(const Control1, Control2, EndPoint: TPointF);begin  FData.cubicTo(Control1.X, Control1.Y, Control2.X, Control2.Y, EndPoint.X, EndPoint.Y);end;procedure TAndroidBezierPath.DrawToBitmap(const ACanvas: TCanvas);var  Surface: TBitmapSurface;  bm: TBitmap;begin  FCanvas.drawColor(0,TJPorterDuff_Mode.Wrap(TJPorterDuff_Mode.JavaClass.CLEAR));  FCanvas.drawPath(FData, FPaint);  //获取结果  Surface := TBitmapSurface.Create;  try    if JBitmapToSurface(FBitmap, Surface) then    begin      bm := TBitmap.Create;      bm.Assign(Surface);      ACanvas.DrawBitmap(bm, bm.BoundsF, TRectF.Create(0,0,FBitmap.getWidth, FBitmap.getHeight), 1);    end;  finally    Surface.Free;  end;end;procedure TAndroidBezierPath.SetPenColor(const Value: TAlphaColor);begin  FPaint.setColor(Value);end;procedure TAndroidBezierPath.SetPenThickness(const Value: Single);begin  FPaint.setStrokeWidth(Value);end;procedure TAndroidBezierPath.Resize(const AWidth, AHeight: Single);begin  if FBitmap<>nil then  begin    FCanvas := nil;    FBitmap.recycle;    FBitmap := nil;  end;  FBitmap := TJBitmap.JavaClass.createBitmap(Round(AWidth), Round(AHeight),    TJBitmap_Config.JavaClass.ARGB_8888);  FCanvas := TJCanvas.JavaClass.init(FBitmap);end;end.

完整源码

测试效果:


最终版效果:


原创粉丝点击