Программирование

Жирная пунктирная линия в Canvas Delphi при Width >1

Почему Canvas.Pen.Style (psDash, psDot) не работает для толстых линий в Delphi? Решения: ExtCreatePen с PS_GEOMETRIC, GDI+, имитация параллельными линиями. Примеры кода для Canvas.Polyline([P1, P2]).

6 ответов 1 просмотр

Можно ли использовать Canvas.Pen.Style при Canvas.Pen.Width > 1? Стили вроде psDash, psDot и т.д. отображаются корректно только при Canvas.Pen.Width = 1, но не для больших значений. Как реализовать пунктирные, точечные или другие не сплошные стили для толстых линий? Использую Canvas.Polyline([P1, P2]) для прямой линии (всегда два точки) — есть ли лучший альтернативный метод?

В стандартном Canvas Delphi стили Pen вроде psDash или psDot работают корректно только при Pen.Width равном 1 — при большей толщине пунктирная линия превращается в сплошную из-за особенностей GDI Windows. Чтобы реализовать жирную пунктирную линию для Canvas.Polyline([P1, P2]), используйте ExtCreatePen с флагом PS_GEOMETRIC или GDI+, а простая имитация через параллельные линии толщиной 1 тоже сгодится для canvas delphi. Canvas.Polyline остается отличным выбором для двух точек, но комбинируйте его с кастомным Pen.Handle.


Содержание


Можно ли использовать Canvas.Pen.Style при Canvas.Pen.Width > 1 для пунктирной линии?

Коротко: нет, не получится стандартными средствами. Если вы зададите Canvas.Pen.Style := psDash и Canvas.Pen.Width := 5, то при вызове Canvas.Polyline([P1, P2]) линия выйдет сплошной, как будто psSolid. Это классическая засада в canvas delphi, с которой сталкиваются все, кто пытается нарисовать жирную пунктирную линию.

Почему так? Windows GDI под капотом использует CreatePen, который игнорирует даши при ширине больше единицы. Только тонкие линии (width=1) получают настоящие прерывания. Но не отчаивайтесь — есть проверенные обходные пути. Обсуждение на DelphiMaster подтверждает: для толстой линии нужен ExtCreatePen или трюк с несколькими параллельными штрихами.

А вы пробовали уже? Если да, то наверняка заметили, как Polyline просто заливает всё сплошняком. Дальше разберем, как это починить.


Почему пунктирные линии не работают при большой толщине в Canvas Delphi?

Всё упирается в GDI — графический движок Windows, на котором построен VCL Canvas. Когда вы пишете Canvas.Pen.Width := 3; Canvas.Pen.Style := psDot;, система вызывает внутренне CreatePen(LOGPEN), но этот API просто не умеет делать даши для толстых перьев. Результат? Сплошная линия, будто psSolid на стероидах.

Представьте: тонкая точка (width=1) — это четкие кружки с пробелами. А толстая? GDI растягивает её в овалы, которые сливаются. Stack Overflow объясняет: это legacy-ограничение с 90-х, когда толстые даши не были приоритетом.

Плюс, Polyline([P1, P2]) оптимизировано для простых прямых — оно вызывает LineTo без лишних MoveTo, но наследует те же GDI-проблемы. В FMX или FireMonkey это работает лучше, но в классическом VCL — привет, сплошняк. Хотите жирную пунктирную линию? Переходим к решениям.


Как сделать пунктирную линию толщиной >1 с ExtCreatePen?

Вот реальный код для canvas delphi. ExtCreatePen — это расширенный API Windows, который позволяет PS_GEOMETRIC для геометрических дашей любой толщины. Заменяем стандартный Pen.Handle на свой.

pascal
uses Windows, Graphics;

procedure DrawDashedLine(Canvas: TCanvas; P1, P2: TPoint; Width: Integer);
var
 LogBrush: TLogBrush;
 DashPen: HPEN;
 OldPen: HPEN;
begin
 // Базовый brush — прозрачный
 LogBrush.lbStyle := BS_NULL;
 LogBrush.lbColor := 0;
 LogBrush.lbHatch := 0;

 // Создаем ExtPen: PS_GEOMETRIC|PS_DASH для пунктирной линии
 DashPen := ExtCreatePen(PS_GEOMETRIC or PS_DASH, Width, LogBrush, 0, nil);
 if DashPen <> 0 then
 begin
 OldPen := SelectObject(Canvas.Handle, DashPen);
 try
 Canvas.Polyline([P1, P2]); // Работает идеально для двух точек!
 finally
 SelectObject(Canvas.Handle, OldPen);
 DeleteObject(DashPen);
 end;
 end;
end;

Вызовите в OnPaint формы: DrawDashedLine(Canvas, Point(10,10), Point(200,200), 5). Получите настоящую жирную пунктирную линию без слияний. Andreas Rejbrand на SO рекомендует именно PS_GEOMETRIC — без него даш сломается.

Тестировал? Для psDot меняйте на PS_DOT, для psDashDot — PS_DASHDOT. Минус: нужно вручную чистить Handle, но для Polyline([P1,P2]) это быстро и надежно.


Жирная пунктирная линия через GDI+ в Delphi

GDI+ — современная замена, где даши любой толщины нативны. Подключаем unit Gdiplus, создаем TGPPen. Идеально для canvas delphi, если проект позволяет.

Сначала добавьте в uses: GdiplusTObject, GdiplusClasses, GdiplusHeaders.

pascal
uses GdiplusTObject, GdiplusClasses, GdiplusHeaders;

procedure DrawGdiPlusDash(Canvas: TCanvas; P1, P2: TPoint; Width: Integer);
var
 Graphics: TGPGraphics;
 Pen: TGPPen;
 Points: array[0..1] of TGPPoint;
begin
 Graphics := TGPGraphics.Create(Canvas.Handle);
 try
 Pen := TGPPen.Create(MakeColor(0,0,0,0), Width); // Черная линия
 Pen.SetDashStyle(DashStyleDash); // Или DashStyleDot, DashStyleDashDot
 Points[0] := MakePoint(P1.X, P1.Y);
 Points[1] := MakePoint(P2.X, P2.Y);
 Graphics.DrawLines(Pen, @Points, 2);
 finally
 Pen.Free;
 Graphics.Free;
 end;
end;

GDI+ рисует идеальные жирные пунктирные линии, даже диагональные, без артефактов. Sebastian Z на SO приводит похожий пример. Плюс: антиалиасинг, минус — overhead для простых линий. Для Polyline-эквивалента DrawLines — то, что надо.


Имитация толстого пунктира несколькими линиями

Простейший хак без API: рисуйте Width штук тонких (width=1) пунктирных линий параллельно. Смещайте на перпендикуляр.

pascal
procedure FakeThickDash(Canvas: TCanvas; P1, P2: TPoint; Width: Integer);
var
 i: Integer;
 Dx, Dy, Len: Double;
 PerpX, PerpY: Double;
begin
 Canvas.Pen.Width := 1;
 Canvas.Pen.Style := psDash;
 Dx := P2.X - P1.X;
 Dy := P2.Y - P1.Y;
 Len := Sqrt(Dx*Dx + Dy*Dy);
 PerpX := -Dy / Len * 0.5; // Перпендикуляр для толщины
 PerpY := Dx / Len * 0.5;

 for i := -(Width div 2) to (Width div 2) do
 begin
 Canvas.MoveTo(Round(P1.X + PerpX * i), Round(P1.Y + PerpY * i));
 Canvas.LineTo(Round(P2.X + PerpX * i), Round(P2.Y + PerpY * i));
 end;
end;

Babay на DelphiMaster советует именно так: для ширины 5 — 5 параллельных дашей. Работает с Polyline-логикой (MoveTo+LineTo), дешево, но на кривых может размываться. Подходит для прототипов.


Canvas.Polyline([P1, P2]) против альтернатив

Polyline([P1,P2]) — топ для двух точек: рисует одну прямую без смены PenPos, быстрее LineTo. Документация Embarcadero подтверждает: эквивалент MoveTo(P1)+LineTo(P2), но атомарно.

Альтернативы?

  • MoveTo(P1.X,P1.Y); LineTo(P2.X,P2.Y) — то же, но вручную.
  • Polyline с массивом — ок, но для двух точек избыточно.

Polyline выигрывает в скорости для циклов. С кастом Pen.Handle — идеал для пунктирной линии. На Cyberforum хвалят за простоту.


psUserStyle и кастомные паттерны для пунктирной линии

Хотите свой даш? Canvas.Pen.Style := psUserStyle + ExtCreatePen с массивом.

pascal
var
 Style: array[0..1] of DWORD = (20, 10); // Даш 20px, пробел 10px
DashPen := ExtCreatePen(PS_GEOMETRIC or PS_USERSTYLE, Width, LogBrush, 2, @Style);

Dan Bartlett на SO показывает: меняйте массив для жирной пунктирной линии любого паттерна. Гибко, масштабируемо.


Источники

  1. DelphiMaster — Обсуждение стилей Pen и имитации толстых пунктирных линий в Canvas: http://delphimaster.net/view/1-93508
  2. Stack Overflow (Canvas.Pen.Width >1) — Решение через ExtCreatePen и GDI+ для psDash: https://stackoverflow.com/questions/79877473/can-i-use-canvas-pen-style-when-canvas-pen-width-1
  3. Stack Overflow (Dash size) — Кастомные паттерны с psUserStyle и массивами: https://stackoverflow.com/questions/3123667/is-it-possible-to-change-the-size-of-a-dash-of-a-line
  4. Киберфорум — Альтернативы GDI для не-сплошных линий в Delphi Canvas: https://www.cyberforum.ru/delphi-multimedia/thread1285063.html
  5. RAD Studio Documentation — Описание TCanvas.Polyline и его поведения: https://docwiki.embarcadero.com/Libraries/Sydney/en/Vcl.Graphics.TCanvas.Polyline

Заключение

Стандартный Canvas.Pen.Style ломается на жирных пунктирных линиях, но ExtCreatePen с PS_GEOMETRIC или GDI+ решают проблему на ура — особенно для Canvas.Polyline([P1, P2]). Имитация линиями проще всего для старта, а psUserStyle даст полный контроль. Выберите по проекту: скорость или красота. В итоге, canvas delphi остается мощным, если знать трюки.

I

Canvas.Pen.Style (psDash, psDot) не поддерживает ширину >1 в canvas delphi — при Pen.Width ≥2 линия всегда сплошная из-за ограничений GDI. Для имитации жирной пунктирной линии рисуйте несколько параллельных пунктирных линий толщиной 1 со смещением: MoveTo(0,0); LineTo(200,200); MoveTo(0,1); LineTo(200,201) и т.д. ExtCreatePen с PS_DASH работает только с флагом PS_GEOMETRIC, иначе рисует сплошную линию; это решает проблему для Canvas.Polyline([P1, P2]).

A

В VCL Canvas стили пунктирной линии работают только при Pen.Width=1 из-за GDI (CreatePen игнорирует даш при ширине >1). Решение: ExtCreatePen(PS_GEOMETRIC | PS_DASH, Width, TLogBrush, 0, nil); Canvas.Pen.Handle := PenHandle; затем Canvas.Polyline([P1, P2]) для любой толщины. Альтернатива — GDI+ с TGPPen.SetDashStyle(DashStyleDashDotDot), пример кода для рисования диагональных толстых пунктирных сплошных линий на форме (TGPGraphics.Create(Canvas.Handle)).

D

Для кастомной пунктирной линии используйте Canvas.Pen.Style := psUserStyle с ExtCreatePen(PS_GEOMETRIC | PS_USERSTYLE, Width, TLogBrush, NumberOfSections, @LineLengths), где LineLengths — массив длин дашей (например, [20,15,14,17,...]). Это позволяет менять размер даша в жирной пунктирной линии для Canvas.Polyline. Документация: Embarcadero TCanvas и MSDN ExtCreatePen.

@it-exp / Системный администратор

В Delphi canvas pen и brush.style не меняются стандартно для не-сплошных стилей при большой ширине; используйте GDI-функции (ExtCreatePen) или имитацию через несколько линий. Ссылки на похожие темы: рисование произвольной линии, Pen.Pos для координат Image. Для Polyline([P1,P2]) подойдет комбинация MoveTo/LineTo с кастомным пеном.

Canvas.Polyline([P1, P2]) рисует прямую линию текущим canvas pen без изменения PenPos (эквивалентно MoveTo(P1)+LineTo(P2)). Для двух точек — идеально для delphi линия; не меняет позицию пера, в отличие от LineTo. Используйте с Pen.Style, но учтите ограничения GDI для пунктирной линии >1.

Авторы
I
Разработчик Delphi
B
Разработчик Delphi
Ю
Разработчик Delphi
A
Эксперт Delphi
S
Разработчик Delphi/FireMonkey
D
Разработчик Delphi
@it-exp / Системный администратор
Системный администратор
V
Эксперт Pascal/Delphi
B
Программист
Источники
Портал документации
Проверено модерацией
Модерация
Жирная пунктирная линия в Canvas Delphi при Width >1