如何增加一条直线上各点之间的间距?
How to increase the spacing between points in a line?
使用标准笔(PS_DOT)画线时,结果如下图(放大)
这条线对我来说有两个问题,第一个是,它为一个点设置了多个像素。第二个是,对于我想做的事情,这些点靠得太近了(画一条非常柔和的线。)
我可以使用 SetPixel,但是性能还有很多不足之处。
我的问题是:是否有一种合理快速的绘制线的方法,可以控制用于绘制点的像素数和点之间的间距?
本质上,这是一种比使用 SetPixel 更快的方法(如果它不是那么慢,可以用来解决问题。)
用 C、C++ 或 Delphi 展示如何完成的代码片段会很棒。
感谢您的帮助。
编辑: 我尝试了 IInspectable 使用 ExtCreatePen 的回答,结果非常接近。似乎获得 pixels/dots 而不是破折号的唯一方法是使用 PS_ALTERNATE 但是,当使用它时,无法指定间距。
作为参考,以防万一我犯了一个我没有看到的错误,下面是我写的测试程序。我想要的是 1 个点(不是破折号)后跟 2 个空格的重复序列。我从测试程序得到的输出如下(放大)所示:(顶行是使用 PS_ALTERNATE 获得的,底行使用指定 1 个点、2 个空格的数组 - 给出 2 个点和 2 个空格。 )
测试程序:
{$APPTYPE GUI}
{$LONGSTRINGS OFF}
{$WRITEABLECONST ON}
program _ExtCreatePen;
uses Windows, Messages;
const
AppName = 'ExtCreatePen';
{$ifdef VER90} { Delphi 2.0 }
type
ptrint = longint; // NativeInt for newer versions
ptruint = dword; // NativeUint " " "
{$endif}
{-----------------------------------------------------------------------------}
function WndProc (Wnd : HWND; Msg : UINT; wParam, lParam : ptrint)
: ptrint; stdcall;
const
PenPattern : packed array[1..4] of DWORD = (1, 2, 1, 2); { 1 dot, 2 spaces}
PenBrush : TLOGBRUSH = (lbStyle:BS_SOLID; lbColor:0; lbHatch:0);
PenWidth : DWORD = 1;
{ !! this doesn't seem to work as expected, expected 1 dot, 2 spaces !! }
PenStyle : DWORD = PS_COSMETIC or PS_USERSTYLE;
StyleCount : DWORD = high(PenPattern);
StylePattern : PDWORD = @PenPattern[low(PenPattern)];
{ this gives 1 dot, 1 space. }
//PenStyle : DWORD = PS_COSMETIC or PS_ALTERNATE;
//StyleCount : DWORD = 0;
//StylePattern : PDWORD = nil;
Pen : HPEN = 0;
var
ps : TPAINTSTRUCT;
ClientRect : TRECT;
begin
WndProc := 0;
case Msg of
WM_CREATE:
begin
PenBrush.lbColor := RGB(255, 0, 0);
Pen := ExtCreatePen(PenStyle,
PenWidth,
PenBrush,
StyleCount,
StylePattern);
exit;
end;
WM_PAINT:
begin
BeginPaint(Wnd, ps);
GetClientRect(Wnd, ClientRect);
SelectObject(ps.hdc, Pen); { use the pen we created }
MoveToEx(ps.hdc, 0, ClientRect.Bottom div 2, nil);
LineTo(ps.hdc, ClientRect.Right, ClientRect.Bottom div 2);
EndPaint(Wnd, ps);
exit;
end;
WM_CLOSE: PostMessage(Wnd, WM_DESTROY, 0, 0);
WM_DESTROY:
begin
if Pen <> 0 then DeleteObject(Pen);
PostQuitMessage(0);
exit;
end; { WM_DESTROY }
end; { case msg }
WndProc := DefWindowProc (Wnd, Msg, wParam, lParam);
end;
{-----------------------------------------------------------------------------}
function InitAppClass: WordBool;
{ registers the application's window classes }
var
cls : TWndClassEx;
begin
cls.cbSize := sizeof(TWndClassEx); { must be initialized }
if not GetClassInfoEx (hInstance, AppName, cls) then
begin
with cls do
begin
{ cbSize has already been initialized as required above }
style := CS_BYTEALIGNCLIENT;
lpfnWndProc := @WndProc; { window class handler }
cbClsExtra := 0;
cbWndExtra := 0;
hInstance := system.hInstance;
hIcon := LoadIcon (hInstance, IDI_APPLICATION);
hCursor := LoadCursor(0, IDC_ARROW);
hbrBackground := COLOR_WINDOW + 1;
lpszMenuName := nil;
lpszClassName := AppName; { Window Class name }
hIconSm := 0;
end; { with }
InitAppClass := WordBool(RegisterClassEx(cls));
end
else InitAppClass := TRUE;
end;
{-----------------------------------------------------------------------------}
function WinMain : integer;
{ application entry point }
var
Wnd : hWnd;
Msg : TMsg;
begin
if not InitAppClass then Halt (255); { register application's class }
{ Create the main application window }
Wnd := CreateWindowEx(WS_EX_CLIENTEDGE,
AppName, { class name }
AppName, { window caption text }
ws_Overlapped or { window style }
ws_SysMenu or
ws_MinimizeBox or
ws_ClipSiblings or
ws_ClipChildren or { don't affect children }
ws_visible, { make showwindow unnecessary }
20, { x pos on screen }
20, { y pos on screen }
400, { window width }
200, { window height }
0, { parent window handle }
0, { menu handle 0 = use class }
hInstance, { instance handle }
nil); { parameter sent to WM_CREATE }
if Wnd = 0 then Halt; { could not create the window }
while GetMessage (Msg, 0, 0, 0) do { wait for message }
begin
TranslateMessage (Msg); { key conversions }
DispatchMessage (Msg); { send to window procedure }
end;
WinMain := Msg.wParam; { terminate with return code }
end;
begin
WinMain;
end.
如果需要控制一行中的破折号和空格的长度,调用ExtCreatePen函数创建PS_USERSTYLE
笔。这允许您指定一个最多包含 16 个条目的数组,每个条目指定破折号或间隙的长度。
您的代码没有任何问题。正如您所描述的那样,我花了一些时间来实现 GDI 自定义虚线。
在 GDI 中,自定义虚线以破折号绘制,如 MSDN 中所述。
If dwPenStyle
is PS_COSMETIC
and PS_USERSTYLE
, the entries in the lpStyle
array specify lengths of dashes and spaces in style units. A style unit is defined by the device where the pen is used to draw a line.
If dwPenStyle
is PS_GEOMETRIC
and PS_USERSTYLE
, the entries in the
lpStyle
array specify lengths of dashes and spaces in logical units.
这个功能可以在GDI+中实现,正如@Michael Chourdakis开头提到的
使用标准笔(PS_DOT)画线时,结果如下图(放大)
这条线对我来说有两个问题,第一个是,它为一个点设置了多个像素。第二个是,对于我想做的事情,这些点靠得太近了(画一条非常柔和的线。)
我可以使用 SetPixel,但是性能还有很多不足之处。
我的问题是:是否有一种合理快速的绘制线的方法,可以控制用于绘制点的像素数和点之间的间距?
本质上,这是一种比使用 SetPixel 更快的方法(如果它不是那么慢,可以用来解决问题。)
用 C、C++ 或 Delphi 展示如何完成的代码片段会很棒。
感谢您的帮助。
编辑: 我尝试了 IInspectable 使用 ExtCreatePen 的回答,结果非常接近。似乎获得 pixels/dots 而不是破折号的唯一方法是使用 PS_ALTERNATE 但是,当使用它时,无法指定间距。
作为参考,以防万一我犯了一个我没有看到的错误,下面是我写的测试程序。我想要的是 1 个点(不是破折号)后跟 2 个空格的重复序列。我从测试程序得到的输出如下(放大)所示:(顶行是使用 PS_ALTERNATE 获得的,底行使用指定 1 个点、2 个空格的数组 - 给出 2 个点和 2 个空格。 )
测试程序:
{$APPTYPE GUI}
{$LONGSTRINGS OFF}
{$WRITEABLECONST ON}
program _ExtCreatePen;
uses Windows, Messages;
const
AppName = 'ExtCreatePen';
{$ifdef VER90} { Delphi 2.0 }
type
ptrint = longint; // NativeInt for newer versions
ptruint = dword; // NativeUint " " "
{$endif}
{-----------------------------------------------------------------------------}
function WndProc (Wnd : HWND; Msg : UINT; wParam, lParam : ptrint)
: ptrint; stdcall;
const
PenPattern : packed array[1..4] of DWORD = (1, 2, 1, 2); { 1 dot, 2 spaces}
PenBrush : TLOGBRUSH = (lbStyle:BS_SOLID; lbColor:0; lbHatch:0);
PenWidth : DWORD = 1;
{ !! this doesn't seem to work as expected, expected 1 dot, 2 spaces !! }
PenStyle : DWORD = PS_COSMETIC or PS_USERSTYLE;
StyleCount : DWORD = high(PenPattern);
StylePattern : PDWORD = @PenPattern[low(PenPattern)];
{ this gives 1 dot, 1 space. }
//PenStyle : DWORD = PS_COSMETIC or PS_ALTERNATE;
//StyleCount : DWORD = 0;
//StylePattern : PDWORD = nil;
Pen : HPEN = 0;
var
ps : TPAINTSTRUCT;
ClientRect : TRECT;
begin
WndProc := 0;
case Msg of
WM_CREATE:
begin
PenBrush.lbColor := RGB(255, 0, 0);
Pen := ExtCreatePen(PenStyle,
PenWidth,
PenBrush,
StyleCount,
StylePattern);
exit;
end;
WM_PAINT:
begin
BeginPaint(Wnd, ps);
GetClientRect(Wnd, ClientRect);
SelectObject(ps.hdc, Pen); { use the pen we created }
MoveToEx(ps.hdc, 0, ClientRect.Bottom div 2, nil);
LineTo(ps.hdc, ClientRect.Right, ClientRect.Bottom div 2);
EndPaint(Wnd, ps);
exit;
end;
WM_CLOSE: PostMessage(Wnd, WM_DESTROY, 0, 0);
WM_DESTROY:
begin
if Pen <> 0 then DeleteObject(Pen);
PostQuitMessage(0);
exit;
end; { WM_DESTROY }
end; { case msg }
WndProc := DefWindowProc (Wnd, Msg, wParam, lParam);
end;
{-----------------------------------------------------------------------------}
function InitAppClass: WordBool;
{ registers the application's window classes }
var
cls : TWndClassEx;
begin
cls.cbSize := sizeof(TWndClassEx); { must be initialized }
if not GetClassInfoEx (hInstance, AppName, cls) then
begin
with cls do
begin
{ cbSize has already been initialized as required above }
style := CS_BYTEALIGNCLIENT;
lpfnWndProc := @WndProc; { window class handler }
cbClsExtra := 0;
cbWndExtra := 0;
hInstance := system.hInstance;
hIcon := LoadIcon (hInstance, IDI_APPLICATION);
hCursor := LoadCursor(0, IDC_ARROW);
hbrBackground := COLOR_WINDOW + 1;
lpszMenuName := nil;
lpszClassName := AppName; { Window Class name }
hIconSm := 0;
end; { with }
InitAppClass := WordBool(RegisterClassEx(cls));
end
else InitAppClass := TRUE;
end;
{-----------------------------------------------------------------------------}
function WinMain : integer;
{ application entry point }
var
Wnd : hWnd;
Msg : TMsg;
begin
if not InitAppClass then Halt (255); { register application's class }
{ Create the main application window }
Wnd := CreateWindowEx(WS_EX_CLIENTEDGE,
AppName, { class name }
AppName, { window caption text }
ws_Overlapped or { window style }
ws_SysMenu or
ws_MinimizeBox or
ws_ClipSiblings or
ws_ClipChildren or { don't affect children }
ws_visible, { make showwindow unnecessary }
20, { x pos on screen }
20, { y pos on screen }
400, { window width }
200, { window height }
0, { parent window handle }
0, { menu handle 0 = use class }
hInstance, { instance handle }
nil); { parameter sent to WM_CREATE }
if Wnd = 0 then Halt; { could not create the window }
while GetMessage (Msg, 0, 0, 0) do { wait for message }
begin
TranslateMessage (Msg); { key conversions }
DispatchMessage (Msg); { send to window procedure }
end;
WinMain := Msg.wParam; { terminate with return code }
end;
begin
WinMain;
end.
如果需要控制一行中的破折号和空格的长度,调用ExtCreatePen函数创建PS_USERSTYLE
笔。这允许您指定一个最多包含 16 个条目的数组,每个条目指定破折号或间隙的长度。
您的代码没有任何问题。正如您所描述的那样,我花了一些时间来实现 GDI 自定义虚线。
在 GDI 中,自定义虚线以破折号绘制,如 MSDN 中所述。
If
dwPenStyle
isPS_COSMETIC
andPS_USERSTYLE
, the entries in thelpStyle
array specify lengths of dashes and spaces in style units. A style unit is defined by the device where the pen is used to draw a line.If
dwPenStyle
isPS_GEOMETRIC
andPS_USERSTYLE
, the entries in thelpStyle
array specify lengths of dashes and spaces in logical units.
这个功能可以在GDI+中实现,正如@Michael Chourdakis开头提到的