summaryrefslogtreecommitdiffstats
path: root/doc/krita/developers-plugins.docbook
diff options
context:
space:
mode:
Diffstat (limited to 'doc/krita/developers-plugins.docbook')
-rw-r--r--doc/krita/developers-plugins.docbook1553
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 &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 Krita’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>
-&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 &mdash; 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 &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( kritatoolstar, ToolStarFactory( "krita" ) )
-
-
-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","kritaplugins/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>
-&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 &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 &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&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. 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 &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 &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&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 &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 &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 &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 &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 &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 &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 &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 &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, &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 &mdash; make sure your paintop’s private name
-does not clash with another paintop! &mdash; 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 &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>
-&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&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-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&amp;, const QCString&amp; to)
-{
- if (to != "application/x-krita") <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 &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>