summaryrefslogtreecommitdiffstats
path: root/doc/chalk/developers-plugins.docbook
diff options
context:
space:
mode:
Diffstat (limited to 'doc/chalk/developers-plugins.docbook')
-rw-r--r--doc/chalk/developers-plugins.docbook1553
1 files changed, 1553 insertions, 0 deletions
diff --git a/doc/chalk/developers-plugins.docbook b/doc/chalk/developers-plugins.docbook
new file mode 100644
index 00000000..81a4ef49
--- /dev/null
+++ b/doc/chalk/developers-plugins.docbook
@@ -0,0 +1,1553 @@
+<sect1 id="developers-plugins">
+<title>Developing &chalk; Plugins</title>
+
+<sect2 id="developers-plugins-introduction">
+<title>Introduction</title>
+
+<para>
+&chalk; is infinitely extensible with plugins. Tools, filters, large
+chunks of the user interface and even colorspaces are plugins. In fact,
+&chalk; recognizes these six types of plugins:
+</para>
+
+<itemizedlist>
+<listitem><para>colorspaces &mdash; these define the channels that constitute
+a single pixel</para></listitem>
+<listitem><para>tools &mdash; anything that is done with a mouse or tablet
+input device</para></listitem>
+<listitem><para>paint operations &mdash; pluggable painting effects for
+tools</para></listitem>
+<listitem><para>image filters &mdash; change all pixels, or just the selected
+pixels in a layer</para></listitem>
+<listitem><para>viewplugins &mdash; extend Chalk’s user interface with new
+dialog boxes, palettes and operations</para></listitem>
+<listitem><para>import/export filters &mdash; read and write all kinds of
+image formats</para></listitem>
+</itemizedlist>
+
+<para>
+&chalk; itself consists of three layered libraries and a directory with some
+common support classes: chalkcolor, chalkimage and chalkui. Within
+&chalk;, 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: &chalk; is still in development. From &chalk; 1.5 to
+1.6 not many API changes are expected, but there may be some. From &chalk; 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 &chalk; and choose to do so in
+&chalk;’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-chalkcolor">
+<title>ChalkColor</title>
+
+<para>
+The first library is chalkcolor. 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 chalkcolor
+library could be used from other applications and does not depend on
+&koffice;.
+</para>
+</sect3>
+
+<sect3 id="developers-plugins-introduction-chalkimage">
+<title>ChalkImage</title>
+
+<para>
+The libchalkimage 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
+libchalkimage. It is our stated goal to make libchalkimage 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 &chalk;. (Adding new brushes, palettes,
+gradients and patterns is easy, of course.) &chalk; follows the guidelines of
+the Create project (<ulink url="http://create.freedesktop.org/" />) for these.
+Adding support for Photoshop's brush file format needs libchalkimage hacking;
+adding more gimp brush data files not.
+</para><para>
+<classname>ChalkImage</classname> loads the following types of plugins:
+</para>
+
+<itemizedlist>
+<listitem><para>&chalk; 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-chalkui">
+<title>ChalkUI</title>
+
+<para>
+The libchalkui 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 chalkpart
+and chalkui 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>ChalkUI</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 &chalk;'s user
+interface. Menu options, dialogs, toolbars &mdash; any kind of user interface
+extension can be a viewplugin. In fact, important functionality like &chalk;'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 &chalk;
+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 &chalk;
+developer API. Only viewplugins should use the &koffice; API. Don’t worry:
+&chalk;’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>
+&chalk; 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
+&chalk; itself, or might have split the header files into either a &koffice;
+dev or a &chalk; dev package. You can find the API documentation for &chalk;'s
+public API at <ulink url="http://koffice.org/developer/apidocs/chalk/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 chalk-plugins project from the &koffice; Subversion
+repository and use it as the basis for your own project. We intend to prepare
+a skeleton &chalk; 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 = chalkLIBRARYNAME.desktop
+
+INCLUDES = $(all_includes)
+
+chalkLIBRARYNAME_la_SOURCES = sourcefile1.cc sourcefile2.cc
+
+kde_module_LTLIBRARIES = chalkLIBRARYNAME.la
+noinst_HEADERS = header1.h header2.h
+
+chalkLIBRARYNAME_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN)
+chalkLIBRARY_la_LIBADD = -lchalkcommon
+
+chalkextensioncolorsfilters_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>
+chalkrcdir = $(kde_datadir)/chalk/chalkplugins
+chalkrc_DATA = LIBRARYNAME.rc
+EXTRA_DIST = $(chalkrc_DATA)
+
+chalkpics_DATA = \
+ bla.png \
+ bla_cursor.png
+chalkpicsdir = $(kde_datadir)/chalk/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=Chalk/Filter
+Type=Service
+X-KDE-Library=chalkLIBRARYNAME
+X-KDE-Version=2
+</programlisting>
+</para><para>
+Possible ServiceTypes are:
+</para>
+
+<itemizedlist>
+<listitem><para>Chalk/Filter</para></listitem>
+<listitem><para>Chalk/Paintop</para></listitem>
+<listitem><para>Chalk/ViewPlugin</para></listitem>
+<listitem><para>Chalk/Tool</para></listitem>
+<listitem><para>Chalk/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 &mdash; a header file and an implementation file.
+</para><para>
+A header file:
+<programlisting>
+#ifndef TOOL_STAR_H_
+#define TOOL_STAR_H_
+
+#include &lt;kparts/plugin.h&gt;
+
+/**
+* A module that provides a star tool.
+*/
+class ToolStar : public KParts::Plugin
+{
+ Q_OBJECT
+public:
+ ToolStar(QObject *parent, const char *name, const QStringList &amp;);
+ virtual ~ToolStar();
+
+};
+
+#endif // TOOL_STAR_H_
+</programlisting>
+</para>
+
+<para>
+And an implementation file:
+<programlisting>
+#include &lt;kinstance.h&gt;
+#include &lt;kgenericfactory.h&gt;
+
+#include &lt;kis_tool_registry.h&gt;
+
+#include "tool_star.h"
+#include "kis_tool_star.h"
+
+
+typedef KGenericFactory&lt;ToolStar&gt; ToolStarFactory;
+K_EXPORT_COMPONENT_FACTORY( chalktoolstar, ToolStarFactory( "chalk" ) )
+
+
+ToolStar::ToolStar(QObject *parent, const char *name, const QStringList &amp;)
+ : KParts::Plugin(parent, name)
+{
+ setInstance(ToolStarFactory::instance());
+ if ( parent->inherits("KisToolRegistry") )
+ {
+ KisToolRegistry * r = dynamic_cast&lt;KisToolRegistry*&gt;( 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&lt;KisFilterRegistry *&gt;(parent);
+ manager->add(new KisFilterInvert());
+ }
+</programlisting>
+</para><para>
+Paintops the paintop registry:
+<programlisting>
+ if ( parent->inherits("KisPaintOpRegistry") ) {
+ KisPaintOpRegistry * r = dynamic_cast&lt;KisPaintOpRegistry*&gt;(parent);
+ r -> add ( new KisSmearyOpFactory );
+ }
+</programlisting>
+</para><para>
+Colorspaces the colorspace registry (with some complications):
+<programlisting>
+ if ( parent->inherits("KisColorSpaceFactoryRegistry") ) {
+ KisColorSpaceFactoryRegistry * f = dynamic_cast&lt;isColorSpaceFactoryRegistry*&gt;(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&lt;KisBasicU8HistogramProducer&gt;
+ (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","chalkplugins/shearimage.rc"), true);
+
+ (void) new KAction(i18n("&amp;Shear Image..."), 0, 0, this, SLOT(slotShearImage()), actionCollection(), "shearimage");
+ (void) new KAction(i18n("&amp;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>
+&chalk; 1.5 loads plugins with <literal>X-KDE-Version=2</literal> set in the
+<literal role="extension">.desktop</literal> file. &chalk; 1.6 plugins will
+probably be binary incompatible with 1.5 plugins and will need the version
+number 3. &chalk; 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 &amp;
+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 &chalk; 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/chalk/chalkcolor/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 chalkcolor
+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 &chalk; 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: &chalk; 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>
+&chalk; 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>
+&chalk; 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/chalk/plugins/filters/example</filename> directory.
+The main method is
+<programlisting>
+KisFilterInvert::process(KisPaintDeviceSP src, KisPaintDeviceSP dst,
+ KisFilterConfiguration* /*config*/, const QRect&amp; 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&amp; 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. Chalk 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 &mdash;
+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 &mdash; 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 &chalk;, so it is possible to divide the work
+over multiple threads. However, future versions of &chalk; 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 &chalk; 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 Chalk 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:
+&chalk; 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 &chalk;’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&lt;KisFilterConfiguration*&gt; KisOilPaintFilter::listOfExamplesConfiguration(KisPaintDeviceSP )
+{
+ std::list&lt;KisFilterConfiguration*&gt; 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 &chalk; developers on
+IRC or on the mailing list.
+</para>
+</sect3>
+
+</sect2>
+
+<sect2 id="developers-plugins-tools">
+<title>Tools</title>
+
+<para>
+Tools appear in &chalk;’s toolbox. This means that there is limited space for
+new tools &mdash; 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 &mdash; 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 &chalk; 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
+chalktoolstar.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 &mdash; which is not translated
+&mdash; 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 &chalk; 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 &chalk; 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 &amp;&amp; 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() &amp; 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 &amp;&amp; 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(), &amp;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&amp; start, const KisPoint&amp; 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 &lt; 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&lt;KRadioAction *&gt;(collection->action(name()));
+
+ if (m_action == 0) {
+ KShortcut shortcut(Qt::Key_Plus);
+ shortcut.append(KShortcut(Qt::Key_F9));
+ m_action = new KRadioAction(i18n("&amp;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
+&mdash; 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&lt;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&lt;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 &chalk; 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 Chalk (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 &mdash; with a lot of work &mdash;
+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, &chalk; 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 &mdash; make sure your paintop’s private name
+does not clash with another paintop! &mdash; and may optionally return a
+pixmap. &chalk; 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 &amp;pos, const KisPaintInformation&amp; 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(), &amp;x, &amp;xFraction);
+ splitCoordinate(pt.y(), &amp;y, &amp;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 &lt; (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>
+&chalk; 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 &chalk; team when developing file filters and do the
+development in the &koffice; filter tree. Note that you can test your filters
+without running &chalk; 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 = chalk_XXX_import.desktop chalk_XXX_export.desktop
+servicedir = $(kde_servicesdir)
+kdelnk_DATA = chalk_XXX.desktop
+kdelnkdir = $(kde_appsdir)/Office
+libchalkXXXimport_la_SOURCES = XXXimport.cpp
+libchalkXXXexport_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&amp; from, const QCString&amp; 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-chalk
+X-KDE-Import=image/x-xcf-gimp
+X-KDE-Weight=1
+X-KDE-Library=libchalkXXXimport
+ServiceTypes=KOfficeFilter
+</programlisting>
+
+<para>
+Export:
+</para>
+
+<programlisting>
+X-KDE-Export=image/x-xcf-gimp
+X-KDE-Import=application/x-chalk
+ServiceTypes=KOfficeFilter
+Type=Service
+X-KDE-Weight=1
+X-KDE-Library=libchalkXXXexport
+</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 &chalk; 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
+&chalk; 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&amp;, const QCString&amp; to)
+{
+ if (to != "application/x-chalk") <co id="import1" />
+ return KoFilter::BadMimeType;
+
+ KisDoc * doc = dynamic_cast&lt;KisDoc*&gt;(m_chain -> outputDocument()); <co id="import2" />
+ KisView * view = static_cast&lt;KisView*&gt;(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(&amp;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 &chalk; 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 &chalk; 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>