Рисование от руки в угловом режиме

Я хотел сделать что-нибудь веселое в преддверии праздников, поэтому решил перенести обводку переменной ширины из библиотеки рисования от руки Flex, которую я создал еще в начале 2010-х годов. Этот штрих на самом деле имеет почтенную историю, восходящую примерно к 1983 году, в качестве упражнения меня назначили ассистентом преподавателя для последипломного курса вычислительной геометрии. Компания инструктора недавно приобрела очень дорогой планшет. Эта система позволяла пользователям сканировать или загружать чертежи, уже в электронной форме, на дисплей и комментировать их нарисованными от руки заметками с использованием штриха фиксированной ширины. Инструктор придумал ход переменной ширины (зависящий от скорости), который станет основой для ряда лабораторных упражнений. Моя работа заключалась в том, чтобы заставить его идею работать в Фортране (да, теперь вы можете смеяться над моим возрастом). Конечно, графические дисплеи Tektronix, которые у нас были в университете, не имели возможности вводить последовательности координат пера, поэтому нам пришлось моделировать их с помощью массивов x- и y-координат. Теперь ты действительно можешь смеяться над моим возрастом!

Я вдохнул жизнь в этот код, когда он был преобразован в ActionScript для использования в проекте Flash, а затем формализован в библиотеку рисования на основе Flex. Теперь он преобразован в Typescript и упакован в директиву атрибутов Angular. Эта директива позволяет вам наделить контейнер (в первую очередь DIV) возможностью рисования от руки.

Конечно, прежде чем мы начнем, укажите в своем дружественном браузере по соседству этот GitHub, чтобы вы могли получить код для использования в своих проектах.



Рисование мазка

В общем, штрих состоит из трех различных действий, первое из которых выполняется при первом нажатии мыши. Второй выполняется постоянно во время движения мыши. Финальное действие выполняется при поднятии курсора мыши.

Действия при нажатии кнопки мыши в основном относятся к бухгалтерскому учету; запишите первое нажатие мыши, создайте соответствующий контейнер в среде рисования и инициализируйте все соответствующие переменные вычисления. Код, который сопровождает эту статью, отрисовывается в Canvas (с использованием PixiJS). Если есть соответствующий интерес, я буду рад опубликовать другую статью, в которой показано, как нарисовать один и тот же штрих в Canvas или SVG и выполнить контракт рисования во время выполнения с использованием системы DI Angular.

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

Ход делится на «конечные точки», первый конец хода и кончик. Между ними противоположные стороны штриха нарисованы с помощью последовательности квадратичных кривых Безье. Каждая сторона штриха по существу представляет собой квадратичный сплайн с непрерывностью C-1, что означает, что сплайн соответствует значениям координат и величине первой производной в каждой точке соединения. Точки, через которые проходит каждый сплайн, определяются с использованием направления последнего сглаженного сегмента, проецируемого перпендикулярно в противоположных направлениях на основе критериев переменной ширины.

Поскольку используется сглаживание, а сглаживание - это вычисление с запаздыванием, вычисления сглаженного штриха выполняются за текущим положением мыши. «Кончик», который простирается от последней сглаженной точки до текущей точки мыши, нарисован парой прямых линий и кружком.

Итак, как все это работает в деталях? Ну, это как… бла, бла, математика, бла, бла, API. Готово :).

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

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

Директива о рисовании от руки

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

/src/app/drawing/freehand-drawing.directive.ts

Селектор директивы является «произвольным», и директива может применяться множеством способов - от автономной интерактивности до отсутствия внутренней интерактивности. Входы могут управлять несколькими параметрами.

Основной шаблон компонента приложения, /src/app/app.component.html, иллюстрирует несколько вариантов использования,

<!-- minimal usage
<div class="drawingContainer" freehand></div>
-->

<!-- caching control and begin/end stroke handlers
<div class="drawingContainer" freehand [cache]="cacheStrokes" (beginStroke)="onBeginStroke()" (endStroke)="onEndStroke()"></div>
-->

<!-- control some drawing properties -->
<div class="drawingContainer" freehand [fillColor]="'0xff0000'"></div>

Обратите внимание, что рисунок от руки применяется к контейнеру (скорее всего, к DIV) как атрибут. Конструктор директивы получает ссылку на контейнер и инициализирует среду рисования PixiJS. Среда рисования тесно связана с директивой в этой реализации для удобства.

Поскольку Входы определены, реализован интерфейс Angular OnChanges. Метод ngOnChanges выполняет легкую проверку входных данных. Обработчики мыши назначаются или удаляются, если интерактивность включена или отключена.

Предупреждение: если в HTML-контейнере не определены Inputs, ngOnChanges не вызывается. Убедитесь, что все значения Input имеют разумные значения по умолчанию.

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

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

Директива допускает полный внешний контроль. Можно загрузить необработанные координаты мыши с сервера, например (т.е. ранее сохраненные штрихи), а затем использовать API, как если бы те же координаты были получены с помощью движения мыши. Таким образом можно полностью перерисовать ранее нарисованные штрихи. Также может быть удобнее управлять взаимодействием с мышью на более высоком уровне, чем контейнер. По этим причинам директива предоставляет общедоступный API для начала, обновления и затем завершения штриха.

public beginStrokeAt(x: number, y: number, index: number = -1): void
public updateStroke(x: number, y: number):void
public endStrokeAt(x: number, y: number): void

Штрих тоже можно стереть,

public eraseStroke(index: number): boolean

Вся коллекция штрихов может быть очищена, а область рисования станет доступной для нового набора штрихов,

public clear(): void

Основная часть работы (и вычислений) выполняется в методе updateStroke (). На самом деле это просто сглаживание, аналитическая геометрия и пара квадратичных сплайнов с динамической вершиной в конце. Как я уже упоминал в начале статьи, не доверяйте алгоритм рисования мне; это восходит, по крайней мере, к 1983 году доктору Теннисону из Техасского университета в Арлингтоне.

Что касается кредита, как насчет того, чтобы отдать должное новому приложению для динамического рисования в Angular? Возьмите код, скопируйте и вставьте и наслаждайтесь веселым кодированием!

Удачи в ваших усилиях по Angular.

EnterpriseNG выходит 4 и 5 ноября 2021 года.

Приходите послушать ведущих спикеров сообщества, экспертов, лидеров и команду Angular, которые в течение двух дней обсудят все, что вам нужно, чтобы максимально использовать Angular в ваших корпоративных приложениях.
Темы будут сосредоточены на следующих четырех областях:
• Monorepos
• Микро-интерфейсы
• Производительность и масштабируемость
• Удобство обслуживания и качество
Подробнее здесь ›› https://enterprise.ng-conf.org/