简介

使用Nuget包:SkiaShap.Views.WPF

  1. 实现一个可移动,可缩放的自定义图像显示控件
  2. 实现一个可调整大小、可移动、可旋转的矩形框,实现在图像控件中选择一个区域

新建自定义控件

Themes\Generic.xaml

csharp
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:skia="clr-namespace:SkiaSharp.Views.WPF;assembly=SkiaSharp.Views.WPF" xmlns:local="clr-namespace:Hawkeye.Controls"> <Style TargetType="{x:Type local:RoiImageView}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type local:RoiImageView}"> <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" ClipToBounds="True"> //超出范围内容截掉不显示 <skia:SKElement x:Name="PART_Canvas" IgnorePixelScaling="false" Margin="{TemplateBinding Padding}"/> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style> </ResourceDictionary>

RoiImageView.cs

csharp
using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using SkiaSharp; using SkiaSharp.Views.Desktop; using SkiaSharp.Views.WPF; [TemplatePart(Name = PART_CanvasName, Type = typeof(SKElement))] public class RoiImageView : Control { private const string PART_CanvasName = "PART_Canvas"; static RoiImageView() { DefaultStyleKeyProperty.OverrideMetadata(typeof(RoiImageView), new FrameworkPropertyMetadata(typeof(RoiImageView))); } // 画布 private SKElement _canvas = default!; // 屏幕缩放不是100%的计算鼠标相对于控件的位置时需要经过比例换算才能得到正确的位置 private DpiScale _dpiScale; // 控制避免循环绘图 private bool _isBusy; public RoiImageView() { //获取当前DpiScale BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Static; System.Type type = typeof(SystemParameters); PropertyInfo dpiXProp = type.GetProperty("DpiX", flags); PropertyInfo dpiYProp = type.GetProperty("Dpi", flags); if (dpiXProp != null && dpiYProp != null) { int x = (int)dpiXProp.GetValue(null, null)!; int y = (int)dpiYProp.GetValue(null, null)!; _dpiScale = new DpiScale(x / 96d, y / 96d); } else { _dpiScale = new DpiScale(1, 1); } } protected override void OnDpiChanged(DpiScale oldDpi, DpiScale newDpi) { _dpiScale = newDpi; } // 获取画布 public override void OnApplyTemplate() { base.OnApplyTemplate(); if (_canvas != null) _canvas.PaintSurface -= OnPaintSurface; _canvas = (SKElement)GetTemplateChild(PART_CanvasName); _canvas.PaintSurface += OnPaintSurface; } protected virtual void OnPaintSurface(object sender, SKPaintSurfaceEventArgs args) { if(_isBusy) return; _isBusy = true; try { // 绘图 } finally { _isBusy = false; } } }

添加显示图像源

添加图像源属性

csharp
public SKImage Source { get => (SKImage)GetValue(SourceProperty); set => SetValue(SourceProperty, value); } public static readonly DependencyProperty SourceProperty = DependencyProperty.Register("Source", typeof(SKImage), typeof(RoiImageView), new FrameworkPropertyMetadata(null, //Source改变后,重新渲染绘图 FrameworkPropertyMetadataOptions.AffectsRender));

绘制图像

csharp
protected virtual void OnPaintSurface(object sender, SKPaintSurfaceEventArgs args) { if(_isBusy) return; _isBusy = true; try { SKImageInfo info = args.Info; //画布信息 SKCanvas canvas = args.Surface.Canvas; //画图工具 // 绘图,从(0,0)位置绘制 if (Source != null) canvas.DrawImage(Source, SKPoint.Empty); } finally { _isBusy = false; } }

加载图像示例

csharp
using var bitmap = SKBitmap.Decode(@"图像路径"); view.Source = SKImage.FromBitmap(bitmap);

移动画布

用鼠标按下、移动和释放的距离差来计算画布偏移量

csharp
//移动画布的位置,用以绘图 private SKPoint _location; //鼠标时按下时画布的偏移位置(_location) private SKPoint _startOrign; //鼠标按下的位置 private SKPoint _pressPoint; //鼠标按下 protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) { if(e.Source != this) return; _startOrign = _location; //把鼠标位置转换成相对于_canvas的位置(经过DpiScale转换的) _pressPoint = GetRealPoint(e.GetPosition(_canvas)); CaptureMouse(); } //鼠标移动 protected override void OnMouseMove(MouseEventArgs e) { if(e.Source != this) return; if(e.LeftButton != MouseButtonState.Pressed) return; //避免在其他控件按下移动到当前控件 if(Mouse.Captured != this) return; //更新位置 var point = GetRealPoint(e.GetPosition(_canvas)); UpdateEndPoint(point); } //鼠标释放 protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e) { try { UpdateEndPoint(GetRealPoint(e.GetPosition(_canvas))); } finally { //释放捕获的鼠标 ReleaseMouseCapture(); } } //位置DpiScale转换 private SKPoint GetRealPoint(Point point) { return new SKPoint((float)(point.X * _dpiScale.DpiScaleX), (float)(point.Y * _dpiScale.DpiScaleY)); } //更新位置 private void UpdateEndPoint(SKPoint point) { //原始位置 + (当前鼠标位置 - 按下时的鼠标位置) _location = _startOrign + point - _pressPoint; //画布重绘 _canvas.InvalidateVisual(); }

在画布重绘中添加偏移位置

csharp
protected virtual void OnPaintSurface(object sender, SKPaintSurfaceEventArgs args) { if(_isBusy) return; _isBusy = true; try { SKImageInfo info = args.Info; //画布信息 SKCanvas canvas = args.Surface.Canvas; //画图工具 //创建偏移矩阵 SKMatrix matrix = SKMatrix.CreateTranslation(_location.X, _location.Y); //画布应用偏移矩阵 canvas.SetMatrix(matrix); // 绘图,此时画布已经偏移,所以仍然从(0,0)位置绘制 if (Source != null) canvas.DrawImage(Source, SKPoint.Empty); } finally { _isBusy = false; } }

缩放画布 - 以鼠标位置为中心点

添加鼠标滚轮事件

csharp
//记录当前的缩放比例 private float _scale = 1; protected override void OnMouseWheel(MouseWheelEventArgs e) { var oldScale = _scale; //每次放大或者缩写0.1倍 var scaleStep = e.Delta > 0 ? 0.1 : -0.1; var newScale = oldScale + scaleStep; if(newScale < 0.1) newScale = 0.1; //最小缩放10倍 else if(newScale > 10) newScale = 10; //最大放大10倍 if(newScale == oldScale) return; _scale = newScale; //(按鼠标位置缩放,需计算位置差与比例差,得到按指定点缩放的偏移差) //(这里可以用SKMatrix来计算偏移差,有兴趣的可以尝试下) //鼠标位置与偏移量之间的差值 var pos = GetRealPoint(e.GetPosition(this)) - _location; //新scale和原先scale的比例 var scaleScale = newScale / _scale; //比例应用到差值上 var posScaled = new SKPoint(scaleScale * pos.X, scaleScale * pos.Y); //更新偏移值 _location -= posScaled - pos; _canvas.InvalidateVisual(); }

更新绘图方法,加入缩放内容

csharp
protected virtual void OnPaintSurface(object sender, SKPaintSurfaceEventArgs args) { if(_isBusy) return; _isBusy = true; try { SKImageInfo info = args.Info; //画布信息 SKCanvas canvas = args.Surface.Canvas; //画图工具 //创建偏移缩放矩阵,水平、垂直方向使用相同的scale比例 SKMatrix matrix = SKMatrix.CreateScaleTranslation(_scale, _scale, _location.X, _location.Y); //画布应用偏移矩阵 canvas.SetMatrix(matrix); // 绘图,此时画布已经偏移、缩放,所以仍然从(0,0)位置绘制 if (Source != null) canvas.DrawImage(Source, SKPoint.Empty); } finally { _isBusy = false; } }

增加旋转矩形

RotationRect.cs

csharp
public struct RotationRect { private float _left,_top,_right, _bottom ,_angle; public float Left { get => _left; set => _left = value; } public float Top { get => _top; set => _top = value; } public float Right { get => _right; set => _right = value; } public float Bottom { get => _bottom; set => _bottom = value; } //Skia角度为顺时针,1、2象限(0,-180)°,3、4象限(180,0)° public float Angle { get => _angle; set => _angle = value; } pubilc RotationRect(){} pubilc RotationRect(float left,float top,float right,float bottom,float angle) { _left=left; _top=top; _right=right; _bottom=bottom; _angle=angle; } public float Width => Right - Left; public float Height => Bottom - Top; public float MidX => (Left + Right)/2; public float MidY => (Top + Bottom)/2; //有需要则交换左右、上下的位置,确保是一个正常的矩形,即Width和Height都不小于0 pubilc RotationRect Uniform() { var left = Left; var top = Top; var right = Right; var bottom = Bottom; if (left > right) { (left, right) = (right, left); } if (top > bottom) { (top, bottom) = (bottom, top); } return new RotationRect(left,top,right,bottom,Angle); } //转换为SKRect, 无旋转角度 public SKRect ToSKRect() => new SKRect(Left, Top, Right, Bottom); //按中心扩展矩形 public RotationRect Expands(float v) => new RotationRect(Left - v, Top - v, Right + v, Bottom + v, Angle); //返回移动后的矩形 pubilc RotationRect Translate(float x,float y) => new RotationRect(Left + x, Top + y, Right + x, Bottom + y, Angle); }

RoiImageView.cs

增加旋转矩形属性

csharp
public RotationRect Roi { get => (RotationRect)GetValue(RoiProperty); set => SetValue(RoiProperty, value); } public static readonly DependencyProperty RoiProperty = DependencyProperty.Register("Roi", typeof(RotationRect), typeof(RoiImageView), new FrameworkPropertyMetadata(new RotationRect(200, 200, 400, 400, 0), //属性改变重新绘图 | 默认TwoWay双向绑定 FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));

绘制矩形

csharp
protected virtual void OnPaintSurface(object sender, SKPaintSurfaceEventArgs args) { if(_isBusy) return; _isBusy = true; try { SKImageInfo info = args.Info; //画布信息 SKCanvas canvas = args.Surface.Canvas; //画图工具 //创建偏移缩放矩阵,水平、垂直方向使用相同的scale比例 SKMatrix matrix = SKMatrix.CreateScaleTranslation(_scale, _scale, _location.X, _location.Y); //画布应用偏移矩阵 canvas.SetMatrix(matrix); // 绘图,此时画布已经偏移、缩放,所以仍然从(0,0)位置绘制 if (Source != null) canvas.DrawImage(Source, SKPoint.Empty); using var roiPaint = new SKPaint { Color = SKColors.Blue, StrokeWidth = 1f / _scale, Style = SKPaintStyle.Stroke, IsAntialias = true, }; canvas.DrawRect(Roi.ToSKRect(), roiPaint); } finally { _isBusy = false; } }

移动矩形

鼠标操作

csharp
// ... 之前的不再贴,只贴改变的 //记录改变前的矩形 private RotationRect _roiOrgin; //记录当前操作的鼠标类型,用以区分移动、调整尺寸、旋转等 private CursorType? _cursorType; //鼠标按下 protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) { if(e.Source != this) return; _startOrign = _location; _roiOrgin = Roi; //把鼠标位置转换成相对于_canvas的位置(经过DpiScale转换的) _pressPoint = GetRealPoint(e.GetPosition(_canvas)); _cursorType = GetCursorFromPoint(_pressPoint); CaptureMouse(); } //鼠标移动 protected override void OnMouseMove(MouseEventArgs e) { if(e.Source != this) return; if(_cursorType == null || e.LeftButton != MouseButtonState.Pressed) return; var point = GetRealPoint(e.GetPosition(_canvas)); //更新位置 UpdateEndPoint(point); } //鼠标释放 protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e) { try { UpdateEndPoint(GetRealPoint(e.GetPosition(_canvas))); _cursorType = null; } finally { //释放捕获的鼠标 ReleaseMouseCapture(); } } //更新位置 private void UpdateEndPoint(SKPoint point) { try { //移动画布 if(_cursorType == CursorType.Arrow) { //原始位置 + (当前鼠标位置 - 按下时的鼠标位置) _location = _startOrign + point - _pressPoint; return; } var matrix = SKMatrix.CreateTranslation(-_location.X, -_location.Y) .PostConcat(SKMatrix.CreateScale(1 / Scale, 1 / Scale)); if(_cursorType == CursorType.SizeAll) { //计算平移、缩放后的位置偏移量 var scaledOffset = matrix.MapPoint(point) - matrix.MapPoint(_pressPoint); Roi = _roiOrign.Translate(scaledOffset.X, scaledOffset.Y); return; } } finally { _canvas.InvalidateVisual(); //画布重绘 } } //获取当前的操作类型,以鼠标位置判断 private CursorType GetCursorFromPoint(SKPoint point) { //把鼠标位置转换成未平移、缩放的位置 var posToSelf = SKMatrix.CreateTranslation(-_location.X, -_location.Y) .PostConcat(SKMatrix.CreateScale(1 / _scale, 1 / _scale)) .MapPoint(point); //判断鼠标位置是否在矩形区域内 if(Roi.ToSKRect().Contains(posToSelf)) return CursorType.SizeAll; //返回移动矩形的鼠标类型 //返回默认鼠标类型,来移动画布 return CursorType.Arrow; }

改变矩形大小

绘制改变大小的控制柄

csharp
//绘制控制柄的半径 private float _handlerRadius = 5f; //鼠标移动到控制柄上时,用以高亮显示控制柄的鼠标类型 private CursorType _displayCursor = CursorType.Arrow; protected virtual void OnPaintSurface(object sender, SKPaintSurfaceEventArgs args) { if(_isBusy) return; _isBusy = true; try { SKImageInfo info = args.Info; //画布信息 SKCanvas canvas = args.Surface.Canvas; //画图工具 //创建偏移缩放矩阵,水平、垂直方向使用相同的scale比例 SKMatrix matrix = SKMatrix.CreateScaleTranslation(_scale, _scale, _location.X, _location.Y); //画布应用偏移矩阵 canvas.SetMatrix(matrix); // 绘图,此时画布已经偏移、缩放,所以仍然从(0,0)位置绘制 if (Source != null) canvas.DrawImage(Source, SKPoint.Empty); using var roiPaint = new SKPaint { Color = SKColors.Blue, StrokeWidth = 1f / _scale, //忽略缩放,显示同样粗细的边框 Style = SKPaintStyle.Stroke, IsAntialias = true, }; canvas.DrawRect(Roi.ToSKRect(), roiPaint); //控制柄半径,经过比例换算,无论画布缩放多大,都显示固定大小的控制柄 var radius = _handlerRadius / _scale; var handlerColor = SKColors.White; //正常Handler颜色 var handlerAccentColor = SKColors.GreenYellow; //鼠标经过、按下时Handler颜色 roiPaint.Style = SKPaintStyle.Fill; //左上控制柄 roiPaint.Color = _displayCursor == CursorType.ScrollNW ? handlerAccentColor : handlerColor; canvas.DrawCircle(new SKPoint(Roi.Left, Roi.Top), radius, roiPaint); //上中控制柄 roiPaint.Color = _displayCursor == CursorType.ScrollN ? handlerAccentColor : handlerColor; canvas.DrawCircle(new SKPoint(Roi.MidX, Roi.Top), radius, roiPaint); //右上控制柄 roiPaint.Color = _displayCursor == CursorType.ScrollNE ? handlerAccentColor : handlerColor; canvas.DrawCircle(new SKPoint(Roi.Right, Roi.Top), radius, roiPaint); //右中控制柄 roiPaint.Color = _displayCursor == CursorType.ScrollE ? handlerAccentColor : handlerColor; canvas.DrawCircle(new SKPoint(Roi.Right, Roi.MidY), radius, roiPaint); //右下控制柄 roiPaint.Color = _displayCursor == CursorType.ScrollSE ? handlerAccentColor : handlerColor; canvas.DrawCircle(new SKPoint(Roi.Right, Roi.Bottom), radius, roiPaint); //下中控制柄 roiPaint.Color = _displayCursor == CursorType.ScrollS ? handlerAccentColor : handlerColor; canvas.DrawCircle(new SKPoint(Roi.MidX, Roi.Bottom), radius, roiPaint); //左下控制柄 roiPaint.Color = _displayCursor == CursorType.ScrollSW ? handlerAccentColor : handlerColor; canvas.DrawCircle(new SKPoint(Roi.Left, Roi.Bottom), radius, roiPaint); //左中控制柄 roiPaint.Color = _displayCursor == CursorType.ScrollW ? handlerAccentColor : handlerColor; canvas.DrawCircle(new SKPoint(Roi.Left, Roi.MidY), radius, roiPaint); } finally { _isBusy = false; } }

鼠标操作

csharp
//鼠标按下 protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) { if(e.Source != this) return; _startOrign = _location; _roiOrgin = Roi; //把鼠标位置转换成相对于_canvas的位置(经过DpiScale转换的) _pressPoint = GetRealPoint(e.GetPosition(_canvas)); _displayCursor = _cursorType = GetCursorFromPoint(_pressPoint); CaptureMouse(); } //鼠标移动 protected override void OnMouseMove(MouseEventArgs e) { if(e.Source != this) return; var point = GetRealPoint(e.GetPosition(_canvas)); if (_cursorType == null) { _displayCursor = GetCursorFromPoint(_pressPoint); _canvas.InvalidateVisual(); } if(_cursorType == null || e.LeftButton != MouseButtonState.Pressed) return; //更新位置 UpdateEndPoint(point); } //更新位置 private void UpdateEndPoint(SKPoint point) { try { //移动画布 if(_cursorType == CursorType.Arrow) { //原始位置 + (当前鼠标位置 - 按下时的鼠标位置) _location = _startOrign + point - _pressPoint; return; } var matrix = SKMatrix.CreateTranslation(-_location.X, -_location.Y) .PostConcat(SKMatrix.CreateScale(1 / Scale, 1 / Scale)); //计算平移、缩放后的位置偏移量 SKPoint offset = matrix.MapPoint(point) - matrix.MapPoint(_pressPoint); if(_cursorType == CursorType.SizeAll) { Roi = _roiOrign.Translate(offset.X, offset.Y); return; } switch (_cursorType) { //左上 case CursorType.ScrollNW: Roi = new RotationRect(_roiOrign.Left + offset.X, _roiOrign.Top + offset.Y, _roiOrign.Right, _roiOrign.Bottom, _roiOrign.Angle).Uniform(); break; //上 case CursorType.ScrollN: Roi = new RotationRect(_roiOrign.Left, _roiOrign.Top + offset.Y, _roiOrign.Right, _roiOrign.Bottom, _roiOrign.Angle).Uniform(); break; //右上 case CursorType.ScrollNE: Roi = new RotationRect(_roiOrign.Left, _roiOrign.Top + offset.Y, _roiOrign.Right + offset.X, _roiOrign.Bottom, _roiOrign.Angle).Uniform(); break; // 右 case CursorType.ScrollE: Roi = new RotationRect(_roiOrign.Left, _roiOrign.Top, _roiOrign.Right + offset.X, _roiOrign.Bottom, _roiOrign.Angle).Uniform(); break; //右下 case CursorType.ScrollSE: Roi = new RotationRect(_roiOrign.Left, _roiOrign.Top, _roiOrign.Right + offset.X, _roiOrign.Bottom + offset.Y, _roiOrign.Angle).Uniform(); break; //下 case CursorType.ScrollS: Roi = GetRotateCorrection( new RotationRect(_roiOrign.Left, _roiOrign.Top, _roiOrign.Right, _roiOrign.Bottom + offset.Y, _roiOrign.Angle).Uniform(), new SKPoint(_roiOrign.MidX,_roiOrign.Top)); break; //左下 case CursorType.ScrollSW: Roi = new RotationRect(_roiOrign.Left + offset.X, _roiOrign.Top, _roiOrign.Right, _roiOrign.Bottom + offset.Y, _roiOrign.Angle).Uniform(); break; //左 case CursorType.ScrollW: Roi = new RotationRect(_roiOrign.Left + offset.X, _roiOrign.Top, _roiOrign.Right, _roiOrign.Bottom, _roiOrign.Angle).Uniform(); break; default: break; } } finally { _canvas.InvalidateVisual(); //画布重绘 } } //获取当前的操作类型,以鼠标位置判断 private CursorType GetCursorFromPoint(SKPoint point) { //把鼠标位置转换成未平移、缩放的位置 var posToSelf = SKMatrix.CreateTranslation(-_location.X, -_location.Y) .PostConcat(SKMatrix.CreateScale(1 / _scale, 1 / _scale)) .MapPoint(point); var radius = _handlerRadius / _scale; if (new SKRect(Roi.Right-radius,Roi.Bottom-radius,Roi.Right+radius,Roi.Bottom+radius).Contains(posToSelf)) return CursorType.ScrollSE; if (new SKRect(Roi.Right-radius,Roi.MidY-radius,Roi.Right+radius,Roi.MidY+radius).Contains(posToSelf)) return CursorType.ScrollE; if (new SKRect(Roi.Right-radius,Roi.Top-radius,Roi.Right+radius,Roi.Top+radius).Contains(posToSelf)) return CursorType.ScrollNE; if (new SKRect(Roi.MidX-radius,Roi.Bottom-radius,Roi.MidX+radius,Roi.Bottom+radius).Contains(posToSelf)) return CursorType.ScrollS; if (new SKRect(Roi.MidX-radius,Roi.Top-radius,Roi.MidX+radius,Roi.Top+radius).Contains(posToSelf)) return CursorType.ScrollN; if (new SKRect(Roi.Left-radius,Roi.MidY-radius,Roi.Left+radius,Roi.MidY+radius).Contains(posToSelf)) return CursorType.ScrollW; if (new SKRect(Roi.Left-radius,Roi.Bottom-radius,Roi.Left+radius,Roi.Bottom+radius).Contains(posToSelf)) return CursorType.ScrollSW; if (new SKRect(Roi.Left-radius,Roi.Top-radius,Roi.Left+radius,Roi.Top+radius).Contains(posToSelf)) return CursorType.ScrollNW; //判断鼠标位置是否在矩形区域内 if(Roi.Expands(-5f / _scale).ToSKRect().Contains(posToSelf)) //往内缩一点,优先控制柄检测 return CursorType.SizeAll; //返回移动矩形的鼠标类型 //返回默认鼠标类型,来移动画布 return CursorType.Arrow; }

旋转矩形

绘制角度控制柄

csharp
protected virtual void OnPaintSurface(object sender, SKPaintSurfaceEventArgs args) { if(_isBusy) return; _isBusy = true; try { SKImageInfo info = args.Info; //画布信息 SKCanvas canvas = args.Surface.Canvas; //画图工具 //创建偏移缩放矩阵,水平、垂直方向使用相同的scale比例 SKMatrix matrix = SKMatrix.CreateScaleTranslation(_scale, _scale, _location.X, _location.Y); //画布应用偏移矩阵 canvas.SetMatrix(matrix); // 绘图,此时画布已经偏移、缩放,所以仍然从(0,0)位置绘制 if (Source != null) canvas.DrawImage(Source, SKPoint.Empty); using var roiPaint = new SKPaint { Color = SKColors.Blue, StrokeWidth = 1f / _scale, //忽略缩放,显示同样粗细的边框 Style = SKPaintStyle.Stroke, IsAntialias = true, }; canvas.DrawRect(Roi.ToSKRect(), roiPaint); //控制柄半径,经过比例换算,无论画布缩放多大,都显示固定大小的控制柄 var radius = _handlerRadius / _scale; var handlerColor = SKColors.White; //正常Handler颜色 var handlerAccentColor = SKColors.GreenYellow; //鼠标经过、按下时Handler颜色 roiPaint.Style = SKPaintStyle.Fill; //左上控制柄 roiPaint.Color = _displayCursor == CursorType.ScrollNW ? handlerAccentColor : handlerColor; canvas.DrawCircle(new SKPoint(Roi.Left, Roi.Top), radius, roiPaint); //上中控制柄 roiPaint.Color = _displayCursor == CursorType.ScrollN ? handlerAccentColor : handlerColor; canvas.DrawCircle(new SKPoint(Roi.MidX, Roi.Top), radius, roiPaint); //右上控制柄 roiPaint.Color = _displayCursor == CursorType.ScrollNE ? handlerAccentColor : handlerColor; canvas.DrawCircle(new SKPoint(Roi.Right, Roi.Top), radius, roiPaint); //右中控制柄 roiPaint.Color = _displayCursor == CursorType.ScrollE ? handlerAccentColor : handlerColor; canvas.DrawCircle(new SKPoint(Roi.Right, Roi.MidY), radius, roiPaint); //右下控制柄 roiPaint.Color = _displayCursor == CursorType.ScrollSE ? handlerAccentColor : handlerColor; canvas.DrawCircle(new SKPoint(Roi.Right, Roi.Bottom), radius, roiPaint); //下中控制柄 roiPaint.Color = _displayCursor == CursorType.ScrollS ? handlerAccentColor : handlerColor; canvas.DrawCircle(new SKPoint(Roi.MidX, Roi.Bottom), radius, roiPaint); //左下控制柄 roiPaint.Color = _displayCursor == CursorType.ScrollSW ? handlerAccentColor : handlerColor; canvas.DrawCircle(new SKPoint(Roi.Left, Roi.Bottom), radius, roiPaint); //左中控制柄 roiPaint.Color = _displayCursor == CursorType.ScrollW ? handlerAccentColor : handlerColor; canvas.DrawCircle(new SKPoint(Roi.Left, Roi.MidY), radius, roiPaint); //角度控制柄 roiPaint.Color = _displayCursor == CursorType.Hand ? handlerAccentColor : handlerColor; canvas.DrawCircle(new SKPoint(Roi.Right + GetAngleHandlerCenter(radius), Roi.MidY), radius, roiPaint); } finally { _isBusy = false; } } private float GetAngleHandlerCenter(float radius) { return (radius * 2) + 20; }

添加角度计算逻辑

csharp
//获取当前的操作类型,以鼠标位置判断 private CursorType GetCursorFromPoint(SKPoint point) { //把鼠标位置转换成未平移、缩放的位置 var posToSelf = SKMatrix.CreateTranslation(-_location.X, -_location.Y) .PostConcat(SKMatrix.CreateScale(1 / _scale, 1 / _scale)) .MapPoint(point); var radius = _handlerRadius / _scale; if (new SKRect(Roi.Right-radius,Roi.Bottom-radius,Roi.Right+radius,Roi.Bottom+radius).Contains(posToSelf)) return CursorType.ScrollSE; if (new SKRect(Roi.Right-radius,Roi.MidY-radius,Roi.Right+radius,Roi.MidY+radius).Contains(posToSelf)) return CursorType.ScrollE; if (new SKRect(Roi.Right-radius,Roi.Top-radius,Roi.Right+radius,Roi.Top+radius).Contains(posToSelf)) return CursorType.ScrollNE; if (new SKRect(Roi.MidX-radius,Roi.Bottom-radius,Roi.MidX+radius,Roi.Bottom+radius).Contains(posToSelf)) return CursorType.ScrollS; if (new SKRect(Roi.MidX-radius,Roi.Top-radius,Roi.MidX+radius,Roi.Top+radius).Contains(posToSelf)) return CursorType.ScrollN; if (new SKRect(Roi.Left-radius,Roi.MidY-radius,Roi.Left+radius,Roi.MidY+radius).Contains(posToSelf)) return CursorType.ScrollW; if (new SKRect(Roi.Left-radius,Roi.Bottom-radius,Roi.Left+radius,Roi.Bottom+radius).Contains(posToSelf)) return CursorType.ScrollSW; if (new SKRect(Roi.Left-radius,Roi.Top-radius,Roi.Left+radius,Roi.Top+radius).Contains(posToSelf)) return CursorType.ScrollNW; //点是否在角度句柄中 var angleHandlePos = new SKPoint(Roi.Right,Roi.MidY); angleHandlePos = new SKPoint(angleHandlePos.X+GetAngleHandlerCenter(radius),angleHandlePos.Y); if (new SKRect(angleHandlePos.X-radius,angleHandlePos.Y-radius,angleHandlePos.X+radius,angleHandlePos.Y+radius).Contains(posToSelf)) return CursorType.Hand; //返回角度使用的鼠标类型 //判断鼠标位置是否在矩形区域内 if(Roi.Expands(-5f / _scale).ToSKRect().Contains(posToSelf)) //往内缩一点,优先控制柄检测 return CursorType.SizeAll; //返回移动矩形的鼠标类型 //返回默认鼠标类型,来移动画布 return CursorType.Arrow; } //更新位置 private void UpdateEndPoint(SKPoint point) { try { //移动画布 if(_cursorType == CursorType.Arrow) { //原始位置 + (当前鼠标位置 - 按下时的鼠标位置) _location = _startOrign + point - _pressPoint; return; } var matrix = SKMatrix.CreateTranslation(-_location.X, -_location.Y) .PostConcat(SKMatrix.CreateScale(1 / Scale, 1 / Scale)); if(_cursorType == CursorType.SizeAll) { //计算平移、缩放后的位置偏移量 var scaledOffset = matrix.MapPoint(point) - matrix.MapPoint(_pressPoint); Roi = _roiOrign.Translate(scaledOffset.X, scaledOffset.Y); return; } if(_cursorType == CursorType.Hand) { //计算鼠标位置,根据矩形中心的角度 var anglePos = matrix.MapPoint(point); var x = anglePos.X - _roiOrign.MidX; var y = anglePos.Y - _roiOrign.MidY; double radians = Math.Atan2(y, x); double angle = (float)(radians * 180 / Math.PI); Roi = new RotationRect(_roiOrign.Left, _roiOrign.Top, _roiOrign.Right, _roiOrign.Bottom, angle % 360); return; } var resizeMatrix = matrix.PostConcat(SKMatrix.CreateRotationDegrees(-Roi.Angle, Roi.MidX, Roi.MidY)); //计算旋转后的偏移量 SKPoint offset = resizeMatrix.MapPoint(point) - resizeMatrix.MapPoint(_pressPoint); switch (_cursorType) { //左上 case CursorType.ScrollNW: Roi = GetRotateCorrection( new RotationRect(_roiOrign.Left + offset.X, _roiOrign.Top + offset.Y, _roiOrign.Right, _roiOrign.Bottom, _roiOrign.Angle).Uniform(), new SKPoint(_roiOrign.Right,_roiOrign.Bottom)); break; //上 case CursorType.ScrollN: Roi = GetRotateCorrection( new RotationRect(_roiOrign.Left, _roiOrign.Top + offset.Y, _roiOrign.Right, _roiOrign.Bottom, _roiOrign.Angle).Uniform(), new SKPoint(_roiOrign.MidX,_roiOrign.Bottom)); break; //右上 case CursorType.ScrollNE: Roi = GetRotateCorrection( new RotationRect(_roiOrign.Left, _roiOrign.Top + offset.Y, _roiOrign.Right + offset.X, _roiOrign.Bottom, _roiOrign.Angle).Uniform(), new SKPoint(_roiOrign.Left,_roiOrign.Bottom)); break; // 右 case CursorType.ScrollE: Roi = GetRotateCorrection( new RotationRect(_roiOrign.Left, _roiOrign.Top, _roiOrign.Right + offset.X, _roiOrign.Bottom, _roiOrign.Angle).Uniform(), new SKPoint(_roiOrign.Left,_roiOrign.MidY)); break; //右下 case CursorType.ScrollSE: Roi = GetRotateCorrection( new RotationRect(_roiOrign.Left, _roiOrign.Top, _roiOrign.Right + offset.X, _roiOrign.Bottom + offset.Y, _roiOrign.Angle).Uniform(), new SKPoint(_roiOrign.Left,_roiOrign.Top)); break; //下 case CursorType.ScrollS: Roi = GetRotateCorrection( new RotationRect(_roiOrign.Left, _roiOrign.Top, _roiOrign.Right, _roiOrign.Bottom + offset.Y, _roiOrign.Angle).Uniform(), new SKPoint(_roiOrign.MidX,_roiOrign.Top)); break; //左下 case CursorType.ScrollSW: Roi = GetRotateCorrection( new RotationRect(_roiOrign.Left + offset.X, _roiOrign.Top, _roiOrign.Right, _roiOrign.Bottom + offset.Y, _roiOrign.Angle).Uniform(), new SKPoint(_roiOrign.Right,_roiOrign.Top)); break; //左 case CursorType.ScrollW: Roi = GetRotateCorrection( new RotationRect(_roiOrign.Left + offset.X, _roiOrign.Top, _roiOrign.Right, _roiOrign.Bottom, _roiOrign.Angle).Uniform(), new SKPoint(_roiOrign.Right,_roiOrign.MidY)); break; default: break; } } finally { _canvas.InvalidateVisual(); //画布重绘 } } //计算旋转后的偏移量,返回偏移之后的矩形 private RotationRect GetRotateCorrection(RotationRect resized, SKPoint fixedPoint) { //改变大小后,按原中心的旋转后的矩形 var roiRect = SKMatrix.CreateRotationDegrees(resized.Angle, _roiOrign.MidX, _roiOrign.MidY).MapRect(resized.ToSKRect()); //定点正常旋转后的位置 var map1 = SKMatrix.CreateRotationDegrees(resized.Angle, _roiOrign.MidX, _roiOrign.MidY).MapPoint(fixedPoint); //按改变大小、旋转后的矩形中心点,反向旋转回去 var map2 = SKMatrix.CreateRotationDegrees(-resized.Angle, roiRect.MidX, roiRect.MidY).MapPoint(map1); //旋转之间的差值 var offset = map2 - fixedPoint; return resized; }