[prev in list] [next in list] [prev in thread] [next in thread]
List: kde-kimageshop
Subject: Re: Scripting Initial Report
From: Raghavendra Kamath <raghu () raghukamath ! com>
Date: 2017-02-15 14:58:55
Message-ID: 2338753.S5ZGaFR3nd () raghu
[Download RAW message or body]
On Wednesday, February 15, 2017 7:04:33 PM IST Boudewijn Rempt wrote:
> I'm pretty sure that we're on the right track now, and getting close
> to a first usable version, so I spent today finishing up my research
> report on scripting. Permanent copy is here: https://phabricator.kde.org/T1624
Thank you very much for working on this Boud.
>
> = Scripting in Krita =
>
> Since early 2016, I've been working on the scripting functionality for Krita. I \
> have tested a number of approaches. This report will describe the various ways we \
> have implemented scripting before in Krita, the approaches I have tried, the final \
> approach chosen. A number of notes on future work are added.
> == Earlier Scripting Plugins in Krita ==
>
> === Krita 1.4: kiskjsembed ===
>
> The Krita 1.4 scripting plugin can be found in \
> calligra-history/krita/plugins/kiskjsembed. It was started in 2004. This plugin was \
> based on the kjs javascript interpreter \
> (https://api.kde.org/frameworks/kjs/html/index.html, \
> https://en.wikipedia.org/wiki/KJS_(software) ). The kiskjsembed plugin offered a \
> javascript api with access to both functions and objects. When the plugin was \
> abandoned, only a KisPaintDevice wrapper without functionality and a KisMainWindow \
> wrapper that could raise, lower and close a window were implemented.
> The functions were implemented as a static objects:
>
> * MainWindow
> - MainWindow.raise() : this function raises the main window
> - MainWindow.lower() : this function lowers the main window
> - MainWindow.close() : this function closes the main window
>
> The objects could be created from Javascript:
>
> Dynamic objects are objects that can be created.
>
> * PaintDevice
>
>
> That was all.
>
> === Krita 1.5: Kross ===
>
> Starting with Krita 1.5, the kiskjsembed plugin was replaced with the 'scripting' \
> plugin. The Krita 1.5 scripting plugin can be found in \
> calligra-history/krita/plugins/viewplugins/scripting. The plugin was retired \
> because of lack of maintenance with the Calligra 2.5 release.
> Based on the kross framework (https://api.kde.org/frameworks/kross/html/index.html, \
> https://techbase.kde.org/Development/Tutorials/Kross/Introduction) which itself is \
> now on life support, the scripting module provided one generic api definition that \
> could be used from any language that was bound to the kross library. Kross also \
> provided a generic way to create e.g. dialogs. Kross makes heavily use of QObject \
> introspection, dynamically building classes from QObject-based wrapper classes.
> The scripting plugin was loaded for every view/window. Scripts were available from \
> a "Scripts Manager" docker. It was not possible to write actual plugins in Krita \
> that would get loaded on startup.
> The scripting api was implemented in the kritacore wrapper library. The wrapper \
> library implemented the following classes:
> * Brush : a brush object
> * Color : represents a color
> * ConstIterator : an iterator for walking over the pixels of an image or layer
> * ConstPaintDevice : a layer within the image where you are able to perform paint \
> operations on
> * Filter : access to the filter plugins in Krita; not the base class for new \
> filters.
> * Histogram : gives access to the histogram of a paintdevice object
> * Image : an image object with some methods to manipulate the image, get some \
> information and create new layers.
> * Iterator: an iterator that allows changing pixels. Pixel values where accessed by \
> calling setPixel on the iterator.
> * Module: based on KoScriptingModule, provides static access to things like the \
> active layer.
> * Monitor : was meant to check whether iterators need to be invalidated, seems \
> unused.
> * PaintDevice : represents a layer that can be painted on
> * Painter: abstracts KisPainter and makes it possible to perform high-level \
> painting on a paintdevice
> * PaintLayer : represents a layer that can be painted on (there seems to be some \
> confusion with PaintDevice, dox-wise)
> * Pattern : wraps a pattern object. When passed to Painter::setPattern, the Painter \
> class retrieves the actual pattern object from the wrapper. The script itself \
> cannot get at the pattern, or its image.
> * Progress: makes it possible to display a progressbar in Krita
> * Wavelet: a fast wavelet object
>
>
> Apart from these classes there were two helper classes. For the first there is no \
> usage example.
> * KisScriptDecoration::KisCanvasDecoration : to implement canvas decorations like \
> assistants, grids, rulers, guides.
> * KisScriptDockfactory: called to create new dockwidgets
>
> A sample script:
>
> ~~~
> require "Krita"
>
> # fetch the image.
> image = Krita.image()
>
> countStar = 60
> maxStarSize = 4
>
> # we like to manipulate the active painting layer.
> layer = image.createPaintLayer("Sky", 255).paintDevice()
>
> # get the height and the width the image has.
> width = image.width()
> height = image.height()
>
> # start drawing
> layer.beginPainting("sky")
>
> painter = layer.createPainter()
>
> painter.setStrokeStyle(1)
> painter.setFillStyle(1)
> painter.setPaintOp("paintbrush")
>
> painter.setPaintColor( Krita.createRGBColor(24,24,24) )
> painter.paintRect(0.0,0.0, width, height, 0.5)
>
> painter.setPaintColor( Krita.createRGBColor(255,255,255) )
> for i in 1..countStar
> size = rand() * maxStarSize
> size = 1 if(size < 1)
> painter.setBrush(Krita.createCircleBrush(size,size, size / 2 +1, size / 2 +1) )
> painter.paintAt(rand() * width, rand() * height, 0.5)
> end
>
> # painting is done now.
> layer.endPainting()
> ~~~
>
> Which scripts were loaded by the scripting docker was determined by a single file, \
> scripts.rc that contained a short title, comment, name, type of interpreter and the \
> single file that contained the script. Scripts could not consist of more than one \
> file, as far as I can tell.
> === PyKrita ===
>
> In the summer of 2016, another scripting plugin was started. This was based on \
> Kate's "pate" plugin. This plugin makes it possible to load plugins written in a \
> scripting language on startup. These plugins integrate with the application's main \
> instance. Based on Pate, the plugin uses sip, PyQt, Python2 or Python3, a wrapper \
> library and hand-written SIP wrapper files. The plugin has never been released, but \
> has been used as a basis for further research.
> == Other Applications ==
>
>
> === Photoshop ===
>
> Photoshop offers one single API in three languages: javascript, vbscript, \
> applescript. There is, of course, no way to figure out how the scripting facility \
> is bound to the application's functionality.
> http://www.adobe.com/devnet/photoshop/scripting.html
>
> When designing various API's for Krita's scripting module, the Photoshop API, which \
> offers a hierarchy of objects acessible starting with the Application object, was \
> used as an example. Scripts are available in the File/Scripts menu and it is \
> possible to create scripts that are run on startup.
> The object model starts with the Application object, which gives access to the \
> Document object, which gives access to the LayerSet object, which contains the \
> layers -- and so on.
>
> === GIMP ===
>
> GIMP's scripting facility is based on the libgimp library. Gimp can be scripted in \
> scheme and python: https://docs.gimp.org/en/gimp-scripting.html . The scripting \
> system makes all functions available through the procedural database. This is \
> exposed through the Procedure Browser, where for every function the script writer \
> can see documentation, information about parameters, return values and additional \
> information.
> The PDB is generated at build time and doesn't seem to be created dynamically as \
> plugins are loaded, but plugins can register and offer functionality for use in \
> scripts.
> == Approaches to Scripting in Krita ==
>
>
> === QtScript ===
>
> QtScript is Qt's scripting module: http://doc.qt.io/qt-5/qtscript-index.html . In \
> Qt5, this module has been deprecated and cannot be used for new development. This \
> is a pity because it is very complete, includes bindings to Qt and has even a \
> complete ide-like script editor and debugger. It is well suited to extending an \
> application with scripting, if javascript is acceptable.
> === QtQuick/QML ===
>
> QtScript has been replaced by QJSEngine and QML. This is another javascript based \
> environment, using a different javascript engine than QtScript. As an experiment, a \
> Krita plugin that uses QJSEngine and QQmlEngine was written:
> http://valdyas.org/~boud/jsdocker.tgz
>
> Since there are no bindings for Qt, the GUI for plugins written with this engine \
> should be written in QtQuick2, which is a declarative user-interface language. \
> Exposing Krita objects works through QObject introspection.
> In the end, the facilities for writing user interface controls that fit with the \
> rest of Krita, debugging and exposing Krita functionality to the script writer were \
> insufficient.
>
> === Python ===
>
> In the VFX industry, Python is the standard scripting language. Applications like \
> Mari are written almost entirely in Python (and glsl), Maya and other applications \
> offer Python as a scripting language. Since Qt is also virtually the standard \
> toolkit in this field, the combination of Python and Qt would make Krita fit right \
> in with the other players in the field.
> The most used binding to Qt is PySide, which was created by Nokia because the \
> long-standing PyQt bindings are only available under a dual GPL and commercial \
> license. PySide is LGPL. However, maintenance of PySide has halted since Nokia \
> divested itself of Qt. There is a wrapper, Qt.py that makes it possible to use the \
> exact same syntax, no matter the underlying wrapper: \
> https://github.com/mottosso/Qt.py/blob/master/Qt.py.
> There are two approaches to exposing application functionality to Python, \
> intersecting with two technical options:
> * wrap the core libraries (kritaimage, kritaui, etc) directly
> * create a wrapper library. the wrapper library can either be generated from a \
> definition file, or hand-written. Gimp's libgimp is an example of the former, as is \
> the Scribus' scripter-ng wrapper library. The kross-based krita scripting plugin \
> was hand-written.
> And either way, one can:
>
> * use qobject introspection
> * define the interface using a interface definition language. For PyQt that's sip. \
> The sip files can be either written manually or generated. Automatically generating \
> sip files means parsing C++ header files and then creating correct sip files/
> ==== QObject Introspection ====
>
> ===== Mikro ======
>
> Inspired by Kross, Mikro or Mini Kross \
> (https://wiki.scribus.net/canvas/ScripterNG), is a qobject-introspecter that \
> automatically builds Python classes from QObject wrapper classes. As found in \
> Scribus, the class didn't work. It turned out to be possible to make it mostly \
> work, with Python 3 and some hacks.
> The fixed class in in krita/plugins/extensions/pykrita/plugins/krita/mikro.py.
>
> The result however was very fragile and very slow. It also turned out to be very \
> hard to create signal/slot connections. The kind of Python that mikro generates \
> looks "weird". The wrapper library needs to provide Q_PROPERTY definitions for \
> getters and setters, Q_SLOTS for all callable methods and Q_SIGNALS for signals. \
> Signals do not work with this solution; we were not able to figure out why.
> Mikro would be packaged with krita.
>
> ===== PythonQt ======
>
> PythonQt (http://pythonqt.sourceforge.net/) is a dynamic Python binding for the Qt \
> framework. It offers an easy way to embed the Python scripting language into your \
> C++ Qt applications. The focus of PythonQt is on embedding Python into an existing \
> C++ application, not on writing the whole application completely in Python. \
> PythonQt is a stable library that was developed to make the Image Processing and \
> Visualization platform MeVisLab scriptable from Python.
> We investigated PythonQt:
>
> https://phabricator.kde.org/T3588
>
> Since PythonQt is another QObject-introspection based solution, we would either \
> need to create a QObject-based wrapper library, or make all relevant classes in \
> Krita itself QObject-based. PythonQt is not generally available in Linux \
> distributions, which makes it an objectionable dependency.
> ==== Wrapper Library ====
>
> ===== Generating SIP files at build time =====
>
> Shaheed Haque and Stephen Kelly were working in 2016 on a way to generate the sip \
> files for a KDE frameworks library. This is done using the extra-cmake-modules \
> framework, which uses clang to parse all relevant header files and then generates \
> the sip files. In order to "help" the clang-based parser/generator, specification \
> files need to be created with details on, for instance, which methods need to be \
> skipped. The format for these files is fairly opaque.
> The work is in:
>
> https://phabricator.kde.org/source/krita/browse/rempt%252FT1625-python-scripting-ecm/
>
> The big disadvantage of this approach is that everyone needs to have clang and \
> python-clang installed in order to generate the bindings; the bindings would be \
> regenerated every time Krita is built. We felt that this would make it too easy for \
> Krita developers to not install the required dependencies, leading to them working \
> on Krita and breaking the bindings without noticing.
>
> ===== Hand-written SIP files =====
>
> SIP (https://riverbankcomputing.com/software/sip/intro) makes it possible to write \
> wrappers for existing C++ headers. These wrappers can be annotated and contain \
> custom code to make the C++ and Python memory and execution model work well \
> together. The technology is already quite old: the first book in the topic appeared \
> in 2001 (http://valdyas.org/python/book.html).
> In the end, hand-writing and maintain SIP files for a single wrapper library turns \
> out to be easier than maintain the specification file that the extra-cmake-modules \
> based automatic sip wrapper generator needs. Automatic generation would be better \
> if we would wrapp all of Krita; but we decided not to do that.
> ==== Wrap Everything ====
>
> An initial investigation into wrapping all Krita's libraries proved that this would \
> be unworkable:
> * After about twenty years of development, the API is very inconsistent
> * There are many C++ constructs used that are very hard to wrap
> * Automatic wrapping at runtime would be the only feasible way to wrap the about \
> 2600 header files in Krita's 20 or so libraries.
> But most importantly, Krita's libraries provide an internal API. Krita's plugins \
> are all in-tree. Wrapping this API and making it available to script authors fixes \
> the API once and for all time, depriving ourselves of the flexibility we need when \
> developing Krita.
> === Conclusion ===
>
> We decided to go with a specialized hand-written wrapper library, libkis. We \
> generated the initial C++ files from an API definition file, and then tweaked the \
> result to be usable both with Mikro and SIP. The we used Shaheed and Stephens SIP \
> generator to generate the initial set of SIP files, and tweaked those.
> The final API has been closely modeled on Photoshop's Javascript API \
> (http://wwwimages.adobe.com/content/dam/Adobe/en/devnet/photoshop/pdfs/photoshop-cc-scripting-guide-2015.pdf).
>
> From that point on, the libkis wrapper library and the sip files have been \
> hand-developed in tandem. It turns out that some Krita classes needed direct \
> wrapping: one current example is KisCubicCurve. We do expect that some more classes \
> will be wrapped like this, with the resource classes for patterns, brushes and so \
> on being good candidates. Those have had a stable API for a long time, and spending \
> time to manually write a C++ wrapper class for each resource type could be seen as \
> a waste of time.
> Currently, it is possible to run ad-hoc scripts in the scripter mini-IDE, mostly \
> written by Eliakin Almeida, add dock widgets to the user interface, add scripts to \
> the tools/scripts menu and, with hackery, to other menus. Plugin scripts, that are \
> loaded on startup, can be enabled and disabled in the Settings window.
> In addition, there is a separate executable, kritarunner, that can run scripts from \
> the command-line. This might be integrated as just another commandline option in \
> the main krita executable (because you cannot have two executables in a Linux \
> appimage).
> === API ===
>
> The current scripting module provides the following objects:
>
> * Action : encapsulates a QAction
> * Canvas : provides access to the canvas, for rotation, zoom etc.
> * Channel : represents a single channel in a Node
> * DockWidgetFactoryBase : baseclass for scripts that want to add a docker to every \
> window
> * DockWidget : base class for dockers implemented as a script.
> * Document : represents the combination of a KisDocument and KisImage (those are \
> split in the internal API for internal reasons, the image contains the image data, \
> the document the filename).
> * Filter : encapsulates a Krita filter. Cannot be used to implement new filters, as \
> in the kross-based scripting plugin
> * Generator : encapsulates a Krita generator. Same restrictions as for Filter hold
> * InfoObject : a key/value store
> * KisCubicCurve : represents the values for a cubic curve
> * Krita : the root class, accessible as Krita.instance() or under the Application \
> and Scripter aliases
> * Node : a node, that is a layer or mask
> * Notifier : emits signals when the application state changes.
> * Resource : a generic wrapper for resources like patterns, gradients or brush \
> tips.
> * Selection : represents a selection of pixels
> * ViewExtension : represents a plugin that can be added to the Krita window. Can be \
> re-implemented by a plugin script.
> * View : a view on an image in a window
> * Window : a single main window
>
> Additional useful classes would be 'Histogram', 'Transformation' and others.
>
> A sample script looks like this:
>
> ~~~
> #
> # Tests the PyKrita API
> #
>
> import sys
> from PyQt5.QtGui import *
> from PyQt5.QtWidgets import *
> from krita import *
>
> def __main__(args):
> print("Arguments:", args)
> Application.setBatchmode(True)
> print("Batchmode: ", Application.batchmode())
> print("Profiles:", Application.profiles("GRAYA", "U16"));
> document = Application.openDocument(args[0])
> print("Opened", document.fileName(), "WxH", document.width(), document.height(), \
> "resolution", document.xRes(), document.yRes(), "in ppi", document.resolution()) \
> node = document.rootNode() print("Root", node.name(), "opacity", node.opacity())
> for child in node.childNodes():
> print("\tChild", child.name(), "opacity", node.opacity(), node.blendingMode())
> #r = child.save(child.name() + ".png", document.xRes(), document.yRes());
> #print("Saving result:", r)
> for channel in child.channels():
> print("Channel", channel.name(), "contents:", \
> len(channel.pixelData(node.bounds())))
> document.close()
>
> document = Application.createDocument(100, 100, "test", "GRAYA", "U16", "")
> document.setBatchmode(True)
> document.saveAs("test.kra")
> ~~~
>
As a python noob and a non programmer this surely sounds familiar and easy to learn \
and master. I hope in coming years we would see nice library of plugins by the \
comunity that add and enhance features that krita currently has.
> === Maintenance ===
>
> The first scripting plugin for Krita was never really finished; the second one \
> bit-rotted because nobody used it and because it was too easy to break it while \
> developing if one hadn't installed the optional kross runtimes.
> This time, we propose to make sip, python and pyqt mandatory dependencies, on Linux \
> at least, where most developers are. The libkis wrapper library is already always \
> built.
> == Future Work ==
>
> === libkis is a generic wrapper ===
>
> Libkis is a generic, QObject-based wrapper library. That means that if anyone steps \
> up and wants to provide another scripting language, libkis can be re-used easily. \
> Libkis is always built, even if the dependencies for the pykrita scripting module \
> are missing.
> === libkis is an API for C++ plugins ===
>
> Libkis is also poised to become a proper C++ API for external plugins. It still \
> isn't intended to use libkis to to implement tools, filters, generators and brush \
> engines as plugins. For those types of plugins, use of Krita's internal API is \
> still needed. But most of the plugins in krita's plugins/extensions group of \
> plugins could be rewritten against a single well-document libkis API without loss \
> of performance.
> === Exposing plugin functionality dynamically ===
>
> Right now, a script writer has access to all the KisAction objects and can toggle \
> them; that will show the dialogs associated with those actions and act on the image \
> and layer that is currently active in the user interface. It would be good to have \
> some generic solution where plugins can not only add actions to the gui, but also \
> methods to the scripting interface.
> Something like gimp's pdb, without the generation step. It should be possible for \
> plugins, and maybe some internal functionality, to register a method or a runner \
> object in a central registry that describes the method, the parameters and the \
> return values, which can then dynamically be added to the Krita object and exposed \
> to the scripting environment.
> === Exposing Krita widgets ===
>
> Many script and script-b
>
>
Thanky ou once again
--
Raghavendra Kamath
Illustrator
raghukamath.com
[prev in list] [next in list] [prev in thread] [next in thread]
Configure |
About |
News |
Add a list |
Sponsored by KoreLogic