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.

© Habrahabr.ru