[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