GIMP Script-Fu Первый Дан. Линейные преобразования на плоскости
Библиотека функций к Script-fu
Итак, настоящая киллер функция по отображению изображений это gimp-item-transform-matrix
. Именно на её основе мы и будем строить все отображения изображений в нашем проекте.
Для использования функции GIMP gimp-item-transform-matrix
, осуществляющей линейное преобразования заданного отображаемого объекта, нам нужна структура в которой мы могли бы хранить матрицу преобразования. На основе этой структуры, мы построим абстракцию линейного двумерного преобразования на плоскости. Я посмотрел все эти преобразования и увидел, что матрица имеет всего 6 значимых полей, остальные два ноль и одно единица, поэтому наша структура будет хранить всего 6 полей.
подготовка
;;(define path-home "D:") ;;для виндовс
(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 "struct2.scm"))
(load (string-append path-lib "point.scm"))
(struct tr2d (m11 m12 m21 m22 dx dy))
Теперь мы можем определить операции преобразования. Первым делом определим операцию преобразования точки.
(define (p-tr2d p tr)
(let ((x (p-x p))
(y (p-y p)))
(p! (+ (* x (tr2d-m11 tr))
(* y (tr2d-m21 tr))
(tr2d-dx tr))
(+ (* x (tr2d-m12 tr))
(* y (tr2d-m22 tr))
(tr2d-dy tr)))))
Вообще говоря, если понимать под точкой, вектор, а преобразование — это матрица, то это умножение вектора на матрицу, с некоторыми особенностями, точка то у нас на плоскости имеет 2 координаты, а вектор умножается 3х значный, одна координата которого всегда 1, которая не храниться в составе точки.
Комбинация преобразований будет тоже двумерным преобразованием, умножение трёхмерных матриц, с особенностями используемой нами матричной модели преобразований, хранятся всего 6 элементов.
(define (comb-tr2d m n)
(let ((m11 (tr2d-m11 m))
(m12 (tr2d-m12 m))
(m21 (tr2d-m21 m))
(m22 (tr2d-m22 m))
(mx (tr2d-dx m))
(my (tr2d-dy m))
(n11 (tr2d-m11 n))
(n12 (tr2d-m12 n))
(n21 (tr2d-m21 n))
(n22 (tr2d-m22 n))
(nx (tr2d-dx n))
(ny (tr2d-dy n)))
(tr2d! (+ (* m11 n11) (* m12 n21)) (+ (* m11 n12) (* m12 n22))
(+ (* m21 n11) (* m22 n21)) (+ (* m21 n12) (* m22 n22))
(+ (* mx n11) (* my n21) nx) (+ (* mx n12) (* my n22) ny))))
Для отладки может понадобиться печать преобразования.
(define (prn-tr2d m)
(display "[ ") (display (tr2d-m11 m)) (display " ")
(display (tr2d-m12 m)) (display " 0 ]") (newline)
(display "[ ") (display (tr2d-m21 m)) (display " ")
(display (tr2d-m22 m)) (display " 0 ]") (newline)
(display "[ ") (display (tr2d-dx m)) (display " ")
(display (tr2d-dy m)) (display " 1 ]") (newline))
Теперь опишем несколько конструкторов создающих простые линейные преобразования.
;; преобразование вращения.
(define (make-tr2d-rot alfa)
(let* ((alfa-r (gr2rad alfa))
(cos-a (cos alfa-r))
(sin-a (sin alfa-r)))
(tr2d! cos-a sin-a
(- sin-a) cos-a
0 0)))
;;преобразование перемещения
(define (make-tr2d-move x y)
(tr2d! 1 0
0 1
x y))
;;преобразование масштабирования
(define (make-tr2d-scale sx sy)
(tr2d! sx 0
0 sy
0 0))
;;сдвиг вдоль оси х на заданный угол.
(define (make-tr2d-shear-x angle)
(tr2d! 1 (tan (gr2rad angle))
0 1
0 0))
;;сдвиг вдоль оси у на угол
(define (make-tr2d-shear-y angle)
(tr2d! 1 0
(tan (gr2rad angle)) 1
0 0))
;;преобразование отражения относительно оси х
(define (make-tr2d-reflect-x)
(tr2d! 1 0
0 -1
0 0))
;;преобразование отражения относительно оси y
(define (make-tr2d-reflect-y)
(tr2d! -1 0
0 1
0 0))
Теперь на основе построенной абстракции, можно написать функцию преобразования изображения, принимающую данное преобразование в качестве аргумента.
(define (draw-from-image-trans dest src tr)
(let* ((dw2 (car (gimp-layer-new-from-drawable
(car (gimp-image-get-active-layer src)) dest)))
(m11 (tr2d-m11 tr))
(m12 (tr2d-m12 tr))
(m21 (tr2d-m21 tr))
(m22 (tr2d-m22 tr))
(mx (tr2d-dx tr))
(my (tr2d-dy tr)))
(gimp-image-add-layer dest dw2 0)
(gimp-item-transform-matrix dw2
m11 m21 mx
m12 m22 my
0 0 1)
(gimp-image-merge-visible-layers dest CLIP-TO-IMAGE)
))
Все эти преобразования определены в файле tr2d.scm
. Давайте выполним демонстрационный пример.
подготовка
;;догрузим необходимые библиотеки.
(load (string-append path-lib "img.scm"))
;;(load (string-append path-lib "img2.6.scm")) ;;для гимп 2.6
(load (string-append path-lib "tr2d.scm"))
(load (string-append path-lib "defun.scm")) ;;для загрузки draw-from-image-trans
(load (string-append path-lib "brush.scm"))
(load (string-append path-lib "fig.scm")) ;; или определите её через консоль.
;;(load (string-append path-lib "brush-2.6.scm")) ;;для гимп 2.6
;;(load (string-append path-lib "fig2.6.scm")) ;; или определите её через консоль.
;;подготовимся к рисованию
(define i1 (create-1-layer-img 640 480))
(define isource1 (car (file-png-load 1
(string-append path-work "i1.png") "i1")))
;;определим две трансформации
(define tr2d-pos1 (make-tr2d-move 100 100)) ;;перемещение в точку 100, 100
(define tr2d30 (make-tr2d-rot 30)) ;; поворот на 30 градусов.
(draw-from-image-trans i1 isource1 tr2d30) ;;поворот в начале координат
(draw-from-image-trans i1 isource1 (comb-tr2d tr2d30 tr2d-pos1 )) ;;поворот и перенос
«Использование двумерных трансформаций для поворота изображения »
Вот ещё пара примеров использования преобразований:
;;увеличим ширину в 2 раза, а высоту уменьшим на половину, повернем на 30 гр. и переместим.
(draw-from-image-trans i1 isource1 (comb-tr2d
(comb-tr2d
(make-tr2d-scale 2 0.5)
tr2d30)
tr2d-pos1 ))
;;уменьшим ширину в 2 раза, а высоту увеличим в полтора, повернем на 45 гр. и переместим.
(draw-from-image-trans i1 isource1 (comb-tr2d
(comb-tr2d
(make-tr2d-scale 0.5 1.5)
(make-tr2d-rot 45))
(make-tr2d-move 200 100) ))
«Использование двумерных трансформаций изображения »
Получилась очень удачная функция заменяющая все ранее написанные функции отображения и преобразования изображений. Единственно, что мне не нравиться. это каскадные вызовы функции comb-tr2d
, давайте перепишем её, чтобы она принимала не произвольное количество аргументов, обрабатывая произвольную последовательность трансформаций.
;;эта функция есть в util.scm
(define (fold f init lst)
(do ((rez init (f rez (car l)))
(l lst (cdr l)))
((null? l) rez)))
(define (comb-tr2d . lst)
(let ((tr (lambda (m n)
(let ((m11 (tr2d-m11 m))
(m12 (tr2d-m12 m))
(m21 (tr2d-m21 m))
(m22 (tr2d-m22 m))
(mx (tr2d-dx m))
(my (tr2d-dy m))
(n11 (tr2d-m11 n))
(n12 (tr2d-m12 n))
(n21 (tr2d-m21 n))
(n22 (tr2d-m22 n))
(nx (tr2d-dx n))
(ny (tr2d-dy n)))
(tr2d! (+ (* m11 n11) (* m12 n21)) (+ (* m11 n12) (* m12 n22))
(+ (* m21 n11) (* m22 n21)) (+ (* m21 n12) (* m22 n22))
(+ (* mx n11) (* my n21) nx) (+ (* mx n12) (* my n22) ny))))))
(fold tr (car lst) (cdr lst))))
Это практически таже самая функция, которая была ранее, только её тело перенесли в лямбду, и последовательно с помощю fold
применили ко всем преобразованиям из списка аргументов.
Для демонстрации работы нашей функции, давайте напишем функцию рисования серий изображений, расположенных по кругу.
(define (rot-img3 dest src x y sc-x sc-y shear-x shear-y num)
(gimp-image-undo-group-start dest)
(let ((a1 (floor (/ 360 num)))
(a-cur 0)
(tr-xy (make-tr2d-move x y))
(tr-pre (comb-tr2d
(make-tr2d-scale sc-x sc-y)
(make-tr2d-shear-y shear-x)
(make-tr2d-shear-x shear-y))))
(while (< a-cur 360)
(draw-from-image-trans i1 isource1 (comb-tr2d
tr-pre
(make-tr2d-rot a-cur)
tr-xy))
(set! a-cur (+ a-cur a1))))
(gimp-image-undo-group-end dest)
)
Обратите внимание, мы разделили создание трансформаций, те из них которые не меняются, мы создаём один раз и затем просто используем в окончательной комбинации трансформаций.
Выполним несколько примеров:
(rot-img3 i1 isource1 100 200 1 1 0 0 6)
(rot-img3 i1 isource1 100 200 1 0.5 20 0 6)
(rot-img3 i1 isource1 330 200 1.3 0.4 20 10 6)
(rot-img3 i1 isource1 215 370 0.5 0.8 10 20 12)
(rot-img3 i1 isource1 30 370 0.5 0.8 10 20 1)
(rot-img3 i1 isource1 30 370 0.5 0.8 10 20 3)
(rot-img3 i1 isource1 500 50 1 1 0 0 1)
«Пример использования rot-img3»
Таким образом мы создали удобный инструмент для линейных преобразований на плоскости, используя который мы можем задать любое линейное преобразование, а функции, такие как gimp-item-transform-matrix
позволят преобразовать и отобразить любое заданное изображение.
На этом с преобразованиями мы заканчиваем и в следующей статье ещё немного поработаем с самим языком Script-fu.