
Porting GimpFu Plugins to Gimp 3 and Python 3

Lloyd Konneker, February 2020


About
-----

This document is for authors of Gimp plugins that use Python and GimpFu .
When this document says "you" or "author" it means a person writing
a plugin for Gimp using the Python language and importing the gimpfu package.

It describes the changes you must make to plugin Python source
so the plugin will run in Gimp 3.

This is for plugins written in Python using GimpFu.
In other words, if you import any of these modules:
   - gimpfu
   - gimpenums (see below)

This document talks in the present tense, but much is not implemented yet.
As of this writing, the document is a specification for what should be implemented.
This document may contain many mistakes,
and is not sanctioned by the Gimp organization.


Quickstart
----------

The document "GIMP Python Documentation" is still mostly in effect.
It is NOT in effect towards the end,
where it talks about tiles, pixel regions, and the gimpplugin module.
"GIMP Python Documentation" mostly describes
what this document calls 'GimpFu', i.e. the gimpfu module.

(Tiles and pixel regions are obsolete and there is not yet a GimpFu adaption,
because few plugins used those objects.
You can use Python and GI to use GEGL buffers.)

(gimpplugin refers to a more complete API that the GimpFu API simplifies.
The phrases  "PyGimp" or "Gimp Python" sometimes refer to both API's.


Changes required by Python 3
----------------------------

All plugins in Gimp 3 run under Python 3.
Python 3 has a very few syntax changes from Python 2.
In such cases, you must change your code.
See elsewhere for a guide to porting Python 2 code to Python 3.

Briefly, if your plugin throws an exception at run time,
and the errant line of code does not appear to involve Gimp objects,
you might suspect that you need to make syntax changes.

Syntax
======

Python3 syntax changed for a few language constructs.

Examples:

    except ValueError, e:      =>   except ValueError as e:
    raise RuntimeError, "msg"  =>   raise RuntimeError("msg")
    print Foo                  =>   print(Foo)


Standard module API
===================

Some standard modules changed API from Python 2 to Python 3

Examples:

    The standard Python module for gettext has changed in Python 3

    import gettext  # standard module
    gettext.install("gimp20-python", gimp.locale_directory, unicode=True) =>
         gettext.install("gimp30-python", Gimp.locale_directory())

    Note that most GimpFu v3 plugins will not become part of Gimp.org.
    Thus they don't need to be internationalized.
    If you distribute your plugin and want to internationalize it,
    you are responsible for building the translation files and installing them.
    GimpFu v3 does put the _() function (that translates) into scope for your plugin.

Obsolete Modules
-----------------------

Some modules part of PyGimp v2 are obsolete :
    gimp
    gimpshelf
    gimpui
    gimpcolor
    gimpenums

To use GimpFu you only need to import the gimpfu module,
which then imports any other modules it needs.
Loosely speaking, that has always been the case for GimpFu.

You should NOT import the above modules.
If you do, and your PYTHONPATH is such that it finds modules by those names,
(which usually only happens for developers)
it will cause mysterious failures.

gimpenums
=========

GimpFu defines certain constants (so-called enums) into the global namespace.
They are upper case, e.g. "NONINTERACTIVE".
They do not need to be qualified with the "gimpenums" prefix e.g. not "gimpenums.NONINTERACTIVE"

Formerly (GimpFu v2), the constants were also defined in the gimpenums namespace,
and many authors used the qualified version.

You will need to not "import gimpenums"
and replace "gimpenums." with the empty string "" everywhere in your plugin.

(This decision may be reversed, for example if significant name clashes are discovered.)

gimpshelf
=========

The gimpshelf module was primarily used to persist plugin settings
within a Gimp session
(so that the values appearing in a plugin dialog were persistent,
and so that "Filter>Repeat Last" executed the plugin with prior choices.)

1) Plugins using the gimpfu module.

The gimpfu module will continue to persist settings
(just not using gimpshelf module anymore.)

2) Plugins that did not use the gimpfu module
and used gimpshelf to persist plugin settings.

You will need to recode to use GI to the new Gimp mechanism for persisting plugin settings.
See example code in a Python plugin that has already been ported to Gimp 3.
For example see gimp/plugins/goat-exercises/goat-exercise-py3.py
(TODO does it persist settings?)

3) Plugins that use gimpshelf module for other purposes.

You will need to use GI as above, or find some other mechanism for persisting.


gimpui
======

The gimpui module was used when a plugin implemented its own GUI,
and needed GTK widgets provided by Gimp (in libgimpui), such as a color picker widget.

GimpFu (and Gimp) will create a dialog automatically for most plugins,
from the declared formal parameters.

If your plugin did implement GUI using the gimpui module,
you will need to recode to use GI to the Gimp objects that implement GUI.


gimpcolor
=========

The gimpcolor module let you define and manipulate colors in colorspaces.
Most plugins do not use it.
The gimpcolor module is obsolete.

Both in v2 and v3 you can use 3-tuples and strings as colors.
However, in v3, you can only use such colors as arguments to the PDB (alias 'pdb'),
not the Gimp library (alias 'gimp'.)
For example:
   gimp.set_foreground((0,255,0)) => pdb.gimp_set_foreground((0,255,0))
   ( OR gimp_context_set_foreground(), the v3 name.)
(TODO can this restriction be eased?)


Gimp model changes
------------------

Channel
=======
The model of Channel in Gimp v3 is expanded from v2.

Some procedures, like gimp-threshold have been replaced e.g. gimp-drawable-threshold
and require a new "channel" parameter.

Threshold/Level parameters
==========================

In v2, some parameters were int in the range [0,255].
In v3, some of those parameters changed to float in range [0.0, 1.0].
GimpFu v3 does not adapt to that change.
You should change the parameter in your plugin source.

Gimp v3 PDB procedure gimp-drawable-levels() has two more "clamp" parameters
than the deprectated v2 gimp-levels()


Layer Modes
===========

In v2, the modes that used colorspaces i.e. HSV values (COLOR, SATURATION, HUE, VALUE )
did not specify which colorspace they used.

In v3, the modes are HSL_COLOR, HSV_SATURATION, HSV_HUE, and HSV_VALUE
and their name tells you which colorspace they use.
Note that the first uses "HSL" instead of "HSV" colorspace (it always has, even in v2.)

ID versus object
================

Formerly, a Python plugin received integer ID's for references to GIMP objects, such as layers.
Most authors were not even aware that the GIMP objects were stored as integer ID's.
(Note that ScriptFu Scheme plugins still receive integer ID's.
GIMP uses IDs's for ScriptFu, a language that does not have pointers.
GIMP also uses ID's across the 'wire protocol' between the GIMP app and plugin processes,
since pointers cannot be passed between processes with different address spaces.)

Now a Python plugin receives a reference to a GObject (a pointer, more or less.)

GimpFu mostly hides all that:

Some PDB procedures, like gimp-item-is-layer(int ID) are no longer in the PDB.
GimpFu provides a backward compatible definition for gimp-item-is-layer(GObject *),
so you don't need to change plugin code.

Now the PDB v3 has gimp-item-ID-is-layer(int ID).
Also ID is a property of an item.
Thus if you want to revise to use ID's, gimp-item-ID-is-layer(item.ID) should also work.

Passing ID's in most other calls to PDB procedures will no longer work.
Almost all PDB procedures now expect a GObject reference, not an ID.
You will need to delete the trailing ".ID".
For example: where you once passed "drawable.ID" to a PDB procedure,
you now pass "drawable".


Other deprecated PDB procedures
===============================

This is a partial list of the most common transformations
for PDB procedures deprecated sometime in the past, and now obsolete.

gimp-image-add-layer(image, layer, position) => gimp-image-insert-layer(image, layer, parent, position)
For an equivalent to the deprecated procedure, the parent should be None.
(See documents, the parent is related to layer groups.)

gimp-drawable-get-image => gimp-item-get-image
GIMP v3 is more polymorphic: many objects inherit Item so you use methods of that class.



Other model changes
========================

TODO

Changes to GimpFu formal declaration of plugin characteristics.
---------------------------------------------------------------

Using GimpFu, a plugin author formally declares the characteristics of a plugin
in arguments to the register() function.

One argument is a list of tuples formally declaring the parameters to a plugin,
each tuple starting with a PF_xxx constant.


Gimp Menu Path Names
=====================

A plugin declares a MenuItem name and a MenuPath name

Gimp v3 allows menu paths:
<Image>", "<Layers>", "<Channels>", "<Vectors>", "<Colormap>", "<Brushes>",
"<Dynamics>", "<MyPaintBrushes>", "<Gradients>", "<Palettes>", "<Patterns>", "<ToolPresets>", "<Fonts>" or "<Buffers>"

But <Filters> also works.  And <Filters/Foo>

Note that e.g. Layers is not the "Layer" menu in the menubar, but the popup menu in Gimp's "Layers" dockable dialog.
Use RMB to popup the menu.

The best reference explaining menu paths is this document:  TODO

Plugin formal parameter names
=============================

In GimpFu v2, the name of a formal parameter (the second element of the declaring tuple)
could contain spaces, and was not required to be unique.
The names were not used in GimpFu.
The formal parameters were required to be in the same order as
the actual parameters of the plugin run_func
but the formal and actual names did not need to match.
(Also Gimp one-to-one correspondence

In GimpFu v3, the name of a formal parameter *should* not contain spaces
but FBC GimpFu v3 allows spaces, and fixes them.

In GimpFu v3, the name of a plugin's formal parameter *must* be unique.
(Since Gimp uses the names to create GObject properties on the plugin.)
(an enhancement to GimpFu v3 could change this.)

GimpFu v3 still does not require the formal (in register())
match the actual (in the run_func) parameter names
(but there is no reason for an author not to match the names.)

(
GimpFu v2 and v3 also allow some mandatory formal parameters (image and drawable) to be omitted.

GimpFu v2 and v3 always hide the need for the run_mode parameter to plugins.
)




The gimp and pdb aliases
------------------------

The symbols 'gimp' and 'pdb' are still supported in v3.
They support all the methods defined by  the "GIMP Python Documentation",
plus all the functions defined on the Gimp and Gimp.PDB classes
(in some document generated from the Gimp .gir files.)

(They are distinct from the 'Gimp' and 'Gimp.get_pdb()' symbols,
which are defined when you use GI directly.)

Note that "gimp.pdb" was allowed in v2 but is obsolete in v3.  Use "pdb".


The GimpFu classes
------------------

The classes Image, Layer, etc. are still supported in v3,
and they have the same methods defined by the "GIMP Python Documentation",
plus all the methods that the underlying Gimp classes have.

(You use the capitalized class names as constructors in the gimp namespace:
e.g.  foo = gimp.Layer(...)
Note that using GI, the constructors for Gimp classes are different
e.g. foo = Gimp.Layer.new(...)  .
)



Enums
-----

In short, GimpFu v3 supports most the enums of v2.  You need to change little.

GimpFu v2 and v3 define enums to be used with Gimp.
They are all caps, e.g. FILL_TRANSPARENT.
They can be used without a prefix e.g. don't use gimp.FILL_TRANSPARENT

The enums TRUE and FALSE are obsolete in GimpFu v3.
Use the Python literals True and False.
(TRUE and FALSE have ancestry in ancient C code.
It is better to convert to modern usage now, than to continue with confusing symbols.)

Their spelling does not always correspond to the spelling used in the Gimp API.
For example, Gimp defines BACKGROUND but GimpFu defines BACKGROUND_FILL.
New code should use the Gimp enums instead of the enums defined by GimpFu.
For example, use Gimp.FillType.BACKGROUND.
You can use a mix of both.

GimpFu v2 defined deprecatated enums TRUE and FALSE for use with Gimp.
GimpFu v3 does not, they are obsolete, use True and False instead.

A few enums defined by GimpFu v2 are still defined by v3, but only for backward compatibility,
that is they are deprecated and new code should not use them.
E.g. Instead of BG_BUCKET_FILL, use BACKROUND_FILL
E.g. FILL_TRANSPARENT => TRANSPARENT_FILL or Gimp.FillType.TRANSPARENT


PDB
---

In v2, PDB procedures often accepted -1 for optional parameters,
and Python authors could use None for the parameter.

GimpFu v3 still supports that.
Use of -1 is deprecated, meaning not recommended for new code.  Use None instead.

PDB procedure docs say to use NULL for optional parameters, but that is for the C language.
In Python, use None.


GimpFu packaging
----------------

Most authors should not care how GimpFu is packaged.
Some authors may want to know to aid debugging.

In v2, gimpfu was many .py files in the same directory, each file a Python module.

In v3, GimpFu is a package, a directory with a __init__.py file, and submodules.
The top directory is in PYTHONPATH when Gimp runs a plugin.

You can import a specific submodule using absolute paths like:
from gui.dialog import *
This refers to .../plug-ins/gimpfu/gui/dialog.py


Miscellaneous changes
---------------------

gimp.pdb
========
Use 'pdb.foo()', not 'gimp.pdb.foo()'.  'pdb' is not in the 'gimp' namespace.

The pdb object cannot be accessed using index notation:
pdb['gimp-edit-fill']()    =>    pdb.gimp-edit-fill()

Property notation
=================

The attributes of GimpFu objects can still be accessed using property notation
without parens
e.g. timg.width
Using GI, Gimp object properties are accessed using getter call notation
e.g. if timg has type Gimp.Image, use timg.width(), *with parenthesis*,
or even timg.get_width(), *with 'get_' prefix and parenthesis.*


Nested calls to gimp
====================

This doesn't work in v3:

pdb.gimp_floating_sel_anchor(pdb.gimp_image_get_floating_sel(img))

Instead, unnest the calls:

foo = pdb.gimp_image_get_floating_sel(img)
pdb.gimp_floating_sel_anchor(foo)

(This is a quirk of the GimpFu implementation, and in the future could be changed.)


String valued PF_RADIO parameters
=================================

GimpFu v2 allowed parameters of type PF_RADIO that returned string values.
E.G. , with "extras"  (("Foo", "foo"), ("Bar", "bar"))

In v3, GimpFu requires integer literals for the values returned
by a radio button group widget.
E.G. , with "extras"  (("Foo", 0), ("Bar", 1))





Rewriting a plugin without GimpFu
---------------------------------

It is possible to rewrite a plugin without using GimpFu,
using just Python and GI.

See examples in  2.99/plug-ins/python


Summary of differences Python/GI versus GimpFu
-----------------------------------

Again, you can use GimpFu or not (just Python with GI of Gimp.)

Briefly, GimpFu makes writing a plugin more Pythonic.

When you use GimpFu, Python remains dynamically typed with duck typing.
For example, you can pass an int to a PDB procedure where it expects a float,
and vice versa
Without GimpFu, in the same case, you need explicit conversion: float(foo).

Without GimpFu, constructors are named 'new', i.e. Gimp.Layer.new()
instead of gimp.Layer().

With GimpFu, Gimp constants are brief, e.g. BACKGROUND_FILL.
Without GimpFu, use fully qualified constant names, e.g. Gimp.FillType.BACKGROUND.

With GimpFu classes, properties use property syntax, without parentheses.
Without GimpFu, properties of Gimp object use getter/setter call syntax with parentheses.

With GimpFu, the boilerplate might be more condensed.
Whether you use GimpFu or not, you can still start creating a plugin from template code.

With GimpFu, arguments to PDB procedures are Pythonic, since GimpFu marshals them before passing to Gimp.
Without GimpFu, you must explicitly create a Gimp.ValueArray to pass to a PDB procedure
(e.g. the "args" for Gimp.get_pdb().run_procedure('plug-in-plasma', args) )



Other notes
-----------

gimp.message() (which resolves to Gimp.message)
since Gimp 3 (???) may write to a status bar instead of present a dialog.


Frequent errors
---------------

Calling error for procedure 'gimp-drawable-mask-bounds':
Item 'Texture' (4) cannot be used because it has not been added to an image.

"has not been added to an image" is the crux.
Probably you are calling gimp_image_add_layer(), which is deprecated.
Instead, use gimp_image_insert_layer()  and add "None" for the third i.e. "parent" parameter.
