[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