求教高手:怎样画闭合的三次样条曲线 ( 积分: 200 )

  • 主题发起人 主题发起人 modico
  • 开始时间 开始时间
M

modico

Unregistered / Unconfirmed
GUEST, unregistred user!
在下有一组二位的离散数据,想绘制一条闭合的三次样条曲线,该怎么做呀?
但是我不想用GDI+,因为我还想将求一些控制点以外的插值点的数据。
 
在下有一组二位的离散数据,想绘制一条闭合的三次样条曲线,该怎么做呀?
但是我不想用GDI+,因为我还想将求一些控制点以外的插值点的数据。
 
我的BLOG:niuwenchai.blogchina.com
 
wk_knife 大侠:
首先要谢谢你的回答。说来惭愧没,有看明白你的那段关于三次样条的代码,能告诉我是怎么写的吗?好像是用节点处的一阶导数法表示的吧?另外大侠好像不是用x,y坐标来表示插值函数的x,y值,而是用了两点间距离吧?我以前写过用节点的二阶导数法求得的自然样条,但是x值必须是递增的所以画不成闭合的曲线:(大侠是怎么做到的呀
 
算?还是画?

如果算好数据了,画你没有任何选择!vg控件最好!

vg is Visual Graph == vgctrl.ovx
 
当然是算啦。算出来了怎么画都好画呀。我的问题主要在两点上难于解决:1)x变量不是递增的,如x= ...,1,2,3,4,6,7,6.5,5.5,5.5 2)端点的导数值如 y=...7,8
 
用的是参数方程
y=f(t)
x=f(t)
t的取值为[0,1],每两点间T的取值都是从起点0到终点1。
所以X就不需要是递增的。任意都可以。
 
wk_knife 大侠:
你说的这个方法我也用过,在x出现折回时还挺好用的,只是处理像...(3,20),(4,40),(3.5,30),(3,50)这样的数据时会有不可导的点,而你的方法好像不错。不过,你在处理类似(3,20),(5,20)(最后两个点时)他们中间的曲线会向上突起,这应该是你没有使用自然样条而人为设置端点导数所产生的。另外在闭合点你的方法好像也会产生数据的异常(我是在调试你的程序时将末端点x,y值修改得与第一个一样时发现的)。其实你的算法真的很好用,只是在两个数据点极其相近或相等时会有点问题,另外两个端点的导数值设定也是一个让我头痛的问题。还望大侠能早日回复!
 
我写了一个DLL,你试试看吧。
http://www.delphibbs.com/delphibbs/dispq.asp?lid=3154383
 
wk_knife 大侠:
这个东西不错:)不过你双击几下试试......我的意思是当有数据很接近时......
怎样能把你的DLL的代码给我学习一下呀?我最近正在学习这些数值算法和人工智能的东西不知有没有兴趣交流?
你给我那篇帖子答案嘛......好像不太好:)当程序接手dos的控制权后就不好办了。至于你说的那个我以前用过,现在客户对界面的要求高呀:(
 
如果你是说会出现尖角的话,那很正常,因为从理论上它依然是二阶连续的。
比方三次B样条就有三重角点的情况,会画出一个尖点,你在我的DEMO中在画B样条时在某点上双击就会出现这个情况。
 
而且这些函数也不是通吃的,比方张力样条只适合小挠度的点集,还应该根据需要选择。
 
函数确实不是通吃的,有时能用就行了,可是你那个DEMO的尖点好象是不可导造成的,画曲线只是为了表现数据的趋势,因为最终的目的是求是求值和导数.
 
计算的时候是浮点数,计算完后在画时是取了整的。在计算机的屏幕上效果肯定不好,但如果坐标系足够大,画出来的就不会是尖的了。
 
试过吗?曲线看上去有尖点有两种可能。一种是该点一阶导树变化很大,一种是在该点二阶不可导,好象你的曲线属于后者:)试过GDI+吗?它画的曲线真的很好,只是它只能画曲线而不能提供数据
 
其实我并不是想和你争这个问题,你的程序已经给了我许多启发,包括尖点问题有一部分确实是除数极小造成的。也有一些,比如你的程序里当最后两点y相等或最后一点x折回y增大时曲线看着很不舒服,这应该是端点导数设置的问题吧?用自然样条可能好一点,可有时在这种x和y折来折去的曲线上真的会出现拐点的:)
 
试过GDI+,但因为它太慢放弃了,尤其是反锯齿的时候,GDI+中好象是B样条和张力样条。

我试了你所说的情况。的确是这样的。尤其点比较稀的时候,不过我想你的程序是做数值分析的,比我的程序要求要高得多,我只是在矢量图里作个曲线而已。所以,你还要继续头痛了:)

我的GDI+的定义文件删掉了,所以也不能做测试,记得GDI可以通过PATH得到GDI绘图时的路径点,不知GDI+是不是也可以?
 
我刚才看了一下MSDN,应该是可以的


GraphicsPath::AddClosedCurve Method

--------------------------------------------------------------------------------

The AddClosedCurve method adds a closed cardinal spline to this path.

Syntax

Status AddClosedCurve( const Point *points,
INT count
);
Parameters

points
[in] Pointer to an array of points that define the cardinal spline. The cardinal spline is a curve that passes through each point in the array.
count
[in] Integer that specifies the number of elements in the points array.
Return Value

If the method succeeds, it returns Ok, which is an element of the Status enumeration.

If the method fails, it returns one of the other elements of the Status enumeration.



Remarks

You should keep a copy of the points array if those points will be needed later. The GraphicsPath object does not store the points passed to the AddClosedCurve method; instead, it converts the cardinal spline to a sequence of B麩er splines and stores the points that define those B麩er splines. You cannot retrieve the original array of points from the GraphicsPath object.

Example


The following example creates a GraphicsPath object path, adds a closed cardinal spline to path, and then draws path.


Hide Example

VOID Example_AddClosedCurve(HDC hdc)
{
Graphics graphics(hdc);

Point pts[] = {Point(50,50),
Point(60,20),
Point(70,100),
Point(80,50)};

GraphicsPath path;
path.AddClosedCurve(pts, 4);

// Draw the path.
Pen pen(Color(255, 255, 0, 0));
graphics.DrawPath(&pen, &path);
}



GraphicsPath::GetPathPoints Method

--------------------------------------------------------------------------------

The GetPathPoints method gets this path's array of points. The array contains the endpoints and control points of the lines and B麩er splines that are used to draw the path.

Syntax

Status GetPathPoints( Point *points,
INT count
);
Parameters

points
[out] Pointer to an array of Point objects that receives the data points. You must allocate memory for this array. You can call the GraphicsPath::GetPointCount method to determine the required size of the array. The size, in bytes, should be the return value of GraphicsPath::GetPointCount multiplied by sizeof(Point).
count
[in] Integer that specifies the number of elements in the points array. Set this parameter equal to the return value of the GraphicsPath::GetPointCount method.
Return Value

If the method succeeds, it returns Ok, which is an element of the Status enumeration.

If the method fails, it returns one of the other elements of the Status enumeration.



Remarks

A GraphicsPath object has an array of points and an array of types. Each element in the array of types is a byte that specifies the point type and a set of flags for the corresponding element in the array of points. Possible point types and flags are listed in the PathPointType enumeration.

Example


The following example creates and draws a path that has a line, a rectangle, an ellipse, and a curve. The code calls the path's GraphicsPath::GetPointCount method to determine the number of data points that are stored in the path. The code allocates a buffer large enough to receive the array of data points and passes the address of that buffer to the GetPathPoints method. Finally, the code draws each of the path's data points.


Hide Example

VOID GetPathPointsExample(HDC hdc)
{
Graphics graphics(hdc);

// Create a path that has a line, a rectangle, an ellipse, and a curve.
GraphicsPath path;

Point points[] = {
Point(200, 200),
Point(250, 240),
Point(200, 300),
Point(300, 310),
Point(250, 350)};

path.AddLine(20, 100, 150, 200);
path.AddRectangle(Rect(40, 30, 80, 60));
path.AddEllipse(Rect(200, 30, 200, 100));
path.AddCurve(points, 5);

// Draw the path.
Pen pen(Color(255, 0, 0, 255));
graphics.DrawPath(&pen, &path);

// Get the path points.
INT count = path.GetPointCount();
Point* dataPoints = new Point[count];
path.GetPathPoints(dataPoints, count);

// Draw the path's data points.
SolidBrush brush(Color(255, 255, 0, 0));
for(INT j = 0; j < count; ++j)
{
graphics.FillEllipse(
&amp;brush,
dataPoints[j].X - 3.0f,
dataPoints[j].Y - 3.0f,
6.0f,
6.0f);
}
delete [] dataPoints;
}
Color(255, 255, 0, 0)

不过这个点不是你要有所有插值点,而是GDI+用来画线的点。我想也许也是不合你用。
 
看来高手不仅是水平高而且还这么负责:)值得我学习呀!
不过我用的是DrawCurve和DrawClosedCurve,不如你给的函数灵活
代码如下:
program GDITESTDrawCurve;

uses
Windows,
Messages,
SysUtils,
GDIPAPI,
GDIPOBJ;


Procedure OnPaint(DC: HDC);
var
graphics : TGPGraphics;
Pen: TGPPen;
const
points: array[0..4] of TPoint =
((x: 0 ; y: 100),
(x: 50 ; y: 80 ),
(x: 100; y: 20 ),
(x: 150; y: 80 ),
(x: 200; y: 100));
begin
graphics := TGPGraphics.Create(DC);
Pen:= TGPPen.Create(MakeColor(255, 0, 0, 255));
graphics.DrawCurve(pen, PPoint(@points), 5);
Pen.Free;
graphics.Free;
end;


function WndProc(Wnd : HWND; message : UINT; wParam : Cardinal; lParam: Integer) : Integer; stdcall;
var
Handle: HDC;
ps: PAINTSTRUCT;
begin
case message of
WM_PAINT:
begin
Handle := BeginPaint(Wnd, ps);
OnPaint(Handle);
EndPaint(Wnd, ps);
result := 0;
end;

WM_DESTROY:
begin
PostQuitMessage(0);
result := 0;
end;

else
result := DefWindowProc(Wnd, message, wParam, lParam);
end;
end;

var
hWnd : THandle;
Msg : TMsg;
wndClass : TWndClass;
begin
wndClass.style := CS_HREDRAW or CS_VREDRAW;
wndClass.lpfnWndProc := @WndProc;
wndClass.cbClsExtra := 0;
wndClass.cbWndExtra := 0;
wndClass.hInstance := hInstance;
wndClass.hIcon := LoadIcon(0, IDI_APPLICATION);
wndClass.hCursor := LoadCursor(0, IDC_ARROW);
wndClass.hbrBackground := HBRUSH(GetStockObject(WHITE_BRUSH));
wndClass.lpszMenuName := nil;
wndClass.lpszClassName := 'GettingStarted';

RegisterClass(wndClass);

hWnd := CreateWindow(
'GettingStarted', // window class name
'Drawing Cardinal Splines', // window caption
WS_OVERLAPPEDWINDOW, // window style
Integer(CW_USEDEFAULT), // initial x position
Integer(CW_USEDEFAULT), // initial y position
Integer(CW_USEDEFAULT), // initial x size
Integer(CW_USEDEFAULT), // initial y size
0, // parent window handle
0, // window menu handle
hInstance, // program instance handle
nil); // creation parameters

ShowWindow(hWnd, SW_SHOW);
UpdateWindow(hWnd);

while(GetMessage(msg, 0, 0, 0)) do
begin
TranslateMessage(msg);
DispatchMessage(msg);
end;
end.



program GDITESTDrawClosedCurve;

uses
Windows,
Messages,
SysUtils,
GDIPAPI,
GDIPOBJ;


Procedure OnPaint(DC: HDC);
var
graphics : TGPGraphics;
pen: TGPpen;
const
points: array[0..5] of TPoint =
((x: 60 ; y: 60),
(x: 150; y: 80),
(x: 200; y: 40),
(x: 180; y: 120),
(x: 120; y: 100),
(x: 80 ; y: 160));
begin
graphics := TGPGraphics.Create(DC);
pen:= TGPPen.Create(MakeColor(255, 0, 0, 255));
graphics.DrawClosedCurve(pen, PPoint(@points), 6);
Pen.Free;
graphics.Free;
end;


function WndProc(Wnd : HWND; message : UINT; wParam : Cardinal; lParam: Integer) : Integer; stdcall;
var
Handle: HDC;
ps: PAINTSTRUCT;
begin
case message of
WM_PAINT:
begin
Handle := BeginPaint(Wnd, ps);
OnPaint(Handle);
EndPaint(Wnd, ps);
result := 0;
end;

WM_DESTROY:
begin
PostQuitMessage(0);
result := 0;
end;

else
result := DefWindowProc(Wnd, message, wParam, lParam);
end;
end;

var
hWnd : THandle;
Msg : TMsg;
wndClass : TWndClass;
begin
wndClass.style := CS_HREDRAW or CS_VREDRAW;
wndClass.lpfnWndProc := @WndProc;
wndClass.cbClsExtra := 0;
wndClass.cbWndExtra := 0;
wndClass.hInstance := hInstance;
wndClass.hIcon := LoadIcon(0, IDI_APPLICATION);
wndClass.hCursor := LoadCursor(0, IDC_ARROW);
wndClass.hbrBackground := HBRUSH(GetStockObject(WHITE_BRUSH));
wndClass.lpszMenuName := nil;
wndClass.lpszClassName := 'GettingStarted';

RegisterClass(wndClass);

hWnd := CreateWindow(
'GettingStarted', // window class name
'Drawing Cardinal Splines', // window caption
WS_OVERLAPPEDWINDOW, // window style
Integer(CW_USEDEFAULT), // initial x position
Integer(CW_USEDEFAULT), // initial y position
Integer(CW_USEDEFAULT), // initial x size
Integer(CW_USEDEFAULT), // initial y size
0, // parent window handle
0, // window menu handle
hInstance, // program instance handle
nil); // creation parameters

ShowWindow(hWnd, SW_SHOW);
UpdateWindow(hWnd);

while(GetMessage(msg, 0, 0, 0)) do
begin
TranslateMessage(msg);
DispatchMessage(msg);
end;
end.

用起来是不爽,关键是没有每一段的函数表达式!还是回到老路上吧,是不是闭合曲线不能用自然样条呀?你给的方法用节点处的一阶导数法表示那又如何表示自然样条呢?
 
我不知道自然样条是怎么一会事,不过我早上研究了研究GDI+的绘图程序,它的闭合曲线的算法是反算B样条,就是根据给定点,反算出过给定点的B样条曲线。我用GDI+的PATH求出了画线的点,结果不同于GDI,它给出的是曲线的特征多边形。
 

Similar threads

S
回复
0
查看
3K
SUNSTONE的Delphi笔记
S
S
回复
0
查看
2K
SUNSTONE的Delphi笔记
S
D
回复
0
查看
1K
DelphiTeacher的专栏
D
后退
顶部