Как устроена динамическая кисть

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

  Динамика эта, однако, предусматривается заранее - художник должен заранее, хорошо зная программу, в которой  он работает, установить нужные опции, а если ему захочется что-то поменять, то он вынужден вновь устанавливать эти опции. Или взять другую кисть.
  Реальные художники так не работают - они придавят кисть к холсту, щетинки кисти раздвинутся, и след от мазка станет шире. Как вы понимаете, это динамика совсем другого рода - кисть в руках мастера сама как бы перестраивается и помогает ему в его работе.
  Подобная лёгкость в компьютерном рисовании весьма проблематична. Конечно можно, одной рукой вести стилус по планшету, а другой - кнопки на клавиатуре нажимать или колёсико какое крутить. Но всё же не заставляйте художника быть многоруким Шивой, ему гораздо удобнее орудовать только кистью, держа её в одной руке.

  А как мы можем орудовать стилусом? В принципе такое возможно. Современные планшеты отслеживают, например, силу нажатия стилуса или его наклон. Но у меня-то планшет старенький и в нём ничего такого нет.
  А возможности бы открывались широкие.
  Например, в редакторе GIMP динамические кисти есть.
Вот https://docs.gimp.org/ru/gimp-tool-dynamics.html подробнейшее описание того, как можно настраивать кисть на подобные действия. И это, в общем-то, впечатляет. Хотя мне такие настройки кажутся чересчур всеобъемлющими, и во многом, излишними. Меня бы вполне устроило бы изменение размера кисти в зависимости от скорости её движения.

    УВЕЛИЧЕНИЕ РАЗМЕРА КИСТИ ПРИ ЕЁ БЫСТРОМ ДВИЖЕНИИ

  И в самом деле – если мы старательно выписываем мелкие детали, то двигаем кисть медленно, и по этому признаку компьютер может понять, что следует сузить размер красящего пятна. А двигаем кистью быстро – пусть компьютер увеличит размер пятна. И это было бы удобно, не нужно было бы отвлекаться на переключение размера кисти, если она сама позволяет таким просты способом регулировать свой размер. Но как это практически сделать?
  Как сделать динамическую кисть в своей собственной программе для компьютерного рисования? Об этом и пойдёт мой дальнейший рассказ.

     СЛЕЖЕНИЕ ЗА ВРЕМЕНЕМ

  До сих пор, за временем в своей программе я следил только по поводу создания анимации. Там я использовал оператор Sleep, чтобы создать паузы нужной длительности между кадрами фильма.
  Теперь мне понадобился компонент Таймер. Действия компонента Timer очень просты – он создаёт события с таким же названием – Timer с заданным временным интервалом. Поскольку минимально возможный интервал - 1 соответствует 15-ти миллисекундам, то таймер можно включить и не выключать – его события очень редки в сравнении с долями микросекунд, временем математических операций, поэтому эффективности компьютерного счёта включённый таймер никак не помешает.
  Вот так я события таймера обрабатываю –

Private Sub Timer1_Timer()
  Ftimer = Ftimer + 1: If Ftimer > 10000 Then Ftimer = 0:
End Sub

  Здесь Ftimer – целое число, глобальная переменная, которая увеличивается на единицу каждые 15 миллисекунд.

  У кисти №1 есть опция – кнопочка на которой написано «0m», и которую я могу нажать и переключить в состояние «1m», и тогда кисть, которой я рисую, станет динамической – при выставленном размере пятна R=1 (это очень маленький размер) размер пятна станет увеличиваться по мере того, как я увеличиваю скорость перемещения стилуса по планшету, и уменьшаться, как только я стану движение стилуса замедлять. Почти все примеры – следы кисти на иллюстрации сделаны в этом режиме - «1m», и вы можете сравнить эти следы, с тем, что получается при «0m», то есть с тем, когда динамика слежения за скоростью отключена.
  Однако, как создать такую динамику?

  РЕАЛИЗАЦИЯ ДИНАМИКИ СЛЕЖЕНИЯ ЗА СКОРОСТЬЮ

  Кисть в моей программе рисует, поочерёдно окрашивая те пиксели, которые включены в состав пиксельного круга, начиная от самого первого – центрального, и кончая самым последним, находящимся на периферии пятна, вот так –

For I = 1 To Im   

Собственно значением Im - номером последнего пикселя, и определяется размер пятна. Увеличим этот номер, и размер пятна увеличится.
  В очень большом пятне может быть до 1327 пикселей, такое пятно имеет радиус 20 пикселей, но кисть №1 работает с пятнами поменьше, 700 для неё максимум. Кисть сложная, в ней есть и акварельная опция, сложные кисти требуют более длительного счёта, а при большом количестве пикселей кисть начинает зависать – её след появляется с запозданием, а часто и в искажённом виде, поскольку те моменты, которые компьютер не успел обработать, он попросту пропускает.

  Итак, чтобы создать скоростную динамику для кисти, мы должны поставить номер Im в зависимость от скорости движения стилуса.
  Делается это так – события move, происходящее при каждом переходе стилуса с текущего пикселя на соседний, отстоящий от предыдущего на расстояние 2, вызывают цикл рисования пикселей, находящихся в круге.
  Вот перед этим циклом мы и можем проанализировать, сколько интервалов времени накопилось в переменной Ftimer за прошедшее время. Если этих интервалов много, скажем, более 30-ти, то пятно можно расширить, но очень немного. Если Ftimer =15, значит прошло 150 мсек, и за это время стилус подвинулся на 2 пикселя, то есть получаем, что скорость его движения составляет 2/0.3 = 13 пикселей в секунду. Это такая, уже довольно умеренная скорость, но всё ещё подходящая для достаточно точной обрисовки границ следа.
  Мы можем взять значение 1/ (Ftimer+1), единица добавлена, чтобы избежать бесконечности, чтобы узнать динамический размер пятна. Сделать это можно так –

Im = Imo
If F53 > 0 Then Im = Ntimer(F53, Imo) 'расширение следа по таймеру
For I = 1 To Im   


Здесь Imo заранее выставленный (не динамический), номер последнего пикселя в круге, F53=1 для опции «1m», равно 2 для опции «2m», и равно нулю для опции «0m», а Ntimer(Imo) это обращение к подпрограмме, которая, проанализировав накопленное со времени предыдущего обращения число Ftimer, возвратит новое число Im, несколько большее заранее выставленного.
Сама же подпрограмма, в немного упрощённом виде, выглядит так –

Public Function Ntimer(M As Integer, NN As Integer) As Integer 'расширение следа по таймеру
Static Ft As Integer, N As Integer
Dim X As Single
 
  If M = -1 Then Ftimer = 0: Ft = 0: N = -1: Ntimer = 0: Vtimer = 50: Exit Function '
  'начальный сброс счётчика, делается при опускании стилуса на планшет

  If NN > 1300 Then Ft = 0: Ftimer = 0: Ntimer = NN: Exit Function '===
      ImN = NN: Ft = Ft + 1:
      If Ft > 2 Then '
        X = Ftimer:
   If F55b = 1 Or F55c = 1 Then Vtimer = X * 0.3 + Vtimer * 0.7:
        If M = 0 Then Ft = 0: Ftimer = 0: Ntimer = 0: Exit Function '===
        X = X^2 / 6 / F53 ‘ F53=1 или =2
        X = NN + (700 - NN) / (1 + X):
        If N < 0 Then N = X Else N = X * 0.3 + N * 0.7 'сглаживание
        Ft = 0: Ftimer = 0:
      End If:
      If N > 0 Then Ntimer = N Else Ntimer = NN:
End Function

Как вы можете видеть, Ntimer принимает решение о расширении пятна не сразу, манипуляции с переменной Ft позволяют накопить Ftimer за три события Move, то есть накоплению числа 10 будет соответствовать скорость не 13 пикселей в секунду, а только 4, а это очень и очень медленное вырисовывание. Но как раз с превышением этой скорости будет наблюдаться небольшое расширение пятна.

  Полученная накопленная величина X преобразуется операторами
X = X^2 / 6:
X = NN + (700 - NN) / (1 + X):
где NN – выставленный (не динамический) номер крайнего пикселя пятна. Номер крайнего пикселя квадратично зависит от размера пятна, поэтому чтобы получить линейную зависимость от скорости, Im нужно регулировать квадратично, потому X возводится в квадрат.
  Последующее деление на 6 эквивалентно делению Х на 2.45, следовательно 
Вычисленная выше скорость возрастёт и составит в итоге 10.6 пикселей в секунду. Предположим, что это и есть скорость движения стилуса, а выставленное значение NN равно 9 – это минимальное значение, диаметр пятна при этом составляет 3 пикселя. Входное Х у нас, как говорилось выше, равно 10, тогда, вычисляя по формулам, получаем после них Х=40, а это уже размер пятна 7 пикселей, при увеличении скорости размер пятна мог бы расти линейно вплоть до входного ограничения 1300, а это пятно 28 пикселей в диаметре.
  По преобразованию X = NN + (700 - NN) / (1 + X) видно, что в действительности увеличивается не размер пятна, а пропорционально скорости уменьшается разница между выставленным размером пятна и числом 700 – максимальным его размером. Эта, и приведённые выше тонкости регулирования не принципиальны, но они нужны для того, чтобы динамической кистью было удобно пользоваться. Чтобы при отключении динамики мы не оказывались неожиданно на размере пятна 700 пикселей вместо тех трёх, которыми мы только что рисовали, медленно двигая динамическую кисть.
  Практика всегда показывает то, что в рисовании оказывается неудобным, и эти недочёты необходимо исправлять, собственно на это все силы программиста и уходят.

  Эксперимент показал, что постоянно использовать накопленное, и тем самым, уже усреднённое значение Ftimer нельзя, оно ещё недостаточно стабильно, в результате ширина следа начинает прыгать - след становится то шире, то уже. Поэтому применяется демпфирующее сглаживание выходной величины - N = X * 0.3 + N * 0.7

     ДИНАМИЧЕСКИЕ ИЗМЕНЕНИЯ ПРОЗРАЧНОСТИ СЛЕДА
 
  Аналогичным методом, отслеживая скорость движения стилуса по той-же подпрограмме, можно изменять прозрачность следа. Для этих целей используется переменная Vtimer, значения которой аналогичным образом сглаживаются. Указанная опция обозначается заглавной буквой «B» на кнопке буквенных опция, и работает независимо от опций, связанных с расширением следа.
  Влияние этой опции на прозрачность регулируется движком – длинной полоской, расположенной выше кнопки буквенных опций. Этот движок называется «Плавность». На иллюстрации выставленные значения Плавности показаны красными цифрами. Для значения 0 увеличение прозрачности при быстром движении максимально заметны, при значении 12 прозрачность следа не меняется, а при значениях 13 и больше прозрачность изменяется в обратную сторону, то есть, при быстром движении стилуса увеличивается плотность следа.

     ДИНАМИЧЕСКИЕ ИЗМЕНЕНИЯ ЦВЕТА СЛЕДА

  Аналогичным образом, введением опции заглавное «C» на кнопке буквенных опций создаёт динамическое изменение цвета. Это изменение вводится независимо от опций расширения следа и опции изменения его прозрачности. И с движком эта опция не связана.
  Если опция «C» установлена, то к красящему активному цвету примешивается цвет, выставленный на кнопке цвета фона. На иллюстрации к бежевому цвету примешивается зелёный.
  Опции «1m», «B» и «C» могут работать как раздельно, так и в комбинациях друг с другом, и это тоже показано на иллюстрации.

  Примешивание одного цвета к другому осуществляет функция C = ccRGB(cBack, Ca, A), где C – красящий цвет, Ca – активный цвет, и cBack – цвет фона. Величина A может меняться от нуля до единицы - это доля примешиваемого цвета.
  Казалось бы, за долю примешиваемого цвета можно взять величину Vtimer, обратную величине скорости движения стилуса, то есть, положить A=Vtimer.
  Но нет, в той зоне, в которой должно быть регулирование, от 0.4 до 20, значения A получаются очень малыми – на иллюстрации эта зависимость показана на графике синими точками, и там где должно быть А=0.8 по синей кривой выходит А=0.2, следовательно в этой точке нужно дополнительно увеличить значение А в четыре раза.
  Но и множитель 4 не помогает – эксперимент показывает, что на малых скоростях пятно сразу становится заметно зелёным. Следовательно, корректирующий множитель нужно взять переменным – он должен быть равен единице для Vtimer=20 и четырём для Vtimer=0.4 – косинусоида, представленная маленьком графике справа мне в этом помогла.
  Оставшийся участок от 0 до 0.4 я сделал линейным, и получил ту прекрасно работающую зависимость A(Vtimer), которая показана на графике красно-зелёными точками. Она очень наглядно демонстрирует, как постепенно, по мере увеличения скорости, красный цвет переходит в зелёный.
 
  Итак, динамическая кисть, при продуманной её конструкции, действительно создаёт удобства в компьютерном рисовании. Особенно это относится к изменению размера красящего пятна. С другой стороны, подобные удобства требуют тщательной продуманности, с тем, чтобы динамических эффект не просто получался, но получался с подходящими для рисования характеристиками, предсказуемо работал бы в разных режимах, и сочетался бы с другими настройками кисти. В этом плане программисту приходится прилагать значительные усилия, а соответствующие программы не получаются очень простыми.

__________
23.06.2023   


Рецензии