Жирная пунктирная линия в Canvas Delphi при Width >1
Почему Canvas.Pen.Style (psDash, psDot) не работает для толстых линий в Delphi? Решения: ExtCreatePen с PS_GEOMETRIC, GDI+, имитация параллельными линиями. Примеры кода для Canvas.Polyline([P1, P2]).
Можно ли использовать 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 Delphi?
- Как сделать пунктирную линию толщиной >1 с ExtCreatePen?
- Жирная пунктирная линия через GDI+ в Delphi
- Имитация толстого пунктира несколькими линиями
- Canvas.Polyline([P1, P2]) против альтернатив
- psUserStyle и кастомные паттерны для пунктирной линии
- Источники
- Заключение
Можно ли использовать 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 на свой.
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.
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) пунктирных линий параллельно. Смещайте на перпендикуляр.
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 с массивом.
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 показывает: меняйте массив для жирной пунктирной линии любого паттерна. Гибко, масштабируемо.
Источники
- DelphiMaster — Обсуждение стилей Pen и имитации толстых пунктирных линий в Canvas: http://delphimaster.net/view/1-93508
- 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
- Stack Overflow (Dash size) — Кастомные паттерны с psUserStyle и массивами: https://stackoverflow.com/questions/3123667/is-it-possible-to-change-the-size-of-a-dash-of-a-line
- Киберфорум — Альтернативы GDI для не-сплошных линий в Delphi Canvas: https://www.cyberforum.ru/delphi-multimedia/thread1285063.html
- 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 остается мощным, если знать трюки.
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]).
В 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)).
Для кастомной пунктирной линии используйте Canvas.Pen.Style := psUserStyle с ExtCreatePen(PS_GEOMETRIC | PS_USERSTYLE, Width, TLogBrush, NumberOfSections, @LineLengths), где LineLengths — массив длин дашей (например, [20,15,14,17,...]). Это позволяет менять размер даша в жирной пунктирной линии для Canvas.Polyline. Документация: Embarcadero TCanvas и MSDN ExtCreatePen.
В Delphi canvas pen и brush.style не меняются стандартно для не-сплошных стилей при большой ширине; используйте GDI-функции (ExtCreatePen) или имитацию через несколько линий. Ссылки на похожие темы: рисование произвольной линии, Pen.Pos для координат Image. Для Polyline([P1,P2]) подойдет комбинация MoveTo/LineTo с кастомным пеном.