Библиотека функций к 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.
ссылка на оригинал статьи https://habr.com/ru/articles/937152/
Добавить комментарий