Здравствуй Читатель! Если ты хочешь научиться программировать расширения для GIMP с помощью Script-fu тебе сюда. Я планирую опубликовать серию статей по данной теме. И эта статья только первый шаг в увлекательный мир лайф-кодинга. Что это значит? Расширение GIMP Script-fu представляет собой обёртку над интерпретатором языка scheme, который позволяет не только загружать и интерпретировать скрипты пользователя, но и работать с ними в интерактивном режиме, давая команды интерпретатору в режиме REPL, когда интерпретатор читает код(Read), оценивает(или ещё говорят вычисляет) то что прочитал(Eval), печатает результат(Print) и повторят всё это заново(Loop), короче REPL.

Предисловие

Однажды изучая книгу, Структура и интерпретация компьютерных программ(SICP), я наткнулся на задание по созданию языка функциональной геометрии придуманного Питером Хендерсоном funcgeo2.pdf. И я решил повторить эту реализация в качестве задания, закрепляющего тему процедурной композиции. Обычно задания я делал используя реализацию языка scheme - racket. Отличный язык для обучения основам программирования. Более простой курс этого обучения, пригодный для школьников описан в книге HtDP — "Как разрабатывать программы", перевод которой недавно вышел в печатном виде в России, есть также моя неофициальная попытка перевода, в виде html страниц: htdp-rus . Более продвинутый курс изучения компьютерных наук на языке Scheme описан в уже упомянутой книге SICP, обычно изучаемой уже студентами ВУЗов. Но в языке racket уже реализованы элементы этого языка манипулирования изображениями, поэтому я решил обратить своё внимание на язык script-fu в GIMP, про который мне на тот момент было практически ничего не известно, кроме того что он также является одной из разновидностью реализаций scheme. Результаты попытки реализации этого задания из SCIP и представлены в настоящей работе.

Что такое GIMP

Гимп(GNU Image Manipulation Program), это графический редактор созданный сообществом разработчиков, реализованный на языке программирования Си. Для его создания была создана целая концепция реализации объектно-ориентированного программирования на Си gobject. На его основе разработана библиотека построения графических интерфейсов GTK и даже разработана система управления управления рабочим столом Gnome.

Внешний вид открытого приложения GIMP версии 2.10 выглядит приблизительно так:

Внешний_вид_GIMP
Внешний_вид_GIMP

Сам гимп предоставляет богатый набор редактирования изображений, про которые я рассказывать не буду. Но значительная часть функциональности гимп создаётся с помощью расширений, которые могут быть написаны языке Си. Одним из таких расширений является расширение Script-fu позволяющее подключить к гимпу отдельный язык написания расширений, но в отличии от расширений на Си, программы на нём не компилируются, а интерпретируются уже в этом расширении с помощью интерпретатора tinyscheme, присоединённого (подлинкованного) в момент сборки этого расширения. Работа данного плагина доступна через меню: Фильтры->Script-Fu->Консоль. Кстати говоря, в последних версиях гимп разработчики добавили возможность писать плагины с помощью питона, добавив плагин Python-fu. Вот так выглядит окно загруженного плагина консоли Script-fu:

"Окно_script-fu"
"Окно_script-fu"

Консоль предоставляет возможность вводить команды и вызвать помощь по командам из Script-fu, являющимися фактически интерфейсом для scheme в мир GIMP. Используя эти команды вы можете управлять объектами GIMP.

Что представляет собой TinyScheme.

ТиниСхема(т.е маленькая схема) представляет собой простой интерпретатор языка scheme, написанный на языке Си. Исходные коды тинисхем�� доступны вместе с исходниками gimp. Но их можно скачать и отдельно из интернета. Что полезного можно узнать из исходного кода о данной реализации схемы, не погружаясь в глубины Си программирования? Осмотрев файл opdefines.h можно ознакомиться со ВСЕМИ операторами, которые предоставляет интерпретатор языка, всего их около 125 штук. И это ВСЕ операторы реализованные на на си, вернее все к которым можно явно обратиться из программы, помимо них, интерпретатор предоставляет некоторое количество специальных форм, с помощью которых и реализован сам язык, такие как quote, if, let, cond и др. Основные функции языка представлены. А вот чтобы научиться программированию на самом языке вам надо будет ознакомиться какой-нибудь литературой. Та же HtDP прекрасно подойдёт. Так же есть коротенькая книжка TSPL4.

В отличии от полноценных реализаций схемы, таких как racket, guile и др. тинисхема, предоставляет минимальный набор базовых операций и типов данных. Комплексные (составные) типы данных представлены двумя типами данных список и вектор. Простые данные это числа, строки, знаки и символы. Очень интересной возможностью тинисхемы является возможность загрузки специально написанных на Си плагинов(это плагины не к гимпу, а именно к тинисхеме), позволяющая практически неограниченно расширять функциональность тинисхемы.

Начинаем изучение:

Арифметика:

(+ 2 2)
4
> (- 34 2 2)
30
> (* 1 2 3 4 5)
120
> (/ 12 4)
3
> (/ 12 5)
2,4.0
> (/ 13 6)
2,166666667.0
> (quotient 27 8)
3
> (remainder 27 8)
3
> (remainder 26 8)
2
> (modulo 23 8)
7

так же реализованы операции exp, log, sin, cos, tan, asin, acos, atan, sqrt, expt, floor, ceiling, truncate, round.

Работа с символьными знаками (charcter).

Почему символьные знаки, а не символы? Дело в том что в Лиспе слово Symbol зарезервировано за специальным типом данных - символами, и что бы не путать их с символьными знаками или просто знаками, я их так назвал.

(char->integer #\a)
;;97

(integer->char 65)
;;#\A#

(char-upcase #\a)
#\A

(char-downcase #\A)
;;#\a

(string #\h #\e #\l #\l #\o)
;;"hello"

;;русский алфавит не поддерживается, не разбирался в чём дело,
;;возможно решается установкой локали.

(string #\п #\р #\и #\в #\е ##\т)
;;Error: undefined sharp expression
`

Есть небольшая проблема с русским языком, но она не столь существенна. На уровне строк, работать с русским языком можно.

Строки.

(make-string 5  #\H)
;;"HHHHH"

(define s1 "Hello World!")

(string-length s1)
;;12

(string-ref s1 3)
;;#\l#

(define s2 "Привет Мир!")

(string-ref s2 0)
;;#\.#

(string-set! s1 3 #\L)
;;Error: string-set!: unable to alter immutable string: "Hello World!"

НЕЛЬЗЯ изменять статически заданную строку.

(define s3 (string #\H #\e #\l #\l #\o))

(string-set! s3 3 #\L)
;;"HelLo"#

s3
;;"HelLo"

(define s4 (string-append "Hello" " " "World" "!"))
;;"Hello World!"

(string-set! s4 3 #\L)
;;"HelLo World!"

(substring s4 6 11)
;;"World"

(string-append "sss" " " "qqq")
;;"sss qqq"

(substring "sss qqq" 4 7)
;;"qqq"

Вывести строку в консоль из программы можно несколькими способами, но самый простой это print.

(print "Привет")
;;"Привет"

Символы.

's1

(define s2 's1)
;;s2
's1
;;s1
s2
;;s1
(symbol? s2)
;;#t

(symbol->string s2)
;;"s1"

(gensym)
;;gensym-3

Составные данные

Пары:

(define p1 (cons 12 'a))
;;p1

(car p1)
12

(cdr p1)
;;a

(set-car! p1 13)
(set-cdr! p1 'b)
;;(13 . a)(13 . b)
p1
;;(13 . b)

Списки:

;;последовательности пар создают списки:
(set-cdr! p1 (cons 'e '()))
;;(13 e)

p1
;;(13 e)

(define l1 '(1 2 3 4 5))
(define l2 (list 'a 'b 'c 'd))

(define l3 (reverse l1))
;;l3
l3
;;(5 4 3 2 1)
l1
;;(1 2 3 4 5)

(define l4 (append l1 l3))
;;l4
l4
;;(1 2 3 4 5 5 4 3 2 1)

(put l4 'a 2)

(get l4 2)

(length l4)
;;10

Ассоциативные списки:

Это списки пар, где первый элемент это ключ, а остаток(cdr) это соответствующее ключу значение, простейшая(вренее медленная) замена хеш-таблицы.

;;alist
(define al1 '((a . 1) (b . 2) (c . 3) (d . 4)))
;;al1

al1
;;((a . 1) (b . 2) (c . 3) (d . 4))

(assq 'c al1)
;;(c . 3)

(define al2 '(("a" . 11) ("b" . 12) ("c" . 13) ("d" . 14)))
(assq "b" al2)
;;#f

(define al3 '((a . 1) (b . 2) (c  . 3) (d  4 5 6 7) (e  8 9)))

(assq 'c al3)
;;(c . 3)
(assq 'd al3)
;;(d 4 5 6 7)

Списки свойств(Property List):

Сама тинисхема их поддерживает(несколькими функциями), но чтобы они работали нужно включить опцию USE_PLIST при компиляции, в моей версии script-fu она не включена, но в принципе она и особо не нужна, их сейчас редко кто использует в программировании.

Массивы:

Массивы в тинисхеме представленны в виде одномерных векторов.

(define v1 (vector 12 14 14 'a 'b 'c))
;; v1
v1
;; #( 12 14 14 a b c )

(define v2 (make-vector 12))
#( () () () () () () () () () () () () )

(define v3 (make-vector 12 3))
 v3
#( 3 3 3 3 3 3 3 3 3 3 3 3 )

(vector-length v1)
;; 6

(vector-ref v1 3)
;; a

(vector-set! v1 3 'aa)
;;#( 12 14 14 aa b c )

(vector-ref v1 3)
;; aa

Управляющие конструкции.

Условное выполнение - if.

(if (vector? v1)
    (print "v1 вектор!!!")
    (print "v1 это не вектор!!!"))

;;печать:  "v1 вектор!!!"
;;возврат: #t

(set! v1 1)

(if (vector? v1)
    (print "v1 вектор!!!")
    (print "v1 это не вектор!!!"))

;;"v1 это не вектор!!!"
;;#t

Операция выбора - cond, или множественный if.

Два примерчика:

(define (cond-test1 v1)
   (cond ((> v1 10)
          (print "больше 10"))
         ((< v1 10)
          (print "меньше 10"))
         ((= v1 10)
          (print "равно 10"))
         (#t
          (print "незнаю!")))
   )

(cond-test1 3)
(cond-test1 11)
(cond-test1 10)
(cond-test1 "12")
;;"меньше 10" #t
;;"больше 10" #t
;;"равно 10"  #t
;;Error: >: argument 1 must be: number
(define (cond-test2 v1)
   (cond ((and (number? v1) (> v1 10))
          (print "больше 10"))
         ((and (number? v1) (< v1 10))
          (print "меньше 10"))
         ((and (number? v1) (= v1 10))
          (print "равно 10"))
         (#t
          (print "незнаю!")))
   )
(cond-test2 3)
(cond-test2 11)
(cond-test2 10)
(cond-test2 "12")
;; "меньше 10" #t
;; "больше 10" #t
;; "равно 10" #t
;; "незнаю!" #t

Перебор случаев - case.

(define (case-test x)
   (case x
      ((5) (print "Угадал! 5"))
      ((4 6) (print "Не угадал!!!"))
      (else    (print "Совсем не угадал!"))))

(case-test 4)
(case-test 5)
(case-test 6)
(case-test 9)
;;"Не угадал!!!"
;;"Угадал! 5"
;;"Не угадал!!!"
;;"Совсем не угадал!"

Операторы цикла.

Базовая конструкция цикла в тинисхеме, это безусловный переход на заранее определённую метку. Для этого служит именованный let. Исторически, в схеме, let это аналог определения и вызова лямбды. Но лямбда это неименованная функция, а именованный let, это аналог обычной встроенной(в смысле inline) функции, а метка нужна что бы обратиться к данной функции из любого места этого let, сделать своеобразный вызов, значит перейти по метке, для этого нужно передать в этот вызов, все параметры определённые в let. Давайте посмотрим пример:

;;операторы цикла

(define (string->list2 s)
     (let loop1 ((n (pred (string-length s))) (l '()))
          (if (= n -1)
               l
               (loop1 (pred n) (cons (string-ref s n) l)))))
(string->list2 "Hello World!")
;;(#\H #\e #\l #\l #\o #\space #\W #\o #\r #\l #\d #\!)

Здесь loop1 это и есть метка в именованном let. Переход на эту метку, или рекурсия, стоит в конце именованного let, при этом в неё передаётся столько же параметров, сколько было определено в let.

Именованный let вещь понятная, но не всегда удобно её использовать, поэтому в тинисхеме определено несколько макросов циклов while и do.

;; from script-fu-compat.init
(define-macro (while test . body)
  `(let loop ()
     (cond
       (,test
         ,@body
         (loop)
       )
     )
   )
)
   
(let ((i 0))
   (while (< i 5)
      (prin1 "i: ") (print i)
      (set! i (succ i))))
;;"i: "0
;;"i: "1
;;"i: "2
;;"i: "3
;;"i: "4
;;()

(do ((i 0 (succ i)))
      ((>= i 5))
   (prin1 "i: ") (print i))
;; тот же вывод

Обратите внимание, что в определении while, именованный let снабжён меткой loop, это не какой то магический оператор цикла(loop - петля), это просто метка, названная так для улучшения понимания макроса.

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

Замечательно! Но не находите, что в этой статье я обещал научить программировать в Гимпе, а рассказываю всё про какую-то тинисхему. Пора исправить это недоразумение, пришло время для первой программы работающей с GIMP.

Но об этом я напишу уже в следующей статье...