GIMP Script-Fu Первый Дан. Берём Кисти и рисуем Точки и Звёздочки

Да да, опять точки. Я ранее уже рассказывал как построить абстракцию точки. А теперь мы займёмся рисованием точек, выбранными кистями, рассмотрим различие в управлении кистями версий 2.10 и 2.6, ознакомимся с понятием «динамика кисти» и как с ней работать, а так же займёмся рисованием звёзд без градиента и с помощью «псевдо градиента». Поехали.

Библиотека функций к Script-fu

Подготовка к рисованию, состоит в загрузке файлов определений функций.

(define path-home (getenv "HOME"))
(define path-lib (string-append path-home "/work/gimp/lib/"))
(define path-work (string-append path-home "/work/gimp/"))
(load (string-append path-lib "util.scm"))
(load (string-append path-lib "defun.scm"))
(load (string-append path-lib "struct.scm"))
(load (string-append path-lib "point.scm"))
(load (string-append path-lib "tr2d.scm"))
(load (string-append path-lib "contour.scm"))
(load (string-append path-lib "img.scm"))
;;(load (string-append path-lib "img2.6.scm"))

Первый вариант рисования точки. Рисование точки не отличается от рисования контура, просто вместо контура передаем одну точку. Кроме этого передаём размер кисти, цвет.

(define (draw-point img p brush sz color)
   (let* ((points     (make-vector 2 'double))
          (dw         (car (gimp-image-get-active-drawable img))))
     (gimp-context-push)
     (gimp-context-set-foreground color) ;; установим цвет
     (gimp-context-set-brush brush)           ;;установим кисть
     (gimp-context-set-brush-size sz)
     (vector-set! points 0       (p-x p))     ;; установить точку
     (vector-set! points 1       (p-y p))
     (gimp-paintbrush  dw 0 2 points PAINT-CONSTANT 0)
     (gimp-context-pop)
	 ))

Продемонстрируем работу функции.

(define i1 (create-1-layer-img 640 480)) ;;подготовим полотно для рисования.

;;Установим в ручную кисть "2. Hardness 025"
(draw-point i1 (p! 50  100) "1. Pixel"        20 '(255 0 255))
(draw-point i1 (p! 150 100) "2. Hardness 025" 40 '(0 255 0))
(draw-point i1 (p! 250 100) "2. Star"         50 '(255 127 0))

;;Установим в ручную кисть "2. Block 03"
(draw-point i1 (p! 50  150) "1. Pixel"        20 '(255 0 255))
(draw-point i1 (p! 150 150) "2. Hardness 025" 40 '(0 255 0))
(draw-point i1 (p! 250 150) "2. Star"         50 '(255 127 0))

«Рисование точек выбранной кистью»

Ой, а что случилось? Мы же кисть то меняем, повторное рисование после Block 03(сплющенная кисть), «сплющили» наши точки?

для 2.6 такой проблемы нет, но там другая проблема

(define (draw-point img p brush sz color)
   (let* ((points     (make-vector 2 'double))
          (dw         (car (gimp-image-get-active-drawable img))))
     (gimp-context-push)
     (gimp-context-set-foreground color) ;; установим цвет
     (gimp-context-set-brush brush)           ;;установим кисть
     ;;(gimp-context-set-brush-size sz)
     (vector-set! points 0       (p-x p))     ;; установить точку
     (vector-set! points 1       (p-y p))
     (gimp-paintbrush  dw 0 2 points PAINT-CONSTANT 0)
     (gimp-context-pop)
	 ))


;;Установим в ручную кисть "Circle Fuzzy (17)"
(draw-point i1 (p! 50  100) "pixel (1x1 square)"        20 '(255 0 255))
(draw-point i1 (p! 150 100) "Circle Fuzzy (17)" 40 '(0 255 0))
(draw-point i1 (p! 250 100) "square (10x10)"         50 '(255 127 0))

;;Установим в ручную кисть "Diagonal Star (11)"
(draw-point i1 (p! 50  150) "pixel (1x1 square)"        20 '(255 0 255))
(draw-point i1 (p! 150 150) "Circle Fuzzy (17)" 40 '(0 255 0))
(draw-point i1 (p! 250 150) "square (10x10)"         50 '(255 127 0))

2.6 просто не поддерживает измение размеров кистей, как с этим бороться? об этом далее.

Как мы видим выполнение одинаковых комманд, при разных установленных в ручную кистей приводит к разным результатам. Дело в том, что внутри функции рисования точки я пытаюсь сохранить параметры рисования (контекст) с помощью функции gimp-context-push и затем восстановить, чтобы не влиять на настройки рисования установленные интерактивно. НО! Скорее всего это ошибка поведения ГИМПА, и здесь имеет место какая то не полная установка параметров кисти, приводящая к тому, что точка рисуется с формой и размером отличным от установленной в программе кисти, а вот всё остальное берётся от восстанавливаемого контекста, т.е от кисти установленной пользователем. И если мы уберём всю эту возню с сохранением и восстановлением контекста, наше рисование будет проходить, так как и задумано.

(define (draw-point-c img p brush sz color)
   (let* ((points     (make-vector 2 'double))
          (dw         (car (gimp-image-get-active-drawable img))))
     (gimp-context-set-foreground color) ;; установим цвет
     (gimp-context-set-brush brush)           ;;установим кисть
     (gimp-context-set-brush-size sz)
     (vector-set! points 0       (p-x p))     ;; установить точку
     (vector-set! points 1       (p-y p))
     (gimp-paintbrush  dw 0 2 points PAINT-CONSTANT 0)
	 ))

Повторяем тест.

;;Установим в ручную кисть "2. Hardness 025"
(draw-point-c i1 (p! 50  100) "1. Pixel"        20 '(255 0 255))
(draw-point-c i1 (p! 150 100) "2. Hardness 025" 40 '(0 255 0))
(draw-point-c i1 (p! 250 100) "2. Star"         50 '(255 127 0))

;;Установим в ручную кисть "2. Block 03"
(draw-point-c i1 (p! 50  150) "1. Pixel"        20 '(255 0 255))
(draw-point-c i1 (p! 150 150) "2. Hardness 025" 40 '(0 255 0))
(draw-point-c i1 (p! 250 150) "2. Star"         50 '(255 127 0))

Как видим рисунки идентичны, но установленная кисть изменилась, на последнюю установленную в программе. И мне кажется, что данное поведение с использованием функции (gimp-context-pop) является не корректным и в будущих версиях гимп его пофиксят.

«Рисование точек выбранной кистью без сохранения контекста»

Ёще раз про кисти.

Итак, посмотреть список кистей, мы можем с помощью функции: (gimp-brushes-list ".*"). Кисти в ГИМП можно редактировать, для этого служит целый набор функций:

gimp-brush-get-info     - информация о кисти
gimp-brush-new          - создать новую кисть
gimp-brush-rename       - переименовать кисть
gimp-brush-duplicate    - создать копию кисти
gimp-brush-delete       - удалить кисть
gimp-brush-is-editable  - возможно ли редактировать кисть

;; функции для редактирования кисти
;; КИСТИ!!!
;; Название кисти
;; фигура        gimp-brush-get-shape        gimp-brush-set-shape    gimp-brush-get-pixels
;; размер        gimp-brush-get-radius       gimp-brush-set-radius
;; соотношение   gimp-brush-get-aspect-ratio gimp-brush-set-aspect-ratio
;; угол          gimp-brush-get-angle        gimp-brush-set-angle
;; Интервал      gimp-brush-get-spacing      gimp-brush-set-spacing
;; жёсткость.    gimp-brush-get-hardness     gimp-brush-set-hardness

Программное редактирование кисти? Зачем оно нужно? Дело в том, что в ранних версиях ГИМП, например 2.6, у кисти не возможно было изменить параметры, например размер. Что бы работать с переменными размерами кистей, мне приходилось создавать копию интересующей меня кисти, переименовывать её, и уже в тогда я получал возможность редактировать параметры кисти. В версии 2.10, этого делать уже не надо, все параметры кисти можно изменять в контексте.

Вот пример одной из процедур, создающих новую кисть в версии 2.6.

(defun (make-brush1 base-name
                    &key name angle aspect-ratio
                    hardness radius shape spacing :spikes)
   (let ((bsh (car (gimp-brush-duplicate  base-name))))
      (if name
          (set! bsh (car (gimp-brush-rename bsh name))))
      (lambda ()
         (gimp-context-set-brush bsh)
         (if angle
             (gimp-brush-set-angle bsh angle))
         (if aspect-ratio
             (gimp-brush-set-aspect-ratio bsh aspect-ratio))
         (if hardness
             (gimp-brush-set-hardnes bsh hardness))
         (if radius
             (gimp-brush-set-radius bsh radius))
         (if shape
             (gimp-brush-set-hardnes bsh shape))
         (if spacing
             (gimp-brush-set-spacing bsh spacing))
         (if spikes
             (gimp-brush-set-spacing bsh spikes))
         )))

В версии 2.10 работать с кистью стало гораздо удобнее и изменять саму кисть или её параметры можно прямо в контексте. Для этого служат функции:

;; для установки контекста
;; КИСТИ!!!
;; Текущая кисть gimp-context-get-brush              gimp-context-set-brush
;; фигура        не меняется в контексте, изменить можно только сменой кисти
;; размер        gimp-context-get-brush-size         gimp-context-set-brush-size
;; соотношение   gimp-context-get-brush-aspect-ratio gimp-context-set-brush-aspect-ratio
;; угол          gimp-context-get-brush-angle        gimp-context-set-brush-angle
;; Интервал      gimp-context-get-brush-spacing      gimp-context-set-brush-spacing
;; жёсткость.    gimp-context-get-brush-hardness     gimp-context-set-brush-hardness
;; Сила          gimp-context-get-brush-force        gimp-context-set-brush-force

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

(defun (make-brush1  &key name size angle aspect-ratio
                     hardness spacing force)
     (lambda ()
         (if name
             (gimp-context-set-brush name))
         (if size
             (gimp-context-set-brush-size size))
         (if angle
             (gimp-context-set-brush-angle angle))
         (if aspect-ratio
             (gimp-context-set-brush-aspect-ratio aspect-ratio))
         (if hardness
             (gimp-context-set-brush-hardness hardness))
         (if spacing
             (gimp-context-set-brush-spacing spacing))
         (if force
             (gimp-context-set-brush-force force))
         ))

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

(define (draw-point2 img p brush color)
   (let* ((points     (make-vector 2 'double))
          (dw         (car (gimp-image-get-active-drawable img))))
     (gimp-context-set-foreground color)      ;; установим цвет
     (if brush (brush))                       ;;установим кисть
     (vector-set! points 0       (p-x p))     ;; установить точку
     (vector-set! points 1       (p-y p))
     (gimp-paintbrush  dw 0 2 points PAINT-CONSTANT 0)
	 ))


(define bsh1 (make-brush1 :name "1. Pixel" :size 25)) 
(define bsh2 (make-brush1 :name "2. Star"  :size 50 :aspect-ratio 20)) 
(define bsh3 (make-brush1 :name "2. Star"  :size 40 :aspect-ratio 0 :hardness 0.25)) 

(draw-point2 i1 (p! 50  100) bsh1 '(255 0 255))
(draw-point2 i1 (p! 150 100) bsh2 '(0 255 0))
(draw-point2 i1 (p! 250 100) bsh3 '(255 127 0))

«Рисование точек выбранной кистью»

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

Контекст.

Помимо стандартных параметров кисти, в контесте храниться ещё и динамика поведения кисти. Что это такое? Это некоторые параметры позволяющие изменять отображаемые контуры в рамках текущих параметров кисти. Что же меняется если ничего не меняется? Для неизменных параметров кисти мы можем изменить динамику кисти. Какие динамики кисти знает ГИМП? В этом нам поможет вызов функции: gimp-dynamics-get-list

(gimp-context-get-dynamics "")
("Dynamics Off")

(gimp-dynamics-get-list ".*")
;;(19 ("Basic Dynamics" "Basic Simple" "Color From Gradient" "Confetti" "Dynamics Off" 
;;"Dynamics Random" "Fade Tapering" "Negative Size Pressure" "Pen Generic" 
;;"Pencil Generic" "Pencil Shader" "Perspective" "Pressure Opacity" "Pressure Size" 
;;"Random Color" "Speed Size Opacity" "Tilt Angle" "Track Direction" "Velocity Tapering"))

Как видно динамику можно отменять — «Dynamics Off» , с помощью динамики можно брать цвет для рисования из градиента и многое другое.

(define c1 (list (p! 20 50) (p! 220 50) (p! 220 300) (p! 20 50))) ;;определим контур треугольника.
(gimp-context-set-dynamics "Confetti")                       ;;установим динамику
(define bsh4 (make-brush1 :name "Pencil Scratch"  :size 50)) ;;определим кисть
(bsh4)                                                       ;; установим кисть
(draw-contour i1 c1)                                         ;; отрисовываем контур

«Рисование контура треугольника кистью — конфетти»

Отрисуем тот же треугольник с помощью переменного градиента.

;;Выясним какие градиенты есть в системе: 
(gimp-gradients-get-list ".*")
;; ("Пользовательский" "Основной в прозрачный" "Основной в фоновый  (HSV против часовой )" 
;; "Основной в фоновый (HSV по часовой )" "Основной в фоновый (RGB)" 
;; "Основной в фоновый (резкий переход)" "Abstract 1" "Abstract 2" "Abstract 3" "Aneurism" "Blinds" 
;; "Blue Green" "Browns" "Brushed Aluminium" "Burning Paper" "Burning Transparency" "CD" "CD Half"..

(gimp-context-set-dynamics "Color From Gradient")                      ;;установим динамику
(gimp-context-set-gradient-repeat-mode  REPEAT-SAWTOOTH)
;;(gimp-context-set-gradient-repeat-mode REPEAT-TRIANGULAR)              ;;способ повторения
(gimp-context-set-gradient "Skyline")
(define bsh5 (make-brush1 :name "2. Hardness 025"  :size 50)) ;;определим кисть
(bsh5)                                                        ;; установим кисть
(draw-contour i1 c1)                                          ;; отрисовываем контур

«Рисование контура треугольника градиентом»

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

Пожалуй на этом я и остановлю свой рассказ о контексте. Желающие поиграть с ним ищите все функции имеющие в названии context.

Как нарисовать градиент без использования градиента? Для демонстрации этой возможности создадим функцию рисования контура звезды.

Звёздочки.

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

(define (make-star radius n k)
   (let ((ret '())
         (delta (/ 360 n))
         (internal-r (* radius k)))
      (let ((internal-fi (/ delta 2))
            (delta-r     (gr2rad delta))
            (close-p     (p! 0 0)))
         (do ((angle-r  delta-r   (+ angle-r  delta-r))
              (p-x      (* radius (cos 0))
                        (* radius (cos angle-r)))
              (p-y      (* radius (sin 0))
                        (* radius (sin angle-r)))
              (angle2-r (+ (gr2rad internal-fi) delta-r)
                        (+ angle2-r delta-r))
              (p2-x     (* internal-r (cos (gr2rad internal-fi)))
                        (* internal-r (cos angle2-r)))
              (p2-y     (* internal-r (sin (gr2rad internal-fi)))
                        (* internal-r (sin angle2-r)))
               (i       1         (+ i 1)) )
              ((> i n) (set! ret (cons close-p ret)) (set! ret (reverse ret)))
            (set! ret (cons (p! p-x  p-y)  ret))
            ;;(print (car ret))
            (set! ret (cons (p! p2-x p2-y) ret))
            ;;(print (car ret))
            )
         (p-x! close-p (p-x (car ret)))
         (p-y! close-p (p-y (car ret)))
         ret)))

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

Для этого используем вот эти функции.

(define (min-pos contour)
   (let ((min-x maxnum)
         (min-y maxnum))
      (do ((cur contour (cdr cur)))
            ((null? cur) (p! min-x min-y))
         (let ((cur-p (car cur)))
            (if (< (p-x cur-p) min-x)
                (set! min-x (p-x cur-p)))
            (if (< (p-y cur-p) min-y)
                (set! min-y (p-y cur-p)))
            )
         )))
            
(define (max-pos contour)
   (let ((max-x minnum)
         (max-y minnum))
      (do ((cur contour (cdr cur)))
            ((null? cur) (p! max-x max-y))
         (let ((cur-p (car cur)))
            (if (> (p-x cur-p) max-x)
                (set! max-x (p-x cur-p)))
            (if (> (p-y cur-p) max-y)
                (set! max-y (p-y cur-p)))
            )
         )))

Проверим как работает функция рисования контура звезды.(Функцию рисования контура draw-contour мы рассматривали ранее, так же мы здесь используем и функции двумерных преобразований, также рассмотренных ранее.)

(load (string-append path-lib "brush.scm"))

(let ((x   0)
      (y   200)
	  (bsh (make-brush1 :name "2. Hardness 025"  :size 5)))
   (bsh) 
   (for-list (el '((4 0.3 25 (0 127 0)) (5 0.4 50 (0 255 0)) (6 0.2 40 (127 255 0)) 
                   (7 0.7 65 (0 0 255)) (8 0.3 60 (127 255 63))))
       (let* ((num   (car    el))
              (k     (cadr   el))
		      (r     (caddr  el))
		      (color (cadddr el))
			  (star  (make-star  r num k))
			  (minp  (min-pos star)))
		   (prn "n: " num ", k: " k ",r: " r ",color: " color "\n")
		   (gimp-context-set-foreground color)
		   (draw-contour i1 (translate-contour star
		                                       (comb-tr2d
											      (make-tr2d-move (- (p-x minp)) (- (p-y minp)))
		                                          (make-tr2d-move x y))))
		   
		   (set! x (+ x 100))
		 )))

«Рисование контуров звёзд»

Эмуляция рисования градиентом.

Вернёмся к эмулированию рисования градиентом. Как этого добиться? Надо просто разбить контур на маленькие отрезки и рисовать их разным цветом, в соответствии с заданным набором цветов.

Функция разбивки контура:

(define (split-contour-by-delta contour delta)
  (let ((rez '()))
    (do ((cur contour (cdr cur)))
        ((null? (cdr cur)) (reverse (cons (car cur) rez)))
      (let* ((start (car cur))
             (end   (cadr cur))
             (dx    (- (p-x end) (p-x start)))
             (dy    (- (p-y end) (p-y start)))
             (nums-segments (round (/ (dist-xy dx dy) delta))))
        (let ((step-x (/ dx nums-segments))
              (step-y (/ dy nums-segments)))
          (do ((i 0 (+ i 1))
               (cur-x (p-x start) (+ cur-x step-x))
               (cur-y (p-y start) (+ cur-y step-y)))
              ((>= i nums-segments))
            (set! rez (cons (p! cur-x cur-y) rez)))
          )))))

Разбитый контур необходимо рисовать с помощью отрезков разного цвета, выбираемого из набора цветов.

;;контур должен иметь как минимум 2 точки
(define (draw-segment-line1 img contour brush color-map)
   (let* ((num-points (length contour))
          (points     (make-vector 4 'double))
          (dw         (car (gimp-image-get-active-drawable img))))
      (brush)
      (do ((i 1 (+ i 1))
           (cur contour (cdr cur))
           (cur-col color-map (if (not (null? (cdr cur-col)))
                                  (cdr cur-col)
                                  color-map)))
            ((>= i num-points))
         (gimp-context-set-foreground (car cur-col))
         (vector-set! points 0 (p-x (car cur)))
         (vector-set! points 1 (p-y (car cur)))
         (vector-set! points 2 (p-x (cadr cur)))
         (vector-set! points 3 (p-y (cadr cur)))
         (gimp-paintbrush  dw 0 4 points  0 0))
	  ))

А теперь продемонстрируем работу функции:

;;(load (string-append path-lib "brush.scm"))

(let* ((x   0)
       (y   200)
	    (delta-bsh   15)
	    (delta-split 10)
	    (bsh (make-brush1 :name "2. Hardness 025"  :size delta-bsh))
	    (cm1 '((153 13 40)
              (236 109 239)
              (60 86 163)
              (25 149 146)
              (76 252 141)
              (248 229 149))))
   ;;(gimp-image-undo-group-start i1)
   (for-list (el '((4 0.3 25 (0 127 0)) (5 0.4 50 (0 255 0)) (6 0.2 40 (127 255 0)) 
                   (7 0.7 65 (0 0 255)) (8 0.3 60 (127 255 63))))
       (let* ((num   (car    el))
              (k     (cadr   el))
              (r     (caddr  el))
		        (color (cadddr el))
			     (star  (make-star  r num k))
			     (minp  (min-pos star)))
		   (prn "n: " num ", k: " k ",r: " r ",color: " color "\n")
		   ;;(gimp-context-set-foreground color)
		   (draw-segment-line1 i1 (split-contour-by-delta 
		                            (translate-contour star
		                                   (comb-tr2d
			            							      (make-tr2d-move (- (p-x minp)) (- (p-y minp)))
		                                          (make-tr2d-move x y)))
                                      delta-split) bsh cm1)
		   
		   (set! x (+ x 100))
		 ))
   ;;(gimp-image-undo-group-end i1)
   )

Практически тот же код что и для рисования обычных контуров, НО есть cm1 — color map — таблица цветов, которая и определяет градиент.

Работает гораздо медленне, но работает. А само замедление имеет две причины, по мимо первой, это увеличение количества отрезков которые надо отрисовать, значительно возрастает стек октата изменений, так называемые undo. Что бы избежать этого, здесь и в дальнешем рекомендую код производящий множество мелких изменений на изображении обрамлять двумя командами:

(gimp-image-undo-group-start i1)
...
код изменяющий i1
...
(gimp-image-undo-group-end i1)

Если же просто выключить накопление стека отката командой: gimp-image-undo-disable то все эти изменения будет тяжело убрать с изображения, откат будет вовсе отменён.

Результаты работы:

«Рисование контура звёзд псевдо градиентом»

Ну и на этом на сегодня всё. Благодарю за внимание!!!

© Habrahabr.ru