diff options
Diffstat (limited to 'doc/krita/developers-plugins.docbook')
-rw-r--r-- | doc/krita/developers-plugins.docbook | 1553 |
1 files changed, 0 insertions, 1553 deletions
diff --git a/doc/krita/developers-plugins.docbook b/doc/krita/developers-plugins.docbook deleted file mode 100644 index 58dd345e..00000000 --- a/doc/krita/developers-plugins.docbook +++ /dev/null @@ -1,1553 +0,0 @@ -<sect1 id="developers-plugins"> -<title>Developing &krita; Plugins</title> - -<sect2 id="developers-plugins-introduction"> -<title>Introduction</title> - -<para> -&krita; is infinitely extensible with plugins. Tools, filters, large -chunks of the user interface and even colorspaces are plugins. In fact, -&krita; recognizes these six types of plugins: -</para> - -<itemizedlist> -<listitem><para>colorspaces — these define the channels that constitute -a single pixel</para></listitem> -<listitem><para>tools — anything that is done with a mouse or tablet -input device</para></listitem> -<listitem><para>paint operations — pluggable painting effects for -tools</para></listitem> -<listitem><para>image filters — change all pixels, or just the selected -pixels in a layer</para></listitem> -<listitem><para>viewplugins — extend Krita’s user interface with new -dialog boxes, palettes and operations</para></listitem> -<listitem><para>import/export filters — read and write all kinds of -image formats</para></listitem> -</itemizedlist> - -<para> -&krita; itself consists of three layered libraries and a directory with some -common support classes: kritacolor, kritaimage and kritaui. Within -&krita;, objects can by identified by a <classname>KisID</classname>, that is -the combination of a unique untranslated string (used when saving, for -instance) and a translated string for GUI purposes. -</para><para> -A word on compatibility: &krita; is still in development. From &krita; 1.5 to -1.6 not many API changes are expected, but there may be some. From &krita; 1.6 -to 2.0 we will move from &Qt;3 to &Qt;4, from &kde;3 to &kde;4, from -<command>automake</command> to <command>cmake</command>: many changes are to -be expected. If you develop a plugin for &krita; and choose to do so in -&krita;’s subversion repository, chances are excellent that we’ll help you -porting. These changes may also render parts of this document out of date. -Always check with the latest API documentation or the header files installed -on your system. -</para> - -<sect3 id="developers-plugins-introduction-kritacolor"> -<title>KritaColor</title> - -<para> -The first library is kritacolor. This library loads the colorspace plugins. -</para><para> -A colorspace plugin should implement the <classname>KisColorSpace</classname> -abstract class or, if the basic capabilities of the new colorspace will be -implemented by <command>lcms</command> (<ulink url="http://www.littlecms.com/" -/>), extend <classname>KisAbstractColorSpace</classname>. The kritacolor -library could be used from other applications and does not depend on -&koffice;. -</para> -</sect3> - -<sect3 id="developers-plugins-introduction-kritaimage"> -<title>KritaImage</title> - -<para> -The libkritaimage library loads the filter and paintop plugins and is -responsible for working with image data: changing pixels, compositing and -painting. Brushes, palettes, gradients and patterns are also loaded by -libkritaimage. It is our stated goal to make libkritaimage independent of -&koffice;, but we currently share the gradient loading code with &koffice;. -</para><para> -It is not easy at the moment to add new types of resources such as brushes, -palettes, gradients or patterns to &krita;. (Adding new brushes, palettes, -gradients and patterns is easy, of course.) &krita; follows the guidelines of -the Create project (<ulink url="http://create.freedesktop.org/" />) for these. -Adding support for Photoshop's brush file format needs libkritaimage hacking; -adding more gimp brush data files not. -</para><para> -<classname>KritaImage</classname> loads the following types of plugins: -</para> - -<itemizedlist> -<listitem><para>&krita; filters must extend and implement the abstract class -<classname>KisFilter</classname>, -<classname>KisFilterConfiguration</classname> and possibly -<classname>KisFilterConfigurationWidget</classname>. -An example of a filter is Unsharp Mask.</para></listitem> -<listitem><para>Paint operations or paintops are the set of operations -painting tools suchs as freehand or circle have access to. Examples of -paintops are pen, airbrush or eraser. Paintops should extend the -<classname>KisPaintop</classname> base class. Examples of new paintops could -be a chalk brush, an oilpaint brush or a complex programmable -brush.</para></listitem> -</itemizedlist> - -</sect3> - -<sect3 id="developers-plugins-introduction-kritaui"> -<title>KritaUI</title> - -<para> -The libkritaui library loads the tool and viewplugins. This library is a -&koffice; Part, but also contains a number of widgets that are useful for -graphics applications. Maybe we will have to split this library in kritapart -and kritaui in the 2.0 release. For now, script writers are not given access -to this library and plugin writers are only allowed to use this library when -writing tools or viewplugins. <classname>KritaUI</classname> loads the -following types of plugins: -</para> - -<itemizedlist> -<listitem><para>Tools are derived from <classname>KisTool</classname> or one -of the specialized tool base classes such as -<classname>KisToolPaint</classname>, <classname>KisToolNonPaint</classname> or -<classname>KisToolFreehand</classname>. A new tool could be a foreground -object selection tool. Painting tools (and that includes tools that paint on -the selection) can use any paintop to determine the way pixels are -changed.</para></listitem> -<listitem><para>Viewplugins are ordinary KParts that use -<command>kxmlgui</command> to insinuate themselves into &krita;'s user -interface. Menu options, dialogs, toolbars — any kind of user interface -extension can be a viewplugin. In fact, important functionality like &krita;'s -scripting support is written as a viewplugin.</para></listitem> -</itemizedlist> - -</sect3> - -<sect3 id="developers-plugins-introduction-importexport"> -<title>Import/Export filters</title> - -<para> -Import/Export filters are &koffice; filters, subclasses of -<classname>KoFilter</classname>. Filters read and write image data in any of -the myriad image formats in existence. And example of a new &krita; -import/export filter could be a PDF filter. Filters are loaded by the -&koffice; libraries. -</para> - -</sect3> - -</sect2> - -<sect2 id="developers-plugins-creating"> -<title>Creating plugins</title> - -<para> -Plugins are written in C++ and can use all of &kde; and &Qt; and the &krita; -developer API. Only viewplugins should use the &koffice; API. Don’t worry: -&krita;’s API’s are quite clear and rather extensively documented (for free -software) and coding your first filter is really easy. -</para><para> -If you do not want to use C++, you can write scripts in Python or Ruby; that -is a different thing altogether, though, and you cannot currently write tools, -colorspaces, paintops or import/export filters as scripts. -</para><para> -&krita; plugins use &kde;'s parts mechanism for loading, so the parts -documentation at <ulink url="http://developer.kde.org" /> is relevant here, too. -</para><para> -Your distribution should have either installed the relevant header files with -&krita; itself, or might have split the header files into either a &koffice; -dev or a &krita; dev package. You can find the API documentation for &krita;'s -public API at <ulink url="http://koffice.org/developer/apidocs/krita/html/" />. -</para> - -<sect3 id="developers-plugins-creating-automake"> -<title>Automake (and CMake)</title> - -<para> -&kde; 3.x and thus &koffice; 1.5 and 1.6 use <command>automake</command>; -&kde; 4.0 and &koffice; 2.0 use <command>cmake</command>. This tutorial -describes the <command>automake</command> way of creating plugins. -<!-- If I have not updated this manual when we release KOffice 2.0, please -remind me to do so. --> -</para><para> -Plugins are &kde; modules and should be tagged as such in their -<filename>Makefile.am</filename>. Filters, tools, paintops, colorspaces and -import/export filters need <literal role="extension">.desktop</literal> files; -viewplugins need a <application>KXMLGui</application> -<filename>pluginname.rc</filename> file in addition. The easiest way to get -started is to checkout the krita-plugins project from the &koffice; Subversion -repository and use it as the basis for your own project. We intend to prepare -a skeleton &krita; plugin pack for KDevelop, but haven’t had the time to do -so yet. -</para> - -<sect4 id="d-p-c-a-makefile"> -<title><filename>Makefile.am</filename></title> - -<para> -Let's look at the skeleton for a plugin module. First, the -<filename>Makefile.am</filename>. This is what &kde; uses to generate the -makefile that builds your plugin: - -<programlisting> -kde_services_DATA = kritaLIBRARYNAME.desktop - -INCLUDES = $(all_includes) - -kritaLIBRARYNAME_la_SOURCES = sourcefile1.cc sourcefile2.cc - -kde_module_LTLIBRARIES = kritaLIBRARYNAME.la -noinst_HEADERS = header1.h header2.h - -kritaLIBRARYNAME_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) -kritaLIBRARY_la_LIBADD = -lkritacommon - -kritaextensioncolorsfilters_la_METASOURCES = AUTO -</programlisting> - -This is the makefile for a filter plugin. Replace -<replaceable>LIBRARYNAME</replaceable> with the name of your work, and you are -set. -</para><para> -If your plugin is a viewplugin, you will likely also install a <literal -role="extension">.rc</literal> file with entries for menubars and toolbars. -Likewise, you may need to install cursors and icons. That is all done through -the ordinary &kde; <filename>Makefile.am</filename> magic incantantions: - -<programlisting> -kritarcdir = $(kde_datadir)/krita/kritaplugins -kritarc_DATA = LIBRARYNAME.rc -EXTRA_DIST = $(kritarc_DATA) - -kritapics_DATA = \ - bla.png \ - bla_cursor.png -kritapicsdir = $(kde_datadir)/krita/pics -</programlisting> - -</para> -</sect4> - -<sect4 id="d-p-c-a-desktop"> -<title>Desktop files</title> - -<para> -The <literal role="extension">.desktop</literal> file announces the type of plugin: - -<programlisting> -[Desktop Entry] -Encoding=UTF-8 -Icon= -Name=User-visible Name -ServiceTypes=Krita/Filter -Type=Service -X-KDE-Library=kritaLIBRARYNAME -X-KDE-Version=2 -</programlisting> -</para><para> -Possible ServiceTypes are: -</para> - -<itemizedlist> -<listitem><para>Krita/Filter</para></listitem> -<listitem><para>Krita/Paintop</para></listitem> -<listitem><para>Krita/ViewPlugin</para></listitem> -<listitem><para>Krita/Tool</para></listitem> -<listitem><para>Krita/ColorSpace</para></listitem> -</itemizedlist> - -<para> -File import and export filters use the generic &koffice; filter framework and -need to be discussed separately. -</para> -</sect4> - -<sect4 id="d-p-c-a-boilerplate"> -<title>Boilerplate</title> - -<para> -You also need a bit of boilerplate code that is called by the &kde; part -framework to instantiate the plugin — a header file and an implementation file. -</para><para> -A header file: -<programlisting> -#ifndef TOOL_STAR_H_ -#define TOOL_STAR_H_ - -#include <kparts/plugin.h> - -/** -* A module that provides a star tool. -*/ -class ToolStar : public KParts::Plugin -{ - Q_OBJECT -public: - ToolStar(QObject *parent, const char *name, const QStringList &); - virtual ~ToolStar(); - -}; - -#endif // TOOL_STAR_H_ -</programlisting> -</para> - -<para> -And an implementation file: -<programlisting> -#include <kinstance.h> -#include <kgenericfactory.h> - -#include <kis_tool_registry.h> - -#include "tool_star.h" -#include "kis_tool_star.h" - - -typedef KGenericFactory<ToolStar> ToolStarFactory; -K_EXPORT_COMPONENT_FACTORY( kritatoolstar, ToolStarFactory( "krita" ) ) - - -ToolStar::ToolStar(QObject *parent, const char *name, const QStringList &) - : KParts::Plugin(parent, name) -{ - setInstance(ToolStarFactory::instance()); - if ( parent->inherits("KisToolRegistry") ) - { - KisToolRegistry * r = dynamic_cast<KisToolRegistry*>( parent ); - r -> add(new KisToolStarFactory()); - } - -} - -ToolStar::~ToolStar() -{ -} - -#include "tool_star.moc" -</programlisting> -</para> -</sect4> - -<sect4 id="d-p-c-a-registries"> -<title>Registries</title> - -<para> -Tools are loaded by the tool registry and register themselves with the tool -registry. Plugins like tools, filters and paintops are loaded only once: view -plugins are loaded for every view that is created. Note that we register -factories, generally speaking. For instance, with tools a new instance of a -tool is created for every pointer (mouse, stylus, eraser) for every few. And a -new paintop is created whenever a tool gets a mouse-down event. -</para> - -<para> -Filters call the filter registry: -<programlisting> - if (parent->inherits("KisFilterRegistry")) { - KisFilterRegistry * manager = dynamic_cast<KisFilterRegistry *>(parent); - manager->add(new KisFilterInvert()); - } -</programlisting> -</para><para> -Paintops the paintop registry: -<programlisting> - if ( parent->inherits("KisPaintOpRegistry") ) { - KisPaintOpRegistry * r = dynamic_cast<KisPaintOpRegistry*>(parent); - r -> add ( new KisSmearyOpFactory ); - } -</programlisting> -</para><para> -Colorspaces the colorspace registry (with some complications): -<programlisting> - if ( parent->inherits("KisColorSpaceFactoryRegistry") ) { - KisColorSpaceFactoryRegistry * f = dynamic_cast<isColorSpaceFactoryRegistry*>(parent); - - KisProfile *defProfile = new KisProfile(cmsCreate_sRGBProfile()); - f->addProfile(defProfile); - - KisColorSpaceFactory * csFactory = new KisRgbColorSpaceFactory(); - f->add(csFactory); - - KisColorSpace * colorSpaceRGBA = new KisRgbColorSpace(f, 0); - KisHistogramProducerFactoryRegistry::instance() -> add( - new KisBasicHistogramProducerFactory<KisBasicU8HistogramProducer> - (KisID("RGB8HISTO", i18n("RGB8 Histogram")), colorSpaceRGBA) ); - } -</programlisting> -</para><para> -View plugins do not have to register themselves, and they get access to a -<classname>KisView</classname> object: -<programlisting> - if ( parent->inherits("KisView") ) - { - setInstance(ShearImageFactory::instance()); - setXMLFile(locate("data","kritaplugins/shearimage.rc"), true); - - (void) new KAction(i18n("&Shear Image..."), 0, 0, this, SLOT(slotShearImage()), actionCollection(), "shearimage"); - (void) new KAction(i18n("&Shear Layer..."), 0, 0, this, SLOT(slotShearLayer()), actionCollection(), "shearlayer"); - - m_view = (KisView*) parent; - } -</programlisting> -</para><para> -Remember that this means that a view plugin will be created for every view the -user creates: splitting a view means loading all view plugins again. -</para> -</sect4> - -<sect4 id="d-p-c-a-versioning"> -<title>Plugin versioning</title> - -<para> -&krita; 1.5 loads plugins with <literal>X-KDE-Version=2</literal> set in the -<literal role="extension">.desktop</literal> file. &krita; 1.6 plugins will -probably be binary incompatible with 1.5 plugins and will need the version -number 3. &krita; 2.0 plugins will need the version number 3. Yes, this is not -entirely logical. -</para> - -</sect4> -</sect3> -</sect2> - -<sect2 id="developers-plugins-colorspaces"> -<title>Colorspaces</title> - -<para> -Colorspaces implement the <classname>KisColorSpace</classname> pure virtual -class. There are two types of colorspaces: those that can use -<command>lcms</command> for transformations between colorspaces, and those -that are too weird for <command>lcms</command> to handle. Examples of the -first are cmyk, rgb, yuv. An example of the latter is watercolor or wet & -sticky. Colorspaces that use <command>lcms</command> can be derived from -<classname>KisAbstractColorSpace</classname>, or of one of the base classes -that are specialized for a certain number of bits per channel. -</para><para> -Implementing a colorspace is pretty easy. The general principle is that -colorspaces work on a simple array of bytes. The interpretation of these bytes -is up to the colorspace. For instance, a pixel in 16-bit GrayA consists of -four bytes: two bytes for the gray value and two bytes for the alpha value. -You are free to use a struct to work with the memory layout of a pixel in your -colorspace implementation, but that representation is not exported. The only -way the rest of &krita; can know what channels and types of channels your -colorspace pixels consist of is through the -<classname>KisChannelInfo</classname> class. -</para><para> -Filters and paintops make use of the rich set of methods offered by -<classname>KisColorSpace</classname> to do their work. In many cases, the -default implementation in <classname>KisAbstractColorSpace</classname> will -work, but more slowly than a custom implementation in your own colorspace -because <classname>KisAbstractColorSpace</classname> will convert all pixels -to 16-bit L*a*b and back. -</para> - -<sect3 id="developers-plugins-colorspaces-kischannelinfo"> -<title><classname>KisChannelInfo</classname></title> - -<programlisting> -(http://websvn.kde.org/trunk/koffice/krita/kritacolor/kis_channelinfo.h) -</programlisting> -<para> -This class defines the channels that make up a single pixel in a particular -colorspace. A channel has the following important characteristics: -</para> -<itemizedlist> -<listitem><para>a name for display in the user interface</para></listitem> -<listitem><para>a position: the byte where the bytes representing this channel -start in the pixel.</para></listitem> -<listitem><para>a type: color, alpha, substance or substrate. Color is plain -color, alpha is see-throughishness, substance is a representation of amount of -pigment or things like that, substrate is the representation of the canvas. -(Note that this may be refactored at the drop of a hat.)</para></listitem> -<listitem><para>a valuetype: byte, short, integer, float — or -other.</para></listitem> -<listitem><para>size: the number of bytes this channel takes</para></listitem> -<listitem><para>color: a <classname>QColor</classname> representation of this -channel for user interface visualization, for instance in -histograms.</para></listitem> -<listitem><para>an abbreviaton for use in the GUI when there’s not much -space</para></listitem> -</itemizedlist> -</sect3> - -<sect3 id="developers-plugins-colorspaces-kiscompositeop"> -<title><classname>KisCompositeOp</classname></title> - -<para> -As per original Porter-Duff, there are many ways of combining pixels to get a -new color. The <classname>KisCompositeOp</classname> class defines most of -them: this set is not easily extensible except by hacking the kritacolor -library. -</para><para> -A colorspace plugin can support any subset of these possible composition -operations, but the set must always include "OVER" (same as "NORMAL") and -"COPY". The rest are more or less optional, although more is better, of -course. -</para> -</sect3> - -<sect3 id="developers-plugins-colorspaces-kiscolorspace"> -<title><classname>KisColorSpace</classname></title> - -<para> -The methods in the <classname>KisColorSpace</classname> pure virtual classs -can be divided into a number of groups: conversion, identification and -manipulation. -</para><para> -All classes must be able to convert a pixel from and to 8 bit RGB (i.e., a -<classname>QColor</classname>), and preferably also to and from 16 bit L*a*b. -Additionally, there is a method to convert to any colorspace from the current -colorspace. -</para><para> -Colorspaces are described by the <classname>KisChannelInfo</classname> vector, -number of channels, number of bytes in a single pixel, whether it supports -High Dynamic Range images and more. -</para><para> -Manipulation is for instance the combining of two pixels in a new -pixel: bitBlt, darkening or convolving of pixels. -</para><para> -Please consult the API documentation for a full description of all methods you -need to implement in a colorspace. -</para><para> -<classname>KisAbstractColorSpace</classname> implements many of the virtual -methods of <classname>KisColorSpace</classname> using functions from the -<command>lcms</command> library. On top of -<classname>KisAbstractColorSpace</classname> there are base colorspace classes -for 8 and 16 bit integer and 16 and 32 bit float colorspaces that define -common operations to move between bit depths. -</para> - -</sect3> -</sect2> - -<sect2 id="developers-plugins-filters"> -<title>Filters</title> - -<para> -Filters are plugins that examine the pixels in a layer and them make changes -to them. Although &krita; uses an efficient tiled memory backend to store -pixels, filter writers do not have to bother with that. When writing a filter -plugin for the &Java; imaging API, Photoshop or The Gimp, you need to take care -of tile edges and <quote>cobble</quote> tiles together: &krita; hides that -implementation detail. -</para> -<note><para>Note that it is theoretically easy to replace the current tile -image data storage backend with another backend, but that backens are not true -plugins at the moment, for performance reasons.</para></note> -<para> -&krita; uses iterators to read and write pixel values. Alternatively, you can -read a block of pixels into a memory buffer, mess with it and then write it -back as a block. But that is not necessarily more efficient, it may even be -slower than using the iterators; it may just be more convenient. See the API -documentation. -</para><para> -&krita; images are composed of layers, of which there are currently four -kinds: paint layers, group layers, adjustment layers (that contain a filter -that is applied dynamically to layers below the adjustment layer) and part -layers. Filters always operate on paint layers. Paint layers contain paint -devices, of the class <classname>KisPaintDevice</classname>. A paint device in -its turn gives access to the actual pixels. -</para><para> -<classname>PaintDevice</classname>s are generally passed around wrapped in -shared pointers. A shared pointer keeps track of in how many places the paint -device is currently used and deletes the paint device when it is no longer -used anywhere. You recognize the shared pointer version of a paint device -through its <literal>SP</literal> suffix. Just remember that you never have to -explicitly delete a <classname>KisPaintDeviceSP</classname>. -</para><para> -Let's examine a very simple filter, one that inverts every pixel. The code for -this filter is in the <filename -class="directory">koffice/krita/plugins/filters/example</filename> directory. -The main method is -<programlisting> -KisFilterInvert::process(KisPaintDeviceSP src, KisPaintDeviceSP dst, - KisFilterConfiguration* /*config*/, const QRect& rect). -</programlisting> -The function gets passed two paint devices, a configuration object (which is -not used in this simple filter) and a <varname>rect</varname>. The -<varname>rect</varname> describes the area of the -paint device which the filter should act on. This area is described by -integers, which means no sub-pixel accuracy. -</para><para> -The <varname>src</varname> paint device is for reading from, the -<varname>dst</varname> paint device for writing to. These parameters may point -to the same actual paint device, or be two different paint devices. (Note: -this may change to only one paint device in the future.) -</para><para> -Now, let's look at the code line by line: -</para> -<programlisting> -void KisFilterInvert::process(KisPaintDeviceSP src, KisPaintDeviceSP dst, - KisFilterConfiguration* /*config*/, const QRect& rect) -{ - Q_ASSERT(src != 0); - Q_ASSERT(dst != 0); - - KisRectIteratorPixel srcIt = src->createRectIterator(rect.x(), rect.y(), rect.width(), rect.height(), false); <co id="invert1" /> - KisRectIteratorPixel dstIt = dst->createRectIterator(rect.x(), rect.y(), rect.width(), rect.height(), true ); <co id="invert2" /> - - int pixelsProcessed = 0; - setProgressTotalSteps(rect.width() * rect.height()); - - KisColorSpace * cs = src->colorSpace(); - Q_INT32 psize = cs->pixelSize(); - - while( ! srcIt.isDone() ) - { - if(srcIt.isSelected()) <co id="invert3" /> - { - memcpy(dstIt.rawData(), srcIt.oldRawData(), psize); <co id="invert4" /> - - cs->invertColor( dstIt.rawData(), 1); <co id="invert5" /> - } - setProgress(++pixelsProcessed); - ++srcIt; - ++dstIt; - } - setProgressDone(); // Must be called even if you don't really support progression -} -</programlisting> - -<calloutlist> -<callout arearefs="invert1"> -<para>This creates an iterator to read the existing pixels. Krita has three -types of iterators: horizontal, vertical and rectangular. The rect iterator -takes the most efficient path through the image data, but does not guarantee -anything about the location of the next pixel it returns. That means that you -cannot be sure that the pixel you will retrieve next will be adjacent to the -pixel you just got. The horizontal and vertical line iterators do guarantee -the location of the pixels they return. -</para></callout> -<callout arearefs="invert2"><para> -(2) We create the destination iterator with the <literal>write</literal> -setting to <literal>true</literal>. This means that if the destination paint -device is smaller than the rect we write, it will automatically be enlarged to -fit every pixel we iterate over. Note that we have got a potential bug here: -if <varname>dst</varname> and <varname>src</varname> are not the same device, -then it is quite possible that the pixels returned by the iterators do not -correspond. For every position in the iterator, <varname>src</varname> may be, -for example, at 165,200, while <varname>dst</varname> could be at 20,8 — -and therefore the copy we perform below may distort the image... -</para></callout> -<callout arearefs="invert3"><para> -Want to know if a pixel is selected? That is easy — use the -<methodname>isSelected</methodname> method. But selectedness is not a binary -property of a pixel, a pixel can be half selected, barely selected or almost -completely selected. That value you can also got from the iterator. Selections -are actually a mask paint device with a range between 0 and 255, where 0 is -completely unselected and 255 completely selected. The iterator has two -methods: <methodname>isSelected()</methodname> and -<methodname>selectedNess()</methodname>. The first returns true if a pixel is -selected to any extent (i.e., the mask value is greater than 1), the other -returns the maskvalue. -</para></callout> -<callout arearefs="invert4"><para> -As noted above, this <literal>memcpy</literal> is a big bad bug... -<methodname>rawData()</methodname> returns the array of bytes which is the -current state of the pixel; <methodname>oldRawData()</methodname> returns the -array of bytes as it was before we created the iterator. However, we may be -copying the wrong pixel here. In actual practice, that will not happen too -often, unless <varname>dst</varname> already exists and is not aligned with -<varname>src</varname>. -</para></callout> -<callout arearefs="invert5"><para> -But this is correct: instead of figuring out which byte represents which -channel, we use a function supplied by all colorspaces to invert the current -pixel. The colorspaces have a lot of pixel operations you can make use of. -</para></callout> -</calloutlist> - -<para> -This is not all there is to creating a filter. Filters have two other -important components: a configuration object and a configuration widget. The -two interact closely. The configuration widget creates a configuration object, -but can also be filled from a pre-existing configuration object. Configuration -objects can represtent themselves as XML and can be created from XML. That is -what makes adjustment layers possible. -</para> - -<sect3 id="developers-plugins-filters-iterators"> -<title>Iterators</title> - -<para> -There are three types of iterators: -</para> - -<itemizedlist> -<listitem><para>Horizontal lines</para></listitem> -<listitem><para>Vertical lines</para></listitem> -<listitem><para>Rectangular iterors</para></listitem> -</itemizedlist> - -<para> -The horizontal and vertical line iterators have a method to move the iterator -to the next row or column: <methodname>nextRow()</methodname> and -<methodname>nextCol()</methodname>. Using these is much faster than creating a -new iterator for every line or column. -</para><para> -Iterators are thread-safe in &krita;, so it is possible to divide the work -over multiple threads. However, future versions of &krita; will use the -<methodname>supportsThreading()</methodname> method to determine whether your -filter can be applied to chunks of the image (&ie;, all pixels modified -independently, instead of changed by some value determined from an examination -of all pixels in the image) and automatically thread the execution your -filter. -</para> -</sect3> - -<sect3 id="developers-plugins-filters-kisfilterconfiguration"> -<title><classname>KisFilterConfiguration</classname></title> - -<para> -<classname>KisFilterConfiguration</classname> is a structure that is used to -save filter settings to disk, for instance for adjustment layers. The -scripting plugin uses the property map that’s at the back of -<classname>KisFilterConfigaration</classname> to make it possible to script -filters. Filters can provide a custom widget that &krita; will show in the -filters gallery, the filter preview dialog or the tool option tab of the -paint-with-filters tool. -</para> -<para> -An example, taken from the oilpaint effect filter: -</para> -<programlisting> -class KisOilPaintFilterConfiguration : public KisFilterConfiguration -{ - -public: - - KisOilPaintFilterConfiguration(Q_UINT32 brushSize, Q_UINT32 smooth) - : KisFilterConfiguration( "oilpaint", 1 ) - { - setProperty("brushSize", brushSize); - setProperty("smooth", smooth); - }; -public: - - inline Q_UINT32 brushSize() { return getInt("brushSize"); }; - inline Q_UINT32 smooth() {return getInt("smooth"); }; - -}; -</programlisting> -</sect3> - -<sect3 id="developers-plugins-filters-kisfilterconfigurationwidget"> -<title><classname>KisFilterConfigurationWidget</classname></title> - -<para> -Most filters can be tweaked by the user. You can create a configuration widget -that Krita will use where-ever your filter is used. An example: -</para> - -<para> -<screenshot> -<screeninfo>The <guilabel>Oilpaint</guilabel> dialog</screeninfo> -<mediaobject> -<imageobject> -<imagedata fileref="dialogs-oilpaint.png" format="PNG" /> -</imageobject> -<textobject> -<phrase>The <guilabel>Oilpaint</guilabel> dialog</phrase> -</textobject> -<caption><para>The <guilabel>Oilpaint</guilabel> dialog</para></caption> -</mediaobject> -</screenshot> -</para> - -<para> -Note that only the left-hand side of this dialog is your responsibility: -&krita; takes care of the rest. There are three ways of going about creating -an option widget: -</para> - -<itemizedlist> -<listitem><para>Use &Qt; Designer to create a widget base, and subclass it for -your filter</para></listitem> -<listitem><para>Use one of the simple widgets that show a number of sliders -for lists of integers, doubles or bools. These are useful if, like the above -screenshot, your filter can be configured by a number of integers, doubles or -bools. See the API dox for <classname>KisMultiIntegerFilterWidget</classname>, -<classname>KisMultiDoubleFilterWidget</classname> and -<classname>KisMultiBoolFilterWidget</classname>.</para></listitem> -<listitem><para>Hand-code a widget. This is not recommended, and if you do so -and want your filter to become part of &krita;’s official release, then I’ll ask -you to replate your hand-coded widget with a &Qt; Designer -widget.</para></listitem> -</itemizedlist> - -<para> -The oilpaint filter uses the multi integer widget: -</para> - -<programlisting> -KisFilterConfigWidget * KisOilPaintFilter::createConfigurationWidget(QWidget* parent, KisPaintDeviceSP /*dev*/) -{ - vKisIntegerWidgetParam param; - param.push_back( KisIntegerWidgetParam( 1, 5, 1, i18n("Brush size"), "brushSize" ) ); - param.push_back( KisIntegerWidgetParam( 10, 255, 30, i18n("Smooth"), "smooth" ) ); - return new KisMultiIntegerFilterWidget(parent, id().id().ascii(), id().id().ascii(), param ); -} - -KisFilterConfiguration* KisOilPaintFilter::configuration(QWidget* nwidget) -{ - KisMultiIntegerFilterWidget* widget = (KisMultiIntegerFilterWidget*) nwidget; - if( widget == 0 ) - { - return new KisOilPaintFilterConfiguration( 1, 30); - } else { - return new KisOilPaintFilterConfiguration( widget->valueAt( 0 ), widget->valueAt( 1 ) ); - } -} - -std::list<KisFilterConfiguration*> KisOilPaintFilter::listOfExamplesConfiguration(KisPaintDeviceSP ) -{ - std::list<KisFilterConfiguration*> list; - list.insert(list.begin(), new KisOilPaintFilterConfiguration( 1, 30)); - return list; -} -</programlisting> - -<para> -You can see how it works: fill a vector with your integer parameters and -create the widget. The <methodname>configuration()</methodname> method -inspects the widget and creates the right filter configuration object, in this -case, of course, <classname>KisOilPaintFilterConfiguration</classname>. The -<methodname>listOfExamplesConfiguration</methodname> method (which should be -renamed to correct English...) returns a list with example configuration -objects for the filters gallery dialog. -</para> -</sect3> - -<sect3 id="developers-plugins-filters-conclusion"> -<title>Filters conclusion</title> - -<para> -There’s more to coding interesting filters, of course, but with this -explanation, the API documentation and access to our source code, you should -be able to get started. Don’t hesitate to contact the &krita; developers on -IRC or on the mailing list. -</para> -</sect3> - -</sect2> - -<sect2 id="developers-plugins-tools"> -<title>Tools</title> - -<para> -Tools appear in &krita;’s toolbox. This means that there is limited space for -new tools — think carefully whether a paint operation isn’t enough for -your purposes. Tools can use the mouse/tablet and keyboard in complex ways, -which paint operations cannot. This is the reason that Duplicate is a tool, -but airbrush a paint operation. -</para><para> -Be careful with static data in your tool: a new instance of your tool is -created for every input device: mouse, stylus, eraser, airbrush — whatever. -Tools come divided into logical groups: -</para> -<itemizedlist> -<listitem><para>shape drawing tools (circle, rect)</para></listitem> -<listitem><para>freehand drawing tools (brush)</para></listitem> -<listitem><para>transform tools that mess up the geometry of a -layer</para></listitem> -<listitem><para>fill tools (like bucket fill or gradient)</para></listitem> -<listitem><para>view tools (that don’t change pixels, but alter the way you -view the canvas, such as zoom)</para></listitem> -<listitem><para>select tools (that change the selection -mask)</para></listitem> -</itemizedlist> - -<para> -The tool interface is described in the API documentation for -<classname>KisTool</classname>. There are three subclasses: -<classname>KisToolPaint</classname>, <classname>KisToolNonPaint</classname> -and <classname>KisToolShape</classname> (which is actually a subclass of -<classname>KisToolPaint</classname>) that specialize -<classname>KisTool</classname> for painting tasks (i.e., changing pixels) , -non-painting tasks and shape painting tasks. -</para><para> -A tool has an option widget, just like filters. Currently, the option widgets -are shown in a tab in a dock window. We may change that to a strip under the -main menu (which then replaces the toolbar) for &krita; 2.0, but for now, -design your option widget to fit in a tab. As always, it’s best to use &Qt; -Designer for the design of the option widget. -</para><para> -A good example of a tool is the star tool: -</para> - -<screen> -kis_tool_star.cc Makefile.am tool_star_cursor.png wdg_tool_star.ui -kis_tool_star.h Makefile.in tool_star.h -kritatoolstar.desktop tool_star.cc tool_star.png -</screen> - -<para> -As you see, you need two images: one for the cursor and one for the toolbox. -<filename>tool_star.cc</filename> is just the plugin loader, similar to what -we have seen above. The real meat is in the implementation: -</para> - -<programlisting> -KisToolStar::KisToolStar() - : KisToolShape(i18n("Star")), - m_dragging (false), - m_currentImage (0) -{ - setName("tool_star"); - setCursor(KisCursor::load("tool_star_cursor.png", 6, 6)); - m_innerOuterRatio=40; - m_vertices=5; -} -</programlisting> - -<para> -The constructor sets the internal name — which is not translated -— and the call to the superclass sets the visible name. We also load the -cursor image and set a number of variables. -</para> - -<programlisting> -void KisToolStar::update (KisCanvasSubject *subject) -{ - KisToolShape::update (subject); - if (m_subject) - m_currentImage = m_subject->currentImg(); -} -</programlisting> - -<para> -The <methodname>update()</methodname> method is called when the tool is -selected. This is not a <classname>KisTool</classname> method, but a -<classname>KisCanvasObserver</classname> method. Canvas observers are notified -whenever something changes in the view, which can be useful for tools. -</para><para> -The following methods (<methodname>buttonPress</methodname>, -<methodname>move</methodname> and <methodname>buttonRelease</methodname>) are -called by &krita; when the input device (mouse, stylus, eraser etc.) is -pressed down, moved or released. Note that you also get move events if the -mouse button isn’t pressed. The events are not the regular &Qt; events, but -synthetic &krita; events because we make use of low-level trickery to get -enough events to draw a smooth line. By default, toolkits like &Qt; (and GTK) -drop events if they are too busy to handle them, and we want them all. -</para> - -<programlisting> -void KisToolStar::buttonPress(KisButtonPressEvent *event) -{ - if (m_currentImage && event->button() == LeftButton) { - m_dragging = true; - m_dragStart = event->pos(); - m_dragEnd = event->pos(); - m_vertices = m_optWidget->verticesSpinBox->value(); - m_innerOuterRatio = m_optWidget->ratioSpinBox->value(); - } -} - -void KisToolStar::move(KisMoveEvent *event) -{ - if (m_dragging) { - // erase old lines on canvas - draw(m_dragStart, m_dragEnd); - // move (alt) or resize star - if (event->state() & Qt::AltButton) { - KisPoint trans = event->pos() - m_dragEnd; - m_dragStart += trans; - m_dragEnd += trans; - } else { - m_dragEnd = event->pos(); - } - // draw new lines on canvas - draw(m_dragStart, m_dragEnd); - } -} - -void KisToolStar::buttonRelease(KisButtonReleaseEvent *event) -{ - if (!m_subject || !m_currentImage) - return; - - if (m_dragging && event->button() == LeftButton) { - // erase old lines on canvas - draw(m_dragStart, m_dragEnd); - m_dragging = false; - - if (m_dragStart == m_dragEnd) - return; - - if (!m_currentImage) - return; - - if (!m_currentImage->activeDevice()) - return; - - KisPaintDeviceSP device = m_currentImage->activeDevice ();; - KisPainter painter (device); - if (m_currentImage->undo()) painter.beginTransaction (i18n("Star")); - - painter.setPaintColor(m_subject->fgColor()); - painter.setBackgroundColor(m_subject->bgColor()); - painter.setFillStyle(fillStyle()); - painter.setBrush(m_subject->currentBrush()); - painter.setPattern(m_subject->currentPattern()); - painter.setOpacity(m_opacity); - painter.setCompositeOp(m_compositeOp); - KisPaintOp * op = - KisPaintOpRegistry::instance()->paintOp(m_subject->currentPaintop(), m_subject->currentPaintopSettings(), &painter); - painter.setPaintOp(op); // Painter takes ownership - - vKisPoint coord = starCoordinates(m_vertices, m_dragStart.x(), m_dragStart.y(), m_dragEnd.x(), m_dragEnd.y()); - - painter.paintPolygon(coord); - - device->setDirty( painter.dirtyRect() ); - notifyModified(); - - if (m_currentImage->undo()) { - m_currentImage->undoAdapter()->addCommand(painter.endTransaction()); - } - } -} -</programlisting> - -<para> -The <methodname>draw()</methodname> method is an internal method of -<classname>KisToolStar</classname> and draws the outline of the star. We call -this from the <methodname>move()</methodname> method to give the user feedback -of the size and shape of their star. Note that we use the -<varname>Qt::NotROP</varname> raster operation, which means that calling -<methodname>draw()</methodname> a second time with the same start and end -point the previously drawn star will be deleted. -</para> - -<programlisting> -void KisToolStar::draw(const KisPoint& start, const KisPoint& end ) -{ - if (!m_subject || !m_currentImage) - return; - - KisCanvasController *controller = m_subject->canvasController(); - KisCanvas *canvas = controller->kiscanvas(); - KisCanvasPainter p (canvas); - QPen pen(Qt::SolidLine); - - KisPoint startPos; - KisPoint endPos; - startPos = controller->windowToView(start); - endPos = controller->windowToView(end); - - p.setRasterOp(Qt::NotROP); - - vKisPoint points = starCoordinates(m_vertices, startPos.x(), startPos.y(), endPos.x(), endPos.y()); - - for (uint i = 0; i < points.count() - 1; i++) { - p.drawLine(points[i].floorQPoint(), points[i + 1].floorQPoint()); - } - p.drawLine(points[points.count() - 1].floorQPoint(), points[0].floorQPoint()); - - p.end (); -} -</programlisting> - -<para> -The <methodname>setup()</methodname> method is essential: here we create the -action that will be plugged into the toolbox so users can actually select the -tool. We also assign a shortcut key. Note that there’s some hackery going on: -remember that we create an instance of the tool for every input device. This -also means that we call <methodname>setup()</methodname> for every input -device and that means that an action with the same name is added several times -to the action collection. However, everything seems to work, so why worry? -</para> - -<programlisting> -void KisToolStar::setup(KActionCollection *collection) -{ - m_action = static_cast<KRadioAction *>(collection->action(name())); - - if (m_action == 0) { - KShortcut shortcut(Qt::Key_Plus); - shortcut.append(KShortcut(Qt::Key_F9)); - m_action = new KRadioAction(i18n("&Star"), - "tool_star", - shortcut, - this, - SLOT(activate()), - collection, - name()); - Q_CHECK_PTR(m_action); - - m_action->setToolTip(i18n("Draw a star")); - m_action->setExclusiveGroup("tools"); - m_ownAction = true; - } -} -</programlisting> - -<para> -The <methodname>starCoordinates()</methodname> method contains some funky math -— but is not too interesting for the discussion of how to create a tool -plugins. -</para> - -<programlisting> -KisPoint KisToolStar::starCoordinates(int N, double mx, double my, double x, double y) -{ - double R=0, r=0; - Q_INT32 n=0; - double angle; - - vKisPoint starCoordinatesArray(2*N); - - // the radius of the outer edges - R=sqrt((x-mx)*(x-mx)+(y-my)*(y-my)); - - // the radius of the inner edges - r=R*m_innerOuterRatio/100.0; - - // the angle - angle=-atan2((x-mx),(y-my)); - - //set outer edges - for(n=0;n<N;n++){ - starCoordinatesArray[2*n] = KisPoint(mx+R*cos(n * 2.0 * M_PI / N + angle),my+R*sin(n *2.0 * M_PI / N+angle)); - } - - //set inner edges - for(n=0;n<N;n++){ - starCoordinatesArray[2*n+1] = KisPoint(mx+r*cos((n + 0.5) * 2.0 * M_PI / N + angle),my+r*sin((n +0.5) * 2.0 * M_PI / N + angle)); - } - - return starCoordinatesArray; -} -</programlisting> - -<para> -The <methodname>createOptionWidget()</methodname> method is called to create -the option widget that &krita; will show in the tab. Since there is a tool per -input device per view, the state of a tool can be kept in the tool. This -method is only called once: the option widget is stored and retrieved the next -time the tool is activated. -</para> - -<programlisting> -QWidget* KisToolStar::createOptionWidget(QWidget* parent) -{ - QWidget *widget = KisToolShape::createOptionWidget(parent); - - m_optWidget = new WdgToolStar(widget); - Q_CHECK_PTR(m_optWidget); - - m_optWidget->ratioSpinBox->setValue(m_innerOuterRatio); - - QGridLayout *optionLayout = new QGridLayout(widget, 1, 1); - super::addOptionWidgetLayout(optionLayout); - - optionLayout->addWidget(m_optWidget, 0, 0); - - return widget; -} -</programlisting> - -<sect3 id="developers-plugins-tools-conclusions"> -<title>Tool Conclusions</title> - -<para> -Tools are relatively simple plugins to create. You need to combine the -<classname>KisTool</classname> and <classname>KisCanvasObserver</classname> -interfaces in order to effectively create a tool. -</para> - -</sect3> - -</sect2> - -<sect2 id="developers-plugins-paintoperations"> -<title>Paint operations</title> - -<para> -PaintOps are one of the more innovative types of plugins in Krita (together -with pluggable colorspaces). A paint operation defines how tools change the -pixels they touch. Airbrush, aliased pencil or antialiased pixel brush: these -are all paint operations. But you could — with a lot of work — -create a paintop that reads Corel Painter XML brush definitions and uses those -to determine how painting is done. -</para><para> -Paint operations are instantiated when a paint tool receives a -<literal>mouseDown</literal> event and are deleted when the mouseUp event is -received by a paint tool. In between, the paintop can keep track of previous -positions and other data, such as pressure levels if the user uses a tablet. -</para><para> -The basic operation of a paint operation is to change pixels at the cursor -position of a paint tool. That can be done only once, or the paint op can -demand to be run at regular intervals, using a timer. The first would be -useful for a pencil-type paint op, the second, of course, for an -airbrush-type paintop. -</para><para> -Paintops can have a small configuration widget which is placed in a toolbar. -Thus, paintop configuration widgets need to have a horizontal layout of -widgets that are not higher than a toolbar button. Otherwise, &krita; will -look very funny. -</para><para> -Let’s look at a simple paintop plugin, one that shows a little bit of -programmatic intelligence. First, in the header file, there’s a factory -defined. This factory creates a paintop when the active tool needs one: -</para> - -<programlisting> -public: - KisSmearyOpFactory() {} - virtual ~KisSmearyOpFactory() {} - - virtual KisPaintOp * createOp(const KisPaintOpSettings *settings, KisPainter * painter); - virtual KisID id() { return KisID("paintSmeary", i18n("Smeary Brush")); } - virtual bool userVisible(KisColorSpace * ) { return false; } - virtual QString pixmap() { return ""; } - -}; -</programlisting> - -<para> -The factory also contains the <classname>KisID</classname> with the public and -private name for the paintop — make sure your paintop’s private name -does not clash with another paintop! — and may optionally return a -pixmap. &krita; can then show the pixmap together with the name for visual -identifcation of your paintop. For instance, a painter’s knife paintop would -have the image of such an implement. -</para><para> -The implementation of a paintop is very straightforward: -</para> - -<programlisting> -KisSmearyOp::KisSmearyOp(KisPainter * painter) - : KisPaintOp(painter) -{ -} - -KisSmearyOp::~KisSmearyOp() -{ -} -void KisSmearyOp::paintAt(const KisPoint &pos, const KisPaintInformation& info) -{ -</programlisting> - -<para> -The <methodname>paintAt()</methodname> method really is where it’s at, with -paintops. This method receives two parameters: the current position (which is -in floats, not in whole pixels) and a -<classname>KisPaintInformation</classname> object. which contains the -pressure, x and y tilt, and movement vector, and may in the future be extended -with other information. -</para> - -<programlisting> - if (!m_painter->device()) return; - - KisBrush *brush = m_painter->brush(); -</programlisting> - -<para> -A <classname>KisBrush</classname> is the representation of a Gimp brush file: -that is a mask, either a single mask or a series of masks. Actually, we don’t -use the brush here, except to determine the <quote>hotspot</quote> under the -cursor. -</para> - -<programlisting> - Q_ASSERT(brush); - - if (!brush) return; - - if (! brush->canPaintFor(info) ) - return; - - KisPaintDeviceSP device = m_painter->device(); - KisColorSpace * colorSpace = device->colorSpace(); - KisColor kc = m_painter->paintColor(); - kc.convertTo(colorSpace); - - KisPoint hotSpot = brush->hotSpot(info); - KisPoint pt = pos - hotSpot; - - // Split the coordinates into integer plus fractional parts. The integer - // is where the dab will be positioned and the fractional part determines - // the sub-pixel positioning. - Q_INT32 x, y; - double xFraction, yFraction; - - splitCoordinate(pt.x(), &x, &xFraction); - splitCoordinate(pt.y(), &y, &yFraction); - - KisPaintDeviceSP dab = new KisPaintDevice(colorSpace, "smeary dab"); - Q_CHECK_PTR(dab); -</programlisting> - -<para> -We don’t change the pixels of a paint device directly: instead we create a -small paint device, a dab, and composite that onto the current paint device. -</para> - -<programlisting> - m_painter->setPressure(info.pressure); -</programlisting> - -<para> -As the comments say, the next bit code does some programmatic work to create -the actual dab. In this case, we draw a number of lines. When I am done with -this paintop, the length, position and thickness of the lines will be -dependent on pressure and paint load, and we’ll have create a stiff, smeary -oilpaint brush. But I haven’t had time to finish this yet. -</para> - -<programlisting> - // Compute the position of the tufts. The tufts are arranged in a line - // perpendicular to the motion of the brush, i.e, the straight line between - // the current position and the previous position. - // The tufts are spread out through the pressure - - KisPoint previousPoint = info.movement.toKisPoint(); - KisVector2D brushVector(-previousPoint.y(), previousPoint.x()); - KisVector2D currentPointVector = KisVector2D(pos); - brushVector.normalize(); - - KisVector2D vl, vr; - - for (int i = 0; i < (NUMBER_OF_TUFTS / 2); ++i) { - // Compute the positions on the new vector. - vl = currentPointVector + i * brushVector; - KisPoint pl = vl.toKisPoint(); - dab->setPixel(pl.roundX(), pl.roundY(), kc); - - vr = currentPointVector - i * brushVector; - KisPoint pr = vr.toKisPoint(); - dab->setPixel(pr.roundX(), pr.roundY(), kc); - } - - vr = vr - vl; - vr.normalize(); -</programlisting> - -<para> -Finally we blt the dab onto the original paint device and tell the painter -that we’ve dirtied a small rectangle of the paint device. -</para> - -<programlisting> - if (m_source->hasSelection()) { - m_painter->bltSelection(x - 32, y - 32, m_painter->compositeOp(), dab.data(), - m_source->selection(), m_painter->opacity(), x - 32, y -32, 64, 64); - } - else { - m_painter->bitBlt(x - 32, y - 32, m_painter->compositeOp(), dab.data(), m_painter->opacity(), x - 32, y -32, 64, 64); - } - - m_painter->addDirtyRect(QRect(x -32, y -32, 64, 64)); -} - - -KisPaintOp * KisSmearyOpFactory::createOp(const KisPaintOpSettings */*settings*/, KisPainter * painter) -{ - KisPaintOp * op = new KisSmearyOp(painter); - Q_CHECK_PTR(op); - return op; -} -</programlisting> - -<para> -That’s all: paintops are easy and fun! -</para> - -</sect2> - -<sect2 id="developers-plugins-viewplugins"> -<title>View plugins</title> - -<para> -View plugins are the weirdest of the bunch: a view plugin is an ordinary KPart -that can provide a bit of user interface and some functionality. For instance, -the histogram tab is a view plugin, as is the rotate dialog. -</para> - -</sect2> - -<sect2 id="developers-plugins-importexport"> -<title>Import/Export filters</title> - -<para> -&krita; works with the ordinary &koffice; file filter architecture. There is a -tutorial, a bit old, but still useful, at: <ulink -url="http://koffice.org/developer/filters/oldfaq.php" />. It is probably best -to cooperate with the &krita; team when developing file filters and do the -development in the &koffice; filter tree. Note that you can test your filters -without running &krita; using the <command>koconverter</command> utility. -</para><para> -Filters have two sides: importing and exporting. These are usually two -different plugins that may share some code. -</para><para> -The important <filename>Makefile.am</filename> entries are: -</para> - -<programlisting> -service_DATA = krita_XXX_import.desktop krita_XXX_export.desktop -servicedir = $(kde_servicesdir) -kdelnk_DATA = krita_XXX.desktop -kdelnkdir = $(kde_appsdir)/Office -libkritaXXXimport_la_SOURCES = XXXimport.cpp -libkritaXXXexport_la_SOURCES = XXXexport.cpp -METASOURCES = AUTO -</programlisting> - -<para> -Whether you are building an import filter or an export filter, your work always -boils down to implementing the following function: -</para> - -<programlisting> -virtual KoFilter::ConversionStatus convert(const QCString& from, const QCString& to); -</programlisting> - -<para> -It is the settings in the <literal role="extension">.desktop</literal> files -that determine which way a filter converts: -</para><para> -Import: -</para> - -<programlisting> -X-KDE-Export=application/x-krita -X-KDE-Import=image/x-xcf-gimp -X-KDE-Weight=1 -X-KDE-Library=libkritaXXXimport -ServiceTypes=KOfficeFilter -</programlisting> - -<para> -Export: -</para> - -<programlisting> -X-KDE-Export=image/x-xcf-gimp -X-KDE-Import=application/x-krita -ServiceTypes=KOfficeFilter -Type=Service -X-KDE-Weight=1 -X-KDE-Library=libkritaXXXexport -</programlisting> - -<para> -And yes, the mimetype chosen for the example is a hint. Please, pretty please, -implement an xcf filter? -</para> - -<sect3 id="plugins-developers-importexport-import"> -<title>Import</title> - -<para> -The big problem with import filters is of course your code to read the data on -disk. The boilerplate for calling that code is fairly simple: -</para> - -<note><para>Note: we really, really should find a way to enable &krita; to keep -a file open and only read data on a as-needed basis, instead of copying the -entire contents to the internal paint device representation. But that would -mean datamanager backends that know about tiff files and so on, and is not -currently implemented. It would be ideal if some file filters could implement -a class provisionally named <classname>KisFileDataManager</classname>, create -an object of that instance with the current file and pass that to KisDoc. But -&krita; handles storage per layer, not per document, so this would be a hard -refactor to do.</para></note> - -<programlisting> -KoFilter::ConversionStatus XXXImport::convert(const QCString&, const QCString& to) -{ - if (to != "application/x-krita") <co id="import1" /> - return KoFilter::BadMimeType; - - KisDoc * doc = dynamic_cast<KisDoc*>(m_chain -> outputDocument()); <co id="import2" /> - KisView * view = static_cast<KisView*>(doc -> views().getFirst()); <co id="import3" /> - - QString filename = m_chain -> inputFile(); <co id="import4" /> - - if (!doc) - return KoFilter::CreationError; - - doc -> prepareForImport(); <co id="import5" /> - - if (!filename.isEmpty()) { - - KURL url(filename); - - if (url.isEmpty()) - return KoFilter::FileNotFound; - - KisImageXXXConverter ib(doc, doc -> undoAdapter()); <co id="import6" /> - - if (view != 0) - view -> canvasSubject() -> progressDisplay() -> setSubject(&ib, false, true); - - switch (ib.buildImage(url)) <co id="import7" /> { - case KisImageBuilder_RESULT_UNSUPPORTED: - return KoFilter::NotImplemented; - break; - case KisImageBuilder_RESULT_INVALID_ARG: - return KoFilter::BadMimeType; - break; - case KisImageBuilder_RESULT_NO_URI: - case KisImageBuilder_RESULT_NOT_LOCAL: - return KoFilter::FileNotFound; - break; - case KisImageBuilder_RESULT_BAD_FETCH: - case KisImageBuilder_RESULT_EMPTY: - return KoFilter::ParsingError; - break; - case KisImageBuilder_RESULT_FAILURE: - return KoFilter::InternalError; - break; - case KisImageBuilder_RESULT_OK: - doc -> setCurrentImage( ib.image()); <co id="import8" /> - return KoFilter::OK; - default: - break; - } - - } - return KoFilter::StorageCreationError; -} -</programlisting> - -<calloutlist> -<callout arearefs="import1"><para>This is supposed to be an importfilter, so -if it is not called to convert to a &krita; image, then something is -wrong.</para></callout> -<callout arearefs="import2"><para>The filter chain already has created an -output document for us. We need to cast it to <classname>KisDocM</classname>, -because &krita; documents need special treatment. It would not, actually, be -all that bad an idea to check whether the result of the cast is not 0, because -if it is, importing will fail.</para></callout> -<callout arearefs="import3"><para>If we call this filter from the GUI, we try -to get the view. If there is a view, the conversion code can try to update the -progressbar.</para></callout> -<callout arearefs="import4"><para>The filter has the filename for our input -file for us.</para></callout> -<callout arearefs="import5"><para><classname>KisDoc</classname> needs to be -prepared for import. Certain settings are initialized and undo is disabled. -Otherwise you could undo the adding of layers performed by the import filter -and that is weird behaviour.</para></callout> -<callout arearefs="import6"><para>I have chosed to implement the actual -importing code in a separate class that I instantiate here. You can also put -all your code right in this method, but that would be a bit -messy.</para></callout> -<callout arearefs="import7"><para>My importer returns a statuscode that I -can then use to set the status of the import filter. &koffice; takes care of -showing error messages.</para></callout> -<callout arearefs="import8"><para>If creating the -<classname>KisImage</classname> has succeeded we set the document's current -image to our newly created image. Then we are done: <literal>return -KoFilter::OK;</literal>.</para></callout> -</calloutlist> - -</sect3> - -</sect2> - -</sect1> |