Библиотека функций к Script-fu
Введение
Нет в этой статье я не буду рассказывать о классах "Вектор", темой моего рассмотрения будет графический элемент изображения в GIMP, под названием vector
и его составляющие под названием stroke
.
Когда то, очень давно, считалось что графические редакторы делятся на растровые и векторные. К векторным относились Coreldraw, Adobe Illustrator, Inkscape и работали они не с отдельными пикселами изображений, а с элементами называемыми векторами, которые можно превращать, с помощью манипуляций в дуги различной формы, называемыми кривыми Безье. К растровым же относились Photoshop, Paint и тот же GIMP. В этих редакторах отсутствовала какая либо геометрия и изображения представляли собой наборы отдельных точек - пикселов. Но время не стоит на месте и элементы растрового редактирования проникали в редакторы, которые считались векторными и наоборот, элементы векторных редакторов переносились в растровые графические редакторы. Примером тому является GIMP.
Представление о том, что GIMP то растровый графический редактор давно уже устарело. Текстовый слой, да и вся работа со шрифтами в GIMPе это работа с векторными объектами представляющими собой кривые Безье. Таким образом целые текстовые слои в GIMP представляют собой векторные объекты. По мимо отдельных текстовых слоёв, в каждом изображении GIMP можно создавать отдельные строки и вектора. Вот с ними мы сегодня и разберёмся.
Векторы и строки.
Stroke
или по простому строка(штрих), это контур, состоящий из отрезков Безье, которые выражаются узлами, где каждый узел представляет собой три точки. Центральная точка это начало и конец отрезка контура, точка относящаяся к предыдущему отрезку, это "рычаг" позволяющий манипулировать предыдущим отрезком безье, точка относящаяся к следующему отрезку. Так же для строки существует флаг обозначающий замкнутые строки(начало соединяется с концом контура). Набор таких строк (stroke
) образует вектор(vector
).
Создание и редактирование векторов в gimp
Выбираем инструмент "Контуры" в режиме создания
. И начинаем отмечать точки контура, создавая последовательность точек. Далее можно в том же режиме выделять мышкой уже созданную точку и перемещать её, изменяя контур. Делая текущей крайнюю точку можно продолжать создание текущего контура. А при выделенной точке в середине контура, можно начать создание нового контура. Нажав в этом режиме "Ctrl" и выбрав противоположный конец контура мы можем замкнуть контур.
Находясь в режиме правка
можно щёлкнуть на противоположном конце контура и замкнуть контур. Также в режиме правки
можно щёлкнуть на конце другого контура и объединить контуры. В режиме Правка
можно изменить форму контура. С каждой точкой контура связаны две точки рычага, один из которых (первый в структуре данных) отвечает за форму предшествующей линии, другой (последний в структуре данных) за последующую линию. Таким образом за каждую линию ответственны две точки, начало и конец, и два рычага,т.е всего четыре точки. Они определяют форму кубической кривой Безье.
В режиме Правка
нажав клавишу "Shift" можно удалять либо соединение между точками, либо сами точки, с помощью указателя мыши.
Так же режимы управляются клавишами "Ctrl" - переключает из режима создания
в режим Правка
. Таким образом можно удалять узлы из режима Создания
нажимая "Ctrl+Shift".
Последний режим это Перемещение
или нажатие клавиши "Alt". Позволяет схватить контур и переместить его в заданную позицию.
На базе контура можно создать выделение - "Выделение из контура" . Можно заполнить цветом фигуру - "Отразить Контур". Можно обрисовать линию установленным цветом и кистью.
Также контур можно создать из выделения, во вкладке "Выделение" последний пункт "В контур".
Работа с векторами и строками из 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 "struct2.scm"))
(load (string-append path-lib "storage.scm"))
(load (string-append path-lib "cyclic.scm"))
(load (string-append path-lib "hashtable3.scm")) ;;хеш который может работать с объектами в качестве ключей!
(load (string-append path-lib "sort2.scm"))
(load (string-append path-lib "tsort.scm"))
;;(load (string-append path-lib "cpl-sbcl.scm"))
(load (string-append path-lib "cpl-mro.scm"))
;;(load (string-append path-lib "cpl-topext.scm"))
(load (string-append path-lib "struct2ext.scm"))
(load (string-append path-lib "queue.scm"))
(load (string-append path-lib "obj5.scm"))
(load (string-append path-lib "obj/object.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 "rect.scm"))
(load (string-append path-lib "vect.scm"))
(load (string-append path-lib "brush.scm"))
(load (string-append path-lib "fig-obj4.scm"))
(load (string-append path-lib "bezier.scm"))
(load (string-append path-lib "fig-obj3bz.scm"))
Изображение можно создать из консоли Script-fu.
(define i1 (create-1-layer-img 640 480)) ;; создаём холст для рисования.
Допустим мы создали вот такой вот контур(вектор) состоящий из двух строк(stroke).

Чтобы получить список контуров Безье на изображении можно воспользоваться функцией.
;; получить список векторов в изображении (всего один вектор с id = 3)
(gimp-image-get-vectors i1) ;;(1 #(3))
;; получить строки из вектора 3, всего 2 строки 1 и 2
(gimp-vectors-get-strokes 3) ;;(2 #(1 2))
;;получить точки из строки 1 вектора 3
(gimp-vectors-stroke-get-points 3 1)
;; (0 30 #(200.0 12.0 113.0 204.0 79.0 394.0 275.0 335.0 275.0 335.0 275.0 335.0 329,0132792.0
;; 287,992666.0 393.0 286.0 471,9329514.0 283,5418804.0 493.0 291.0 564.0 326.0 503.0
;; 196.0 391.0 145.0 391.0 145.0 391.0 145.0) 1)
;;получить точки из строки 2 вектора 3
(gimp-vectors-stroke-get-points 3 2)
;; (0 30 #(443.0 75.0 497.0 71.0 500.0 73.0 570.0 57.0 570.0 57.0 570.0 57.0 598.0 183.0 562.0
;; 190.0 562.0 190.0 529,6023787.0 203,7115205.0 514.0 196.0 488,5900101.0
;; 183,4410395.0 475.0 147.0 475.0 147.0 475.0 147.0) 1)
Работать с такими данными невозможно поэтому я создал простую структуру для хранения данных о контуре безье.
;;Контур безье.
;; контур безье состоящий из точек безье(это не математическое понятие)
;; и флага замкнутости контура
(struct bezier (points closed))
;;тройка точек из которых состоит контур безье, центр- одна из точек контура
;;прев- рычаг управляющей входящей линией безье в контуре
;;некст- рычак управляющей исходящей линией безье в контуре.
(struct bezier-p (center prev next))
Для работы с этой структурой я написал несколько функций, создающую структуру bezier
по получаемому из GIMP массиву, и выполняющую обратное преобразование, по имеющейся структуре bezier
создающую массив чисел, который функции GIMP могут использовать для построения строк и векторов на изображении.
;;points должен быть списком точек bezier-p
(defun (make-bezier points &key (closed 0))
(if (and (pair? points)
(bezier-p? (car points)))
(bezier! points closed)
#f))
;;из массива полученного из GIMP получаем список точек безье; т.е троек
;;из которых состоит контур
(define (parse-stroke-points arr)
(let* ((nums (vector-length arr))
(num-points (/ nums 6))
(rez '()))
(if (exact? num-points)
(for (i 0 (- num-points 1))
(let ((ind (* i 6)))
(set! rez
(cons
(bezier-p!
(p! (vector-ref arr (+ ind 2)) (vector-ref arr (+ ind 3)))
(p! (vector-ref arr (+ ind 0)) (vector-ref arr (+ ind 1)))
(p! (vector-ref arr (+ ind 4)) (vector-ref arr (+ ind 5)))
) rez))))
rez)
(reverse rez)))
;;из списка точек безье получаем массив координат для использования в функциях
;;гимпа для рисования strokes - контуров безье.
(define (make-stroke-points lst)
(let* ((num-points (length lst))
(nums (* num-points 6))
(arr (make-vector nums))
(i 0))
(for-list (el lst)
(vector-set! arr (+ i 0) (p-x (bezier-p-prev el)))
(vector-set! arr (+ i 1) (p-y (bezier-p-prev el)))
(vector-set! arr (+ i 2) (p-x (bezier-p-center el)))
(vector-set! arr (+ i 3) (p-y (bezier-p-center el)))
(vector-set! arr (+ i 4) (p-x (bezier-p-next el)))
(vector-set! arr (+ i 5) (p-y (bezier-p-next el)))
(set! i (+ i 6)))
arr))
И теперь можно написать функции получающие из изображения GIMP строки и создающие структуру bezier
. Я написал всего две функции, получить из активного вектора первую строку и просто получить из изображения заданную строку заданного вектора.
;;создать кривую безье из имеющегося активного вектора на изображении
(define (make-bezier-from-img img)
(let* ((v (car (gimp-image-get-active-vectors img)))
(strokes (gimp-vectors-get-strokes v)))
(if (> (car strokes) 0)
(let* ((stroke-id (vector-ref (cadr strokes) 0))
(points (gimp-vectors-stroke-get-points v stroke-id)))
(make-bezier (parse-stroke-points (caddr points))
:closed (cadddr points)))))
)
(define (make-bezier-from-img-by-id img vec stroke-id)
(let ((points (gimp-vectors-stroke-get-points vec stroke-id)))
(make-bezier (parse-stroke-points (caddr points))
:closed (cadddr points))))
Вот как ими можно воспользоваться:
(define b1 (make-bezier-from-img i1))
;;#(bezier (#(bezier-p #(p 113.0 204.0) #(p 200.0 12.0) #(p 79.0 394.0)) ....
(define b2 (make-bezier-from-img-by-id i1 3 2))
;;#(bezier (#(bezier-p #(p 497.0 71.0) #(p 443.0 75.0) #(p 500.0 73.0))
;;#(bezier-p #(p 570.0 57.0) #(p 570.0 57.0) #(p 570.0 57.0)) ....
Таким образом строки и вектора GIMP могут служить замечательным средством ВВОДА данных в Script-fu и использоваться в самых различных целях.
Для работы с абстракцией контура состоящего из отрезков безье я написал несколько функций: двумерного преобразования контура безье и определения его габаритов.
;;трансформация точки входящей в контур безье
(define (bezier-p-tr2d bp tr)
(bezier-p!
(p-tr2d (bezier-p-center bp) tr)
(p-tr2d (bezier-p-prev bp) tr)
(p-tr2d (bezier-p-next bp) tr)))
;; преобразование контура безье(всех точек списка) с помощью матрицы tr
(define (translate-bezier bc tr)
(bezier!
(map
(lambda (p)
(bezier-p-tr2d p tr))
(bezier-points bc))
(bezier-closed bc)))
Габариты у кривой безье нечёткие и трудно определимые, можно работать с точками определяющий габарит одного отрезка безье, а можно работать с центральными точками, не обращая внимания на то что линия безье это не прямая, а кривая(как правило) и её габариты не определяются крайними точками начала и конца линии.
работа с габаритами
;;минимальная координата тройки точек узла безье
(define (min-bezier-all-p bp)
(let ((min-x (p-x (bezier-p-center bp)))
(min-y (p-y (bezier-p-center bp))))
(if (< (p-x (bezier-p-prev bp)) min-x)
(set! min-x (p-x (bezier-p-prev bp))))
(if (< (p-y (bezier-p-prev bp)) min-y)
(set! min-y (p-y (bezier-p-prev bp))))
(if (< (p-x (bezier-p-next bp)) min-x)
(set! min-x (p-x (bezier-p-next bp))))
(if (< (p-y (bezier-p-next bp)) min-y)
(set! min-y (p-y (bezier-p-next bp))))
(p! min-x min-y)))
;;максимальная координата тройки точек узла контура безье
(define (max-bezier-all-p bp)
(let ((max-x (p-x (bezier-p-center bp)))
(max-y (p-y (bezier-p-center bp))))
(if (> (p-x (bezier-p-prev bp)) max-x)
(set! max-x (p-x (bezier-p-prev bp))))
(if (> (p-y (bezier-p-prev bp)) max-y)
(set! max-y (p-y (bezier-p-prev bp))))
(if (> (p-x (bezier-p-next bp)) max-x)
(set! max-x (p-x (bezier-p-next bp))))
(if (> (p-y (bezier-p-next bp)) max-y)
(set! max-y (p-y (bezier-p-next bp))))
(p! max-x max-y)))
;;хотя эти функции можно было бы и переделать для учёта значений только центральной
;;точки!!!
;;минимальная координата тройки точек узла безье
(define min-bezier-p (lambda (p) (bezier-p-center p)))
;;максимальная координата тройки точек узла контура безье
(define max-bezier-p (lambda (p) (bezier-p-center p)))
;;считаем что минимальная и максимальная точка, это всегда центральная точка
;;узла безье!!!
;;минимальная позиция контура безье
(defun (min-bezier bc &key (min-p min-bezier-p))
(let* ((contour (bezier-points bc))
(tmp-p (min-p (car contour)))
(min-x (p-x tmp-p))
(min-y (p-y tmp-p)))
(do ((cur (cdr contour) (cdr cur)))
((null? cur) (p! min-x min-y))
(let ((cur-p (min-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)))
)
)))
;;максимальная позиция контура безье
(defun (max-bezier bc &key (max-p max-bezier-p))
(let* ((contour (bezier-points bc))
(tmp-p (max-p (car contour)))
(max-x (p-x tmp-p))
(max-y (p-y tmp-p)))
(do ((cur (cdr contour) (cdr cur)))
((null? cur) (p! max-x max-y))
(let ((cur-p (max-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))
())
)
)))
(define (gabarite-bezier-all bc)
(let ((min-p (min-bezier bc :min-p min-bezier-all-p))
(max-p (max-bezier bc :max-p max-bezier-all-p)))
(p! (- (p-x max-p) (p-x min-p))
(- (p-y max-p) (p-y min-p)))
))
(define (gabarite-bezier bc)
(let ((min-p (min-bezier bc))
(max-p (max-bezier bc)))
(p! (- (p-x max-p) (p-x min-p))
(- (p-y max-p) (p-y min-p)))
))
;;трансформация контура безье в начало координат.условная, т.к это не точные координаты
(define (bezier-to-origin bc)
(let ((p-min (min-bezier bc)))
;;(prn "p-min: " p-min "\n")
(translate-bezier bc
(make-tr2d-move (- (p-x p-min)) (- (p-y p-min))))))
Также я написал пару функций для размещению контуров кривых безье на изображение.
;;помещает контур безье на изображение создавая вектор и черту(строчку)
(defun (bezier-to-image img bc &key (name "work vector"))
(let* ((num-points (length (bezier-points bc)))
(points (make-stroke-points (bezier-points bc)))
;;(v (car (gimp-vectors-new img name)))
(tmp1 (prn "v:" v ", len: " (vector-length points) ", p: " points "\n"))
(stroke-id (gimp-vectors-stroke-new-from-points
v 0
(vector-length points) points
(bezier-closed bc))))
(gimp-image-add-vectors img v 0)
(cons v stroke-id)))
(defun (bezier-to-image-by-id img bc vec)
(let* ((num-points (length (bezier-points bc)))
(points (make-stroke-points (bezier-points bc)))
(stroke-id (gimp-vectors-stroke-new-from-points
vec 0
(vector-length points) points
(bezier-closed bc))))
(cons vec stroke-id)))
Пример использования
Что бы продемонстрировать работу функций вставки строк и векторов в изображение GIMP, давайте уже полученные из GIMP контуры Безье немного модифицируем и вставим обратно в GIMP.
Первый контур уменьшим в два раза и смести его положение тоже уменьшив позицию контура в два раза. И поместим его в уже имеющийся вектор 3.
(define min-b1 (min-bezier b1)) ;;#(p 113.0 145.0)
(define gab-b1 (gabarite-bezier b1)) ;;#(p 451.0 190.0)
(define min-b1-new (p-tr2d min-b1 (make-tr2d-scale 0.5 0.5))) ;;#(p 56,5.0 72,5.0)
(define b1t (translate-bezier b1
(comb-tr2d
(make-tr2d-move (- (p-x min-b1)) (- (p-y min-b1))) ;;в начало
(make-tr2d-scale 0.5 0.5) ;;масштабируем
(make-tr2d-move (p-x min-b1-new) (p-y min-b1-new))))) ;;в новую точку.
;;#(bezier (#(bezier-p #(p 56,5.0 102.0) #(p 100.0 6.0) ...
(bezier-to-image-by-id i1 b1t 3) ;;(3 3)#

Второй контур повернём на 90 градусов и переместим в центр изображения. А сам контур безье поместим в новый вектор.
(define min-b2 (min-bezier b2)) ;;#(p 475.0 57.0)
(define gab-b2 (gabarite-bezier b2)) ;;#(p 95.0 139.0)
(define min-b2-new (p-tr2d (p! (car (gimp-image-width i1))
(car (gimp-image-height i1)))
(make-tr2d-scale 0.5 0.5))) ;;(p 320.0 240.0)
(define b2t (translate-bezier b2
(comb-tr2d
(make-tr2d-move (- (p-x min-b2)) (- (p-y min-b2))) ;;в начало
(make-tr2d-rot 90) ;;поворачиваем
(make-tr2d-move (p-x min-b2-new) (p-y min-b2-new))))) ;;в новую точку.
;;#(bezier (#(bezier-p #(p 306.0 262.0) #(p 302.0 208.0) ...
(bezier-to-image i1 b2t :name "My new vector")

Заключение.
Векторные возможности графического редактора GIMP серьёзно улучшают его функциональность при построении изображений. Их можно использовать и как средство взаимодействия со скриптами Script-fu, а также непосредственно при построении изображений из Script-fu.