Как писать скрипты для Gimp 2.x? Осваиваем Scheme (Script-Fu). Часть 1.

от
Работа с графикой    gimp, script-fu

В этой статье я покажу основы создания скриптов на Scheme (Script-Fu) для Gimp 2.x. Мы разберем:

1. Основы программирования на Scheme для Gimp 2.6 - 2.8.
2. Загрузку скриптов из файлов.
3. Добавление скрипта в меню Gimp’а.
4. Окна и его элементы.
5. Передачу параметров с окна в скрипт.
6. Написание простейшего скрипта, создающего изображение с тектом и фоном.

Статью разобью на 2 части. В 1-й пройдемся по Scheme, во второй разберем взаимодействие с функциями Gimp'а.

Кто не знает, что такое и чем полезны скрипты в графических редакторах – прочтите спойлер:

Открыть спойлер

Теперь – к делу!

Сначала откроем консоль. В Gimp 2.6 и Gimp 2.8 это делается одинаково:

• Запустите Gimp 2.x
• Выберите «фильтры»
• Наведите курсор на «Script-Fu» в самом низу списка
• В появившемся меню выберите «Консоль»
• Ждем 10-15 секунд, и наша консоль открыта!

script-fu-console.jpg
 
Запустим «Привет, мир» – внизу для этого есть текстовое поле.

  console-vvod.jpg
 
Наберите "Привет, мир!" (обязательно в кавычках) и нажмите «Enter». Вот что получится:

  console-hello-world.jpg
  
Работает? Отлично! Едем дальше…

Знакомимся со Scheme
Язык прост до безобразия. Однако – все выражения заключаются в скобки. Скобок получается столько, что путаешься на раз-два.

Чтобы избежать говололомки, установите текстовый редактор с подсветкой кода и скобок. Например, Notepad++.

Но об этом позже. Постигать основы языка на маленьких примерах удобнее прямо в консоли. Делать это будем так.

Я даю примеры кода и объясняю, как он работает. Вы его перепечатываете в консоль (не копировать => вставить, именно перепечатываете!) и запускаете. Так материал усвоится эффективнее.

Все манипуляции с кодом в консоли Gimp запоминает. Если мы набрали в одной строчке код, объявляющий переменную, она останется в памяти. Второй строчкой в эту переменную можно что-то записать, а третьей прочитать.

Чтобы Gimp забыл все действия, закройте консоль и запустите ее снова.

Все примеры рабочие – проверял их лично.

1. Переменные
1.1 Глобальные переменные

Работа с ними выглядит проще всего. Например – вводим в консоль сначала одну строку, потом другую:

  1. (define x)
  2. (set! x 8)

Что мы сделали? Оператор define создает переменную (или функцию, до них дойдем чуть ниже). В первой строке мы объявили переменную «x».

Оператор set! задает значение переменной. Второй строкой в переменную «x» мы записали значение «8».

Проверяем – вводим в консоль имя нашей переменной:

  1. x

На выходе видим:

  global-var.jpg
  
1.2 Локальные переменные
Пишем такой пример:

  1. (let* ((a 4)) a)

Тут начинается путаница со скобками. Разберем по частям:

1. Оператор let* объявляет локальные переменные. Они действуют внутри скобок, в которые заключен весь наш пример.

2. ((a 4)) – да, именно двойные скобки. Чтобы было понятней, почему, определим сразу две переменные: ((a 4) (b 8)). Как видим, каждая определяется в своих скобках – сначала имя, потом значение. А участок кода, где определяются все переменные, заключен в свои скобки.

3. Имя переменной «a» в конце выражения выводит ее значение в консоль.

Запустим тот же код, но с двумя переменными:

  1. (let* ((a 4) (b 8)) a)
  2. (let* ((a 4) (b 8)) b)

Поочередно выводим значения «a» и «b» в консоль и убеждаемся, что все работает.

1.3 Переменные и строки
Строка заключается в кавычки – "строка". Она легко присваивается переменной:

  1. (define x)
  2. (set! x "строка")

Проверяем:

  1. x

Видим в консоли:

  var-string.jpg
 
Работает!

1.4 Списки
Список – это массив. Определяется он таким образом:

  1. '(1 2 3 4)

или

  1. (list 1 2 3 4)

В первом варианте все значения в скобочках, записанные через пробел – элементы списка. Во втором первое значение является оператором объявления списка, а все остальные его элементы. На мой взгляд, удобней пользоваться первым вариантом.

Список может быть пустым:

  1. '()

Список может содержать как числа, так и строки:

  1. '(1 2 3 4 "пять")

Список можно присвоить переменной:

  1. (define mylist)
  2. (set! mylist '(1 2 3 4 "пять"))

Проверяем:

  1. mylist

В консоли видим: (1 2 3 4 "пять").

1.5 Вложенные списки
Список может состоять из списков, или включать в себя списки (аналог многомерных массивов). Например:

  1. '( '(1 2) '(3 4) '(5 6))

Мы создали список из 3-х списков – '(1 2), '(3 4), '(5 6). Или так:

  1. '('(1 2) '(3 4) 5 6)

Теперь он содержит 2 вложенных списка '(1 2), '(3 4) и 2 числа – 5, 6.

Чуть ниже мы узнаем о том, как работать со списками.

2. Вычисления
Работа с вычислениями в Scheme отличается от того, как это происходит во многих языках программирования. Выполните в консоли такой код:

  1. (* 8 8)

Что сейчас вообще произошло? Вот что:

1. Оператор (вернее, функция) * выполняет умножение. Он пишется в начале выражения.
2. Далее пишутся данные, с которыми работает оператор. В данном случае – 2 числа, 8 и 8.

То есть, если вы напишите (8 * 8) – увидите ошибку, ибо интерпретатор примет первое значение в выражении, число 8, за оператора.

Дальше – веселее:

  1. (/ (* 8 8) (* 8 8))

Сейчас мы пытаемся посчитать вот это:

  numbers.jpg
 
Сначала считаются выражения с умножением. Потом получившиеся результаты делятся. На выходе получается число 1.

2.1 Вычисления и переменные
С глобальными переменными все просто:

  1. (define x)
  2. (define y)
  3. (set! x 8)
  4. (set! y 8)
  5. (+ x y)

Тогда как с локальными – один момент:

  1. (let* ((a 8) (b 2)) (- a b))

Выражения с использованием этих переменных нужно писать после блока их определения. Сколько угодно. Например:

  1. (let* ((a 8) (b 2)) (- a b) (+ a b))

(В консоль выведется только результат последнего выражения)

За пределами скобок оператора let* локальные переменные не существуют, следственно и работа с ними там невозможна.

3. Функции
Как я говорил выше, команда define также создает функции:

  1. (define (double-func a) (+ a a))

1. Первый блок в скобках после define – (double-func a), задает ее имя (первое значение) и входные данные (остальные значения).

2. Второй блок после define – (+ a a), выражения с исполняемым кодом функции. В данном случае мы удваиваем значение входной переменной «a».

Проверим работу функции:

  1. (double-func 10)

На выходе получим значение «20».

Теперь напишем что-нибудь сложнее:

  1. (define (dlina-p m n) (+ (* m 2) (* n 2)))

Эта функция вычисляет длину сторон прямоугольника. Вот как это работает:

1. Сначала нам надо узнать длину «m» и ширину «n» прямоугольника – (dlina-p m n).
2. Затем удвоить их – (* m 2), (* n 2).
3. И потом сложить получившиеся значения – (+ (* m 2) (* n 2)).

Проверяем:

  1. (dlina-p 10 4)

На выходе получится число «28».

Примечание

Обратите внимание – не называйте свою функцию именем уже существующей. Если вы это сделаете, пользоваться ей не сможете.

Например, есть предопределенная функция sin. Она вычисляет синус (спасибо, кэп!). Проверим:

  1. (sin 4)

Получаем число «-0,7568024953.0». А теперь делаем так:

  1. (define (sin x) (* x x))

Проверяем еще раз, не закрывая консоли:

  1. (sin 4)

Что видим? На выходе число «16»! А все потому, что теперь функция под именем «sin» вычисляет квадрат числа – (* x x).

Также можно связать имя существующей функции с именем переменной:

  1. (define my-func)
  2. (set! my-func sin)

Теперь, если мы напишем так:

  1. (my-func 4)

это будет равносильно выражению:

  1. (sin 4)

3.1 Функции для работы со строками
Строки можно объединять:

  1. (define mystr)
  2. (set! mystr (string-append "Это" " - " "строка"))

Тут функция string-append объединяет несколько строк "Это", " - ", "строка" в одну.

Теперь, не закрывая консоль, выполняем сразу следующее выражение:

  1. (string-length mystr)

Функция string-length считает количество символов в строке. У нас результат ее работы будет «12» – переменная mystr содержит строку "Это - строка", длина которой 12 символов.

3.2 Функции работы со списками
В списки можно добавлять новые элементы ( в т.ч. и другие списки):

  1. (define listx)
  2. (set! listx '(1 2 3 4 5))
  3. (cons 0 listx)

Функция cons добавляет перед первым элементом списка любое значение. В нашем случае – цифру 0. Первый аргумент функции тот самый элемент списка, который добавляем. Второй и есть наш список, куда добавляем.

В итоге он будет выглядеть так – (0 1 2 3 4 5).

Списки можно объединять:

  1. (define lista)
  2. (define listb)
  3. (set! lista '(0 1 2))
  4. (set! listb '(3 4 5))
  5. (set! lista (append lista listb))

Что мы сейчас сделали?

1. Создали списки '(0 1 2) и '(3 4 5) в переменных lista и listb.
2. Объединили эти два списка функцией append и записали получившееся в переменную lista. На выходе получаем список (0 1 2 3 4 5).

Из списка можно извлечь первый элемент:

  1. (define listx)
  2. (set! listx '(1 2 3 4 5))
  3. (set! listx (car listx))

Функция car извлекает из списка первый элемент (или, как еще говорят, голову). Который мы записываем в переменную listx, где ранее содержался наш список.

Теперь listx содержит значение «1».

Из списка можно удалить первый элемент:

  1. (define listx)
  2. (set! listx '(1 2 3 4 5))
  3. (set! listx (cdr listx))

Все точно так же, только теперь функция cdr возвращает список без первого элемента (хвост списка) – (2 3 4 5). Который мы записываем в переменную listx.

Это все, что можно делать со списками.

А что, если мы имеем такое: (1 2 '("три" "четыре") 5)? И нам надо добраться до первого значения вложенного списка '("три" "четыре").

Алгоритм выглядит так:

1. Идем до вложенного списка, поочередно удаляя первые элементы.
2. Когда первым элементом становится он, извлекаем его функцией car.
3. Извлекаем первое значение вложенного списка функцией car.

Воплощаем в жизнь:

  1. (define listx)
  2. (set! listx '(1 2 ("три" "четыре") 5))
  3. (set! listx (car (car (cdr ( cdr listx)))))

Обратите внимание на порядок исполнения кода! Он читается не слева направо, а из младшей скобки в старшую. Сначала срабатывают функции cdr, потом car. Результат – значение "три" в переменной listx.

Впрочем, можно написать проще:

  1. (define listx)
  2. (set! listx '(1 2 '("три" "четыре") 5))
  3. (set! listx (caaddr listx))

Теперь посмотрите на функцию caaddr. Она выполняет то же, что и нагромождение car и cdr. Две буквы a обозначают две функции car, а две буквы d обозначают две функции cdr.

Можно использовать функцию cadr, caadr, caddr… Все они выполняют последовательность car-cdr соответственно количеству букв a и d в названии.

4. Логические операции.
4.1 Условия.

Запустите в консоли такое выражение:

  1. (if ( > 3 0 ) "правда" "неправда")

Функция if проверяет истинность выражения в скобках. Если оно верно, выполняется первое условие. Иначе – второе, хотя его можно не использовать. В таком случае, если выражение ложно, ничего выполняться не будет.

Результат работы нашего кода будет «правда».

Теперь исполните это:

  1. (if ( < 3 0 ) "правда" "неправда")

Выражение ложно – результат работы «неправда». Можно также использовать функции =, >=, <=.

Теперь пример сложнее.

  1. (define x)
  2. (define y)
  3. (define say)
  4. (set! x 12)
  5. (set! y 11)
  6. (if (= x y) (set! say "правда") (set! say "неправда"))

Он более практичен, т.к. все данные у нас в переменных, и в условии работаем мы тоже с ними.

В x и y записываем два числа, которые сравниваем в условии.

Если условие верно, записываем в переменную say «правда». Иначе – «неправда».

И еще лучше…

4.2 Рекурсия в одну строку
Запустите в консоли это:

  1. (define (rec_in n) (if (> n 0) (rec_in( - n 1)) n ))
  2. (rec_in 10)

Это – рекурсия, когда функция вызывает саму себя. Продолжается бесконечное число раз, если в коде не предусмотрим процесс выхода из нее.

Как работает рекурсия в нашем примере?

1. Оператор define создает функцию rec_in с одним входящим значением n.
2. Условие проверяет истинность выражения n > 0.
3. Если истина – вызываем функцию rec_in. Как видите, в самой же rec_in.
4. Во второй строке мы вызываем нашу функцию, задавая ей число 10. При последующих вызовах rec_in в ней самой мы посылаем ей это значение, каждый раз уменьшаемое на единицу – ( - n 1).
5. В конце концов, оно станет равно нулю и условие n > 0 станет ложным.
6. Выполнится второе выражение – функция просто вернет значение переменной n и закончит свою работу. То есть «0».

4.3 Цикл while
  1. (define n)
  2. (define m)
  3. (set! n 10)
  4. (set! m 0)
  5. (while (> n 0) (set! m (+ m n)) (set! n (- n 1)) )
  6. m

1. Определяем переменные m и n.
2. Устанавливаем значения n = 10, m = 0.
3. Цикл будет продолжаться, пока n > 0.
4. Первое выражение - присваиваем переменной m ее же значение, сложенное с n.
5. Второе выражение - присваиваем переменной n ее же значение, уменьшенное на единицу.
6. Смотрим значение переменной m. Оно должно быть 55.

Как видите, мы посчитали сумму всех чисел в промежутке от 1 до 10 (10+9+8+7… +1).

Итог: В этой части мы изучили основы Scheme. По крайней мере, его модификации, присутствующей в Gimp 2.x. Как видите, ничего сложного нет.

Кстати – если кому-то пригодится, генератор случайных чисел тут выглядит вот так: (rand 255). Где 255 – любое число, в промежутке от 0 до которого нужно генерировать случайные числа.

Вот пример использования:

  1. (define r)
  2. (set! r (rand 255))

***
В следующей части я расскажу о загрузке функций из файлов, регистрации в меню, работе с окнами и создании первого скрипта, взаимодействующего с функционалом Gimp.
  • +4
  • views 7802