Как стать автором
Поиск
Написать публикацию
Обновить

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

Время на прочтение10 мин
Количество просмотров175

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

Теги:
Хабы:
+4
Комментарии1

Публикации

Ближайшие события