GIMP Script-Fu Первый Дан. Наивные графические преобразования

Напомню нашу цель, это создание языка функциональной геометрии. Одно из базовых операций в нём является отрисовка некоторого изображения в заданный прямоугольник, вернее даже не прямоугольник, а рамку, которая может представлять из себя параллелограмм, повёрнутый на произвольный угол относительно горизонтальной оси координат, расположенный в любой точке плоскости рисования. Пока мы и близко не приблизились к этой цели. Продолжим исследование возможностей GIMPа по отображению изображений.

Библиотека функций к 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 "img.scm"))
(load (string-append path-lib "struct.scm"))
(load (string-append path-lib "point.scm")) ;;там операции с углами.

Напишем первую функцию:

;;установили цвет бекграунда
(define i1 (create-1-layer-img 640 480))
(define isource1 (car (file-png-load 1
                                     (string-append path-work "i1.png") "i1")))

(define (draw-from-image1 dest dest-x dest-y src)
  (let ((dw2 (car (gimp-layer-new-from-drawable
               (car (gimp-image-get-active-layer src)) dest))))
    (gimp-image-add-layer dest dw2 0)
    (gimp-layer-set-offsets dw2 dest-x dest-y)
    (gimp-image-merge-visible-layers dest CLIP-TO-IMAGE)))

(draw-from-image1 i1 10 50 isource1)

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

«Размещение одного изображения в другом на заданном смещении»

Целевой прямоугольник может не совпадать, с размером изображения, поэтому мы должны уметь масштабировать размещаемое изображение. Напишем вторую функцию:

(define (draw-from-image-scale dest dest-x dest-y src sc-x sc-y)
  (let* ((dw2 (car (gimp-layer-new-from-drawable
                    (car (gimp-image-get-active-layer src)) dest)))
         (h (car (gimp-drawable-height dw2)))
         (w (car (gimp-drawable-width  dw2))))
    (gimp-image-add-layer dest dw2 0)
    (gimp-layer-scale dw2 (* w sc-x) (* h sc-y) TRUE)
    (gimp-layer-set-offsets dw2 dest-x dest-y)
    (gimp-image-merge-visible-layers dest CLIP-TO-IMAGE)))

(draw-from-image-scale i1 10 50 isource1 0.5 3)
(draw-from-image-scale i1 150 50 isource1 2.5 1.4)
(draw-from-image-scale i1 320 50 isource1 0.9 1.2)

«Размещение изображений с масштабированием »

Повороты

А теперь разберёмся с поворотами. Изображения нужно не только сдвигать и масштабировать, но и поворачивать.

(define (draw-from-image-rotate dest dest-x dest-y src sc-x sc-y alfa)
  (let* ((dw2 (car (gimp-layer-new-from-drawable
                    (car (gimp-image-get-active-layer src)) dest)))
         (h (car (gimp-drawable-height dw2)))
         (w (car (gimp-drawable-width  dw2)))
         (delta 0.001)
         (alfa-r (gr2rad alfa)))
    (gimp-image-add-layer dest dw2 0)
    (if (or (> (abs (- sc-x 1)) delta)
            (> (abs (- sc-y 1)) delta))
        (gimp-layer-scale dw2 (* w sc-x) (* h sc-y) TRUE))
    (if (> (abs alfa-r) delta)
        (gimp-drawable-transform-rotate dw2 alfa-r FALSE 0 0
                                        TRANSFORM-FORWARD
                                        INTERPOLATION-LINEAR
                                        FALSE 1
                                        TRANSFORM-RESIZE-ADJUST))
    (gimp-layer-set-offsets dw2 dest-x dest-y)
    (gimp-image-merge-visible-layers dest CLIP-TO-IMAGE)))

По функции видно последовательность действий: сначала масштабируем, потом поворачиваем

«Размещение изображений с поворотом »

Как вам? По мне так получилось — не очень! Точка начала вставляемого рисунка уехала при повороте! Это надо исправить! Пишем второй вариант.

;; что то вроде остатка по модулю от 360
(define (angle-360 angle)
  (cond
   ((>= angle 360) (angle-360 (- angle 360)))
   ((< angle 0)   (angle-360 (+ angle 360)))
   (#t angle)))

;; непосредственно функция вычисления смещения, которое зависит от величины угла поворота.
(define (calc-offset-for-rotate alfa w h)
  (let* ((a (angle-360 alfa))
         (a-r (gr2rad a)))
    (cond
     ((<= a 90)
      (cons (- (* h (sin a-r))) 0))
     ((<= a 180)
      (cons (- (- (* h (sin a-r))
                  (* w (cos a-r))))
            (* h (cos a-r))))
     ((<= a 270)
      (cons (* w (cos a-r))
            (+ (* w (sin a-r))
               (* h (cos a-r)))))
     ((<= a 360)
      (cons 0
            (* w (sin a-r))))
     )))
	 
(define (draw-from-image-rotate dest dest-x dest-y src sc-x sc-y alfa)
  (gimp-image-undo-disable dest)
  (let* ((dw2 (car (gimp-layer-new-from-drawable
                    (car (gimp-image-get-active-layer src)) dest)))
         (h (car (gimp-drawable-height dw2)))
         (w (car (gimp-drawable-width  dw2)))
         (delta 0.001)
         (alfa-r (gr2rad alfa)))

    (gimp-image-add-layer dest dw2 0)
    (if (or (> (abs (- sc-x 1)) delta)
            (> (abs (- sc-y 1)) delta))
        (gimp-layer-scale dw2 (* w sc-x) (* h sc-y) TRUE))
    (if (> (abs alfa-r) delta)
        (set! dw2 (car (gimp-drawable-transform-rotate dw2 alfa-r FALSE 0 0
                                                   TRANSFORM-FORWARD
                                                   INTERPOLATION-LINEAR
                                                   FALSE 1
                                                   TRANSFORM-RESIZE-ADJUST))))
    (let* ((deltas (calc-offset-for-rotate alfa (* w sc-x) (* h sc-y)))
           )
      (gimp-layer-set-offsets dw2
                              (+ dest-x (car deltas))
                              (+ dest-y (cdr deltas))))
    (gimp-image-merge-visible-layers dest CLIP-TO-IMAGE))
  (gimp-image-undo-enable dest))

Здесь я применяю ещё и отмену от создания списка отката редактирования, уж больно большие списки отката создаются, но делать это не обязательно.
Выполняем тестирование.

(draw-from-image-rotate i1 100 200 isource1 1.0 1.0 0)
(draw-from-image-rotate i1 100 200 isource1 1.0 1.0 30)
(draw-from-image-rotate i1 100 200 isource1 1.0 1.0 45)
(draw-from-image-rotate i1 100 200 isource1 1.0 1.0 90)

«Корректное размещение изображений с поворотом »

Давайте создадим на основе этой функции другую, создающую вращение изображений вокруг заданного центра.

(define (rot-img1 dest src x y scale num)
  (let ((a1 (floor (/ 360 num)))
        (a-cur 0))
    (while (< a-cur 360)
      (draw-from-image-rotate dest x y src scale scale a-cur)
      (set! a-cur (+ a-cur a1)))))

(rot-img1 i1 isource1 300 200 0.5 5)
(rot-img1 i1 isource1 100 100 0.3 4)

«Композиция изображений с поворотом »

При построении функции draw-from-image-rotate мы использовали три функции GIMP: gimp-layer-scale, gimp-drawable-transform-rotate, gimp-layer-set-offsets, это получилось в следствии того что к её окончательному виду мы подходили постепенно, эволюционно, задействуя средства ГИМПа по мере необходимости. GIMP же в свою очередь имеет функцию, позволяющую заменить все эти три функции одной:

;; новый подход через функции трансформации
(define (draw-from-image-rotate2 dest dest-x dest-y src sc-x sc-y alfa)
  (let* ((dw2 (car (gimp-layer-new-from-drawable
                    (car (gimp-image-get-active-layer src)) dest)))
         (h (car (gimp-drawable-height dw2)))
         (w (car (gimp-drawable-width  dw2)))
         (delta 0.001)
         (alfa-r (gr2rad alfa)))
    (gimp-image-add-layer dest dw2 0)
    (gimp-item-transform-2d dw2 0 0 sc-x sc-y alfa-r dest-x dest-y)
    (gimp-image-merge-visible-layers dest CLIP-TO-IMAGE)
    ))

Протестируем её с помощью функции кратного вращения изображения.

(define (rot-img2 dest src x y scale num)
  (gimp-image-undo-group-start dest)
  (let ((a1 (floor (/ 360 num)))
        (a-cur 0))
    (while (< a-cur 360)
      (draw-from-image-rotate2 dest x y src scale scale a-cur)
      (set! a-cur (+ a-cur a1))))
  (gimp-image-undo-group-end dest))
  
(rot-img2 i1 isource1 250 200 0.5 14)
(rot-img2 i1 isource1 450 200 0.4 5)

«Использование оптимизированной функции поворота изображения »

Как видите GIMP имеет огромное разнообразие функций позволяющих прийти к одному результату разными путями. Далее мы рассмотрим использование функции трансформации изображений позволяющую заменить весь набор рассмотренных ранее, тех которые рассмотрены не были, настоящую киллер функцию, остальных функции преобразования, с небольшой поправочкой — линейных преобразований.

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

© Habrahabr.ru