Жирная пунктирная линия в Delphi.Canvas

Жирная пунктирная линия в Delphi.Canvas

Всем известно, что в стандартном Canvas средствами Delphi нарисовать пунктирную линию, толщиной больше единицы, невозможно. На самом деле возможно. Просто надо добавить самую малость GDI API.

Зачем нужна пунктирная линия толще единицы

Если пишете серьёзный софт, связанный с графикой, да ещё с соблюдением разных ГОСТов, СП и прочих идиотских специфических формальностей, где необходимо масштабировать, соблюдать все миллиметры, жирная пунктирная линия ой как нужна.

Про масштаб и GDI+ чуть подробнее в конце статьи.

Собственно, статья для тех, кто не хочет лезть в дебри GDI+ и хочет выжать из стандартного GDI максимум.

Почему нельзя

Потому что Delphi.Canvas по умолчанию использует так называемое косметическое перо. Оно не позволяет всяких там излишеств, зато быстро работает. Создаётся оно функцией CreatePenIndirect. Параметром функции выступает структура TLogPen. По ссылкам можно прочитать чуть больше.

Если заглянем под капот Delphi.TPen можно увидеть следующее:

Также, создать перо можно функцией CreatePen. Параметры функции имеют ровно тот же смысл, что и поля структуры TLogPen.

Чтобы использовать перо в Delphi через функции GDI API, рекомендую придерживаться следующей структуры кода. Это касается любого GDI-объекта, не только пера. Рекомендация, не значит правило, всё зависит от замысла автора и здравого смысла )))

  1. Создали hNew := Create…();
  2. Установили в контекст hOld := SelectObject(Handle, hNew);
  3. Поставили блок try … finally … end;
  4. В try … finally рисуем что угодно;
  5. В finally … end восстанавливаем сохраненное значение hOld SelectObject(Handle, hOld) и удаляем hNewDeleteObject(hNew).

Как сделать можно

Для этого надо использовать геометрическое перо, которое обладает приличным набором излишеств и работает чутка помедленней. Всех излишеств мы конечно не коснёмся, затронем лишь небольшую часть.

Для создания геометрического пера необходимо воспользоваться функцией ExtCreatePen (еще можно глянуть тут). Строго говоря, этой же функцией можно создать и косметическое перо, правда, при условии, что толщина пера будет равна единице.

PenStyleСтиль пера
WidthТолщина пера
BrushАтрибуты кисти
StyleCountДлина массива настроек стиля
StyleМассив пользовательского стиля

StyleCount и Style мы здесь использовать не будем, поэтому они будут равны 0 и nil соответственно.

Результатом будет дескриптор геометрического пера.

Стиль геометрического пера

Стиль геометрического пера формируется объединением атрибутов типа, стиля, наконечников линий и соединения линий с помощью побитового оператора OR. Например:

PenStyle := PS_GEOMETRIC OR PS_DOT OR PS_ENDCAP_FLAT OR PS_JOIN_MITER.

Тип пера

Мы не будем создавать косметическое перо этой функцией. Это параметр у нас будет равен всегда PS_GEOMETRIC.

Стили пера

Стиль пера может быть одним из следующих значений.

PS_SOLIDСплошная линя.
PS_DASHЛиния из отрезков.
PS_DOTЛиния из точек.
PS_DASHDOTЧередование тире и точки.
PS_DASHDOTDOTЧередование тире и двойной точки.
PS_NULLНевидимая линия.
PS_INSIDEFRAMEСплошное перо. Если этот перо используется в любой функции рисования GDI, которая принимает ограничивающий прямоугольник, размеры фигуры сжимаются таким образом, чтобы он полностью вписывался в ограничивающий прямоугольник, учитывая ширину пера. Это относится только к геометрическим перьям.
PS_USERSTYLEПеро использует массив стилей, предоставленный пользователем.
PS_ALTERNATE
Стиль используется только косметическим пером, и определяется набором пикселов.

Типы наконечников линий

Позволяет задать поведение на концах отрезков, из которых состоит линия.

PS_ENDCAP_ROUNDК окончанию линии добавляется полукруг диаметром, равным ширине линии. На рис.1 указан «граничный» прямоугольник. Наконечники не равные FLAT выходят за пределы заданного прямоугольника на половину ширины линии. Если важно строгое соблюдение границ, этот факт надо учитывать.
PS_ENDCAP_SQUAREКонцы отрезков расширяются половиной квадрата, сторона которого равна ширине линии.
PS_ENDCAP_FLATКонцы отрезков просто отсекаются
Рис.1. Типы наконечников линий

Типы соединения линий

Определяет, как линии состыкуются друг с другом.

PS_JOIN_BEVELУсечённое соединение.
PS_JOIN_ROUNDЗакруглённое соединение.
PS_JOIN_MITERСоединение линий образует угол. Остриё ограничивается функцией SetMiterLimit . Если значение превышает это ограничение, соединение будет обрезано.
При превышении углового предела MiterLimit угол отсекается полностью, превращаясь в BEVEL-соединение.
Рис.2. Типы соединения линий и типы наконечников

Угловой предел MiterLimit

Очень нужный и по факту нелогичный параметр. При MITER-соединении линии могут образовать очень острый угол, который может испортить всю картину. Поэтому наличие такого параметра сильно напрашивается. Но нелогичность заключается в том, что при превышении этого параметра, отбрасывается вся «угловая» часть, обрубая все под BEVEL. Логичней было бы обрубать под величину этого параметра.

Рис.3. Угловой параметр

Угловой предел — это максимальное отношение длины заострения к толщине линии. При превышении этого параметра MITER превратится в отсеченный BEVEL. Без компромиссов. Сказал, как отрезал.

Слева угловой предел равен 1, справа — 2. Правой стрелке повезло больше.

Устанавливается функцией:

Всегда можно поинтересоваться текущим угловым пределом функцией:

Угловой предел по умолчанию равен 10.

Атрибуты кисти

Представляет собой структуру TLogBrush. Мы не будем сейчас использовать все возможности. Давайте рассмотрим два режима — заливка цветом и заливка растром.

Чтобы указать перу цвет заливки необходимо сделать следующее:

Чтобы указать перу, что заливка будет происходить растром, делаем так:

Функция создания геометрического пера

Как видим, всё до умопомрачения просто. Теперь как использовать это сокровище.

Геометрическое перо и Canvas

Предположим, у нас есть список точек

И мы хотим его нарисовать в виде ломаной с заданными параметрами цвета (ColorBox1), толщины (SpinEdit1), стиля пера (ComboBox1), а также типами соединения (ComboBox2) и окончания (ComboBox3) отрезков. В особом случае (WithPattern.Checked) , вместо цвета заливки используется подготовленный ранее растр.

Работа с кэшированным растром происходит следующим образом. При изменении размера окна, растр уничтожается. При обращении к методу, он создаётся, если это требуется.

Отрисовка происходит в обработчике события OnPaint компонента pb: TPaintBox.

Таким образом, суммируя всё вышесказанное, можно написать такой небольшой код. Небольшой, если убрать все комментарии. Их тут больше, чем кода.

Что тут есть хорошего. Хорошее то, что мы рисуем привычными методами Canvas’а, просто подменили косметическое перо на геометрическое.

Комментарии к программе

Зачем переключатель ToArray

Чтобы увидеть прелести тонкой настройки геометрического пера, необходимо рисовать одной линией. Например, так:

И тогда, с заданными настройками, увидим то, что и ожидали увидеть — заострённые уголки, отрезки равной длины:

Рис.4. Ломаная одной функцией

Если же мы будем рисовать отрезками, как, например тут:

То увидим не совсем то, что ожидалось. И возникают вопросы, типа: «Я сделала всё так, как вы сказали, но у меня не получается». Поэтому заостряю тут внимание — что сделали, то и получили. В этом случае каждая отдельная линия — это прямая, которая ничего не знает про другие линии ломаной, с которыми надо соединяться. Параметр LineJoin, равный Miter, при таком подходе не значит ровным счётом ничего.

Также, размеры «тире» тут непредсказуемы, в отличие от «сплошной» ломаной.

Рис.5. Ломаная отрезками

Как нарисовать свою ломаную

Вот прямо сейчас кликаем в окне. Текущая линия сбросится и за курсором потянулась линия — клик, вершина зафиксировалась, двигаем курсор мыши дальше. Чтобы закончить линию, необходимо снова кликнуть в её последнюю точку, либо двойной клик, либо нажать Enter. Если нажать Esc, поле очистится.

Пока линия строится, она красного цвета.

Что это за картинка снизу

Это изображение, которое можно использовать в качестве заливки линии, вместо цвета. Для того, чтобы выбрать изображение в качестве заливки, необходимо установит галку в WithPattern.

Если угрюмая постапокалиптическая картинка не нравится, заходим в браузер, находим жизнерадостную картинку, копируем, вызываем на картинке в программе правой кнопкой мыши контекстное меню и жмём «Paste«. Новая картинка вставится.

Что такое CrazyMouse

Это когда линия формируется без кликов, на каждое движение мышки. То есть получается не ломаная, а прям произвольная линия, как бы от руки.

Допустим, мы выбрали картинку и теперь хотим сделать какой-нибудь эффект, наподобие этого:

Рис.6. Баловство 1. Рисуем ломаную, как единый объект

Если снять галку с ToArray, то будет по другому. И ещё не факт, что первое баловство лучше.

Рис.7. Баловство 2. Рисуем отрезками.

Для любознательных: Как строится ломаная

На PaintBox’е отрабатываем два события: OnMouseDown и OnMouseMove.

Как строится ломаная

Приватное поле формы FFinish: Boolean отвечает на вопрос — строится ли сейчас линия или уже построена. Если True, значит уже построена.

[свернуть]

Что будет если применить масштаб к косметическому перу

Давайте не будем сами считать толщину и прочую самодеятельность. Для масштабирования есть аффинное преобразование масштаба. Как применять аффинное преобразование в GDI можно подсмотреть тут. Правда, там преобразование поворота. Но суть понятна.

Преобразование применяется к точке начала координат (0,0), поэтому никаких дополнительных преобразований не нужно, просто масштаб:

Перед рисованием ломаной вставляем такой код (перед try):

А в блоке finally добавляем следующее:

В контекстном меню на поле отрисовки можно выбрать масштаб. Получаем следующее:

Рис.8. Косметическое перо, толщина 1, масштаб не применяется
Рис.9. Косметическое перо, толщина 1, масштаб x3

Как видно на рисунке 9, толщина пера по-прежнему 1, и по идее он должен рисоваться пунктиром, но нет, при масштабировании он рисуется сплошной линией. GDI API не обманешь.

Рис.10. Геометрическое перо, толщина 1, масштаб x3

А вот геометрическое перо спокойной переносит смену масштаба.

Для получения такого приятного бонуса, как жирная пунктирная линия, люди прикручивают GDI+. В этом есть смысл, потому что главный недостаток всего GDI — это целочисленные параметры. А в GDI+ координаты — это вещественные числа. И работает он только с вещественными числами. Просто не включаем анти-алиас, чтобы не тормозило, и пользуем GDI+.

Если мы в GDI применим дробный масштаб, то рискуем получить не сильно привлекательный результат:

Рис.11. Геометрическое перо, толщина 1, масштаб x1.5

Поэтому умеет смысл поглядеть в сторону GDI+, там всё чётко и ровно при любом масштабе.

А где MiterLimit?

А нету ))) Показалось лишним в этом контексте. Качайте, экспериментируйте. Исходник очень маленький, пространство для экспериментов огромное.

Один мужик сказал в коворкинге «Тот случай дал мне ценный опыт» вместо «Тот кейс дал мне ценный экспириенс» и его тут же осмеяли, облили смузи и перевели в чуханы


Скачать

Друзья, спасибо за внимание!

Исходник (zip) 231 Кб. Delphi XE 7, XE 10, XE 11

Исполняемый файл (zip) 1.06 Мб.

Друзья! Буду чрезвычайно признателен за комментарии, регистрацию на сайте, подписку в телеге. Это очень значимая моральная поддержка для меня.


5 6 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
0
Не нашли ответ на свой вопрос? Задайте его здесь!...x