Пользовательский стиль пера. Часть II: Бегущие муравьи

Это продолжение разговора, начатого в первой части, про эффект рамки «бегущие муравьи». Было также сказано, что лучше этого не делать в GDI. Поэтому сейчас сделаем бегущих муравьёв в GDI и GDI+, и сравним.

Класс бегущих муравьёв

Давайте вынесем отрисовку насекомых в отдельный модуль и отдельный класс. Пусть в классе будет собственный таймер, который будет инкрементировать внутренний счетчик и генерировать событие OnPaint: TNotifyEvent — отрисуй меня.

Предусмотрим возможность хранить относительные координаты, значения X и Y которых находятся в интервале (0..1). Это нужно для масштабирования. Поэтому в конструкторе сразу указываем параметр ASingleRange. Он сообщает классу, что при формировании координат необходимо учитывать размеры текущей области рисования.

Чтобы получить координаты для отображаемой линии, предусмотрим метод GetPoints, куда параметром передаём текущий прямоугольник отрисовки и на выходе получаем посчитанный массив координат.

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

Бегущие муравьи в GDI

В нашем классе есть метод, который реализует алгоритм для GDI. В отличие от реализации в предыдущей статье, перед отрисовкой пользовательским стилем, рисуем белую сплошную линию по той же траектории. Еще отличие состоит в том, что мы создаём геометрическое перо с нужным стилем непосредственно в методе. Потому что точно знаем, какие параметры нужны. Зачем нам отдельная функция для создания пера?

Как использовать

Где-то в момент инициализации, например в OnCreate формы, создаём экземпляр класса FList: TRunningAnts. На его событие OnPaint сразу вешаем обработчик отрисовки нашего героического пайнтбокса pb:

На pb обрабатываем события мыши и инициализируем линию муравьёв:

Обработчик события OnPaint пайнтбокса pb выглядит так:

Остаётся загадкой, что такое FillRegion. Это опция, которая говорит — подзакрась выделенную область чем-нибудь полупрозрачным. Для метода PaintGDI это третий параметр AFillEffect: Boolean.

В итоге получаем следующее:

Рис.1. Бегущие муравьи GDI

Бегущие муравьи в GDI+

Для отрисовки муравьёв в GDI+ у нас есть специальный метод:

Пользовательский стиль для GDIP пера устанавливается его методом SetDashPattern. Первый параметр — указатель на массив стиля, второй — количество элементов в массиве.

Хорошая новость: количество элементов не ограничено максимум 16-тью, как для GDI-пера (ограничения).

Плохая новость в том, что он точно также, как и косметическое GDI-перо, ненавидит нулевые значения в массиве стиля. Поэтому мы просто заменим ноль очень маленьким числом Zero = 1e-8.

Собственно:

Рис.2. Бегущие муравьи GDI+

Чем GDI+ отличается от GDI?

В первую очередь, временем выполнения. Сравните вот эти скачущие циферки внизу окна у GDI и GDI+. Это одна и та же траектория. Но в первом случае там мелькает даже 47 msec, а у второго максимум 11. Если мы максимизируем окно, точные числа станут вообще не важны. В этом случае у GDI может и до полсекунды доходить, в то время как GDI+ будет примерно 50 msec. Быстрый и шустрый GDI проигрывает GDI+ вчистую.

GDI+ медленный… GDI+ глючный… да?

Да и остальных плюсов масса — одна полупрозрачная заливка чего стоит.

Бегущие муравьи в GDI+ по другому

Есть в GDI+ способ, чтобы обойти этот трюк с очень маленьким Zero и сделать код куда более понятным.

У пера GDI+ есть метод SetDashOffset, который меняет стартовую позицию первого штриха. Поэтому мы не будем назначать всякий раз другие стили, пытаться обходить нулевые значения, а просто зададим один очень простой стиль:

И дальше будем просто смещать стартовую позицию на 2 логические единицы. Или пикселя.

И теперь весь код метода, без лишних комментариев:

Всё очень просто.

Рис.3. Бегущие муравьи GDI+ SetDashOffset

Оба GDIP-метода примерно одинаковы по времени. И сильно быстрее GDI.

Практическое использование

Это конечно некое приближение. Писать полноценный редактор — формат статьи никак не позволяет. А исходник для такой статьи, в силу своей сложности, мало кому будет нужен. Поэтому это попытка показать практическое применение при минимальной сложности.

Итак, нужную область мы выделили. Теперь нужно произвести какое-то действие с этой областью. В нашем случае — сделать серым, повысить яркость, насыщенность или просто скопировать.

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

Дополнительно в класс

Основная сложность — получить данные о выделенной области. Какая точка принадлежит ей, какая нет. Для этой цели добавим в класс перегруженный метод PtInArea:

Медленная реализация PtInArea

Метод GetPath отдаёт кэшируемый объект типа TGPGraphicsPath, подобно массиву точек. Если путь был удалён ранее при изменении чего либо в классе, то метод создаст и проинициализирует его. TGPGraphicsPath умеет ответить на вопрос — принадлежит ли ему точка. Это метод IsVisible. Если требуется узнать, лежит ли точка на его внешней границе, надо использовать IsOutlineVisible.

[свернуть]

Эти методы используют TGPGraphicsPath в качестве источника информации о принадлежности точки. Эти методы оказались медленными. Это означает, что при выделении области мы не увидим немедленный эффект внутри области и будут очень неприятные тормоза.

Поэтому пишем быстрый метод PtInAreaEx, который использует весьма нетривиальный способ определения принадлежности точки.

И GetBitmap, и GetPath возвращают кэшируемые данные. Они очищаются на любое изменение данных класса. На любое изменение в данных у нас появляется новое событие OnChange и метод его генерирующий:

В котором помимо генерации происходит очистка кэша.

Методы получения кэшированных данных

Метод, возвращающий кэшированную траекторию:

И метод, возвращающий кэшированный битмап, с форматом пикселя в один байт.

Помимо самого битмапа, кэшируются сопутствующие данные, используемые в PtInAreaEx.

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

[свернуть]

Действия с выбранной областью

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

с таким обработчиком:

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

Обработка выбранной области

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

Сама функция обработки битмапа такова (функции эффектов находятся в IP76.ColorUtils):

Параметр Bounds: TRect — это объемлющий прямоугольник траектории выделенной области. Его задача — сузить поиск точек, входящих в эту область.

[свернуть]
Рис.4. Действия с выбранной областью

Копирование выбранной области

И наконец, копирование. Чтобы не засорять код — просто принцип, копируем область из натурального, не отмасштабированного изображения, без применения эффектов. Просто вырезаем выделенный кусок. Доступно по контекстному меню на изображении. Или Ctrl+C.

Поддержка альфа-канала тут весьма условна. Под ней понимается следующее: участок изображения вне области выделения должен стать прозрачным. Но это верно только для тех приложений, которые конкретно этот альфа-канал понимают. Например, Paint.net. Другие, те которые не понимают, вставят с черным фоном. Для них нужны особенные танцы с бубном.

Копировать выделенный участок изображения в буфер обмена

[свернуть]
Рис.5. Выделяем, копируем…
Рис.6. И вставляем в Paint.net. Всё что мимо — прозрачно.

Бегущие муравьи: Листинг

Вкратце получилось так. Это сугубо моё творчество, ни на что не претендующее. Тем более на истину в последней инстанции. Мне понадобилась такая рамка с муравьями, когда нужно было подсвечивать некие области произвольной формы. В инете ничего толкового не нашел, а хотелось быстро и просто. Думаю, что получилось достаточно просто. И надеюсь, полезно.

Для полноты картины привожу полный листинг модуля.

RunningAnts.pas

[свернуть]

Скачать

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

Бегущие муравьи: Рисование в GDI, GDI+

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

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

Бегущие муравьи: Использование

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

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

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


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

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