GIMP Script-Fu ООП. Векторы

от автора

Библиотека функций к 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).

Контуры безье в GIMP

Контуры безье в GIMP

Чтобы получить список контуров Безье на изображении можно воспользоваться функцией.

;; получить список векторов в изображении (всего один вектор с 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)# 
Результат вставки контура безье в изображение GIMP

Результат вставки контура безье в изображение GIMP

Второй контур повернём на 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

Результат вставки контуров безье в изображение GIMP

Заключение.

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


ссылка на оригинал статьи https://habr.com/ru/articles/937152/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *