WPF inkcanvas选中笔迹缩放、旋转,放大缩小画布、移动画布 获取缩放、旋转后的宽高 多点触控 图形画笔

源码_gitee
源码_github
使用装饰层实现

支持蚁行线
可以直接用inkcanvas的GestureOnly模式实现

预览绘制形状

使用说明
按住ctrl+鼠标滚轮缩放画布
按住空格+按住鼠标左键+移动鼠标位置拖动画布(需要放大才能使用)

不使用inkcanvas的select即可屏蔽默认的装饰层

在MoveRotateAdorner类中的OnRender绘制装饰层

预览的逻辑是添加一个装饰层绘制预览效果,在mouseup事件进行最后实际效果的绘制

注意重绘的代码将自身传入,以获取相关属性,同时保证引用不发生变化
与控制相关的部分代码

      private void Rotate(double angle)
        {
            if (selectionStrokes.Count == 0)
            {
                return;
            }
            //var bound = selectionStrokes.GetBounds();

            Matrix matrix = new Matrix();
            matrix.RotateAt(angle, bound.Left + bound.Width / 2, bound.Top + bound.Height / 2);
            selectionStrokes.Transform(matrix, false);


            bound = selectionStrokes.GetBounds();

        }

        //double lastScaleX = 1;
        //double lastScaleY = 1;
        /// <summary>
        /// 缩放
        /// </summary>
        /// <param name="scaleX">缩放比例</param>
        /// <param name="scaleY">缩放比例</param>
        /// <param name="middleX">缩放中心点</param>
        /// <param name="middleY">缩放中心点</param>
        private void ReSize(double scaleX, double scaleY, double middleX, double middleY)
        {
            if ((selectionStrokes?.Count ?? 0) == 0)
            {
                return;
            }

            Matrix matrix = new Matrix();

            matrix.ScaleAt(scaleX, scaleY, middleX, middleY);

            Trace.WriteLine($"scale x:{ scaleX}, scale y:{ scaleY}");
            selectionStrokes.Transform(matrix, false);
            scaleRound = selectionStrokes.GetBounds();

            bound = selectionStrokes.GetBounds();
            ReDraw(this);
        }
        public void Move(double x, double y)
        {
            if ((selectionStrokes?.Count ?? 0) == 0)
            {
                return;
            }

            Matrix matrix = new Matrix();

            matrix.OffsetX = x;
            matrix.OffsetY = y;

            selectionStrokes.Transform(matrix, false);
            scaleRound = selectionStrokes.GetBounds();

            bound = selectionStrokes.GetBounds();
            ReDraw(this);
        }

        public void ReDraw(MoveRotateAdorner adorner = null)
        {
            ClearAdorner();
            ReDrawAdorner(adorner);
        }

        public void ClearAdorner()
        {
            var layer = AdornerLayer.GetAdornerLayer(adornedElement);
            var arr = layer.GetAdorners(adornedElement);//获取该控件上所有装饰器,返回一个数组

            if (arr != null)
            {
                for (int i = arr.Length - 1; i >= 0; i--)
                {
                    layer.Remove(arr[i]);
                }
            }
        }
        public MoveRotateAdorner ReDrawAdorner(MoveRotateAdorner adorner = null)
        {
            var layer = AdornerLayer.GetAdornerLayer(adornedElement);

            adorner = adorner ?? new MoveRotateAdorner(adornedElement, selectionStrokes);
            layer.Add(adorner);
            return adorner;
        }

选择笔迹

//在画布位置处理点击事件
//mouse up
//选中笔迹
 foreach (var item in writeBorad.Strokes)
{
  var result = item.HitTest(paths, 1);
  Trace.WriteLine(result);
  if (result)
  {
    selectionStrokes.Add(item);
  }
}
adorner = new MoveRotateAdorner(writeBorad, selectionStrokes);
if (selectionStrokes.Count == 0)
{
  adorner?.ClearAdorner();
}
else
{
  adorner?.ReDraw();
}

获取旋转后的宽高

 var bounds = borderTest.TransformToAncestor((Visual)borderTest.Parent)
                  .TransformBounds(new Rect(borderTest.RenderSize));
            Debug.WriteLine($"x:{bounds.X}-y:{bounds.Y},width:{bounds.Width},height:{bounds.Height}");

非标准形状可通过path进行绘制,通过设置 Stretch="Fill" 进行缩放
inkcanvas内部元素也可被选中,通过设置缩放即可实现调整大小

设置橡皮擦大小

//切换到擦除模式
inkCanvas.EditingMode = InkCanvasEditingMode.EraseByPoint;
        
inkCanvas.EraserShape = new EllipseStylusShape(40, 40);

/先重置成不同的模式触发后续的橡皮形状大小刷新        
inkCanvas.EraserShape = new EllipseStylusShape(40, 40);
inkCanvas.EditingMode = InkCanvasEditingMode.None;
inkCanvas.EditingMode = InkCanvasEditingMode.EraseByPoint;

图形画笔

/// <summary>
    /// 图形笔
    /// </summary>
    public class ImageStroke : Stroke
    {
        private string imageFile_;
        private System.Windows.Media.ImageSource imageSource_;
        private Point curPoint;
        public ImageStroke(System.Windows.Input.StylusPointCollection points, DrawingAttributes da, string file)
            : base(points, da)
        {
            this.imageFile_ = file;
            this.imageSource_ = new System.Windows.Media.Imaging.BitmapImage(new Uri( this.imageFile_));
        }

        protected override void DrawCore(System.Windows.Media.DrawingContext drawingContext, DrawingAttributes drawingAttributes)
        {
            double num = drawingAttributes.Width + 20.0;
            System.Windows.Media.StreamGeometry streamGeometry = new System.Windows.Media.StreamGeometry();
            using (System.Windows.Media.StreamGeometryContext streamGeometryContext = streamGeometry.Open())
            {
                streamGeometryContext.BeginFigure((Point)base.StylusPoints[0], false, false);
                drawingContext.DrawImage(this.imageSource_, new Rect(((Point)base.StylusPoints[0]).X - num / 2.0, ((Point)base.StylusPoints[0]).Y - num / 2.0, num, num));
                this.curPoint = (Point)base.StylusPoints[0];
                foreach (System.Windows.Input.StylusPoint current in base.StylusPoints)
                {
                    ImageStroke.DrawImage(ref this.curPoint, (Point)current, num, drawingContext, this.imageSource_);
                }
            }
        }

        public static double GetDistance(Point a, Point b)
        {
            return System.Math.Abs(Point.Subtract(a, b).Length);
        }
        public static Point GetPointBetween(Point a, Point b, double len)
        {
            Vector vector = Point.Subtract(a, b);
            vector.Normalize();
            vector = Vector.Multiply(vector, len);
            return Point.Subtract(a, vector);
        }

        public static void DrawImage(ref Point a, Point b, double width, System.Windows.Media.DrawingContext drawingContext, System.Windows.Media.ImageSource imageSource)
        {
            for (double distance = ImageStroke.GetDistance(a, b); distance >= width; distance = ImageStroke.GetDistance(a, b))
            {
                Point pointBetween = ImageStroke.GetPointBetween(a, b, width);
                drawingContext.DrawImage(imageSource, new Rect(pointBetween.X - width / 2.0, pointBetween.Y - width / 2.0, width, width));
                a = pointBetween;
            }
        }
    }

多点触控问题

如果在笔迹绘制的时候会多一段线条然后在结束绘制的时候不包含这个多出的线条的话可能是由于屏幕本身对多点触摸的支持问题,可以参考多点触摸的解决方案

添加一个支持多点触摸的画布

/// <summary>
    /// 支持多点触摸的InkCanvas
    /// </summary
    public class MultiTouchCanvas : FrameworkElement
    {
        private InkCanvasProxy _inkCanvas = new InkCanvasProxy();
        private Grid transparentOverlay = new Grid();
        private StrokeType _strokeType = StrokeType.Stroke;
        private Dictionary<object, StrokeCollection> _strokes = new Dictionary<object, StrokeCollection>();
        private Dictionary<object, Stroke> _currentStroke = new Dictionary<object, Stroke>();

        private Color _penColor = Colors.Green;
        public Color PenColor{
            get{
                return this._inkCanvas.DefaultDrawingAttributes.Color;
            }
            set{
                this._penColor = value;
                this._inkCanvas.DefaultDrawingAttributes.Color = value;
                if (this.PenType == StrokeType.HighlighterStroke)
                {
                    value.ScA = System.Convert.ToSingle(0.5);
                    this._inkCanvas.DefaultDrawingAttributes.Color = value;
                }
            }
        }

        private double _penWidth = 4.0;
        public double PenWidth
        {
            get { return this._penWidth; }
            set
            {
                this._penWidth = value;
                this._inkCanvas.DefaultDrawingAttributes.Width = value;
                this._inkCanvas.DefaultDrawingAttributes.Height = value;
            }
        }

        private double _eraseWidth = 30.0;
        public double EraseWidth
        {
            get
            {
                return this._eraseWidth;
            }
            set
            {
                this._eraseWidth = value;
                this._inkCanvas.DefaultDrawingAttributes.Height = value;
                this._inkCanvas.DefaultDrawingAttributes.Width = value;
            }
        }

   public InkCanvasEditingMode EditingMode
   {
       get { return _inkCanvas.EditingMode; }
       set
       {
           _inkCanvas.EditingMode = value;
       }
   }


        public StrokeType PenType
        {
            get { return this._strokeType; }
            set
            {
                this._strokeType = value;
                if (this._strokeType == StrokeType.Stroke || this._strokeType == StrokeType.HighlighterStroke)
                {
                    this._inkCanvas.EditingMode = InkCanvasEditingMode.Ink;
                    this._inkCanvas.DefaultDrawingAttributes.Width = this.PenWidth;
                    this._inkCanvas.DefaultDrawingAttributes.Height = this.PenWidth;
                    this._inkCanvas.DefaultDrawingAttributes.Color = this.PenColor;
                }

                if (this._strokeType == StrokeType.HighlighterStroke)
                {
                    Color dColor = this.PenColor;
                    dColor.ScA = System.Convert.ToSingle(0.5);
                    this._inkCanvas.DefaultDrawingAttributes.Color = dColor;
                }

                if (this._strokeType == StrokeType.EraseByPoint)
                {
                    this._inkCanvas.EditingMode = InkCanvasEditingMode.EraseByPoint;
                    this._inkCanvas.DefaultDrawingAttributes.Width = this.EraseWidth;
                    this._inkCanvas.DefaultDrawingAttributes.Height = this.EraseWidth;
                }
            }
        }

        public Brush Background
        {
            get { return this._inkCanvas.Background; }
            set
            {
                this._inkCanvas.Background = value;
            }
        }

        protected override int VisualChildrenCount
        {
            get
            {
                return 2; //grid + inkcanvas
            }
        }
            
        public MultiTouchCanvas(){
            base.IsManipulationEnabled = true;
            this.transparentOverlay.Background = Brushes.Transparent;
            base.IsEnabled =true;

            this.InitInkCanvasPropertys();
            this._inkCanvas.DefaultDrawingAttributes = new DrawingAttributes();
            this._inkCanvas.DefaultDrawingAttributes.Color = Colors.Green;
            this._inkCanvas.DefaultDrawingAttributes.Width = 4;
            this._inkCanvas.DefaultDrawingAttributes.Height = 4;

            this.PenType = StrokeType.Stroke;
        }

        public void ClearStrokes(object device)
        {
            if (this._strokes.ContainsKey(device) && this._inkCanvas.Strokes != null && this._inkCanvas.Strokes.Count > 0)
            {
                StrokeCollection sc = this._strokes[device];
                this._inkCanvas.Strokes.Remove(sc);
                this._strokes.Remove(device);
            }
        }

        public StrokeCollection GetStrokes(object device)
        {
            return this._strokes.ContainsKey(device) ? this._strokes[device] : null;
        }

        #region Event handle
        protected override void OnPreviewTouchDown(TouchEventArgs e)
        {
            TouchPoint tp = e.GetTouchPoint(this);
            if (this._inkCanvas.EditingMode == InkCanvasEditingMode.Ink)
            {
                this._startStroke(e.Device, tp.Position);
            }
            else
            {
                if (this._inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint)
                {
                    this._removeStroke(e.Device, tp.Position);
                }
            }

            e.Handled = true;
            base.Focusable = true;
            base.Focus();
            base.Focusable = false;
            e.TouchDevice.Capture(this);
        }

        protected override void OnPreviewTouchMove(TouchEventArgs e)
        {
            _handleTouchMove(e);
        }

        protected override void OnTouchUp(TouchEventArgs e)
        {
            e.TouchDevice.Capture(null); //
        }

        protected override void OnMouseDown(MouseButtonEventArgs e)
        {
            if (base.Visibility == System.Windows.Visibility.Visible)
            {
                if (this._inkCanvas.EditingMode == InkCanvasEditingMode.Ink)
                {
                    this._startStroke(e.Device, e.GetPosition(this));
                }
                else
                {
                    if (this._inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint)
                    {
                        this._removeStroke(e.Device, e.GetPosition(this));
                    }
                }

                e.MouseDevice.Capture(this);
            }
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            if (e.LeftButton == MouseButtonState.Pressed)
            {
                if (this._inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint)
                {
                    this._removeStroke(e.Device, e.GetPosition(this));
                    return;
                }
                if (this._strokes.ContainsKey(e.Device) && this._currentStroke.ContainsKey(e.Device))
                {
                    this._addPointToStroke(e.Device, e.GetPosition(this));
                }
                else
                {
                    this._startStroke(e.Device, e.GetPosition(this));
                }
            }
        }

        protected override void OnMouseUp(MouseButtonEventArgs e)
        {
            e.MouseDevice.Capture(null);
        }
        #endregion

        protected override Visual GetVisualChild(int index)
        {
            switch (index)
            {
                case 0: return this._inkCanvas;
                case 1: return this.transparentOverlay;
                default:
                    throw new ArgumentOutOfRangeException("index");
            }
        }

        protected override Size MeasureOverride(Size availableSize)
        {
            this._inkCanvas.Measure(availableSize);
            this.transparentOverlay.Measure(availableSize);
            return this._inkCanvas.DesiredSize;
        }

        protected override Size ArrangeOverride(Size finalSize)
        {
            this._inkCanvas.Arrange(new Rect(finalSize));
            this.transparentOverlay.Arrange(new Rect(finalSize));
            return base.ArrangeOverride(finalSize);

        }

        protected override void OnInitialized(EventArgs e)
        {
            base.OnInitialized(e);
            base.AddVisualChild(this._inkCanvas);
            base.AddVisualChild(this.transparentOverlay);
        }
        private  void _handleTouchMove(TouchEventArgs e)
        {
            if(this._inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint){
                this._removeStroke(e.Device , e.GetTouchPoint(this).Position);
                e.Handled = true;
                return ;
            }

            if(this._strokes.ContainsKey(e.Device) && this._currentStroke.ContainsKey(e.Device))
            {
                Stroke stroke = this._currentStroke[e.Device];
                StylusPointCollection sps = stroke.StylusPoints;
                if(sps != null){
                    TouchPoint tp = e.GetTouchPoint(this);
                    Point p = tp.Position;
                    this._addPointToStroke( e.Device , p);
                }
            }
            else
            {
                TouchPoint tp = e.GetTouchPoint(this);
                this._startStroke( e.Device ,tp.Position);
            }
            e.Handled = true;
        }

        private void _addPointToStroke(object device, Point position)
        {
            Stroke stroke = this._currentStroke[device];
            if (stroke != null)
            {
                StylusPointCollection spc = stroke.StylusPoints;
                if (spc != null)
                {
                    spc.Add(new StylusPoint(position.X, position.Y, 0.5f));
                }
            }
        }
            
        private void _removeStroke(object device, Point position)
        {
            for (int i = 0; i < this._inkCanvas.Strokes.Count; i++)
            {
                Stroke stroke = this._inkCanvas.Strokes[i];
                StrokeCollection sc = stroke.GetEraseResult(new Rect(position.X, position.Y, this._inkCanvas.DefaultDrawingAttributes.Width, this._inkCanvas.DefaultDrawingAttributes.Height));
                this._inkCanvas.Strokes.Replace(stroke, sc);
            }
        }

        private void _startStroke(object device, Point inputPosition)
        {
            StylusPointCollection stylusPointCollection = new StylusPointCollection();
            stylusPointCollection.Add(new StylusPoint(inputPosition.X, inputPosition.Y, 0.5f));

            if (stylusPointCollection.Count > 0)
            {
                Stroke stroke = new Stroke(stylusPointCollection);
                stroke.DrawingAttributes.Width = this._inkCanvas.DefaultDrawingAttributes.Width;
                stroke.DrawingAttributes.Height = this._inkCanvas.DefaultDrawingAttributes.Height;
                stroke.DrawingAttributes.Color = this._inkCanvas.DefaultDrawingAttributes.Color;
                this._inkCanvas.Strokes.Add(stroke);//添加到canvas
                if (this._currentStroke.ContainsKey(device))
                {
                    this._currentStroke.Remove(device);
                }
                this._currentStroke.Add(device, stroke);

                if (this._strokes.ContainsKey(device))
                {
                    this._strokes[device].Add(this._currentStroke[device]);
                    return;
                }

                this._strokes.Add(device, new StrokeCollection { this._currentStroke[device] });
            }
        }

        private void InitInkCanvasPropertys(){
            this._inkCanvas.Focusable = base.Focusable;
            this._inkCanvas.Background = Brushes.Transparent;
            this._inkCanvas.EditingMode = InkCanvasEditingMode.Ink;
            this._inkCanvas.UseCustomCursor = true;
        }
    }

添加辅助的代理类型

public enum StrokeType
    {
        Stroke,
        HighlighterStroke,
        EraseByPoint
    }

    public class InkCanvasProxy : InkCanvas
    {
        public InkCanvasProxy()
            : base()
        {
           // base.IsHitTestVisible = false;
          //  base.StylusPlugIns.Remove(base.DynamicRenderer);
        }
    }

测试

<Window x:Class="Metro.G.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="600" Width="600"
        xmlns:ll="clr-namespace:Metro.G">
    <Canvas Name="container" >
        <ll:MultiTouchCanvas Width="600" Height="600" x:Name="_canvas"></ll:MultiTouchCanvas>
   </Canvas>
</Window>

[参考]
多点触摸画板(MultiTouchCanvas)
多点触摸

posted @ 2021-06-29 17:23  Hey,Coder!  阅读(1066)  评论(0编辑  收藏  举报