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

Прежде чем мы начнем

Прежде всего, это руководство написано для Gimp 2.10+ и, по доверенности, Python 2.7. Тем не менее, Gimp 3 находится в разработке, и он должен принести Python 3 на стол, возможно, с другими API. Когда он станет основным, я, вероятно, немного обновлю эту статью.

Во-вторых, Python — не единственный способ написания плагинов Gimp. Вы также можете использовать Scheme, что-то вроде диалекта Lisp. Такие скрипты ориентированы на преобразование, но я хотел рассказать вам и о графическом интерфейсе. Кроме того, Python намного проще для понимания.

В-третьих, вероятно, есть какой-то Gimp SDK для Python, чтобы вы могли разрабатывать плагины с полной мощью отладчика и т. д. Мне было все равно, и я разработал все свои плагины даже без установленного интерпретатора Python (я все еще использовал онлайн https:// www.online-python.com/ для некоторых фрагментов). Таким образом, все, что вам нужно, чтобы следовать этому руководству, — это просто текстовый редактор и Gimp с установленными модулями Python.

Настройка

Прежде чем начать, убедитесь, что при открытии Gimp и переходе в «Фильтры» в меню есть опция «Python-fu». Если нет, вам нужно переустановить Gimp с поддержкой Python.

Второе, что вам нужно сделать, это перейти в Правка -> Настройки -> Папки -> Плагины и открыть одну из перечисленных здесь папок. Это место, где вы «устанавливаете» свои плагины, просто копируя их туда. В папке плагинов создайте подпапку с именем example и поместите туда следующий минимальный скрипт как example.py. Перезапустите GIMP.

#! /usr/bin/python

from gimpfu import *
import gtk
import gimpui
import gobject

def example_plugin_entry(_image, _drawable):
    window = gtk.Window()
    window.set_title("Example")
    window.connect('destroy',  close_plugin_window)
    window_box = gtk.VBox()
    window.add(window_box)
    window.show_all()
    gtk.main()

def close_plugin_window(ret):
    gtk.main_quit()

register(
          "example_plugin_entry",
          "Description",
          "Description",
          "Author name",
          "Apache 2 license",
          "2022",
          "Example",
          "*",
          [
              (PF_IMAGE, "image", "Input image", None),
              (PF_DRAWABLE, "drawable", "Input drawable", None),
          ],
          [],
          example_plugin_entry, menu="<Image>/Filters")
main()

Анатомия скрипта

Весь импорт там по соглашению. Эти модули установлены в Gimp. Обратите внимание, что другие нестандартные модули Python будут вам недоступны.

Затем есть два определения метода:

  • example_plugin_entry — это ваша точка входа, ваша «основная» функция. Убедитесь, что вы сохранили параметр _image. Это ссылка на открытое в данный момент изображение. _drawable должен ссылаться на выбранный в данный момент слой или аналогичный объект рисования. Вы можете получить его позже из ссылки на изображение.
  • close_plugin_window — это деструктор вашего плагина.

Вызов register показывает ваш плагин внутренним компонентам Gimp, поэтому он помещает его в правильное подменю, печатает всплывающие подсказки и знает, какие параметры должны быть переданы ему из среды.

Больше ничего нет. Вы можете создавать новые методы, новые классы, вы даже можете использовать глобальные переменные (но они действительны только для текущего открытого плагина, они не переносятся, когда пользователь закрывает и снова открывает плагин). Модули не пробовал, но для простых плагинов они обычно не нужны.

Первая загрузка

После загрузки Gimp вы должны увидеть новый элемент под названием Пример в меню Фильтры. Он должен быть серым, пока вы не откроете/не создадите изображение. После этого вы сможете запустить его, и вы должны получить пустое окно:

В противном случае может возникнуть несколько проблем, и все их трудно отследить. Вы можете запустить двоичный файл Gimp с параметром --verbose, но в большинстве случаев он не может сказать мне, что не так.

  1. Скрипт не может быть проанализирован интерпретатором Python. Просто попробуйте запустить скрипт вручную в интерпретаторе Python. Либо произойдет сбой с какой-то синтаксической ошибкой (исправление идентификатора), либо, если ваш скрипт хорош, произойдет сбой с ошибкой ModuleNotFoundError: No module named ‘gimpfu’
  2. Скрипт помещен в недопустимое меню. Видите menu="‹Image›/Filters" в конце скрипта? Вы можете использовать его, чтобы разместить свой скрипт в другом месте. Вы даже можете создавать совершенно новые подменю (я поместил инструменты для работы с пиксельной графикой в ​​раздел ‹Изображение›/Инструменты/Пиксельная графика). Если вы измените его так, как это неудобно для Gimp, он может не показать его вам.
  3. Скрипт зарегистрирован под кодом, связанным с другим скриптом. Я говорю о первом параметре вызова register. Просто придумайте что-то уникальное здесь. Хэш может быть хорошей идеей.

Общее развитие

Как только ваш плагин станет доступен из меню, вам не нужно перезапускать Gimp. Внесите изменения в исходный код, сохраните и снова откройте плагин. Убедитесь, что у вас открыта консоль ошибок (Windows -> Закрепляемые диалоговые окна -> Консоль ошибок).

Предупреждение. Если вы получаете сообщение об ошибке о сбое вашего плагина, предлагающее вам перезапустить Gimp, чтобы очистить, возможно, поврежденную память — НЕ ДЕЛАЙТЕ ЭТОГО! Вероятно, вы только что сделали опечатку, и ваш скрипт не может быть проанализирован. Прогони через интерпретатор, исправь ошибки, попробуй еще раз. Все будет хорошо. Если вы перезапустите Gimp до этого, плагин не загрузится, пока вы его не исправите, и вам придется перезапускать Gimp снова.

Если ваш код приводит к каким-либо ошибкам во время выполнения, плагин просто перестанет делать то, что он делал, он останется открытым и ничего вам не скажет. Попытка отлова в каждой отдельной функции, вероятно, является хорошей идеей. Также полезно использовать pdb.gimp_message(), чтобы просто распечатать журналы отладки и использовать их, чтобы выяснить, где ваш код дал сбой и остановил регистрацию. Эти журналы будут распечатаны в консоли ошибок.

В любой момент вы можете использовать встроенную консоль Python для тестирования прямо в Gimp (Фильтры -> Python-fu -> Консоль). Эта консоль также полезна по другой причине. Вы можете просматривать атрибуты часто используемых типов, таких как изображения и слои (help(gimp.Image), help(gimp.Layer)). Еще более интересен браузер процедур (Browse…). Вы можете найти имена и параметры для всех методов, которые могут вам понадобиться. Просто убедитесь, что вы дважды щелкнули по методу, который хотите использовать — имя указано для схемы, а параметры иногда необязательны. Двойной щелчок вставит вызов функции Python в консоль, чтобы вы знали, как ее вызывать.

Создание пользовательского интерфейса

PyGTK используется для графического интерфейса. Документацию к нему можно найти здесь: https://developer-old.gnome.org/pygtk/stable/. Используйте классы с префиксом gtk. HBox, VBox, Button, Combo, Entry и Label — ваши лучшие друзья в большинстве случаев использования.

В исходном скрипте вы можете заметить строку window_box = gtk.VBox(). Это точка входа для создания всех ваших элементов графического интерфейса. VBox — это контейнер для элементов, в котором каждый новый элемент помещается под предыдущим (например, размещение разделителей в HTML). HBox, с другой стороны, помещает все элементы в одну строку (как ‹span› в HTML). Все элементы в VBox будут иметь одинаковую ширину. Все элементы в HBox имеют одинаковую высоту.

Используйте метод pack_start в классе VBox/HBox, чтобы поместить элементы в эти контейнеры. Это выглядит так:

my_hbox.pack_start(my_element, grow, True, spacing)

spacing определяет отступ вокруг элемента в контейнере. grow — логическое значение, указывающее, поддерживает ли элемент минимально возможную ширину/высоту (высота для HBox и ширина для VBox) или он увеличивается чтобы соответствовать доступному пространству.

Правило большого пальца: при перемещении элемента ввода в VBox/HBox установите для увеличения значение True. Когда вы вставляете VBox/HBox в другой VBox/HBox, False обычно является разумным значением по умолчанию.

При присоединении обратных вызовов к элементам gtk всегда помните, что первый параметр обратного вызова должен быть ссылкой на вызывающий его виджет, например:

def handle_btn_click(button_ref, param1, param2):
    pdb.gimp_message("Button clicked: {} {}".format(param1, param2)

btn = gtk.Button()
btn.connect("clicked", param1, param2)

Советы и хитрости

Когда плагин запускается, он получает ссылку на текущее изображение. Вы можете сколько угодно манипулировать этим изображением, но, скорее всего, вы захотите сделать копию, отредактировать эту копию, а затем показать пользователю предварительный просмотр для подтверждения. В таком случае всегда делайте временное изображение (поскольку скопированные слои должны быть привязаны к какому-либо изображению):

temp_img = pdb.gimp_image_new(
    image_ref.width,
    image_ref.height,
    image_ref.base_type)
temp_img.disable_undo()

Затем скопируйте слои:

for layer in image_ref.layers:
    temp_layer = pdb.gimp_layer_new_from_drawable(layer, temp_img)
    temp_img.insert_layer(temp_layer)
    if len(layer.children) == 0: # Cannot add alpha for layer group
        pdb.gimp_layer_add_alpha(temp_layer)
    pdb.gimp_drawable_set_visible(temp_layer, True)

После этого вы можете масштабировать или переводить скопированные слои или даже делать с ними что-то более дикое. В конце вы можете вызвать temp_img.flatten() или temp_img.merge_visible_layers(), чтобы получить один рисуемый объект, который можно экспортировать или визуализировать в предварительном просмотре.

Примечание: flatten() удаляет альфа-канал, для функции merge_visible_layers() требуется вызов gimp_drawable_set_visible(), как указано выше.

А как насчет того предварительного просмотра, о котором я упоминал ранее? У вас есть два варианта:

  • gimpui.DrawablePreview
  • gimpui.ZoomПредварительный просмотр

Для обоих требуется один параметр — drawable (например, результат flatten()/merge_visible_layers()). Оба они создают небольшой холст с предварительным просмотром данного рисунка в реальном времени. Если, например, вы дадите ему слой из исходного изображения, то, если пользователь отредактирует это изображение, предварительный просмотр также будет обновлен.

Однако. Есть две проблемы:

  1. Возможно, вы захотите перепривязать его к другому рисуемому
  2. Масштабирование в предварительном просмотре — отстой (сложно описать, попробуйте сами)

Вы можете решить вторую проблему, создав собственные кнопки увеличения/уменьшения масштаба и масштабируя слой, который вы передаете в DrawablePreview. Однако это напрямую приводит к первой проблеме. Чтобы решить эту проблему, сделайте что-то вроде этого (каждый раз, когда вам нужно обновить это изображение):

if preview_box is not None:
   display_box.remove(preview_box)

preview_box = gimpui.DrawablePreview(my_new_layer)
display_box.pack_start(preview_box, True, True, 0)
window.show_all()

display_box — это просто HBox/VBox. preview_box имеет значение None или DrawablePreview. Если вам нужно обновить его, вы просто уничтожаете его и создаете новый. window.show_all() перерисовывает пользовательский интерфейс.

Это решение снижает производительность, плюс вы теряете функциональность «живого предварительного просмотра», если только вы не выполняете эту логику десятки раз в секунду. Это выполнимо, но производительность зависит от того, насколько дорого обходится создание слоя, который вы хотите нарисовать.

Примечание. Помните, что я говорил о параметре роста функции pack_start? В display_box для этого параметра должно быть установлено значение True, и он должен быть упакован непосредственно в window_box. Таким образом, если вы измените размер плагина, DrawablePreview будет расти, в то время как другие элементы пользовательского интерфейса останутся прежними.

Заключение

Собственно больше ничего и не нужно — обозреватель процедур должен помочь вам найти нужные вам методы. Иногда они не работают так, как ожидалось. Например, вы можете экспортировать GIF-анимацию из обычного изображения, используя Файл -> Экспортировать как…, но вы не можете сделать это программно, используя любой доступный метод, просто потому, что они требуют, чтобы изображение было в индексированном режиме RGB. Но подобных проблем я не нашел.

Вы можете ознакомиться с моей коллекцией плагинов здесь: nerudaj/gimp-pixel-art-utils и не стесняйтесь использовать ее в качестве справочного материала при реализации своих собственных плагинов.