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

В предыдущей статье мы рассмотрели имеющиеся в GIMP возможности векторной графики. Здесь мы рассмотрим как эти возможности использовать при построении графических примитивов - Фигур. Для построения абстракций фигур мы уже написали несколько классов: Фигуры рисуемых по контуру Кистью и Карандашом, Фигур заполняемых определённым цветом, Комбинированных Фигур, Фигур использующих Изображения и Фигур использующих Текст. Здесь я продемонстрирую, как легко и непринуждённо мы можем встроить новые абстракции в существующую иерархию классов. А заодно рассмотрим как вся эта иерархия классов может использоваться в языке функциональной геометрии, рассмотренном в предыдущем цикле статей.

Подготовка

В целях демонстрации работы с векторами я решил подготовить какой-нибудь сложный вектор, содержащий несколько строк (stroke), т.к. при использовании вектора для построения изображения, например заполнении цветом или текстурой используются одновременно все строки вектора, порой образуя очень сложные комбинации заполнения. Поэтому в качестве подопытного вектора я решил нарисовать ... СМАЙЛИК! Смайлик состоит из головы, двух глаз и улыбки. Как вы думаете легко ли построить контур из линий безье отображающий круг?

подготовка к подготовке загружаем библиотеки и создаём холст для рисования.
(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 "bezier.scm"))

(load (string-append path-lib "fig-obj4.scm"))
(load (string-append path-lib "fig-obj4bz.scm"))

(define i1 (create-1-layer-img 640 480)) ;; создаём холст для рисования.
  • Первая попытка была построить круг в ручную:

Контур круга из отрезков безье
Контур круга из отрезков безье
  • Вторая попытка использовать функцию рисования круга, а затем на заполненной фигуре применить выделение по цвету и к выделению применить, пункт меню «Выделение» «В контур».

(define c1 (make-circle-n 50 50 40 30))
(define fg-color (car (gimp-context-get-foreground)))
(define fig1 (shape-fig! :contour c1 :color fg-color))
(draw fig1 i1 #f) ;;нарисовали фигуру по контуру без трансформации.
(gimp-image-select-color i1 CHANNEL-OP-ADD (car (gimp-image-get-active-drawable i1)) fg-color) ;#t
Контур круга из выделения
Контур круга из выделения
  • Третья попытка это использовать встроенную функцию GIMP по построению элипсов.

операции манипулирования строками в векторах.
(gimp-vectors-bezier-stroke-new-ellipse vectors x0 y0 radius-x radius-y angle)
(gimp-vectors-stroke-scale vectors stroke-id scale-x scale-y)
(gimp-vectors-stroke-translate vectors stroke-id off-x off-y)

Для построения овальных фигур в смайлике воспользуемся функцией предоставляемой GIMPом gimp-vectors-bezier-stroke-new-ellipse.

(gimp-image-get-vectors i1)    ;;(3 #(153 152 3))
(gimp-vectors-bezier-stroke-new-ellipse 153 50 50 40 40 0)
(gimp-vectors-bezier-stroke-new-ellipse 153 41 40 4 10 0)
(gimp-vectors-bezier-stroke-new-ellipse 153 61 40 4 10 0)

улыбку пришлось строить вручную.

Контур смайлика построенный с помощью внутренних функций GIMP
Контур смайлика построенный с помощью внутренних функций GIMP

теперь мы можем получить координаты вектора, представляющего самйлик из GIMPа в Script-fu

(define bzs1 (make-beziers-from-img-by-vector i1 153))
(define bzs1o (beziers-to-origin bzs1))
(define min-bzs1 (min-beziers bzs1o)) ;;#(p 0.0 0.0)
(define max-bzs1 (max-beziers bzs1o)) ;;#(p 80.0 80.0)
(define bzs1t (translate-beziers bzs1o ;;перевернули смайлик так чтобы он правильно отображался в фигурах				 (comb-tr2d				  (make-tr2d-reflect-x)				  (make-tr2d-move 0 (+ (p-y min-bzs1) (p-y max-bzs1))))))

выведем эти данные в консоль Script-fu:

данные о смайлике в кривых безье из консоли.
;;улыбка
(car bzs1t)
;; #(bezier (#(bezier-p #(p 17,125.0 32,625.0) #(p 16,18532994.0 31,34665689.0) #(p 17,57473711.0 30,4158742.0)) #(bezier-p #(p 42,375.0 19,625.0) #(p 31.0 19,375.0) #(p 54,75.0 19,625.0)) #(bezier-p #(p 64,375.0 33,75.0) #(p 65,5817278.0 33,19513436.0) #(p 65,19396945.0 33,35153491.0)) #(bezier-p #(p 50,75.0 16,75.0) #(p 58,375.0 21.0) #(p 48,125.0 14,625.0)) #(bezier-p #(p 35,125.0 16.0) #(p 40,625.0 14,375.0) #(p 25,375.0 19,125.0))) 1)
;;голова
(cadr bzs1t)
;; #(bezier (#(bezier-p #(p 80.0 40.0) #(p 80.0 62,09138999.0) #(p 80.0 17,90861001.0)) #(bezier-p #(p 40.0 0.0) #(p 62,09138999.0 0.0) #(p 17,90861001.0 0.0)) #(bezier-p #(p 0.0 40.0) #(p 0.0 17,90861001.0) #(p 0.0 62,09138999.0)) #(bezier-p #(p 40.0 80.0) #(p 17,90861001.0 80.0) #(p 62,09138999.0 80.0))) 1)
;;глаза
(caddr bzs1t)
;; #(bezier (#(bezier-p #(p 35.0 50.0) #(p 35.0 55,5228475.0) #(p 35.0 44,4771525.0)) #(bezier-p #(p 31.0 40.0) #(p 33,209139.0 40.0) #(p 28,790861.0 40.0)) #(bezier-p #(p 27.0 50.0) #(p 27.0 44,4771525.0) #(p 27.0 55,5228475.0)) #(bezier-p #(p 31.0 60.0) #(p 28,790861.0 60.0) #(p 33,209139.0 60.0))) 1)
(cadddr bzs1t)
;; #(bezier (#(bezier-p #(p 55.0 50.0) #(p 55.0 55,5228475.0) #(p 55.0 44,4771525.0)) #(bezier-p #(p 51.0 40.0) #(p 53,209139.0 40.0) #(p 48,790861.0 40.0)) #(bezier-p #(p 47.0 50.0) #(p 47.0 44,4771525.0) #(p 47.0 55,5228475.0)) #(bezier-p #(p 51.0 60.0) #(p 48,790861.0 60.0) #(p 53,209139.0 60.0))) 1)

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

Данные о смайлике, готовые к исполнению:
(define head-bc
  #(bezier (#(bezier-p #(p 80.0 40.0) #(p 80.0 62.09138999) #(p 80.0 17.90861001))
	    #(bezier-p #(p 40.0 0.0) #(p 62.09138999 0.0) #(p 17.90861001 0.0))
	    #(bezier-p #(p 0.0 40.0) #(p 0.0 17.90861001) #(p 0.0 62.09138999))
	    #(bezier-p #(p 40.0 80.0) #(p 17.90861001 80.0) #(p 62.09138999 80.0))) 1))

(define smile-bc
  #(bezier
    (#(bezier-p #(p 17.125 32.625) #(p 16.18532994 31.34665689) #(p 17.57473711 30.4158742))
     #(bezier-p #(p 42.375 19.625) #(p 31.0 19.375) #(p 54.75 19.625))
     #(bezier-p #(p 64.375 33.75) #(p 65.5817278 33.19513436) #(p 65.19396945 33.35153491))
     #(bezier-p #(p 50.75 16.75) #(p 58.375 21.0) #(p 48.125 14.625))
     #(bezier-p #(p 35.125 16.0) #(p 40.625 14.375) #(p 25.375 19.125))) 1))

(define eye1-bc
  #(bezier
    (#(bezier-p #(p 35.0 50.0) #(p 35.0 55.5228475) #(p 35.0 44.4771525))
     #(bezier-p #(p 31.0 40.0) #(p 33.209139 40.0) #(p 28.790861 40.0))
     #(bezier-p #(p 27.0 50.0) #(p 27.0 44.4771525) #(p 27.0 55.5228475))
     #(bezier-p #(p 31.0 60.0) #(p 28.790861 60.0) #(p 33.209139 60.0))) 1))

(define eye2-bc
  #(bezier (#(bezier-p #(p 55.0 50.0) #(p 55.0 55.5228475) #(p 55.0 44.4771525))
	        #(bezier-p #(p 51.0 40.0) #(p 53.209139 40.0) #(p 48.790861 40.0))
	        #(bezier-p #(p 47.0 50.0) #(p 47.0 44.4771525) #(p 47.0 55.5228475))
	        #(bezier-p #(p 51.0 60.0) #(p 48.790861 60.0) #(p 53.209139 60.0))) 1))

Создаём классы фигур Безье.

В качестве фигур безье я решил создать два типа фигур: фигуры контурные и фигуры заполняемые выбранным цветом.

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

функции для работы со списком контуров Безье.
;;преобразование списка строк(а список строк как раз и представляет собой вектор)
(define (translate-beziers bclist tr)
  (map
   (lambda (bc)
     (translate-bezier bc tr))
   bclist))

;;минимальная позиция СПИСКА контуров безье, т.е для вектора.
(defun (min-beziers bzlist &key (min-p min-bezier-p))
  (let* ((tmp-p  (min-bezier (car bzlist) :min-p min-p))
         (min-x  (p-x tmp-p))
         (min-y  (p-y tmp-p)))
    (do ((cur (cdr bzlist) (cdr cur)))
        ((null? cur) (p! min-x min-y))
      (let ((cur-p (min-bezier (car cur) :min-p min-p)))
        (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-beziers bzlist &key (max-p max-bezier-p))
  (let* ((tmp-p   (max-bezier (car bzlist) :max-p max-p))
	 (max-x   (p-x tmp-p))
         (max-y   (p-y tmp-p)))
    (do ((cur (cdr bzlist) (cdr cur)))
        ((null? cur) (p! max-x max-y))
      (let ((cur-p (max-bezier (car cur) :max-p max-p)))
        (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 (beziers-to-origin bclist)
   (let ((p-min (min-beziers bclist)))
      (translate-beziers bclist
                         (make-tr2d-move (- (p-x p-min)) (- (p-y p-min))))))

;;помещает на изображение вектор, т.е список контуров безье создавая новый вектор.
(defun (beziers-to-img img beziers &key (name "work vector"))
  (let ((v          (car (gimp-vectors-new img name))))
    (for-list (bezier beziers)
       (let ((num-points (length (bezier-points bezier)))
	         (points     (make-stroke-points (bezier-points bezier))))
	     (gimp-vectors-stroke-new-from-points
          v 0                           ;;добавляем вверх стека.
          (vector-length points) points
          (bezier-closed bezier))))
    (gimp-image-add-vectors img v 0)
    v))

;;создать список кривых безье из указанного вектора на изображении
(define (make-beziers-from-img-by-vector img vec)
  (let ((strokes-id (cadr (gimp-vectors-get-strokes vec)))
	(rez '()))
    (for-vect (i strokes-id)
	      (push rez (make-bezier-from-img-by-id img vec (vector-ref strokes-id i))))
    (reverse rez)))

Теперь используя функции для работы со списком контуров фигур мы можем построить классы представляющие фигуры рисуемые с помощью контуров Безье.

(defclass beziered ()
  (beziers)) ;;будем хранить для каждого объекта список контуров безье, т.е stroke


(defclass bezier-fig (fig beziered)
  ())


;;одноконтурная фигура рисуемая кистью
(defclass brush-bz-fig (bezier-fig colored brushed)
  ())

;;закрашиваемая фигура, ксть ей нужна потому что в ней устанавливается прозрачность.
(defclass shape-bz-fig (bezier-fig colored brushed)
  ())

Обратите внимание, на то что классы верхнего уровня не имеют переменных, т.к. их свойста контуры, цвет и кисть, ортогональные и наследуются из соответсвующих миксинов. Так же создаётся промежуточный класс bezier-fig ,необходимый для согласования габаритов и габаритов списка контуров безье.

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

(defmethods beziered
  (parsed-change (parsed)
     (if (next-method-p) (call-next-method))
     (awhen (assq :beziers parsed)  (set! self.beziers  (cdr it)))
     self)
  )


(def-key :overall-size)
(defmethods bezier-fig
  (parsed-change (parsed)
     (if (next-method-p) (call-next-method))
     (let* ((has-overall-size (assq :overall-size parsed))
	        (overall-size (if has-overall-size (cdr has-overall-size) #f))
	        (min-bezier-p-func (if overall-size min-bezier-all-p min-bezier-p))
	        (max-bezier-p-func (if overall-size max-bezier-all-p max-bezier-p)))
       (let ((min-p (min-beziers self.beziers :min-p min-bezier-p-func))
             (max-p (max-beziers self.beziers :max-p max-bezier-p-func)))
	     (assign-undefined self (p-x min-p) (p-y min-p) (p-x max-p) (p-y max-p)))
       self)
     )
  )

Функции рисования фигур Безье.

(defmethods brush-bz-fig
  (draw (img tr)
     (let* ((beziers    (if tr
                           (translate-beziers self.beziers tr)
			               self.beziers))
	       (dw         (car (gimp-image-get-active-drawable img)))
	       (v          (beziers-to-img img beziers)))
       (gimp-edit-stroke-vectors  dw  v)
       (gimp-image-remove-vectors img v))
     )
  )


(defmethods shape-bz-fig
  (draw (img tr)
     (let* ((beziers    (if tr
                           (translate-beziers self.beziers tr)
                           self.beziers))
	        (dw         (car (gimp-image-get-active-drawable img)))
	        (v          (beziers-to-img img beziers)))
       (gimp-vectors-to-selection v  CHANNEL-OP-REPLACE FALSE
				                  FALSE 0 0)
       (gimp-edit-fill dw  FOREGROUND-FILL)
       (gimp-selection-none img)
       (gimp-image-remove-vectors img v))
     )
  )

Ну и это ВЕСЬ код, который потребовалось написать, для получения возможности работать с фигурами Безье.

Рисуем смайлик с помощью объектов класса фигур.

Давайте нарисуем пару смайликов с помощью наших классов Фигур.

;; Определяем три набора списков контуров Безье.
(define bz1 (list head-bc smile-bc eye1-bc eye2-bc))
(define bz2 (list head-bc))
(define bz3 (list smile-bc eye1-bc eye2-bc))

;;Рисуем первый смайлик (жёлтый)
(define sh-bz1 (shape-bz-fig! :beziers bz1 :color '(255 255 0)))
(define r1 (rect! (p! 20 220) (p! 200 0) (p! 0 -200)))
(draw-to-rect sh-bz1 i1 r1)

;;Рисуем второй смайлик (зеленоватый).
(define sh-bz2 (complex-fig!
		:figs (list (shape-bz-fig! :beziers bz2 :color '(100 255 0))
			        (shape-bz-fig! :beziers bz3 :color '(0 0 0))
			        (brush-bz-fig! :beziers (list head-bc eye1-bc eye2-bc) :color '(0 0 255))
			        (brush-bz-fig! :beziers (list smile-bc ) :color '(200 0 0)))))
(define r2 (rect! (p! 230 220) (p! 200 0) (p! 0 -200)))
(draw-to-rect sh-bz2 i1 r2)

Как можете видеть из примера, классы фигур безье отлично работают совместно с ранее описанным классом составных фигур.

Два смайлика нарисованные с помощью классов фигур.
Два смайлика нарисованные с помощью классов фигур.

Сопряжение новых классов фигур с ранее описанным языком Функциональной геометрии.

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

(load (string-append path-lib "pic.scm"))
;;создать рисунок(picture) из объекта фигуры
(defun (pic! fig-obj)
   (lambda (img dest-r)
      (draw-to-rect fig-obj  img dest-r)))

Проверка работы новых классов фигур в языке Функциональной геометрии.

Для проверки работы надо загрузить функции языка из файла pic.scm. Определим рамки, в которых будет производиться рисование и отобразим их.

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

(define r1 (make-rect-by-vect (p! 10  210) (p! 200 0)  (p!   0 -200)))
(define r2 (make-rect-by-vect (p! 270 210) (p! 200 0)  (p! -40 -200)))
(define r3 (make-rect-by-vect (p! 100 370) (p! 100 0)  (p!   0 -100)))
(define r4 (make-rect-by-vect (p! 250 370) (p! 100 0)  (p!   0 -100)))
(define r5 (make-rect-by-vect (p! 400 370) (p! 150 40) (p!  30 -150)))


(define c2 (make-rect-contour 0 0 50 50))
(define f2 (brush-fig! :name 'rect1   :color '(255 0 0)   :contour c2))

;;рамки для обозначения габаритов рисования.
(draw-to-rect f2 i1 r1)
(draw-to-rect f2 i1 r2)
(draw-to-rect f2 i1 r3)
(draw-to-rect f2 i1 r4)
(draw-to-rect f2 i1 r5)

Далее остаётся создать рисунки(picture) и применить к ним любую операцию языка функциональной геометрии. Первым делом просто проверим работает ли это на операции pic-beside

(define p1 (pic! sh-bz1))
(define p2 (pic! sh-bz2))

((pic-beside p1 p2 0.7) i1 r1)
((pic-beside p1 p2 0.3) i1 r2)

(define bs (pic-beside p1 p2 0.4))

(bs i1 r3)
(bs i1 r4)

((pic-beside p1 p1 0.8) i1 r5)
Применение операции beside к картинкам смайликов.
Применение операции beside к картинкам смайликов.

Создадим картинку смайлика из фигуры со слегка смещёнными габаритами и рамки для куда будут отображаться результаты операций с картинками:

(define sh-bz3 (complex-fig!
		:figs (list (shape-bz-fig! :beziers bz2 :color '(100 255 0))
			    (shape-bz-fig! :beziers bz3 :color '(0 0 0))
			    (brush-bz-fig! :beziers (list head-bc eye1-bc eye2-bc) :color '(0 0 255))
			    (brush-bz-fig! :beziers (list smile-bc ) :color '(200 0 0)))
                :min-x 5 :min-y -5 :max-x 85 :max-y 75))
(define pic-f (pic! sh-bz3))


(get-gabarite sh-bz3) ;;(5 -5 85 75)

(define r1 (make-rect-by-vect (p! 10  110) (p! 100 0)  (p!   0 -100)))
(define r2 (make-rect-by-vect (p! 170 110) (p! 100 0)  (p! -40 -100)))
(define r3 (make-rect-by-vect (p! 300 110) (p! 100 20) (p!  30 -100)))
(define r4 (make-rect-by-vect (p! 440 110) (p! 100 0)  (p!   0 -100)))
(define r5 (make-rect-by-vect (p! 10  350) (p! 100 0)  (p!   0 -180)))
(define r6 (make-rect-by-vect (p! 150 350) (p! 100 0)  (p!   0 -180)))
(define r7 (make-rect-by-vect (p! 250 350) (p! 150 40) (p!  30 -180)))
(define r8 (make-rect-by-vect (p! 470 380) (p! 150 40) (p!  -30 -180)))

(define c2 (make-rect-contour 0 0 50 50))
(define f2 (brush-fig! :name 'rect1   :color '(255 0 0)   :contour c2))

(define f3 (brush-fig! :name 'rect2   :color '(127 0 0)   :contour c2))
(define pic3 (pic! f3))

;;рамки для обозначения габаритов рисования.
(draw-to-rect f2 i1 r1)
(draw-to-rect f2 i1 r2)
(draw-to-rect f2 i1 r3)
(draw-to-rect f2 i1 r4)
(draw-to-rect f2 i1 r5)
(draw-to-rect f2 i1 r6)
(draw-to-rect f2 i1 r7)
(draw-to-rect f2 i1 r8)

И отобразим результаты операций над смайликом:

(pic-f i1 r1)
((pic-rotate90 pic-f) i1 r2)
((pic-flip     pic-f) i1 r3)
((pic-rotate90 (pic-flip  pic-f)) i1 r4)

((pic-above  pic-f pic-f 0.4)  i1 r5)
((pic-beside pic-f pic-f 0.7)  i1 r6)
((pic-above  (pic-beside pic-f pic-f 0.3) pic-f 0.6) i1 r7)
((pic-rotate45 (pic-over pic-f pic3)) i1 r8)
Применение различных операций функциональной геометрии к картинкам смайликов.
Применение различных операций функциональной геометрии к картинкам смайликов.

Заключение.

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

GIMP Script-Fu ООП. Классы. Начало

GIMP Script-Fu ООП. Основной алгоритм в ООП системах с множественным наследованием

GIMP Script-Fu ООП. Обобщённые функции

GIMP Script-Fu ООП. Основной дизайн (а-ля CLOS)

GIMP Script-Fu ООП. Тестирование на «РОМБЕ СМЕРТИ»

GIMP Script-Fu ООП. Статические поля класса

GIMP Script-Fu ООП. Dot синтаксис и другой синтаксический сахар

GIMP Script-Fu ООП. Небольшой рефакторинг объектной системы. Изюминка всего проекта

GIMP Script-Fu ООП. Обобщённые функции и примитивные типы данных

GIMP Script-Fu ООП. ООП на миксинах или сказ о том: «Да что оно может ваше множественное наследование?»

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

GIMP Script-Fu ООП. Встраиваем векторы в систему классов Фигур и все фигуры в язык Функциональной геометрии.

Надеюсь эта серия статей помогла Вам проникнуться красотой объектно ориентированного программирования и его воплощением в CLOS и моей маленькой, скромной, но вполне функциональной, объектной системой. Что же в ней реализовано? Классы, наследование, причём МНОЖЕСТВЕННОЕ, обобщённые функции, комбинации основных и вспомогательных методов описываемых квалификаторами, мультиметоды (это когда выбор исполняемого метода определяется по нескольким типам аргументов), статические поля класса, спецификация параметров методов примитивными типами данных, синтаксический сахар в виде ДОТ синтаксиса. Чего ещё можно пожелать от Объектной системы? Я не знаю.

Комментарии (0)