• C#基数样条曲线的模拟实现(对应Graphics的DrawCurve)


    C#的绘图函数中有一个绘制样条曲线的函数DrawCurve,当只传入Pen和Point数组时,采用的是基数样条曲线绘制。如果只是绘制样条曲线,那这个函数已经满足了。但是项目中要求不但要绘制曲线,还要将曲线以方格的形式模拟来实现。为此,就必须知道样条曲线是如何绘制的,才有办法知道都有哪些点,然后再用格子来模拟。

    起初,使用了很粗暴的方法,即使用DrawCurve在内存中绘制到Image中,然后从Image中取出黑白点,然后形成黑白点的矩阵,进而利用这些矩阵点对应到像素点来绘制方格。做了简单的实现,但效果不理想。原因有几个。

    1.不断使用内存绘制到Image中需要消耗大量的内存。

    2.利用像素点来采集矩阵的点时,难以确定一个采集的范围。

    3.至少需要对像素进行X和Y的双重循环遍历才能达成,这样时间复杂度会随着X、Y的增加而不断加大。

    后面找到了一份模拟实现,经过改造,初步达成了目的。来看看模拟实现的和DrawCurve的拟合效果图,见下图。


    注:图中黑色部分使用DrawCurve来绘制,黑色线中间的白色部分采用的是模拟绘制。从测试的结果来看,符合程度比较理想。下面是实现的代码。

    using System;
    using System.Collections.Generic;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    
    namespace SplineTest
    {
        /// <summary>    
        /// 样条曲线。每根样条曲线包含4个控制点
        /// </summary>
        public class Spline
        {       
    
            /// <summary>
            /// 样点数。在点Pk和Pk+1之间,将会生成若干个样点。所以"u"将会从0.00F增长到0.05F.
            /// </summary>
            private static readonly int _samplePointCount = 20;
    
            /// <summary>
            /// 在基数算法中的t
            /// </summary>
            private static readonly float _tension = 0.0F;
    
    
            #region 属性
            private PointF _startControlPoint;
    
            /// <summary>
            /// "Pk-1"点(起始控制点)
            /// </summary>
            public PointF StartControlPoint
            {
                get
                {
                    return this._startControlPoint;
                }
                set
                {
                    this._startControlPoint = value;
                }
            }
    
            private PointF _startPoint;
            /// <summary>
            ///  "Pk"点(起始点)
            /// </summary>
            public PointF StartPoint
            {
                get
                {
                    return this._startPoint;
                }
                set
                {
                    this._startPoint = value;
                }
            }
    
    
            
            private PointF _endPoint;
            /// <summary>
            /// "Pk+1"点(结束点)
            /// </summary>
            public PointF EndPoint
            {
                get
                {
                    return this._endPoint;
                }
                set
                {
                    this._endPoint = value;
                }
            }
    
            
            private PointF _endControlPoint;
            /// <summary>
            /// "Pk+2"点(结束控制点)
            /// </summary>
            public PointF EndControlPoint
            {
                get
                {
                    return this._endControlPoint;
                }
                set
                {
                    this._endControlPoint = value;
                }
            }
    
            
            private PointF[] _ctrlPoints;
            /// <summary>
            /// 曲线点(控制点及模拟的样点)
            /// </summary>
            public PointF[] CtrlPoints
            {
                get
                {
                    return this._ctrlPoints;
                }
            }
    
    
            private bool _isFirst = false;
            /// <summary>
            /// 标识当前样条曲线是否是第一条,如果是m_startControlPoint 和 m_startPoint将会相同。
            /// 因为在Pk和Pk+1之间需要4个点来决定样条曲线,所以我们需要在Pk-1点前手动添加一个点。
            /// 这样我们才能在Pk-1和Pk+1之间绘制样条曲线。
            /// 同样的,最后一根样条曲线的Pk+2点会与它的"Pk+1"点相同,
            /// 这样我们才能在Pk+1和Pk+2之间绘制样条曲线。
            /// </summary>
            public bool IsFirst
            {
                get
                {
                    return this._isFirst;
                }
                set
                {
                    this._isFirst = value;
                }
            }
            #endregion
    
            public Spline()
            {
                _startControlPoint = new PointF();
                _startPoint = new PointF();
                _endPoint = new PointF();
                _endControlPoint = new PointF();
                _ctrlPoints = new PointF[_samplePointCount + 1];
                for (int i = 0; i < _ctrlPoints.Length; i++)
                {
                    _ctrlPoints[i] = new PointF();
                }
            }
    
            /// <summary>
            ///添加关节。将新控制点添加到控制点列表中,并更新前面的样条曲线。
            /// </summary>
            /// <param name="prevSpline">前一根样条曲线</param>
            /// <param name="currentPoint">当前点</param>
            public void AddJoint(Spline prevSpline, PointF currentPoint)
            {
                //前一根样条曲线(prevSpline)为null,说明控制点列表中只有一个点,所以4个控制点样同。
                //当第2个及之后的控制点添加到控制点列表中时,那第1根样条曲线的Pk+1和Pk+2点需要更新
                if (null == prevSpline)
                {
                    this._startControlPoint = currentPoint;
                    this._startPoint = currentPoint;
                    this._endPoint = currentPoint;
                    this._endControlPoint = currentPoint;
                    this._isFirst = true;
                }
                else//前一根样条曲线不为null,所以更新前一根样条曲线的控制点列表,同时更新当前样条曲线的控制点列表。
                {
                    //前一根样条曲线是第1根样条曲线,更新它的Pk+1和Pk+2点
                    if (true == prevSpline._isFirst)
                    {
                        this._startControlPoint = prevSpline.StartControlPoint;
                        this._startPoint = prevSpline.StartPoint;
                        this._endPoint = currentPoint;
                        this._endControlPoint = currentPoint;
                        GenerateSamplePoint();
                        return;
                    }
                    else///前一根样条曲线不是第1根样条曲线,仅更新它的Pk+2点
                    {
                        prevSpline.EndControlPoint = currentPoint;
                        prevSpline.GenerateSamplePoint();
    
                        //模拟当前样条曲线的样点
                        this._startControlPoint = prevSpline._startPoint;
                        this._startPoint = prevSpline._endPoint;
                        this._endPoint = currentPoint;
                        this._endControlPoint = currentPoint;
                        GenerateSamplePoint();
    
                    }
                }
            }
    
            /// <summary>
            /// 使用基数算法生成样点
            /// </summary>
            public void GenerateSamplePoint()
            {
                PointF startControlPoint = this.StartControlPoint;
                PointF startPoint = this.StartPoint;
                PointF endPoint = this.EndPoint;
                PointF endControlPoint = this.EndControlPoint;
                float step = 1.0F / (float)_samplePointCount;
                float uValue = 0.00F;
    
                for (int i = 0; i < _samplePointCount; i++)
                {
                    PointF pointNew = GenerateSimulatePoint(uValue, startControlPoint, startPoint, endPoint, endControlPoint);
                    this.CtrlPoints[i] = pointNew;
                    uValue += step;
                }
                this.CtrlPoints[_ctrlPoints.Length - 1] = endPoint;
            }
    
            /// <summary>
            /// 绘制样条曲线
            /// </summary>
            /// <param name="g"></param>
            public void Draw(Graphics g, Pen pen)
            {
                for (int i = 0; i < _ctrlPoints.Length - 1; i++)
                {
                    PointF lastPoint = _ctrlPoints[i];
                    PointF nextPoint = _ctrlPoints[i + 1];
                    g.DrawLine(pen, lastPoint, nextPoint);
                }
            }
    
    
            #region GenerateSimulatePoint
            /// <summary>
            /// 生成曲线模拟点,该点在startPoint和endPoint之间
            /// </summary>
            /// <param name="u">介于0和1之间的变量</param>
            /// <param name="startControlPoint">起始点startPoint之前的控制点, 协助确定曲线的外观</param>
            /// <param name="startPoint">目标曲线的起始点startPoint,当u=0时,返回结果为起始点startPoint</param>
            /// <param name="endPoint">目标曲线的结束点endPoint, 当u=1时,返回结果为结束点endPoint</param>
            /// <param name="endControlPoint">在起结点startPoint之后的控制点, 协助确定曲线的外观</param>
            /// <returns>返回介于startPoint和endPoint的点</returns>
            private PointF GenerateSimulatePoint(float u,
                                    PointF startControlPoint,
                                    PointF startPoint,
                                    PointF endPoint,
                                    PointF endControlPoint)
            {
                float s = (1 - _tension) / 2;
                PointF resultPoint = new PointF();
                resultPoint.X = CalculateAxisCoordinate(startControlPoint.X, startPoint.X, endPoint.X, endControlPoint.X, s, u);
                resultPoint.Y = CalculateAxisCoordinate(startControlPoint.Y, startPoint.Y, endPoint.Y, endControlPoint.Y, s, u);
                return resultPoint;
            }
    
            /// <summary>
            /// 计算轴坐标
            /// </summary>
            /// <param name="a"></param>
            /// <param name="b"></param>
            /// <param name="c"></param>
            /// <param name="d"></param>
            /// <param name="s"></param>
            /// <param name="u"></param>
            /// <returns></returns>
            private float CalculateAxisCoordinate(float a, float b, float c, float d, float s, float u)
            {
                float result = 0.0F;
                result = a * (2 * s * u * u - s * u * u * u - s * u)
                       + b * ((2 - s) * u * u * u + (s - 3) * u * u + 1)
                       + c * ((s - 2) * u * u * u + (3 - 2 * s) * u * u + s * u)
                       + d * (s * u * u * u - s * u * u);
                return result;
            }
            #endregion
    
            /// <summary>
            /// 获取样条曲线上的点
            /// </summary>
            /// <param name="g"></param>
            /// <param name="pen"></param>
            /// <param name="points"></param>
            public static List<PointF> FetchPoints(PointF[] points)
            {
                if (points == null || points.Length <= 0)
                {
                    return null;
                }
    
                List<Spline> _splines = new List<Spline>();
                Spline splineNew = null;
                Spline lastNew = null;
                foreach (PointF nowPoint in points)
                {
                    if (null == _splines || 0 == _splines.Count)
                    {
                        splineNew = new Spline();
                        splineNew.AddJoint(null, nowPoint);
                        _splines.Add(splineNew);
                    }
                    else
                    {
                        splineNew = new Spline();
                        lastNew = _splines[_splines.Count - 1] as Spline;
                        splineNew.AddJoint(lastNew, nowPoint);
                        _splines.Add(splineNew);
                    };
                }
    
                List<PointF> _points = new List<PointF>();
                foreach (Spline spline in _splines)
                {
                    if (spline.IsFirst)
                    {
                        continue;
                    }
                    foreach (PointF point in spline.CtrlPoints)
                    {
                        if (_points.Contains(point))
                        {
                            continue;
                        }
    
                        _points.Add(point);
                    }               
                }
                return _points;
            }
        }
    
        /// <summary>
        /// Graphics扩展
        /// </summary>
        public static class GraphicsExtension
        {
            /// <summary>
            /// 绘制样条曲线
            /// </summary>
            /// <param name="g"></param>
            /// <param name="pen"></param>
            /// <param name="points"></param>
            public static void DrawSpline(this Graphics g, Pen pen, PointF[] points)
            {
                if (g == null)
                {
                    return;
                }
    
                if (pen == null)
                {
                    return;
                }
    
                if (points == null || points.Length <= 0)
                {
                    return;
                }
    
                List<Spline> _splines = new List<Spline>();
                Spline splineNew = null;
                Spline lastNew = null;
                foreach (PointF nowPoint in points)
                {
                    if (null == _splines || 0 == _splines.Count)
                    {
                        splineNew = new Spline();
                        splineNew.AddJoint(null, nowPoint);
                        _splines.Add(splineNew);
                    }
                    else
                    {
                        splineNew = new Spline();
                        lastNew = _splines[_splines.Count - 1];
                        splineNew.AddJoint(lastNew, nowPoint);
                        _splines.Add(splineNew);
                    }
                }
                
                Spline spline = null;
                for (int i = 0; i < _splines.Count; i++)
                {
                    spline = _splines[i];
                    if (spline.IsFirst)
                    {
                        continue;
                    }
                    spline.Draw(g, pen);
                }
            }
        }
    }
    
    注:

    1.Spline部分最核心的算法是CalculateAxisCoordinate,网上有很多类似的实现,但都不理想,这个是比较理想的一个。

    2.为了便于在Graphics中直接调用,这里对Graphics增加了一个扩展方法DrawSpline,这样就可以像调用DrawCurve一样调用,即g.DrawSpline(pen,points).

    3.在绘制出样条曲线后,还需要能得到其所模拟的点,于是在Spline中增加了一个FetchPoints的方法。

    转载请注明出处http://blog.csdn.net/xxdddail/article/details/47662983。

  • 相关阅读:
    php 配置文件
    读MBA经历回顾(下)做法决定结果——北漂18年(49)
    虚拟化的发展历程和实现方式
    虚拟化的发展历程和实现方式
    Rhino -- 基于java的javascript实现
    php mvc 框架演示
    Web报表工具FineReport二次开发JS之字符串
    红帽虚拟化RHEV-PXE批量安装RHEV-H
    红帽虚拟化RHEV-PXE批量安装RHEV-H
    css 样式
  • 原文地址:https://www.cnblogs.com/sparkleDai/p/7604957.html
Copyright © 2020-2023  润新知