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。

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-14 06:20:20

C#基数样条曲线的模拟实现(对应Graphics的DrawCurve)的相关文章

Graphics

C#中Graphics的画图代码[转] 架上图片了你就可以在画板上涂改了啊 我要写多几个字上去string str = "Baidu"; //写什么字?Font font = Font("宋体",30f); //字是什么样子的?Brush brush = Brushes.Red; //用红色涂上我的字吧:PointF point = new PointF(10f,10f); //从什么地方开始写字捏? //横着写还是竖着写呢?System.Drawing.Strin

C# Graphics类详解

Brush 类.NET Framework 4定义用于填充图形形状(如矩形.椭圆.饼形.多边形和封闭路径)的内部的对象. 属于命名空间:  System.Drawing这是一个抽象基类,不能进行实例化,若要创建一个画笔对象,使用从 Brush 派生出的类,如 SolidBrush.TextureBrush 和 LinearGradientBrush. SolidBrush 类.NET Framework 4定义单色画笔. 画笔用于填充图形形状,如矩形.椭圆.扇形.多边形和封闭路径. 此类不能被继

【转载】C# Graphics类详解

封装一个 GDI+ 绘图图面. 此类不能被继承.System.Drawing 命名空间 名称 说明 Clip  获取或设置 Region,该对象限定此 Graphics 的绘图区域. ClipBounds  获取一个 RectangleF 结构,该结构限定此 Graphics 的剪辑区域. CompositingMode  获取一个值,该值指定如何将合成图像绘制到此 Graphics. CompositingQuality  获取或设置绘制到此 Graphics 的合成图像的呈现质量. DpiX

GDI+编程小结

GDI+(Graphics Device Interface Plus图形设备接口加)是Windows XP和Windows Server 2003操作系统的子系统,也是.NET框架的重要组成部分,负责在屏幕和打印机上绘制图形图像和显示信息. GDI+不但在功能上比GDI 要强大很多,而且在代码编写方面也更简单,因此会很快成为Windows图形图像程序开发的首选. 一.              GDI+的特点和新增功能 GDI+与GDI一样,都具有设备无关性.应用程序的程序员可利用GDI+这样

GDI GDI+ 的区别

GDI+是GDI的下一个版本,它进行了很好的改进,并且易用性更好.GDI的一个好处就是你不必知道任何关于数据怎样在设备上渲染的细节,GDI+更好的实现了这个优点,也就是说,GDI是一个中低层API,你还可能要知道设备,而GDI+是一个高层的API,你不必知道设备.例如你如果要设置某个控件的前景和背景色,只需设置BackColor和ForeColor属性. 编程模式的变化 “GDI uses a stateful model, whereas GDI+ uses a stateless”——GDI

ASP.NET中数据棒图,饼图,柱状图的实现

Web中绘制图形的方法大致有: 1. VML方式:功能强大,但是非常麻烦. 推荐:http://www.elook.net.cn/vml/ 2.使用控件:Dandus, Aspose.chart,ComponentOne使用方便.虽然有破解,但非开源. ComponetOne: http://blog.csdn.net/ChengKing/category/146827.aspx 3.结合OFFICE Web Components开发. 4.使用商用工控组件. 5.自己编写程序. 数据棒图实现

.netGDI+(转)

架上图片了你就可以在画板上涂改了啊我要写多几个字上去 string str = "Baidu"; //写什么字? Font font = Font("宋体",30f); //字是什么样子的? Brush brush = Brushes.Red; //用红色涂上我的字吧: PointF point = new PointF(10f,10f); //从什么地方开始写字捏?//横着写还是竖着写呢? System.Drawing.StringFormat sf = new

HDU 4716 A Computer Graphics Problem(模拟啊 )

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4716 Problem Description In this problem we talk about the study of Computer Graphics. Of course, this is very, very hard. We have designed a new mobile phone, your task is to write a interface to displa

JAVA写的模拟收发器设备面板工作状态并输出图形界面

java模拟收发器工作状态,每个光口.电口.状态灯都可以跟随鼠标移动,显示提示信息.变异jiava代码生成class文件后,html文件调用. 源代码,可以在附件里下载 mb.java 源代码如下: import java.awt.*; import java.awt.event.*; import javax.swing.*; import java.applet.*; import java.net.URL; import java.net.MalformedURLException; pu