Как делается кисть для рисования волос

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

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

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

     ЗАЧЕМ НУЖЕН  КРУГ ИЗ ТОЧЕК

   Итак, проблема первая - какой формы будет у нас кисть? Проще всего копировать точки с образца на поле для рисования из одной прямоугольной области в другую такую же область. Но тогда и кисть у нас получится прямоугольной формы. А удобнее было бы иметь форму круглую и иметь возможность размер кисти регулировать.
   Но как узнать, принадлежит ли точка к кругу заданного диаметра, или она выпадает из него?
   Возводить в квадрат X и Y каждой точки, складывать, извлекать квадратный корень, и сравнивать полученную величину с заданным радиусом? Это излишняя работа, замедляющая процесс рисования, и эту работу следует провести заранее.

   В самом начале работы программы, сразу после её загрузки, делается так называемый круг из точек.
   У меня это массив   RXY(1400, 3),  в нулевом  столбце которого стоят значения радиуса, а в первом и втором столбцах - координаты X и Y для каждой точки.
    Имеется также список   R20(21) - это ссылки на адреса массива точек.     Например, между индексами R20(R1) и  R20(R2) в массиве  RXY(1400, 3) находится информация о точках, отстоящих от центра на расстояние большее, чем R1, но не большее, чем R1.  Пожалуйста - можете координаты этих точек находить, и копировать точки, если это нужно.

     КАК ПРОХОДИТ ПРОЦЕСС РИСОВАНИЯ

     Последовательно происходящие события мыши - нажатие кнопки (MouseDown), движение мыши, переводящее указатель с одного пикселя на другой (MouseMove), и отжатие кнопки (MouseUp) отслеживаются  программой. При этом на процесс рисования влияют только первые два события -  MouseDown позволяет подготовиться к рисованию и совершить некие предварительные действия (например, определить радиус закрашиваемого круга), а MouseMove приводит к непосредственному нанесению точек на поверхность рисунка.
     При каждом перемещении мыши точки наносятся сообразно новому её положению.
     А как они наносятся?
     Обычно существуют некие запреты. В моей программе точки не наносятся на чёрный цвет или на цвет фона, если этот фон со своим цветом объявлен прозрачным. В Фотошопе и в других программах - свои примочки.
Кроме того и перекрашивать пиксель в новый цвет можно не совсем. Если смешивать старый цвет OldC и новый цвет C в некоторой наперёд заданной пропорции (установим эту пропорцию через число А, меньшее единицы, показывающее долю старого цвета в смеси), то тогда можно достичь зрительного эффекта прозрачности наносимого мазка. Разумеется, другого рода прозрачность будут создавать и незаполняемые места между наносимыми точками.
      Вариантов много.
      Такого рода окрас для кисти состоящей из разрозненных мелких точек, и из точек, объединённых в небольшие круги, можно видеть на двух первых примерах иллюстрации, в левой части каждого примера.
       В первом случае получается "искрящаяся" прическа, во втором случае волосы курчавятся.

       И то, и другое неплохо. Но как бы попробовать избавиться от "искрения" в первом примере? Избавиться от тех мелких точек, которые остаются незакрашенными. Нужно понять причину такого явления.
Причина мелкой незакрашенности заключается в том, что одиночные точки, проходя по поверхности, не всегда попадают  на пиксели смежные по горизонтали или вертикали, а часто перескакивают и по диагонали.
      А что, если закрасить эти смежные точки? Очень просто!  Решил я, и попробовал ставить на рисунок точки размером не в один, а в два пикселя.
     Эффект получился для меня неожиданным - рисунок сразу приобрёл свойства мокрой акварели.
     См. правую часть первого и второго примеров.
    
     Это интересно.   Чем больше возможностей оказывается у метода, тем лучше, поскольку будет из чего выбирать.
     Что касается "неожиданности", то её можно было бы и предвидеть -  точку я наношу четырьмя пикселями (по два на каждую сторону), а считываю цвет пикселей по-прежнему, поодиночке. Следовательно, считываю я иногда не совсем старый цвет, а цвет уже мною изменённый, вот цвет и расползается по рисунку по мере того, как я ставлю на рисунке точки.
     Ну чем не эффект мокрой акварели? Она самая и есть.
    Прекрасно, сказал я  себе, и привязал эту возможность к кнопочке, которая весьма кстати оказалась в моём редакторе - эта кнопка переключает режимы Гуашь-Акварель. Правда создавал я её для другого метода рисования. Но это ничего, пусть кнопочка послужит и здесь.

      ОСОБЕННОСТИ семплированного и несемплированного рисования.

   Картинки точек в круге, которые я использовал в первых двух опытах, я делал чёрным цветом на светло-сером фоне. А что, если разнообразить цвета этих точек? Или даже расположить в круге некую картинку, и затем репродуцировать её по поверхности рисунка, проводя по нему кистью.

   Именно так работает Фотошоп. В нём мазок кистью семплирован - он состоит из отдельных, но близко расположенных семплов, своеобразных штампов, отпечатки которых, слегка, или сильно изменяясь, последовательно накладываются друг на друга в том направлении, в котором мы ведём линию.
   В этом и состоит общая идеология рисования в фотошопе.
   Я же делаю несколько по иному - при работе кистью отпечатки не разделяются семплированием, а тесно примыкают друг к другу. Можно, разумеется, назвать и эти отпечатки семплами,  но какие же это семплы, когда расстояние между ними 1 пиксель. То есть, расстояния как бы и нет вовсе.

    Оба этих подхода к рисованию кистью имеют и свои плюсы, и свои минусы.
    Семплированный метод работает быстрее, зато метод непрерывного рисования проще. Кроме того я постарался освободить его от лишних нагрузок - в Фотошопе рисование кистями унифицировано, в частности, все кисти используют вот этот самый круг с чёрными точками, или с размещённой на круге картинкой - так называемый патерн. А я сделал кисти, не использующие патерн, и стало быть, работающие быстрее, и только  кисть №5 этот патерн использует.
    Эту кисть я сделал специально для рисования волос, хотя, создавая интересную фактуру, кисть может быть использована и в других целях. Как бы там ни было, но и эта кисть тоже  должна работать быстро. Насколько быстро, это зависит от количества тёмных точек в патерне.

      СТАТУС ТОЧЕК ПАТЕРНА

     В связи со сказанным давайте вернёмся к вопросу - А стоит ли  разнообразить цвета точек в патерне? Стоит ли дополнительно нагружать компьютер анализом цвета этих точек?
И как использовать эти разноцветные точки? Делать с них отпечатки, как это делается в Фотошопе? Или использовать различие в цвете как-то по иному?
    И я нашёл оригинальное решение, помогающее кстати и основной цели - получить в приемлемом качестве пути для рисования прядей волос. Итак, рассказываю.

    В тот момент, когда для рисования кистью №5 выбирается подходящий для этого патерн, компьютер не загружен непосредственно рисованием, и следовательно, вполне может провести некую подготовительную работу. Работа эта заключается в заполнении третьего столбца массива RXY(1400, 3) статусом точек патерна.
    Статус точки определяется по степени её темноты в соответствии с порогами 47, 99, 151 и 203 для синей составляющей цвета.
   Чёрные точки по этому принципу получают статус 4, а светло-серые точки фона и ярко-голубая точка в центре - статус 0. Точки со статусом 0 в рисовании не участвуют. Напротив того - точки со статусом 4 в рисовании участвуют всегда.
    Как вы уже догадались, точки с промежуточной степенью темноты получают статус 1, 2 или 3. Для рисования всех этих точек на патерне вполне подходят цвета из набора программы msPaint. Эти цвета - чёрный, два серых и темно-зелёный, на палитре программы Paint показаны на иллюстрации стрелочками.
    Как и заполнять патерн точками тоже очень удобно в стандартной программе Paint при увеличении 6х.
Поэтому специальных средств для создания патерна я в своей программе не делал.

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

    Последний пример на иллюстрации получен с использованием специально сделанного патерна. В нём точки собраны по три, наподобие маленьких букв Г, и эти буквы - чёрные, тёмно-зелёные и серые размещены на поверхности патерна в соответствии с описанными выше принципами - те, что потемнее, помещены поближе к центру. Результат вы видите.
___________
 Если кому-нибудь интересно, как это всё реализовано, то фрагмент программы на языке Визуал Бейсик я привожу ниже.
    Любопытно, я так долго  говорил об особенностях рисования волос, так много текста размещено выше, а для реализации этого всего, смотрите, совсем немного нужно:


'=========общий раздел для всех кистей
  If M = 2 Then Exit Sub  'MouseUp не отрабатывается
  DW = Form1.Picture1.DrawWidth: Form1.Picture1.DrawWidth = 1: Cb = -2  'фон непрозрачен
  A = Form1.Command34.Caption: If InStr(Form1.Text1.Text, "п") > 0 Then Cb = cBack  'фон прозрачен
  R = Form1.Command9.Caption
 
If Fbra = 5 Then  '===========кисть №5 для рисования волос
  If M = 4 Then  'MouseDown
     If L4 = 1 Then Ko = 3.5: dK = 1 / (DW + 8) Else Ko = 0.3: dK = 0
     Form1.Picture1.DrawWidth = DW: Exit Sub
  End If   '========= далее отрабатывается событие MouseMove
  If AG = 1 Then Form1.Picture1.DrawWidth = 2
  If R > 5 Then Im = R20(2 * R - 4) Else Im = R20(R + 1)
  If A > 6 Then A = A / 5 - 0.85 Else A = (A + 1) / 20
  Ko = Ko - dK: If Ko < 0.2 Or Ko > 3.5 Then dK = -dK
  For I = 1 To Im
    K = RXY(I, 3)
    If K > Ko Then '<====проверяется соответствие статуса точки
      X = RXY(I, 1) + Xm: Y = RXY(I, 2) + Ym
      C = Form1.Picture1.Point(X, Y): If C = Cb Then C = -1
      If C > 0 Then C = ccRGB(OldC, C, A): C = C Or 1: Form1.Picture1.PSet (X, Y), C
    End If
  Next I
End If
  Form1.Picture1.DrawWidth = DW
End Sub
_________________