C# maui暂时没有官方支持InkCanvas,但是不影响,自己实现一个就行了。目前支持画图,选择,移动和删除。同时支持自定义橡皮擦形状,也支持绑定自定义的形状列表。
实现一个Converter类,以后所有的绑定类型转换都在这个类中实现。- using System;
- using System.Collections.Generic;
- using System.Diagnostics;
- using System.Linq;
- using System.Text;
- using System.Threading.Tasks;
- namespace Shares.Utility
- {
- public class Converter : IValueConverter
- {
-
- <local:Converter x:Key="Converter"/>public object? Convert(object? value, Type targetType, object? parameter, System.Globalization.CultureInfo culture)
-
- <local:Converter x:Key="Converter"/>{
-
- <local:Converter x:Key="Converter"/> // Implement conversion logic here
-
- <local:Converter x:Key="Converter"/> if (value is List<string> list)
-
- <local:Converter x:Key="Converter"/> {
-
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>return string.Join(", ", list); // 自定义分隔符
-
- <local:Converter x:Key="Converter"/> }
-
- <local:Converter x:Key="Converter"/> else if (value is int intValue && targetType.IsEnum)
-
- <local:Converter x:Key="Converter"/> {
-
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>return Enum.ToObject(targetType, intValue); // 将整数转换为枚举类型
-
- <local:Converter x:Key="Converter"/> }
-
- <local:Converter x:Key="Converter"/> return value;
-
- <local:Converter x:Key="Converter"/>}
-
- <local:Converter x:Key="Converter"/>public object? ConvertBack(object? value, Type targetType, object? parameter, System.Globalization.CultureInfo culture)
-
- <local:Converter x:Key="Converter"/>{
-
- <local:Converter x:Key="Converter"/> // Implement conversion back logic here
-
- <local:Converter x:Key="Converter"/> return value;
-
- <local:Converter x:Key="Converter"/>}
- }
- }
复制代码 然后在MyStyles.xaml中添加Converter类的引用,这样以后所有项目都可以使用了,local是
xmlns:local="clr-namespace:Shares.Utility;assembly=Shares"-
- <local:Converter x:Key="Converter"/>
复制代码 InkCanvas重写GraphicsView- public class InkCanvas : GraphicsView, IDrawable {
- <local:Converter x:Key="Converter"/>public class DrawingPath
- <local:Converter x:Key="Converter"/>{
- <local:Converter x:Key="Converter"/> private RectF? cachedBounds;
- <local:Converter x:Key="Converter"/> private bool isDirty = true;
- <local:Converter x:Key="Converter"/> public Guid Id { get; } = Guid.NewGuid();
- <local:Converter x:Key="Converter"/> public PathF Path { get; set; } = new PathF();
- <local:Converter x:Key="Converter"/> public Color? StrokeColor { get; set; }
- <local:Converter x:Key="Converter"/> public float StrokeThickness { get; set; }
- <local:Converter x:Key="Converter"/> public bool IsSelected { get; set; }
- <local:Converter x:Key="Converter"/> public PointF Pos { get; set; }
- <local:Converter x:Key="Converter"/> public RectF Bounds
- <local:Converter x:Key="Converter"/> {
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>get
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>{
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> if (!isDirty && cachedBounds.HasValue)
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>return cachedBounds.Value;
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> if (Path.Count == 0)
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> {
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>cachedBounds = RectF.Zero;
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>return RectF.Zero;
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> }
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> var points = Path.Points;
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> float minX = float.MaxValue, minY = float.MaxValue;
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> float maxX = float.MinValue, maxY = float.MinValue;
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> foreach (var point in points)
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> {
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>float x = point.X + Pos.X;
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>float y = point.Y + Pos.Y;
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>minX = Math.Min(minX, x);
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>minY = Math.Min(minY, y);
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>maxX = Math.Max(maxX, x);
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>maxY = Math.Max(maxY, y);
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> }
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> cachedBounds = new RectF(minX, minY, maxX - minX, maxY - minY);
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> isDirty = false;
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> return cachedBounds.Value;
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>}
- <local:Converter x:Key="Converter"/> }
- <local:Converter x:Key="Converter"/> public void InvalidateBounds() => isDirty = true;
- <local:Converter x:Key="Converter"/> public void LineTo(float x, float y)
- <local:Converter x:Key="Converter"/> {
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>Path.LineTo(x, y);
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>InvalidateBounds();
- <local:Converter x:Key="Converter"/> }
- <local:Converter x:Key="Converter"/> public bool IntersectAt(PointF eraserPos, float eraserRadius)
- <local:Converter x:Key="Converter"/> {
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>if (Path.Count == 0)
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> return false;
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>// 优化点接触检查
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>foreach (var point in Path.Points)
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>{
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> float dx = point.X + Pos.X - eraserPos.X;
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> float dy = point.Y + Pos.Y - eraserPos.Y;
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> if (dx * dx + dy * dy = 2)
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>{
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> var points = Path.Points;
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> for (int i = 1; i < points.Count(); i++)
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> {
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>var start = new PointF(points.ElementAt(i - 1).X + Pos.X, points.ElementAt(i - 1).Y + Pos.Y);
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>var end = new PointF(points.ElementAt(i).X + Pos.X, points.ElementAt(i).Y + Pos.Y);
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>if (PointToLineDistance(start, end, eraserPos) = 0 && minDistance 1)
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>{
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> var newPath = new DrawingPath
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> {
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>StrokeColor = StrokeColor,
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>StrokeThickness = StrokeThickness,
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>Pos = Pos
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> };
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> newPath.Path.MoveTo(points.ElementAt(1));
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> for (int i = 2; i < points.Count(); i++)
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> {
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>newPath.Path.LineTo(points.ElementAt(i));
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> }
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> newPaths.Add(newPath);
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>}
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>return newPaths;
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> }
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> // 终点处理
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> if (bestIndex == points.Count() - 1)
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> {
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>if (points.Count() > 1)
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>{
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> var newPath = new DrawingPath
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> {
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>StrokeColor = StrokeColor,
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>StrokeThickness = StrokeThickness,
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>Pos = Pos
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> };
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> newPath.Path.MoveTo(points.ElementAt(0));
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> for (int i = 1; i < points.Count() - 1; i++)
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> {
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>newPath.Path.LineTo(points.ElementAt(i));
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> }
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> newPaths.Add(newPath);
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>}
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>return newPaths;
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> }
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> // 中间点处理
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> if (bestIndex > 0 && bestIndex < points.Count() - 1)
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> {
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>// 第一段路径
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>var path1 = new DrawingPath
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>{
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> StrokeColor = StrokeColor,
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> StrokeThickness = StrokeThickness,
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> Pos = Pos
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>};
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>path1.Path.MoveTo(points.ElementAt(0));
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>for (int i = 1; i 0 && minDistance 1)
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> {
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>var path1 = new DrawingPath
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>{
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> StrokeColor = StrokeColor,
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> StrokeThickness = StrokeThickness,
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> Pos = Pos
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>};
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>path1.Path.MoveTo(points.ElementAt(0));
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>for (int i = 1; i < bestIndex; i++)
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>{
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> path1.Path.LineTo(points.ElementAt(i));
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>}
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>newPaths.Add(path1);
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> }
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> // 第二段路径
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> if (bestIndex < points.Count() - 1)
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> {
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>var path2 = new DrawingPath
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>{
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> StrokeColor = StrokeColor,
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> StrokeThickness = StrokeThickness,
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> Pos = Pos
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>};
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>path2.Path.MoveTo(points.ElementAt(bestIndex));
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>for (int i = bestIndex + 1; i < points.Count(); i++)
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>{
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> path2.Path.LineTo(points.ElementAt(i));
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>}
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>newPaths.Add(path2);
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> }
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>}
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>return newPaths;
- <local:Converter x:Key="Converter"/> }
- <local:Converter x:Key="Converter"/>}
- <local:Converter x:Key="Converter"/>public enum InkCanvasEditingMode { Ink, Select, Erase }
- <local:Converter x:Key="Converter"/>public static readonly BindableProperty EditingModeProperty =
- <local:Converter x:Key="Converter"/> BindableProperty.Create(nameof(EditingMode), typeof(InkCanvasEditingMode), typeof(InkCanvas),
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>InkCanvasEditingMode.Ink, BindingMode.TwoWay, propertyChanged: OnEditingModeChanged);
- <local:Converter x:Key="Converter"/>private static void OnEditingModeChanged(BindableObject bindable, object oldValue, object newValue)
- <local:Converter x:Key="Converter"/>{
- <local:Converter x:Key="Converter"/> if (bindable is InkCanvas canvas)
- <local:Converter x:Key="Converter"/> {
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>canvas.ClearSelection();
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>canvas.Invalidate();
- <local:Converter x:Key="Converter"/> }
- <local:Converter x:Key="Converter"/>}
- <local:Converter x:Key="Converter"/>public InkCanvasEditingMode EditingMode
- <local:Converter x:Key="Converter"/>{
- <local:Converter x:Key="Converter"/> get => (InkCanvasEditingMode)GetValue(EditingModeProperty);
- <local:Converter x:Key="Converter"/> set => SetValue(EditingModeProperty, value);
- <local:Converter x:Key="Converter"/>}
- <local:Converter x:Key="Converter"/>public ObservableCollection Paths { get; set; } = new ObservableCollection();
- <local:Converter x:Key="Converter"/>public DrawingPath Eraser { get; }
- <local:Converter x:Key="Converter"/>public float EraserRadius { get; set; } = 15f; // 增大橡皮擦半径
- <local:Converter x:Key="Converter"/>private DrawingPath? currentPath;
- <local:Converter x:Key="Converter"/>private RectF? selectionRect;
- <local:Converter x:Key="Converter"/>private PointF lastTouchPoint;
- <local:Converter x:Key="Converter"/>private bool isMovingSelection;
- <local:Converter x:Key="Converter"/>// 橡皮擦轨迹跟踪
- <local:Converter x:Key="Converter"/>private readonly List eraserTrail = new List();
- <local:Converter x:Key="Converter"/>private const int MaxEraserTrailPoints = 5;
- <local:Converter x:Key="Converter"/>public Color StrokeColor { get; set; } = Colors.Black;
- <local:Converter x:Key="Converter"/>public Color SelectionColor { get; set; } = Colors.Red;
- <local:Converter x:Key="Converter"/>public float SelectionStrokeThickness { get; set; } = 1f;
- <local:Converter x:Key="Converter"/>public float StrokeThickness { get; set; } = 1f;
- <local:Converter x:Key="Converter"/>public InkCanvas()
- <local:Converter x:Key="Converter"/>{
- <local:Converter x:Key="Converter"/> Drawable = this;
- <local:Converter x:Key="Converter"/> BackgroundColor = Colors.Transparent;
- <local:Converter x:Key="Converter"/> Eraser = CreateEraserPath();
- <local:Converter x:Key="Converter"/> StartInteraction += OnTouchStarted;
- <local:Converter x:Key="Converter"/> DragInteraction += OnTouchMoved;
- <local:Converter x:Key="Converter"/> EndInteraction += OnTouchEnded;
- <local:Converter x:Key="Converter"/>}
- <local:Converter x:Key="Converter"/>private DrawingPath CreateEraserPath()
- <local:Converter x:Key="Converter"/>{
- <local:Converter x:Key="Converter"/> var path = new PathF();
- <local:Converter x:Key="Converter"/> var points = new[]
- <local:Converter x:Key="Converter"/> {
- <local:Converter x:Key="Converter"/> new PointF(107.4f, 13), new PointF(113.7f, 28.8f),
- <local:Converter x:Key="Converter"/> new PointF(127.9f, 31.3f), new PointF(117.6f, 43.5f),
- <local:Converter x:Key="Converter"/> new PointF(120.1f, 60.8f), new PointF(107.4f, 52.6f),
- <local:Converter x:Key="Converter"/> new PointF(94.6f, 60.8f), new PointF(97.1f, 43.5f),
- <local:Converter x:Key="Converter"/> new PointF(86.8f, 31.3f), new PointF(101f, 28.8f)
- <local:Converter x:Key="Converter"/>};
- <local:Converter x:Key="Converter"/> path.MoveTo(points[0]);
- <local:Converter x:Key="Converter"/> for (int i = 1; i < points.Length; i++)
- <local:Converter x:Key="Converter"/> {
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>path.LineTo(points[i]);
- <local:Converter x:Key="Converter"/> }
- <local:Converter x:Key="Converter"/> path.Close();
- <local:Converter x:Key="Converter"/> return new DrawingPath { Path = path, StrokeColor = Colors.Black, StrokeThickness = 1f };
- <local:Converter x:Key="Converter"/>}
- <local:Converter x:Key="Converter"/>private void OnTouchStarted(object? sender, TouchEventArgs e)
- <local:Converter x:Key="Converter"/>{
- <local:Converter x:Key="Converter"/> if (e.Touches.Length == 0) return;
- <local:Converter x:Key="Converter"/> var point = e.Touches[0];
- <local:Converter x:Key="Converter"/> lastTouchPoint = new PointF(point.X, point.Y);
- <local:Converter x:Key="Converter"/> eraserTrail.Clear(); // 清除历史轨迹
- <local:Converter x:Key="Converter"/> switch (EditingMode)
- <local:Converter x:Key="Converter"/> {
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>case InkCanvasEditingMode.Ink:
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> StartInking(lastTouchPoint);
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> break;
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>case InkCanvasEditingMode.Select:
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> StartSelection(lastTouchPoint);
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> break;
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>case InkCanvasEditingMode.Erase:
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> StartErase(lastTouchPoint);
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> eraserTrail.Add(lastTouchPoint); // 添加起始点
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> break;
- <local:Converter x:Key="Converter"/> }
- <local:Converter x:Key="Converter"/> Invalidate();
- <local:Converter x:Key="Converter"/>}
- <local:Converter x:Key="Converter"/>private void StartInking(PointF startPoint)
- <local:Converter x:Key="Converter"/>{
- <local:Converter x:Key="Converter"/> currentPath = new DrawingPath
- <local:Converter x:Key="Converter"/> {
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>StrokeColor = StrokeColor,
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>StrokeThickness = StrokeThickness,
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>Pos = PointF.Zero
- <local:Converter x:Key="Converter"/> };
- <local:Converter x:Key="Converter"/> currentPath.Path.MoveTo(startPoint.X, startPoint.Y);
- <local:Converter x:Key="Converter"/> Paths.Add(currentPath);
- <local:Converter x:Key="Converter"/>}
- <local:Converter x:Key="Converter"/>private void StartSelection(PointF startPoint)
- <local:Converter x:Key="Converter"/>{
- <local:Converter x:Key="Converter"/> isMovingSelection = Paths.Any(p => p.IsSelected && p.Bounds.Contains(startPoint));
- <local:Converter x:Key="Converter"/> if (!isMovingSelection)
- <local:Converter x:Key="Converter"/> {
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>ClearSelection();
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>var clickedPath = Paths.LastOrDefault(p => p.Bounds.Contains(startPoint));
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>if (clickedPath != null)
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>{
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> clickedPath.IsSelected = true;
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> isMovingSelection = true;
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>}
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>else
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>{
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> selectionRect = new RectF(startPoint, SizeF.Zero);
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>}
- <local:Converter x:Key="Converter"/> }
- <local:Converter x:Key="Converter"/>}
- <local:Converter x:Key="Converter"/>private void StartErase(PointF startPoint)
- <local:Converter x:Key="Converter"/>{
- <local:Converter x:Key="Converter"/> Eraser.Pos = new PointF(startPoint.X - Eraser.Path.Bounds.Width / 2,
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> startPoint.Y - Eraser.Path.Bounds.Height / 4);
- <local:Converter x:Key="Converter"/> Eraser.IsSelected = true;
- <local:Converter x:Key="Converter"/>}
- <local:Converter x:Key="Converter"/>private void OnTouchMoved(object? sender, TouchEventArgs e)
- <local:Converter x:Key="Converter"/>{
- <local:Converter x:Key="Converter"/> if (e.Touches.Length == 0) return;
- <local:Converter x:Key="Converter"/> var currentPoint = new PointF(e.Touches[0].X, e.Touches[0].Y);
- <local:Converter x:Key="Converter"/> switch (EditingMode)
- <local:Converter x:Key="Converter"/> {
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>case InkCanvasEditingMode.Ink:
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> ContinueInking(currentPoint);
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> break;
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>case InkCanvasEditingMode.Select:
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> UpdateSelection(currentPoint);
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> break;
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>case InkCanvasEditingMode.Erase:
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> UpdateEraser(currentPoint);
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> ErasePaths();
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> break;
- <local:Converter x:Key="Converter"/> }
- <local:Converter x:Key="Converter"/> Invalidate();
- <local:Converter x:Key="Converter"/>}
- <local:Converter x:Key="Converter"/>private void ContinueInking(PointF currentPoint)
- <local:Converter x:Key="Converter"/>{
- <local:Converter x:Key="Converter"/> if (currentPath == null) return;
- <local:Converter x:Key="Converter"/> const float minDistance = 1.0f;
- <local:Converter x:Key="Converter"/> float dx = currentPoint.X - lastTouchPoint.X;
- <local:Converter x:Key="Converter"/> float dy = currentPoint.Y - lastTouchPoint.Y;
- <local:Converter x:Key="Converter"/> if (dx * dx + dy * dy > minDistance * minDistance)
- <local:Converter x:Key="Converter"/> {
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>currentPath.LineTo(currentPoint.X, currentPoint.Y);
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>lastTouchPoint = currentPoint;
- <local:Converter x:Key="Converter"/> }
- <local:Converter x:Key="Converter"/>}
- <local:Converter x:Key="Converter"/>private void UpdateSelection(PointF currentPoint)
- <local:Converter x:Key="Converter"/>{
- <local:Converter x:Key="Converter"/> if (isMovingSelection)
- <local:Converter x:Key="Converter"/> {
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>MoveSelectedPaths(currentPoint);
- <local:Converter x:Key="Converter"/> }
- <local:Converter x:Key="Converter"/> else if (selectionRect.HasValue)
- <local:Converter x:Key="Converter"/> {
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>UpdateSelectionRect(currentPoint);
- <local:Converter x:Key="Converter"/> }
- <local:Converter x:Key="Converter"/>}
- <local:Converter x:Key="Converter"/>private void UpdateEraser(PointF currentPoint)
- <local:Converter x:Key="Converter"/>{
- <local:Converter x:Key="Converter"/> Eraser.Pos = new PointF(currentPoint.X - Eraser.Path.Bounds.Width / 2,
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> currentPoint.Y - Eraser.Path.Bounds.Height / 4);
- <local:Converter x:Key="Converter"/> // 添加到橡皮擦轨迹
- <local:Converter x:Key="Converter"/> eraserTrail.Add(Eraser.Pos);
- <local:Converter x:Key="Converter"/> if (eraserTrail.Count > MaxEraserTrailPoints)
- <local:Converter x:Key="Converter"/> {
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>eraserTrail.RemoveAt(0);
- <local:Converter x:Key="Converter"/> }
- <local:Converter x:Key="Converter"/> lastTouchPoint = currentPoint;
- <local:Converter x:Key="Converter"/>}
- <local:Converter x:Key="Converter"/>// 优化擦除逻辑
- <local:Converter x:Key="Converter"/>private void ErasePaths()
- <local:Converter x:Key="Converter"/>{
- <local:Converter x:Key="Converter"/> // 倒序遍历所有路径
- <local:Converter x:Key="Converter"/> for (int i = Paths.Count - 1; i >= 0; i--)
- <local:Converter x:Key="Converter"/> {
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>var path = Paths[i];
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>// 检查橡皮擦轨迹上的所有点
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>foreach (var trailPoint in eraserTrail)
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>{
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> if (path.IntersectAt(trailPoint, EraserRadius))
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> {
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>var newPaths = path.SplitAt(trailPoint, EraserRadius);
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>if (newPaths.Count > 0)
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>{
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> Paths.RemoveAt(i);
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> foreach (var newPath in newPaths)
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> {
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>if (newPath.Path.Count >= 2) // 只添加有效路径
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>{
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> Paths.Add(newPath);
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>}
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> }
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> break; // 路径已被处理,跳出循环
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>}
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>else
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>{
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> // 没有新路径表示整个路径应被删除
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> Paths.RemoveAt(i);
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> break;
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>}
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> }
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>}
- <local:Converter x:Key="Converter"/> }
- <local:Converter x:Key="Converter"/>}
- <local:Converter x:Key="Converter"/>private void MoveSelectedPaths(PointF currentPoint)
- <local:Converter x:Key="Converter"/>{
- <local:Converter x:Key="Converter"/> float deltaX = currentPoint.X - lastTouchPoint.X;
- <local:Converter x:Key="Converter"/> float deltaY = currentPoint.Y - lastTouchPoint.Y;
- <local:Converter x:Key="Converter"/> foreach (var path in Paths)
- <local:Converter x:Key="Converter"/> {
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>if (path.IsSelected)
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>{
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> path.Pos = new PointF(path.Pos.X + deltaX, path.Pos.Y + deltaY);
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> path.InvalidateBounds();
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>}
- <local:Converter x:Key="Converter"/> }
- <local:Converter x:Key="Converter"/> lastTouchPoint = currentPoint;
- <local:Converter x:Key="Converter"/>}
- <local:Converter x:Key="Converter"/>private void UpdateSelectionRect(PointF currentPoint)
- <local:Converter x:Key="Converter"/>{
- <local:Converter x:Key="Converter"/> float x = Math.Min(lastTouchPoint.X, currentPoint.X);
- <local:Converter x:Key="Converter"/> float y = Math.Min(lastTouchPoint.Y, currentPoint.Y);
- <local:Converter x:Key="Converter"/> float width = Math.Abs(currentPoint.X - lastTouchPoint.X);
- <local:Converter x:Key="Converter"/> float height = Math.Abs(currentPoint.Y - lastTouchPoint.Y);
- <local:Converter x:Key="Converter"/> selectionRect = new RectF(x, y, width, height);
- <local:Converter x:Key="Converter"/>}
- <local:Converter x:Key="Converter"/>private void OnTouchEnded(object? sender, TouchEventArgs e)
- <local:Converter x:Key="Converter"/>{
- <local:Converter x:Key="Converter"/> switch (EditingMode)
- <local:Converter x:Key="Converter"/> {
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>case InkCanvasEditingMode.Select when selectionRect.HasValue:
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> FinalizeSelection();
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> break;
- <local:Converter x:Key="Converter"/> }
- <local:Converter x:Key="Converter"/> currentPath = null;
- <local:Converter x:Key="Converter"/> selectionRect = null;
- <local:Converter x:Key="Converter"/> isMovingSelection = false;
- <local:Converter x:Key="Converter"/> Eraser.IsSelected = false;
- <local:Converter x:Key="Converter"/> eraserTrail.Clear(); // 清除橡皮擦轨迹
- <local:Converter x:Key="Converter"/> Invalidate();
- <local:Converter x:Key="Converter"/>}
- <local:Converter x:Key="Converter"/>private void FinalizeSelection()
- <local:Converter x:Key="Converter"/>{
- <local:Converter x:Key="Converter"/> var selection = selectionRect!.Value;
- <local:Converter x:Key="Converter"/> foreach (var path in Paths)
- <local:Converter x:Key="Converter"/> {
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>if (!selection.IntersectsWith(path.Bounds)) continue;
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>if (selection.Contains(path.Bounds))
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>{
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> path.IsSelected = true;
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> continue;
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>}
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>foreach (var point in path.Path.Points)
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>{
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> var absolutePoint = new PointF(point.X + path.Pos.X, point.Y + path.Pos.Y);
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> if (selection.Contains(absolutePoint))
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> {
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>path.IsSelected = true;
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>break;
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/> }
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>}
- <local:Converter x:Key="Converter"/> }
- <local:Converter x:Key="Converter"/>}
- <local:Converter x:Key="Converter"/>public void ClearSelection()
- <local:Converter x:Key="Converter"/>{
- <local:Converter x:Key="Converter"/> foreach (var path in Paths)
- <local:Converter x:Key="Converter"/> {
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>path.IsSelected = false;
- <local:Converter x:Key="Converter"/> }
- <local:Converter x:Key="Converter"/>}
- <local:Converter x:Key="Converter"/>public void Draw(ICanvas canvas, RectF dirtyRect)
- <local:Converter x:Key="Converter"/>{
- <local:Converter x:Key="Converter"/> canvas.FillColor = BackgroundColor;
- <local:Converter x:Key="Converter"/> canvas.FillRectangle(dirtyRect);
- <local:Converter x:Key="Converter"/> canvas.StrokeLineCap = LineCap.Round;
- <local:Converter x:Key="Converter"/> canvas.StrokeLineJoin = LineJoin.Round;
- <local:Converter x:Key="Converter"/> // 绘制所有路径
- <local:Converter x:Key="Converter"/> foreach (var path in Paths)
- <local:Converter x:Key="Converter"/> {
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>DrawPath(canvas, path);
- <local:Converter x:Key="Converter"/> }
- <local:Converter x:Key="Converter"/> // 绘制橡皮擦(如果被选中)
- <local:Converter x:Key="Converter"/> if (Eraser.IsSelected)
- <local:Converter x:Key="Converter"/> {
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>DrawEraser(canvas);
- <local:Converter x:Key="Converter"/> }
- <local:Converter x:Key="Converter"/> // 绘制选择框
- <local:Converter x:Key="Converter"/> if (selectionRect.HasValue)
- <local:Converter x:Key="Converter"/> {
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>DrawSelectionRect(canvas, selectionRect.Value);
- <local:Converter x:Key="Converter"/> }
- <local:Converter x:Key="Converter"/>}
- <local:Converter x:Key="Converter"/>private void DrawPath(ICanvas canvas, DrawingPath path)
- <local:Converter x:Key="Converter"/>{
- <local:Converter x:Key="Converter"/> var strokeColor = path.StrokeColor ?? Colors.Black;
- <local:Converter x:Key="Converter"/> float strokeSize = path.IsSelected ? path.StrokeThickness * 1.5f : path.StrokeThickness;
- <local:Converter x:Key="Converter"/> if (!path.IsSelected)
- <local:Converter x:Key="Converter"/> {
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>strokeColor = strokeColor.WithAlpha(0.5f);
- <local:Converter x:Key="Converter"/> }
- <local:Converter x:Key="Converter"/> canvas.StrokeColor = strokeColor;
- <local:Converter x:Key="Converter"/> canvas.StrokeSize = strokeSize;
- <local:Converter x:Key="Converter"/> canvas.SaveState();
- <local:Converter x:Key="Converter"/> canvas.Translate(path.Pos.X, path.Pos.Y);
- <local:Converter x:Key="Converter"/> canvas.DrawPath(path.Path);
- <local:Converter x:Key="Converter"/> canvas.RestoreState();
- <local:Converter x:Key="Converter"/>}
- <local:Converter x:Key="Converter"/>private void DrawEraser(ICanvas canvas)
- <local:Converter x:Key="Converter"/>{
- <local:Converter x:Key="Converter"/> canvas.SaveState();
- <local:Converter x:Key="Converter"/> canvas.Translate(Eraser.Pos.X, Eraser.Pos.Y);
- <local:Converter x:Key="Converter"/> canvas.Scale(0.2f, 0.2f);
- <local:Converter x:Key="Converter"/> canvas.StrokeColor = Eraser.StrokeColor ?? Colors.Black;
- <local:Converter x:Key="Converter"/> canvas.StrokeSize = Eraser.StrokeThickness;
- <local:Converter x:Key="Converter"/> canvas.FillColor = Color.FromArgb("#FFD700");
- <local:Converter x:Key="Converter"/> canvas.FillPath(Eraser.Path);
- <local:Converter x:Key="Converter"/> canvas.DrawPath(Eraser.Path);
- <local:Converter x:Key="Converter"/> canvas.RestoreState();
- <local:Converter x:Key="Converter"/>}
- <local:Converter x:Key="Converter"/>private void DrawSelectionRect(ICanvas canvas, RectF rect)
- <local:Converter x:Key="Converter"/>{
- <local:Converter x:Key="Converter"/> canvas.SaveState();
- <local:Converter x:Key="Converter"/> canvas.StrokeColor = SelectionColor;
- <local:Converter x:Key="Converter"/> canvas.StrokeSize = SelectionStrokeThickness;
- <local:Converter x:Key="Converter"/> canvas.StrokeDashPattern = new float[] { 5, 3 };
- <local:Converter x:Key="Converter"/> canvas.DrawRectangle(rect);
- <local:Converter x:Key="Converter"/> canvas.RestoreState();
- <local:Converter x:Key="Converter"/>}
- <local:Converter x:Key="Converter"/>// 静态工具方法
- <local:Converter x:Key="Converter"/>public static float Distance(PointF a, PointF b)
- <local:Converter x:Key="Converter"/> => (float)Math.Sqrt(Math.Pow(a.X - b.X, 2) + Math.Pow(a.Y - b.Y, 2));
- <local:Converter x:Key="Converter"/>public static float DistanceSquared(PointF a, PointF b)
- <local:Converter x:Key="Converter"/> => (a.X - b.X) * (a.X - b.X) + (a.Y - b.Y) * (a.Y - b.Y);
- <local:Converter x:Key="Converter"/>public static float PointToLineDistance(PointF lineStart, PointF lineEnd, PointF point)
- <local:Converter x:Key="Converter"/>{
- <local:Converter x:Key="Converter"/> float l2 = DistanceSquared(lineStart, lineEnd);
- <local:Converter x:Key="Converter"/> if (l2 == 0) return Distance(point, lineStart);
- <local:Converter x:Key="Converter"/> float t = Math.Max(0, Math.Min(1, Vector2.Dot(
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>new Vector2(point.X - lineStart.X, point.Y - lineStart.Y),
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>new Vector2(lineEnd.X - lineStart.X, lineEnd.Y - lineStart.Y)) / l2));
- <local:Converter x:Key="Converter"/> PointF projection = new PointF(
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>lineStart.X + t * (lineEnd.X - lineStart.X),
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>lineStart.Y + t * (lineEnd.Y - lineStart.Y)
- <local:Converter x:Key="Converter"/> );
- <local:Converter x:Key="Converter"/> return Distance(point, projection);
- <local:Converter x:Key="Converter"/>} }
复制代码 SimpleInkCanvas.xaml-
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
- <local:Converter x:Key="Converter"/>
复制代码 对应的cs代码- using static Shares.Utility.InkCanvas;namespace MauiViews.MauiDemos.Book._03;public partial class SimpleInkCanvas : ContentPage{ public SimpleInkCanvas() { InitializeComponent(); foreach (InkCanvasEditingMode mode in Enum.GetValues(typeof(InkCanvasEditingMode))) {
- <local:Converter x:Key="Converter"/> lstEditingMode.Items.Add(mode.ToString()); lstEditingMode.SelectedIndex = 0;
- <local:Converter x:Key="Converter"/>} }}
复制代码 运行效果
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |