BOUNDARY. libpython-clj. TWO things live here, and confusing them is the whole difficulty of this integration.
It cannot import GIMP. GIMP 3 plugins run in GIMP's own embedded CPython,
linked against that process's GObject introspection typelibs and attached to
its main loop and PDB. Pointing libpython-clj at that same interpreter and
running gi.require_version('Gimp', '3.0'); from gi.repository import Gimp
SUCCEEDS, which is the trap: the module imports and is inert.
Gimp.get_images() answers for a GIMP that this process is not part of.
There is no in-process route to a running GIMP, from a JVM or from anything
else that GIMP did not itself launch.
Two useful things, and both are real.
PythonTransport drives the GIMP plugin socket FROM the embedded
interpreter. Python opens the socket, frames the request and reads the
answer; the JVM never touches a file descriptor. This is precisely the path
the reference project's own bg_remove.py takes, executed in-process instead
of as a subprocess. It satisfies IGimpTransport, so it is a drop-in
alternative to hive-gimp.transport.socket and every command works through
it unchanged.
Why have it when the native socket exists: it puts GIMP work and host-side Python work in ONE interpreter, sharing state. A workflow that exports a layer, runs rembg over it, and re-imports the result is then one Python context rather than two runtimes passing files, and the reference project's Python helpers can be imported and driven directly.
HostPython is the other half: the Python image ecosystem GIMP does not
ship (rembg, PIL, numpy, scikit-image). See hive-gimp.pixel.
Every libpython-clj var is resolved SOFTLY, at call time. The dependency
lives behind the :python alias, so a deployment without it loads this
namespace, reports :python/no-libpython, and keeps the native socket
transport and every GIMP command working.
BOUNDARY. libpython-clj. TWO things live here, and confusing them is the
whole difficulty of this integration.
## What libpython-clj CANNOT do
It cannot import GIMP. GIMP 3 plugins run in GIMP's own embedded CPython,
linked against that process's GObject introspection typelibs and attached to
its main loop and PDB. Pointing libpython-clj at that same interpreter and
running `gi.require_version('Gimp', '3.0'); from gi.repository import Gimp`
SUCCEEDS, which is the trap: the module imports and is inert.
`Gimp.get_images()` answers for a GIMP that this process is not part of.
There is no in-process route to a running GIMP, from a JVM or from anything
else that GIMP did not itself launch.
## What libpython-clj CAN do, and does here
Two useful things, and both are real.
`PythonTransport` drives the GIMP plugin socket FROM the embedded
interpreter. Python opens the socket, frames the request and reads the
answer; the JVM never touches a file descriptor. This is precisely the path
the reference project's own bg_remove.py takes, executed in-process instead
of as a subprocess. It satisfies `IGimpTransport`, so it is a drop-in
alternative to `hive-gimp.transport.socket` and every command works through
it unchanged.
Why have it when the native socket exists: it puts GIMP work and host-side
Python work in ONE interpreter, sharing state. A workflow that exports a
layer, runs rembg over it, and re-imports the result is then one Python
context rather than two runtimes passing files, and the reference project's
Python helpers can be imported and driven directly.
`HostPython` is the other half: the Python image ecosystem GIMP does not
ship (rembg, PIL, numpy, scikit-image). See `hive-gimp.pixel`.
Every libpython-clj var is resolved SOFTLY, at call time. The dependency
lives behind the `:python` alias, so a deployment without it loads this
namespace, reports `:python/no-libpython`, and keeps the native socket
transport and every GIMP command working.BOUNDARY. The only namespace that touches a socket.
One connection per command, deliberately. That is not a simplification, it
is the plugin's actual behaviour: _handle_client ends with
if self.auto_disconnect_client: client.close()
and auto_disconnect_client starts true. The plugin hangs up after every
command unless a client first sends the bare string disable_auto_disconnect,
so a client that assumes a persistent connection is really reconnecting on
every other call and discovering it at the least convenient moment. Opening
per command makes that explicit and costs a loopback TCP handshake.
Everything above this namespace speaks IGimpTransport and therefore never
learns that any of this happened.
BOUNDARY. The only namespace that touches a socket.
One connection per command, deliberately. That is not a simplification, it
is the plugin's actual behaviour: `_handle_client` ends with
if self.auto_disconnect_client: client.close()
and `auto_disconnect_client` starts true. The plugin hangs up after every
command unless a client first sends the bare string `disable_auto_disconnect`,
so a client that assumes a persistent connection is really reconnecting on
every other call and discovering it at the least convenient moment. Opening
per command makes that explicit and costs a loopback TCP handshake.
Everything above this namespace speaks `IGimpTransport` and therefore never
learns that any of this happened.cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |