Библиотека функций к Script-fu
Язык функциональной геометрии
В предыдущей части мы рассмотрели базовые операции языка функциональной геометрии, так сказать кирпичики языка. Сегодня мы ознакомимся с более сложными функциями, т.е покажем что из этих кирпичиков можно построить. Базовым изображением для работы, будет "рыба" Питера Хендерсона.
Это svg файл и гимп может его использовать непосредственно:
Загрузка библиотек
(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 "struct.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.scm"))
(load (string-append path-lib "obj2.scm"))
(load (string-append path-lib "fig-obj2.scm"))
(load (string-append path-lib "pic.scm"))
(define i1 (create-1-layer-img 640 480)) ;; создаём холст для рисования.
Создаём рисунок рыбы, загрузив её из файла.
(define i-fish (img-load (join-to-str path-work "fish.svg")))
(define len-fish (car (gimp-image-width i-fish)))
(define fish-fig (image-fig! :image i-fish :flip #t :min-x 51 :max-x (- len-fish 51)))
(define fish (make-picture fish-fig))
(define r1 (rect! (p! 100 100) (p! 100 0) (p! 0 -100)))
(define c2 (make-rect-contour 0 0 50 50))
(define f2 (brush-fig! :name 'rect1 :color '(255 0 0) :contour c2))
(f2 :draw-to-rect i1 r1)
(fish i1 r1)
Или можно использовать уже предварительно обработанный файл с "рыбой" png. Но для этого надо проделать конвертацию из svg в png.
конвертация рыбы svg в png
(define (save-png img path name)
(gimp-file-save 1 img (car (gimp-image-active-drawable img))
(join-to-str path name) name))
(define i-fish (img-load (join-to-str path-work "fish.svg")))
(define fish-h (car (gimp-image-height i-fish)))
(define fish-w (car (gimp-image-width i-fish)))
(define i2 (create-transparent-layer-img (- fish-w 51) fish-h))
(define fish-fig (image-fig! :image i-fish :flip #f))
(define r2 (rect! (p! 0 fish-h) (p! fish-w 0) (p! 0 (- fish-h))))
(fish-fig :draw-to-rect i2 r2)
(save-png i2 path-work "fish2.png")
теперь загрузим полученное изображение и проверим, что у нас получилось.
(define i-fish (img-load (join-to-str path-work "fish2.png")))
(define fish-fig (image-fig! :image i-fish :flip #f :min-x 51))
(define fish (make-picture fish-fig))
(define r1 (rect! (p! 100 100) (p! 100 0) (p! 0 -100)))
(define c2 (make-rect-contour 0 0 50 50))
(define f2 (brush-fig! :name 'rect1 :color '(255 0 0) :contour c2))
(f2 :draw-to-rect i1 r1)
(fish i1 r1)

Видно что "рыба" немного выходит за границы рамки плавником и щекой.
При демонстрации возможностей языка функциональной геометрии я пойду прямо по брошюре funcgeo2.pdf.
Двоечка.
Создадим простейшую комбинацию из рыб.
;;рамка для демонстрации габаритов.
(define c2 (make-rect-contour 0 0 50 50))
(define f2 (brush-fig! :name 'rect1 :color '(255 0 0) :contour c2))
(define r1 (rect! (p! 100 110) (p! 100 0) (p! 0 -100)))
(define r2 (rect! (p! 250 110) (p! 100 30) (p! 10 -100)))
(f2 :draw-to-rect i1 r1)
(f2 :draw-to-rect i1 r2)
(define twice (pic-over fish (pic-rotate180 fish)))
(twice i1 r1)
(twice i1 r2)

Троечка.
Комбинация из трёх рыб.
;;рамка для демонстрации габаритов.
(define c2 (make-rect-contour 0 0 50 50))
(define f2 (brush-fig! :name 'rect1 :color '(255 0 0) :contour c2))
(define r1 (rect! (p! 100 110) (p! 100 0) (p! 0 -100)))
(define r2 (rect! (p! 250 110) (p! 100 30) (p! 10 -100)))
(f2 :draw-to-rect i1 r1)
(f2 :draw-to-rect i1 r2)
(define fish2 (pic-flip (pic-rotate45 fish)))
(define fish3 (pic-rotate270 fish2))
(define three (pic-over fish (pic-over fish2 fish3)))
(three i1 r1)
(three i1 r2)

Здесь интересно то, как мы "уменьшили" рыбу, повернув рисунок на 45 градусов.
Четвёрка.
Комбинация из четырёх рыб.
;;рамка для демонстрации габаритов.
(define c2 (make-rect-contour 0 0 50 50))
(define f2 (brush-fig! :name 'rect1 :color '(255 0 0) :contour c2))
(define r1 (rect! (p! 100 110) (p! 100 0) (p! 0 -100)))
(define r2 (rect! (p! 250 110) (p! 100 30) (p! 10 -100)))
(f2 :draw-to-rect i1 r1)
(f2 :draw-to-rect i1 r2)
(define fish1 (pic-flip (pic-rotate45 fish)))
(define fish2 (pic-rotate90 fish1))
(define fish3 (pic-rotate180 fish1))
(define fish4 (pic-rotate270 fish1))
(define four (pic-over fish1 fish2 fish3 fish4))
(four i1 r1)
(four i1 r2)

Квартет
Функция объединяющая четыре рисунка.
(define-m (pic-quartet1 u1 u2 u3 u4)
(pic-above (pic-beside u1 u2 0.5) (pic-beside u3 u4 0.5) 0.5))
;;или можно реализовать на базе функции:
;; отобразить четыре изображения в рамке.
;; p1 p2
;; p3 p4
(defun (pic-quartet p1 p2 p3 p4 &opt (kw 0.5) (kh 0.5))
(lambda (img dest-r)
(let* ((r1 (rect! (rect-origin dest-r) ;; r3 r4
(vect-scale (rect-horiz dest-r) kw) ;; r1 r2
(vect-scale (rect-vert dest-r) kh)))
(r2 (rect! (vect+ (rect-origin dest-r)
(rect-horiz r1))
(vect-scale (rect-horiz dest-r) (- 1 kw))
(rect-vert r1)))
(r3 (rect! (vect+ (rect-origin dest-r)
(rect-vert r1))
(rect-horiz r1)
(vect-scale (rect-vert dest-r) (- 1 kh))))
(r4 (rect! (vect+ (rect-origin r2)
(rect-vert r2))
(rect-horiz r2)
(rect-vert r3))))
(p1 img r3)
(p2 img r4)
(p3 img r1)
(p4 img r2))))
(define-m (pic-quartet2 u1 u2 u3 u4)
(pic-quartet u3 u4 u1 u2 0.5 0.5))
Функцию квартет можно выполнить в двух вариантах, в виде комбинации функций базовых примитивов языка функциональной геометрии, или в виде комбинации операций более низкого уровня, оперирующих векторами и рамками.
тестируем квартет
(define r1 (rect! (p! 50 210) (p! 200 0) (p! 0 -200)))
(define r2 (rect! (p! 270 210) (p! 200 30) (p! 20 -200)))
(define r3 (rect! (p! 50 430) (p! 200 0) (p! 0 -200)))
(define r4 (rect! (p! 270 440) (p! 200 30) (p! 20 -200)))
(f2 :draw-to-rect i1 r1)
(f2 :draw-to-rect i1 r2)
(f2 :draw-to-rect i1 r3)
(f2 :draw-to-rect i1 r4)
(define fish1 (pic-flip (pic-rotate45 fish)))
(define fish2 (pic-rotate90 fish1))
(define fish3 (pic-rotate180 fish1))
(define fish4 (pic-rotate270 fish1))
(define four (pic-over fish1 fish2 fish3 fish4))
((pic-quartet1 four four four four) i1 r1)
((pic-quartet four four four four 0.6 0.7) i1 r2)
(begin
(gimp-image-undo-disable i1)
((pic-quartet2 four four four four) i1 r3)
((pic-quartet2 four four four four) i1 r4)
(gimp-image-undo-enable i1))
Даже такие простые функции как pic-quartet накапливают большую историю изменений, поэтому для увеличения скорости я блокирую накопление изменений.

Цикл
Изображение создаёт четыре исходных копии в рамке поделённой на 4 части, при этом копии поворачиваются на 90 градусов.
;; можно сделать как указано в брошюре
;; и у автора там кажется небольшая ошибка, он вращает фигуру не в ту сторону.
;;(define-m (pic-cycle p)
;; (pic-quartet p (pic-rotate90 p) (pic-rotate180 p) (pic-rotate270 p)))
;;вот так правильно.
(define-m (pic-cycle p)
(pic-quartet1 p (pic-rotate270 p) (pic-rotate90 p) (pic-rotate180 p)))
;;или можно реализовать на базе функции pic-embedded
(defun (pic-cycle2 p &opt (kw 0.5) (kh 0.5))
(pic-quartet p (pic-rotate270 p) (pic-rotate90 p) (pic-rotate180 p) kw kh))
тестируем цикл
(define r1 (rect! (p! 50 210) (p! 200 0) (p! 0 -200)))
(define r2 (rect! (p! 270 210) (p! 200 30) (p! 20 -200)))
(define r3 (rect! (p! 50 430) (p! 200 0) (p! 0 -200)))
(define r4 (rect! (p! 270 440) (p! 200 30) (p! 20 -200)))
(f2 :draw-to-rect i1 r1)
(f2 :draw-to-rect i1 r2)
(f2 :draw-to-rect i1 r3)
(f2 :draw-to-rect i1 r4)
(define fish2 (pic-flip (pic-rotate45 fish)))
(define fish3 (pic-rotate270 fish2))
(define three (pic-over fish (pic-over fish2 fish3)))
((pic-cycle (pic-rotate90 three)) i1 r1)
((pic-cycle2 (pic-rotate90 three) 0.6 0.7) i1 r2)
(begin
(gimp-image-undo-disable i1)
((pic-cycle2 (pic-rotate90 three)) i1 r3)
((pic-cycle2 (pic-rotate90 three)) i1 r4)
(gimp-image-undo-enable i1))

Квартет циклов
Тут просто комбинация функций.
(define i1 (create-1-layer-img 740 480)) ;; создаём холст для рисования.
(define r1 (rect! (p! 20 320) (p! 300 0) (p! 0 -300)))
(define r2 (rect! (p! 370 330) (p! 300 30) (p! -30 -300)))
(f2 :draw-to-rect i1 r1)
(f2 :draw-to-rect i1 r2)
(define v (pic-cycle (pic-rotate90 three)))
(define cf1 (pic-quartet v v v v))
(begin
(gimp-image-undo-disable i1)
(cf1 i1 r1)
(cf1 i1 r2)
(gimp-image-undo-enable i1))

Собираем картинку Эшера.
Сразу поясню, это картинка состоящая из 9 различных картинок: одной- центральной, 4-х сторон и 4-х углов.
Сначала сформируем функцию рисования стороны.
;;функция создания "тройки"
(define (pic-t-tile p1)
(let* ((pt1 (pic-rotate45 (pic-rotate270 (pic-flip p1)))))
(pic-over p1
(pic-over pt1 (pic-rotate270 pt1)))))
;;функция создания "четвёрки"
(define (pic-u-tile p1)
(let* ((pt1 (pic-rotate45 (pic-rotate270 (pic-flip p1))))
(pt2 (pic-over pt1 (pic-rotate90 pt1))))
(pic-over pt2
(pic-rotate180 pt2))))
(define (pic-side-n p1 n)
(let ((pt1 (pic-t-tile p1)))
(if (= n 0)
(pic-empty)
(pic-quartet
(pic-side-n p1 (- n 1))
(pic-side-n p1 (- n 1))
(pic-rotate90 pt1) pt1))))
тестируем рисование стороны.
(define r1 (rect! (p! 50 210) (p! 200 0) (p! 0 -200)))
(define r2 (rect! (p! 270 210) (p! 200 30) (p! 20 -200)))
(f2 :draw-to-rect i1 r1)
(f2 :draw-to-rect i1 r2)
;;((pic-side-n fish 0) i1 r1) ;; НИЧЕГО не рисует!
((pic-side-n fish 1) i1 r1)
((pic-side-n fish 2) i1 r2)

Теперь сформируем функцию рисования угла.
(define (pic-corner-n p1 n)
(let ((pt1 (pic-u-tile p1)))
(if (= n 0)
(pic-empty)
(pic-quartet
(pic-corner-n p1 (- n 1))
(pic-side-n p1 (- n 1))
(pic-rotate90 (pic-side-n p1 (- n 1)))
pt1))))
тестируем рисование угла.
(define r1 (rect! (p! 50 210) (p! 200 0) (p! 0 -200)))
(define r2 (rect! (p! 270 210) (p! 200 30) (p! 20 -200)))
(f2 :draw-to-rect i1 r1)
(f2 :draw-to-rect i1 r2)
;;((pic-corner-n fish 0) i1 r1) ;; НИЧЕГО не рисует!
((pic-corner-n fish 1) i1 r1)
((pic-corner-n fish 2) i1 r2)

И заключительные функции рисования квадрата Эшера.
;;рисунок из 9 рисунков
;; p1 p2 p3
;; p4 p5 p6
;; p7 p8 p9
(define-m (pic-nonet p1 p2 p3 p4 p5 p6 p7 p8 p9)
(let* ((k1 (/ 1 3)))
;;(print "make pic-nonet")
(lambda (img dest-r)
(let* ((v-h (vect-scale (rect-horiz dest-r) k1))
(v-v (vect-scale (rect-vert dest-r) k1))
(r1 (rect! (rect-origin dest-r)
v-h v-v))
(r2 (rect! (vect+ (rect-origin dest-r)
v-h)
v-h v-v))
(r3 (rect! (vect+ (rect-origin r2)
v-h)
v-h v-v))
(r4 (rect! (vect+ (rect-origin r1)
v-v)
v-h v-v))
(r5 (rect! (vect+ (rect-origin r2)
v-v)
v-h v-v))
(r6 (rect! (vect+ (rect-origin r3)
v-v)
v-h v-v))
(r7 (rect! (vect+ (rect-origin r4)
v-v)
v-h v-v))
(r8 (rect! (vect+ (rect-origin r5)
v-v)
v-h v-v))
(r9 (rect! (vect+ (rect-origin r6)
v-v)
v-h v-v)))
(p1 img r7) (p2 img r8) (p3 img r9)
(p4 img r4) (p5 img r5) (p6 img r6)
(p7 img r1) (p8 img r2) (p9 img r3)))))
(define (pic-square-limit-n p1 n)
(if (= n 0)
(pic-empty)
(let ((pt1 (pic-u-tile p1))
(pt2 (pic-corner-n p1 (- n 1)))
(pt3 (pic-side-n p1 (- n 1))))
(pic-nonet
pt2 pt3 (pic-rotate270 pt2)
(pic-rotate90 pt3) pt1 (pic-rotate270 pt3)
(pic-rotate90 pt2) (pic-rotate180 pt3) (pic-rotate180 pt2)))))
тестируем рисование квадрата Эшера.
;;(define i1 (create-1-layer-img 740 480)) ;; создаём холст для рисования.
(define r1 (rect! (p! 20 320) (p! 300 0) (p! 0 -300)))
(define r2 (rect! (p! 370 330) (p! 300 30) (p! -30 -300)))
(f2 :draw-to-rect i1 r1)
(f2 :draw-to-rect i1 r2)
((pic-square-limit-n fish 1) i1 r1)
((pic-square-limit-n fish 2) i1 r2)

тестируем рисование квадрата Эшера.
;;(define i1 (create-1-layer-img 740 480)) ;; создаём холст для рисования.
(define r1 (rect! (p! 20 320) (p! 300 0) (p! 0 -300)))
(define r2 (rect! (p! 370 330) (p! 300 30) (p! -30 -300)))
(f2 :draw-to-rect i1 r1)
(f2 :draw-to-rect i1 r2)
(begin
(gimp-image-undo-disable i1)
((pic-square-limit-n fish 3) i1 r1)
((pic-square-limit-n fish 4) i1 r2)
(gimp-image-undo-enable i1))

Покрупнее.
(define i1 (create-1-layer-img 500 500)) ;; создаём холст для рисования.
(define r1 (rect! (p! 0 500) (p! 500 0) (p! 0 -500)))
(f2 :draw-to-rect i1 r1)
(begin
(gimp-image-undo-disable i1)
((pic-square-limit-n fish 4) i1 r1)
(gimp-image-undo-enable i1))
(save-png i1 path-work "square-limit-4.png")

Заключение
На этом я и завершаю описание восхождения на первую ступень мастерства в программировании на Scrip-fu в GIMP. Здесь я напомню какой путь мы прошли:
Шаг первый
Первое приветствие
Работа, Печать, Отладка
Макросы. Первое знакомство
Расширения к Script-fu
Погружение в программирование графики
Точки, Контуры, Кисти и Градиенты
Сортировка
Ускоряем Script-fu
Наивные графические преобразования
Реализация Хеш-Таблицы
Линейные преобразования на плоскости
Удобная передача параметров в функцию
Берём Кисти и рисуем Точки и Звёздочки
Рамки
Фигуры
Объектно-ориентрованное программирование в Scheme. Простая реализация
Фигуры. Объектный подход
Выходим за Рамки
Язык функциональной геометрии. Рисунки(картинки) и базовые операции
Язык функциональной геометрии. Итоги.
Конечно я осветил далеко не все темы программирования на Script-fu. За рамками моего повествования остались и интерактивная работа и работа с векторами(думаю уже многие знают что GIMP это уже давно не растровый редактор, а редактор сделавший серьёзную заявку на работу с векторной графикой) и различные тонкости использования множества функций GIMP и их комбинаций и многое многое другое. Но это была лишь только первая ступенька мастерства.