# &chalk;-Module entwickeln

## Einleitung

&chalk; kann durch Module erweitert werden. Werkzeuge, Filter, große Teile der Bedienoberfläche und sogar Farbräume sind Module. &chalk; erkennt diese sechs Typen von Modulen:

- Farbräume — diese definieren die Kanäle, die ein einzelnes Pixel bilden

- Werkzeuge — alles, was mit der Maus oder einem Tablett gemacht wird

- Malverfahren — modulare Maleffekte für Werkzeuge

- Bildfilter — ändern alle oder die gewählten Pixel einer Ebene

- Ansichtenmodule — erweitern Chalks Benutzeroberfläche um neue Dialoge, Paletten oder Operationen.

- Import- und Exportfilter — lesen oder schreiben verschiedener Bildformate

&chalk; besteht aus drei Bibliotheksebenen und einem Verzeichnis mit einigen Klassen zur Unterstützung: chalkcolor, chalkimage und chalkui. In &chalk; können Objekte als `KisID`-Objekt dargestellt werden, was eine Kombination aus einer eindeutigen, nicht übersetzbaren Zeichenkette (wird beim Speichern verwendet) und einer übersetzbaren Zeichenkette für die Benutzeroberfläche ist.

Ein Wort zur Kompatibilität: &chalk; befindet sich noch in der Entwicklung. Sollten Sie ein Modul in &chalk;s Subversion-Repository entwickeln möchten, stehen die Chancen gut, dass wir Sie bei der Portierung unterstützten. Diese Änderungen gefährden auch die Aktualität dieses Dokuments. Lesen Sie daher immer die API-Dokumentation oder die Header-Dateien, die auf Ihrem System installiert wurden.

### ChalkColor

Die erste Bibliothek ist chalkcolor. Diese Bibliothek lädt die Farbraummodule.

Ein Farbraum-Modul sollte die abstrakte Klasse `KisColorSpace` implementieren oder, wenn die grundlegenden Fähigkeiten von `lcms` () implementiert werden, `KisAbstractColorSpace` erweitern. Die Bibliothek chalkcolor kann von anderen Anwendungen verwendet werden und hängt nicht von &koffice; ab.

### ChalkImage

Die Bibliothek libchalkimage lädt die Module Filter und Malverfahren und ist für die Arbeit mit Bilddaten verantwortlich: Pixel ändern, mischen und malen. libchalkimage lädt zudem Pinsel, Paletten und Muster.Wir haben uns das Ziel gesetzt, libchalkimage von &koffice; unabhängig zu machen, teilen aber derzeit den Code zum Laden eines Verlaufs mit &koffice;.

Im Moment ist es nicht einfach, neue Arten von Hilfsmitteln wie Pinsel, Paletten, Verläufe oder Muster zu &chalk; hinzuzufügen. (Das Hinzufügen von neuen Pinseln, Paletten, Verläufen und Mustern ist natürlich einfach.) &chalk; folgt in diesem Punkt den Richtlinien des Create-Projekts (). Um Unterstützung für das Dateiformat für Photoshop-Pinsel hinzuzufügen, bedarf es noch einiger Arbeit an libchalkimage, Gimp-Pinsel hinzuzufügen nicht.

`ChalkImage` lädt die folgenden Modultypen:

- Filter in &chalk; müssen die abstrakten Klassen `KisFilter`, `KisFilterConfiguration` und möglicherweise `KisFilterConfigurationWidget` erweitern und implementieren. Ein Filter ist z. B. "Unscharf maskieren".

- Malverfahren oder "paintops" sind eine Reihe von Verfahren, die mit Malwerkzeugen, wie z. B. Freihand oder Kreis verfügbar sind. Beispiele von paintops sind Stift, Airbrush und Radierer. Paintops sollten die Basisklasse `KisPaintop` erweitern. Beispiele für neue paintops könnten ein Kreidepinsel, ein Ölfarbenpinsel oder ein komplexer, programmierbarer Pinsel sein.

### ChalkUI

libchalkui lädt die Werkzeug- und Ansichtenmodule. Diese Bibliothek ist eine &koffice;-Part-Anwendung, enthält aber zusätzlich einige nützliche Elemente für Graphikanwendungen. Möglicherweise wird diese Bibliothek für die Version 2.0 in chalkpart und chalkui aufgeteilt. Derzeit haben Skriptautoren keinen Zugriff auf diese Bibliothek, und Modulautoren nur unter der Voraussetzung, dass sie Werkzeug- oder Ansichtenmodule schreiben. `ChalkUI` lädt die folgenden Modultypen:

- Werkzeuge werden abgeleitet von `KisTool` oder einer der spezialisierten Werzeug-Basis-Klassen, wie `KisToolPaint`, `KisToolNonPaint` oder `KisToolFreehand`. Ein neues Werkzeug könnte z. B. ein Auswahlwerkzeug für Vordergrundobjekte sein. Malwerkzeuge (das beinhaltet auch Werkzeuge zum Malen einer Auswahl) können jedes Malverfahren benutzen um zu bestimmen, wie die Pixel geändert werden.

- Ansichtenmodule sind normale KPart-Anwendungen, die `kxmlgui` verwenden, um sich selbst &chalk;s Benutzerschnittstelle zu unterstellen. Menüpunkte, Dialoge, Werkzeugleisten — jede Art von Erweiterung der Benutzeroberfläche kann ein Ansichtenmodul sein. Tatsächlich sind wichtige Teile von &chalk;, wie die Unterstützung für Skripte als Ansichtenmodul geschrieben.

### Import/Export-Filter

Import/Export-Filter sind &koffice;-Filter; Unterklassen von `KoFilter`. Filter lesen und schreiben Bildinformationen in jedes der vielen existierenden Bildformate. Ein Beispiel für einen neuen Import/Export-Filter für &chalk; könnte ein PDF-Filter sein. Filter werden von den &koffice;-Bibliotheken geladen.

## Module erstellen

Module werden in C++ geschrieben und können die APIs von &tde;, &TQt; und &chalk; verwenden. Nur Ansichtenmodule sollten die &koffice;-API verwenden. Die API von &chalk; ist ziemlich sauber und ausführlich dokumentiert (für Freie Software) und einen ersten Filter zu schreiben, ist einfach.

Wenn Sie nicht C++ benutzen möchten, können Sie Skripte in Python oder Ruby schreiben. Dies ist jedoch etwas ganz anderes, und Sie können derzeit keine Skripte für Werkzeuge, Farbräume, Malverfahren und Import/Export-Filter schreiben.

Module in &chalk; verwenden den Parts-Mechanismus von &kde;, daher ist die Dokumentation unter auch hier wichtig.

Ihre Distribution sollte entweder die benötigten Header-Dateien mit &chalk; mitinstallieren, oder sie in eigene &koffice;-dev- oder &chalk;-dev-Pakete ausgelagert haben. Sie finden die API-Dokumentation für &chalk; unter .

### Automake (und CMake)

&kde; 3.x und demnach auch &koffice; 1.5 und 1.6 verwenden `automake`; &kde; 4.0 und &koffice; 2.0 benutzen `cmake`. Diese Anleitung beschreibt die Erstellung von Modulen über `automake`.

Module sind &kde;-Module und sollten im `Makefile.am` entsprechend als solche markiert werden. Filter, Werkzeuge, Malverfahren, Farbräume und Import/Export-Filter benötigen `.desktop`-Dateien; Ansichtenmodule zusätzlich eine KXMLGui-`pluginname.rc`-Datei. Zu Beginn ist es einfacher, das chalk-plugin-Projekt aus dem Subversion-Repository von &koffice; auszuchecken und es als Basis für das eigene Projekt zu verwenden. Wir beabsichtigen, ein Vorlagen-Paket für &chalk;-Module in KDevelop zu integrieren, wozu jedoch bisher die Zeit fehlte.

#### `Makefile.am`

Werfen wir einen Blick auf den Rohbau eines Moduls. Zuerst die Datei `Makefile.am`. Diese verwendet &kde;, um das Makefile zu erstellen, das Ihr Modul baut:

    kde_services_DATA = chalkLIBRARYNAME.desktop

    INCLUDES = $(all_includes)

    chalkLIBRARYNAME_la_SOURCES = sourcefile1.cpp sourcefile2.cpp

    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

Dies ist das Makefile für ein Filtermodul. Ersetzen Sie `LIBRARYNAME` durch den Namen Ihrer Arbeit.

Ist Ihr Modul ein Ansichtenmodul, möchten Sie wahrscheinlich auch eine `.rc`-Datei mit den Einträgen für Menü- und Werkzeugleisten installieren. Ebenso Mauszeiger und Symbole. All das erledigen die normalen &kde;-`Makefile.am`-Zauberformeln:

    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

#### Desktop-Dateien

Die `.desktop`-Datei zeigt die Art des Moduls an:

    [Desktop Entry]
    Encoding=UTF-8
    Icon=
    Name=User-visible Name
    ServiceTypes=Chalk/Filter
    Type=Service
    X-TDE-Library=chalkLIBRARYNAME
    X-TDE-Version=2

Mögliche Service-Arten sind:

- Chalk/Filter

- Chalk/Paintop

- Chalk/ViewPlugin

- Chalk/Tool

- Chalk/ColorSpace

Import- und Exportfilter für Dateien verwenden das &koffice;-Filter-Framework und müssen gesondert behandelt werden.

#### Vorlage

Sie benötigen eine kleine Vorlage, die vom &kde;-Part-Framework aufgerufen wird, um das Modul zu instanziieren — eine Header-Datei und eine Anwendungsdatei.

Eine Headerdatei:

    #ifndef TOOL_STAR_H_
    #define TOOL_STAR_H_

    #include <tdeparts/plugin.h>

    /**
    * A module that provides a star tool.
    */
    class ToolStar : public KParts::Plugin
    {
       TQ_OBJECT
    public:
       ToolStar(TQObject *parent, const char *name, const QStringList &);
       virtual ~ToolStar();

    };

    #endif // TOOL_STAR_H_

Und eine Anwendungsdatei:

    #include <kinstance.h>
    #include <kgenericfactory.h>

    #include <kis_tool_registry.h>

    #include "tool_star.h"
    #include "kis_tool_star.h"

    typedef KGenericFactory<ToolStar> ToolStarFactory;
    K_EXPORT_COMPONENT_FACTORY( chalktoolstar, ToolStarFactory( "chalk" ) )

    ToolStar::ToolStar(TQObject *parent, const char *name, const QStringList &)
       : KParts::Plugin(parent, name)
    {
       setInstance(ToolStarFactory::instance());
       if ( parent->inherits("KisToolRegistry") )
       {
           KisToolRegistry * r = dynamic_cast<KisToolRegistry*>( parent );
           r -> add(new KisToolStarFactory());
       }

    }

    ToolStar::~ToolStar()
    {
    }

    #include "tool_star.moc"

#### Registrierungen

Werzeuge werden von der Werkzeugregistrierung geladen und registrieren sich selbst mit der Werkzeugregistrierung. Module wie Werkzeuge, Filter und Malverfahren werden nur einmal geladen: Ansichtenmodule werden für jede neu erstellte Ansicht erneut geladen. Beachten Sie, dass grob gesagt "Fabriken" registriert werden. Z. B. wird bei Werzeugen für jeden Zeiger (Maus, Stift, Radierer) eine neue Instanz des Werkzeuges erzeugt. Ein neues Malverfahren wird erzeugt, wenn ein Werkzeug ein "Mouse-Down"-Ereignis bekommt.

Filter rufen die Filterregistrierung auf:

    if (parent->inherits("KisFilterRegistry")) {
           KisFilterRegistry * manager = dynamic_cast<KisFilterRegistry *>(parent);
           manager->add(new KisFilterInvert());
       }

Paintops die paintop-Registrierung

    if ( parent->inherits("KisPaintOpRegistry") ) {
               KisPaintOpRegistry * r = dynamic_cast<KisPaintOpRegistry*>(parent);
               r -> add ( new KisSmearyOpFactory );
       }

Farbräume die Farbraum-Registrierung (mit einigen Komplikationen):

    if ( parent->inherits("KisColorSpaceFactoryRegistry") ) {
            KisColorSpaceFactoryRegistry * f = dynamic_cast<isColorSpaceFactoryRegistry*>(parent);

           KisProfile *defProfile = new KisProfile(cmsCreate_sRGBProfile());
           f->addProfile(defProfile);

           KisColorSpaceFactory * csFactory = new KisRgbColorSpaceFactory();
           f->add(csFactory);

           KisColorSpace * colorSpaceRGBA = new KisRgbColorSpace(f, 0);
           KisHistogramProducerFactoryRegistry::instance() -> add(
                   new KisBasicHistogramProducerFactory<KisBasicU8HistogramProducer>
                   (KisID("RGB8HISTO", i18n("RGB8 Histogram")), colorSpaceRGBA) );
       }

Ansichtenmodule müssen sich nicht registrieren, und sie erhalten Zugriff auf ein `KisView`-Objekt:

    if ( parent->inherits("KisView") )
       {
           setInstance(ShearImageFactory::instance());
           setXMLFile(locate("data","chalkplugins/shearimage.rc"), true);

           (void) new TDEAction(i18n("&Shear Image..."), 0, 0, this, TQ_SLOT(slotShearImage()), actionCollection(), "shearimage");
           (void) new TDEAction(i18n("&Shear Layer..."), 0, 0, this, TQ_SLOT(slotShearLayer()), actionCollection(), "shearlayer");

           m_view = (KisView*) parent;
       }

Merken Sie sich, dass für jede Ansicht, die der Benutzer öffnet, ein Ansichtenmodul erstellt wird: bei der Aufteilung der Ansicht werden alle Module erneut geladen.

#### Modulversionen

&chalk; 1.5 lädt Module mit der Einstellung `X-TDE-Version=2` in der `.desktop`-Datei. &chalk;-1.6-Module sind inkompatibel zu 1.5-Modulen und benötigen die Versionsnummer 3. &chalk;-2.0-Module werden die Versionnummer 3 benötigen.

## Farbräume

Farbräume implementieren die virtuelle Klasse `KisColorSpace`. Es gibt zwei Arten von Farbräumen: die einen können `lcms` zum konvertieren zwischen Farbräumen verwenden, die anderen sind zu bizarr, dass `lcms` sie beherrschen könnte. Beispiel der ersten Art sind cmyk, rgb, yuf. Ein Beispiel der zweiten Art ist Wasserfarben oder "Nass & Klebrig". Farbräume, die `lcms` verwenden, können von `KisAbstractColorSpace`, oder einer der Basisklassen abgeleitet werden, die auf eine bestimmte Anzahl Bits pro Kanal spezialisiert sind.

Einen Farbraum zu implementieren ist recht einfach. Das allgemeine Prinzip ist, dass Farbräume mit einem einfachen Array aus Bytes arbeiten. Die Interpretation dieser Bytes ist dem Farbraum überlassen. Z. B. besteht ein Pixel in 16-bit-GrayA aus vier Bytes: zwei Bytes für den Grauwert und zwei Bytes für den Alphawert. Sie können auch ein "struct" verwenden, um mit dem Speicherabbild eines Pixels zu arbeiten; diese Information wird aber nicht exportiert. Der einzige Weg, wie der Rest von &chalk; erfährt, aus welchen Kanäle und Arten von Kanälen die Pixel Ihres Farbraumes bestehen, ist über die Klasse `KisChannelInfo`.

Filter und Malverfahren verwenden die große Anzahl Methoden, die von `KisColorSpace` zur Verfügung gestellt werden. In den meisten Fällen reicht die Standardimplementierung aus `KisAbstractColorSpace`. Diese ist jedoch langsamer als eine eigene Implementierung, da `KisAbtractColorSpace` alle Pixel in 16-bit-L*a*b* unz zurück konvertiert.

### `KisChannelInfo`

    (http://websvn.kde.org/trunk/koffice/chalk/chalkcolor/kis_channelinfo.h)

Diese Klasse definiert die Kanäle, aus denen in einem bestimmten Farbraum ein Pixel besteht. Ein Kanal besitzt die folgenden, wichtigen Nenndaten:

- einen Namen, der in der Benutzeroberfläche angezeigt wird

- eine Position: das Byte in dem Pixel, an dem die Bytes beginnen, die den Kanal darstellen.

- einen Typ: Farbe, Alpha, Substanz oder Träger. Farbe ist reine Farbe, Alpha ist die Durchsichtigkeit, Substanz enthält Informationen wie die Menge der Pigmente, Träger enthält die Art der Zeichenfläche.

- einen Wertetyp: byte, short, integer, float — oder andere.

- Größe: die Anzahl der Bytes, die dieser Kanal benötigt

- Farbe: eine `TQColor`-Darstellung dieses Kanals für die grafische Benutzeroberfläche, z. B. für das Histogramm.

- eine Abkürzung zur Benutzung in der grafischen Benutzeroberfläche, falls nicht genügend Platz zur Verfügung steht

### `KisCompositeOp`

Es gibt mehrere Wege, Pixel zu einer neuen Farbe zu kombinieren. Die Klasse `KisCompositeOp` definiert die meisten davon: diese Liste ist nicht einfach erweiterbar, außer durch Veränderung der Bibliothek chalkcolor.

Ein Farbraummodul kann jede Untergruppe dieser möglichen Verfahren unterstützen, es muss aber immer "OVER" (das gleiche wie "NORMAL") und "COPY" enthalten. Alle anderen sind mehr oder weniger optional, wobei natürlich mehr besser ist.

### `KisColorSpace`

Die Methoden in der virtuellen Klasse `KisColorSpace` können in Gruppen unterteilt werden: Konvertierung, Erkennung und Manipulation.

Alle Klassen müssen in der Lage sein, ein Pixel aus und in 8-bit-RGB zu konvertieren (d. h., ein `TQColor`), und möglichst auch in und aus 16-bit-L*a*b*. Zusätzlich gibt es eine Methode, um vom aktuellen Farbraum in jeden anderen Farbraum zu konvertieren.

Farbräume werden vom `KisChannelInfo`-Vektor beschrieben. Anzahl der Kanäle, Anzahl Bytes pro Pixel, ob High Dynamic Range unterstützt wird, und mehr.

Manipulationen sind z. B. das Kombinieren von zwei Pixeln in ein neues Pixel: bitBlt, Abdunkeln oder Falten von Pixeln.

Bitte lesen Sie die API-Dokumentation für eine vollständige Liste der Methoden, die Sie in einem Farbraum implementieren müssen.

`KisAbstractColorSpace` implementiert viele der virtuellen Methoden von `KisColorSpace`, wobei Funktionen aus der `lcms`-Bibliothek verwendet werden. Oberhalb von `KisAbstractColorSpace` gibt es Basis-Farbraum-Klassen für Farbräume in 8- und 16-bit-Integer und 16- und 32-bit-Float, die allgemeine Verfahren zum Verschieben zwischen Farbtiefen definieren.

## Filter

Filter sind Module, die Pixel einer Ebene untersuchen und diese dann verändern. Obwohl &chalk; ein effizientes Speicher-Backend zum Speichern der Pixel verwenden, brauchen sich Filterautoren darum nicht zu kümmern. Wenn Sie ein Filtermodul für die &Java;-Graphik-API, Photoshop oder Gimp schreiben, müssen Sie sich über die Position der Pixel im Speicher Gedanken machen und die die einzelnen Teile zusammenflicken: &chalk; versteckt die Details der Implementierung.

Beachten Sie, dass es theoretisch einfach ist, das aktuelle Speicher-Backend durch eine neues zu ersetzen, sie aber derzeit aus Performancegründen nicht modular gestaltet sind.

&chalk; benutztz Iteratoren zum Lesen und Schreiben von Pixelwerten. Alternativ können Sie einen Pixelblock in den Speicher laden, mit diesem Block arbeiten und dann als Block wieder zurückschreiben. Dieses Vorgehen ist aber nicht grunsdätzlich effizienter und kann sogar langsamer als die Benutzung eines Iterators sein; es könnte allerdings angenehmer sein. Ziehen Sie hierfür die API-Dokumentation zu Rate.

Bilder in &chalk; sind auf Ebenen zusammen gesetzt, von denen es derzeit vier Arten gibt: Farbebenen, Gruppenebenen, Justierungsebenen (die einen Filter enthalten, der dynamisch auf Ebenen unter der Ebene angewandt werden) und Teilebenen. Filter arbeiten immer auf Farbebenen. Farbebenen enthalten Malwerzeuge der Klasse `KisPaintDevice`. Ein Malwerkzeug gewährt Zugriff auf die eigentlichen Pixel.

`PaintDevice`s werden über "Shared Pointers" verwendet. Ein "Shared Pointer" verwaltet Informationen darüber, an wie vielen Stellen das Zeichengerät verwendet wird und löscht es, wenn es nirgends mehr verwendet wird. Sie erkennen einen "Shared Pointer" an der Endung `SP`. Denken Sie daran, dass Sie sich nicht um das Löschen eines `KisPaintDeviceSP` kümmern müssen.

Lassen Sie uns einen sehr einfachen Filter untersuchen, einen, der jedes Pixel invertiert. Der Quelltext dieses Filters liegt im verzeichnis `koffice/chalk/plugins/filters/example`. Die Hauptmethode ist

    KisFilterInvert::process(KisPaintDeviceSP src, KisPaintDeviceSP dst,
                            KisFilterConfiguration* /*config*/, const QRect& rect).

Die Funktion bekommt zwei Maleinheiten übergeben, ein Konfigurationsobjekt (das in diesem einfachen Filter nicht verwendet wird) und ein Rechteck (`rect`). Das `rect` beschreibt die Fläche auf der der Filter arbeiten soll. Diese Fläche wird durch Integer beschrieben, was keine Subpixelgenauigkeit zulässt.

Die `src`-Maleinheit arbeitet lesend, die `dst`-Maleinheit arbeitet schreibend. Diese Parameter können auf die gleiche Maleinheit oder auf zwei verschiedene Maleinheiten zeigen. (Beachten Sie, dass sich dies in Zukunft ändern kann.)

Lassen Sie uns nun den Quelltext Zeile für Zeile durchgehen:

    void KisFilterInvert::process(KisPaintDeviceSP src, KisPaintDeviceSP dst,
                                 KisFilterConfiguration* /*config*/, const QRect& rect)
    {
       Q_ASSERT(src != 0);
       Q_ASSERT(dst != 0);

       KisRectIteratorPixel srcIt = src->createRectIterator(rect.x(), rect.y(), rect.width(), rect.height(), false);
       KisRectIteratorPixel dstIt = dst->createRectIterator(rect.x(), rect.y(), rect.width(), rect.height(), true );

       int pixelsProcessed = 0;
       setProgressTotalSteps(rect.width() * rect.height());

       KisColorSpace * cs = src->colorSpace();
       Q_INT32 psize = cs->pixelSize();

       while( ! srcIt.isDone() )
       {
           if(srcIt.isSelected())
           {
               memcpy(dstIt.rawData(), srcIt.oldRawData(), psize);

               cs->invertColor( dstIt.rawData(), 1);
           }
           setProgress(++pixelsProcessed);
           ++srcIt;
           ++dstIt;
       }
       setProgressDone(); // Must be called even if you don't really support progression
    }

- Diese erzeugt einen Iterator, um vorhandene Pixel zu lesen. Chalk hat drei Arten von Iteratoren: horizontale, vertokale und rechteckige. Der rechteckige Iterator nimmt den effizientesten Weg durch die Bilddaten, garantiert aber keine Informationen über die Position des nächten Pixels, den er zurück gibt. Das bedeutet, dass Sie nicht sicher sein können, dass das nächste Pixel, den Sie erhalten, an das aktuelle Pixel angrenzt. Der horizontale und der vertikale Iterator garantieren die Position des nächsten Pixels, den sie zurück geben.

- (2) Wir erstellen den Ziel-Iterator, indem wir `write` auf `true` setzen. Das bedeutet, dass, wenn die Ziel-Maleinheit kleiner ist als das Rechteck, das geschrieben wird, erstere automatisch vergrößert wird, damit alle Pixel iteriert werden können. Beachten Sie, dass hier Potential für Fehler besteht: sind `dst` und `src` nicht die gleiche Einheit, ist es sehr warscheinlich, dass die zurückgegebenen Pixel nicht zusammen passen. Der `src`-Iterator könnte z. B. an der Position 165,200 stehen, während der `dst`-Iterator bei 20,8 steht — die ausgeführte Aktion verschiebt also das Bild.

- Wollen Sie wissen, ob ein Pixel ausgewählt ist? Das ist einfach — benutzen Sie dazu die `isSelected`-Methode. Allerdings sind Auswahlen keine binäre Eigenschaft eines Pixels; er kann halb gewählt, kaum gewählt oder fast vollständig gewählt sein. Diesen Wert bekommen Sie vom Iterator. Auswahlen sind eigentlich maskierte Maleinheiten mit einem Bereich von 0 bis 255, wobei 0 keine Auswahl und 255 eine vollständige Auswahl bedeuten. Der Iterator hat zwei Methoden: `isSelected()` und `selectedNess()`. Die erste gibt "true" zurück, wenn das Pixel in irgendeiner Weise ausgewählt ist (d. h. der Wert größer 1 ist), die andere gibt den Wert zurück.

- Wie weiter oben erwähnt, ist `memcpy` ein großer, schlimmer Fehler... `rawData()` gibt ein Array aus Bytes zurück, das den aktuellen Zustand des Pixels enthält; `oldRawData()` gibt ein Array aud Bytes mit dem Zustand zurück, den das Pixel hatte, bevor der Iterator erstellt wurde. An dieser Stelle könnten wir das falsche Pixel kopieren. In der Praxis passiert das nicht sehr oft, solange nicht `dst` schon existiert und mit `src` verbunden ist.

- Eines ist aber richtig: anstatt herauszufinden, welches Byte welchen Kanal darstellt, benutzen wir eine Funktion, die von allen Farbräumen bereitgestellt wird, um das aktuelle Pixel zu invertieren. Farbräume bieten viele Operationen, die Sie benutzen können.

Das war noch nicht alles zum Erstellen von Filtern. Filter haben zwei weitere wichtige Komponenten: ein Konfigurationsobjekt und ein Konfigurationswidget. Diese beiden arbeiten eng zusammen. Das Konfigurationswidget erstellt ein Konfigurationsobjekt, kann aber auch von einem, schon existierenden Konfigurationsobjekt gefüllt werden. Konfigurationsobjekte können als XML dargestellt werden und aus XML erstellt werden. Diese Eigenschaft macht Justierungsebenen möglich.

### Iteratoren

Es gibt drei Arten von Iteratoren:

- Waagerechte Linien

- Senkrechte Linien

- Rechteckige Iteratoren

Die horizontalen und vertikalen Zeileniteratoren haben eine Methode, um den Iterator in die nächste Zeile oder Spalte zu bewegen: `nextRow()` und `nextCol()`. Diese zu verwenden ist viel schneller als einen neuen Iterator für jede Zeile oder Spalte zu erstellen.

Iteratoren in &chalk; sind thread-sicher. Es ist als möglich, die Arbeit auf mehrere Threads aufzuteilen. In zukünftigen Versionen wird &chalk; die Methode `supportsThreading()`, um zu bestimmen, ob Ihr Filter auf Teile des Bildes angewandt werden kann (d. h. alle Pixel unabhängig voneinander geändert werden, anstatt Pixel abhängig von anderen Pixeln zu verändern), und diesen dann automatisch in Threads auszuführen.

### `KisFilterConfiguration`

`KisFilterConfiguration` ist eine Struktur zum Speichern der Filtereinstellungen auf den Datenträger (z. B. für Justierungsebenen). Das Skriptmodul benutzt die Eigenschaftenkarte, die hinter `KisFilterConfiguration` steht, damit Filter in Skripten verwendet werden können. Filter können ein eigenes Widget besitzen, das &chalk; in der Filtergalerie, dem Filtervorschau-Dialog und der Werkzeug-Karteikarte des "Mit Filtern malen"-Werkzeugs anzeigt.

Ein Beipiel vom Ölgemäldefilter:

    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"); };

    };

### `KisFilterConfigurationWidget`

Die meisten Filter können vom Benutzer justiert werden. Sie können eine Konfigurationsvorrichtung erstellen, die Chalk verwenden wird, wenn Ihr Filter verwendet wird. Ein Beispiel:

Der Dialog Ölgemälde Der Dialog Ölgemälde

Der Dialog Ölgemälde

Beachten Sie, dass Sie sich nur um den linken Teil kümmern müssen: &chalk; erledigt den Rest. Sie können eine Einstellungen-Vorrichtung auf drei Arten erstellen:

- Benutzen Sie den &TQt;-Designer um eine Basisvorrichtung zu bekommen, und erstellen Sie sich daraus eine Unterklasse für Ihren Filter

- Benutzen Sie eines der einfachen Widgets mit Schiebereglern für Integer, Doubles oder Bool'sche Werte. Diese sind nützlich, wenn Ihr Filter , wie in dem obigen Bildschirmphoto, über eine Reihe Zahlen aus Integern, Doubles oder Bool'schen Werten konfiguriert werden kann. Lesen Sie die API-dox zu `KisMultiIntegerFilterWidget`, `KisMultiDoubleFilterWidget` und `KisMultiBoolFilterWidget`.

- Ein Widget von Hand schreiben. Diese Vorgehensweise wird nicht empfohlen; sollten Sie dies tun und Ihren Filter gerne als Teil der offiziellen &chalk;-Version sehen, werde ich Sie bitten, das handgeschriebene Widget durch ein TQt-Designer-Widget zu erstetzen.

Der Ölgemälde-Filter verwendet die Multi-Integer-Vorrichtung:

    KisFilterConfigWidget * KisOilPaintFilter::createConfigurationWidget(TQWidget* 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(TQWidget* nwidget)
    {
       KisMultiIntegerFilterWidget* widget = (KisMultiIntegerFilterWidget*) nwidget;
       if( widget == 0 )
       {
           return new KisOilPaintFilterConfiguration( 1, 30);
       } else {
           return new KisOilPaintFilterConfiguration( widget->valueAt( 0 ), widget->valueAt( 1 ) );
       }
    }

    std::list<KisFilterConfiguration*> KisOilPaintFilter::listOfExamplesConfiguration(KisPaintDeviceSP )
    {
       std::list<KisFilterConfiguration*> list;
       list.insert(list.begin(), new KisOilPaintFilterConfiguration( 1, 30));
       return list;
    }

Sehen Sie es in Aktion: füllen Sie einen "vector" mit Ihren Integer-Parametern und erstellen Sie das Widget. Die Methode `configuration()` untersucht das Widget und erstellt das richtige Konfigurationsobjekt für den Filter, in diesem Fall `KisOilPaintFilterConfiguration`. Die Methode `listOfExamplesConfiguration` (die in richtiges Englisch umbenannt werden sollte...) gibt eine Liste mit Beispielkonfigurationsobjekten für die Filtergalerie zurück.

### Zusammenfassung zu Filtern

Es gehört natürlich mehr dazu, interessante Filter zu schreiben, aber mit dieser Beschreibung, der API-Dokumentation und Zugriff auf unseren Quelltext, sollten Sie in der Lage sein, den Einstieg zu schaffen. Zögern Sie nicht, die &chalk;-Entwickler im IRC oder über die Mailingliste zu kontaktieren.

## Werkzeuge

Werkzeuge erscheinen in &chalk;s Werkzeugkasten. Das bedeutet, dass der Platz für neue Werkzeuge beschränkt ist — denken Sie darüber nach, ob ein Malverfahren möglicherweise für Ihren Verwendungszweck ausrecht. Werkzeuge können Maus/Tablett und Tastatur auf viele Weise nutzen, was Malverfahren nicht können. Das ist der Grund dafür, dass "Duplizieren" ein Werkzeug ist und "Airbrush" ein Malverfahren.

Seien Sie vorsichtig mit statischen Daten in Ihrem Werkzeug: für jedes Eingabegerät wird eine neue Instanz erzeugt: Maus, Schreibstift, Radierer, Airbrush — was auch immer. Werkzeuge werden in logische Gruppen unterteilt:

- Werkzeuge zum Malen von Formen (Kreis, Rechteck)

- Freihand-Malwerkzeuge (Pinsel)

- Werkzeuge zum Umformen, die die Geometrie Ihrer Ebene verändern

- Füllwerkzeuge (wie Zusammenhängend füllen oder Verlauf)

- Ansichtswerkzeuge (die keine Pixel verändern, aber die Art verändern, wie Sie die Zeichenfläche betrachten, wie z. B. Zoom)

- Auswahlwerkzeuge (die die Auswahlmaske verändern)

Das Interface für Werkzeuge wird in der API-Dokumentation zu `KisTool` beschrieben. Es gibt drei Unterklassen: `KisToolPaint`, `KisToolNonPaint` und `KisToolShape` (was eigentlich eine Unterklasse von `KisToolPaint` ist), die `KisTool` für Malaufgaben (d. h. zum Ändern von Pixeln), Aufgaben, die nicht malen, und welchen, die Formen malen, spezialisieren.

Ein Werkzeug hat ein Einstellungs-Widget, ebenso wie Filter. Derzeit werden diese Widgets auf einer Karteikarte in einem angedockten Fenster angezeigt. Möglicherweise ändern wir dies für &chalk; 2.0 in einen Streifen unter dem Hauptmenü (der dann die Werkzeugleiste ersetzt), entwerfen Sie Ihr Widget aber vorerst so, dass es auf die Karteikarte passt.Wie immer ist es am besten, dazu den &TQt; Designer zu verwenden.

Ein gutes Beispiel für ein Werkzeug ist das Stern-Werkzeug:

    kis_tool_star.cpp       Makefile.am   tool_star_cursor.png  wdg_tool_star.ui
    kis_tool_star.h        Makefile.in   tool_star.h
    chalktoolstar.desktop  tool_star.cpp  tool_star.png

Wie Sie sehen, benötigen Sie zwei Symbole. Eines für den Mauszeiger und eines für den Werkzeugkasten. `tool_star.cpp` ist nur der Aufrufer für das Modul, ähnlich dem oben gesehenen. Den wichtigen Code enthält die Implementierung:

    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;
    }

Der Konstruktor setzt den internen Namen — der nicht übersetzt wird — und der Aufruf der Überklasse setzt den sichtbaren Namen. Wir laden auch den Mauszeiger und setzten einige Variablen.

    void KisToolStar::update (KisCanvasSubject *subject)
    {
       KisToolShape::update (subject);
       if (m_subject)
           m_currentImage = m_subject->currentImg();
    }

Die Methode `update()` wird aufgerufen, wenn ein Werkzeug ausgewählt wird. Dies ist keine `KisTool`-Methode, sondern eine `KisCanvasObserver`-Methode. Canvas Observer (Zeichenflächenüberwachung) werden benachrichtigt, sogald sich etwas in der Ansicht ändert, was nützlich für Werkzeuge sein kann.

Die folgenden Methoden (`buttonPress`, `move` und `buttonRelease`) werden von &chalk; aufgerufen, wenn das Eingebegerät (Maus, Stift, Radierer &etc;) gedrückt, bewegt, oder losgelassen wird.Beachten Sie, dass Sie auch "move"-Ereignisse bekommen, wenn die Maustaste nicht gedrückt ist. Die Ereignisse sind keine normalen &TQt;-Events, sondern künstliche &chalk;-Events, da wir ein paar Low-Level-Tricks verwenden um genügend Ereignisse für eine glatte Linie zu bekommen.Toolkits wie &TQt; (und GTK) lassen Ereignisse fallen, wenn sie zu beschäftigt sein, und wir brauchen alle.

    void KisToolStar::buttonPress(KisButtonPressEvent *event)
    {
       if (m_currentImage && event->button() == LeftButton) {
           m_dragging = true;
           m_dragStart = event->pos();
           m_dragEnd = event->pos();
           m_vertices = m_optWidget->verticesSpinBox->value();
           m_innerOuterRatio = m_optWidget->ratioSpinBox->value();
       }
    }

    void KisToolStar::move(KisMoveEvent *event)
    {
       if (m_dragging) {
           // erase old lines on canvas
           draw(m_dragStart, m_dragEnd);
           // move (alt) or resize star
           if (event->state() & TQt::AltButton) {
               KisPoint trans = event->pos() - m_dragEnd;
               m_dragStart += trans;
               m_dragEnd += trans;
           } else {
               m_dragEnd = event->pos();
           }
           // draw new lines on canvas
           draw(m_dragStart, m_dragEnd);
       }
    }

    void KisToolStar::buttonRelease(KisButtonReleaseEvent *event)
    {
       if (!m_subject || !m_currentImage)
           return;

       if (m_dragging && event->button() == LeftButton) {
           // erase old lines on canvas
           draw(m_dragStart, m_dragEnd);
           m_dragging = false;

           if (m_dragStart == m_dragEnd)
               return;

           if (!m_currentImage)
               return;

           if (!m_currentImage->activeDevice())
               return;

           KisPaintDeviceSP device = m_currentImage->activeDevice ();;
           KisPainter painter (device);
           if (m_currentImage->undo()) painter.beginTransaction (i18n("Star"));

           painter.setPaintColor(m_subject->fgColor());
           painter.setBackgroundColor(m_subject->bgColor());
           painter.setFillStyle(fillStyle());
           painter.setBrush(m_subject->currentBrush());
           painter.setPattern(m_subject->currentPattern());
           painter.setOpacity(m_opacity);
           painter.setCompositeOp(m_compositeOp);
           KisPaintOp * op =
               KisPaintOpRegistry::instance()->paintOp(m_subject->currentPaintop(), m_subject->currentPaintopSettings(), &painter);
           painter.setPaintOp(op); // Painter takes ownership

           vKisPoint coord = starCoordinates(m_vertices, m_dragStart.x(), m_dragStart.y(), m_dragEnd.x(), m_dragEnd.y());

           painter.paintPolygon(coord);

           device->setDirty( painter.dirtyRect() );
           notifyModified();

           if (m_currentImage->undo()) {
               m_currentImage->undoAdapter()->addCommand(painter.endTransaction());
           }
       }
    }

Die Methode `draw()` ist eine interne Methode von `KisToolStar` und malt den Umriss eines Sterns. Wir rufen dieses aud `move()` auf um dem Benutzer Rückmeldungen über die Größe und Form des Sterns zu geben. Beachten Sie, dass wir die Rasteroperation `TQt::NotROP` verwenden, was bedeutet, dass ein zweiter Aufruf von `draw()` mit dem gleichen Start- und Endpunkt den vorherigen Stern löscht.

    void KisToolStar::draw(const KisPoint& start, const KisPoint& end )
    {
       if (!m_subject || !m_currentImage)
           return;

       KisCanvasController *controller = m_subject->canvasController();
       KisCanvas *canvas = controller->kiscanvas();
       KisCanvasPainter p (canvas);
       QPen pen(TQt::SolidLine);

       KisPoint startPos;
       KisPoint endPos;
       startPos = controller->windowToView(start);
       endPos = controller->windowToView(end);

       p.setRasterOp(TQt::NotROP);

       vKisPoint points = starCoordinates(m_vertices, startPos.x(), startPos.y(), endPos.x(), endPos.y());

       for (uint i = 0; i < points.count() - 1; i++) {
           p.drawLine(points[i].floorQPoint(), points[i + 1].floorQPoint());
       }
       p.drawLine(points[points.count() - 1].floorQPoint(), points[0].floorQPoint());

       p.end ();
    }

Die Methode `setup()` ist notwendig: hier erstellen wir die Aktion, die in den Werkzeugkasten integriert wird, damit Benutzer das Werkzeug auswählen können. Zudem weisen wir ein Tastenkürzel zu. Beachten Sie, dass an diesem Bereich gerade gearbeitet wird: es könnten Dinge geändert werden.

    void KisToolStar::setup(TDEActionCollection *collection)
    {
       m_action = static_cast<TDERadioAction *>(collection->action(name()));

       if (m_action == 0) {
           TDEShortcut shortcut(TQt::Key_Plus);
           shortcut.append(TDEShortcut(TQt::Key_F9));
           m_action = new TDERadioAction(i18n("&Star"),
                                       "tool_star",
                                       shortcut,
                                       this,
                                       TQ_SLOT(activate()),
                                       collection,
                                       name());
           TQ_CHECK_PTR(m_action);

           m_action->setToolTip(i18n("Draw a star"));
           m_action->setExclusiveGroup("tools");
           m_ownAction = true;
       }
    }

Die `starCoordinates()`-Methode enthält etwas erschreckende Mathematik — sie ist aber nicht so interessant in der Auseinandersetzung mit dem Erstellen eines Werkzeugmoduls.

    KisPoint KisToolStar::starCoordinates(int N, double mx, double my, double x, double y)
    {
       double R=0, r=0;
       Q_INT32 n=0;
       double angle;

       vKisPoint starCoordinatesArray(2*N);

       // the radius of the outer edges
       R=sqrt((x-mx)*(x-mx)+(y-my)*(y-my));

       // the radius of the inner edges
       r=R*m_innerOuterRatio/100.0;

       // the angle
       angle=-atan2((x-mx),(y-my));

       //set outer edges
       for(n=0;n<N;n++){
           starCoordinatesArray[2*n] = KisPoint(mx+R*cos(n * 2.0 * M_PI / N + angle),my+R*sin(n *2.0 * M_PI / N+angle));
       }

       //set inner edges
       for(n=0;n<N;n++){
           starCoordinatesArray[2*n+1] = KisPoint(mx+r*cos((n + 0.5) * 2.0 * M_PI / N + angle),my+r*sin((n +0.5) * 2.0 * M_PI / N + angle));
       }

       return starCoordinatesArray;
    }

Die Methode `createOptionWidget()` wird aufgerufen, um ein Einstellungs-Widget zu erstellen, das &chalk; auf der Karteikarte anzeigt. Da es ein Werkzeug pro Eingabegeräte pro Ansicht ist, kann der Zustand eines Werkzeugs im Werkzeug gehalten werden. Diese Methode wird nur einmal aufgerufen: das Einstellungs-Widget wird gespeichert und beim nächsten Aufruf wieder hergestellt.

    TQWidget* KisToolStar::createOptionWidget(TQWidget* parent)
    {
       TQWidget *widget = KisToolShape::createOptionWidget(parent);

       m_optWidget = new WdgToolStar(widget);
       TQ_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;
    }

### Zusammenfassung über Werkzeuge

Werkzeuge sind nicht schwer zu erstellen. Sie müssen die Schnittstellen von `KisTool` und `KisCanvasObserver` kombinieren, um ein Werkzeug zu erstellen.

## Malverfahren

Malverfahren gehören zu den innovativeren Arten von Modulen in Chalk (zusammen mit modularen Farbräumen). Ein Malverfahren definiert, wie Werkzeuge die Pixel verändern, die sie berühren. Airbrush, Treppeneffekt-Pinsel, geglätteter Pixelpinsel: all das sind Malverfahren. Sie können auch — mit viel Arbeit — ein Malverfahren erstellen, das Pinseldefinitionen aus Corel-Painter-XML liest und diese verwendet um zu bestimmen, wie gemalt wird.

Malverfahren werden instanziiert, wenn ein Malwerkzeug ein `mouseDown`-Ereignis empfängt, und werden bei einem mouseUp-Ereignis gelöscht. Dazwischen kann das Malverfahren vorherige Positionen und andere Daten, wie Druckstufen beobachten, wenn der Benutzer ein Tablett benutzt.

Die grundlegende Funktion eines Malverfahrens ist, die Pixel an der Mauszeigerposition zu verändern. Dies kann einmal gemacht werden, oder das Malverfahren kann verlangen, in regelmäßigen Abständen zu laufen. Das erste Verfahren ist z. B. für einen Stift nützlich, das zweite z. B. für ein Airbrush-Malverfahren.

Malverfahren können ein kleines Einstellungs-Widget haben, das in einer Werkzeugleiste plaziert wird. Daher sollte es horizontal aufgebaut sein und nicht höher als ein Werkzeugleistenknopf sein.

Lassen Sie uns ein einfaches Malverfahrenmodul betrachten, eines das etwas programmierte Intelligenz zeigt. Zuerst wird in der Header-Datei eine Fabrik definiert. Diesee Fabrik erstellt ein Malverfahren, wenn das aktive Werkzeug eines benötigt:

    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 TQString pixmap() { return ""; }

    };

Die Fabrik enthält auch die `KisID` mit dem privaten und öffentlichen Namen für das Malverfahren — stellen Sie sicher, dass der private Name Ihres Malverfahrens nicht mit anderen Malverfahren kollidiert! — und kann wahlweise eine Pixmap zurück geben. &chalk; kann diese Pixmap zusammen mit dem Namen anzeigen, um den Wiedererkennungswert zu erhöhen.

Die Anwendung eines Malverfahrens ist überschaubar:

    KisSmearyOp::KisSmearyOp(KisPainter * painter)
       : KisPaintOp(painter)
    {
    }

    KisSmearyOp::~KisSmearyOp()
    {
    }
    void KisSmearyOp::paintAt(const KisPoint &pos, const KisPaintInformation& info)
    {

Die Methode `paintAt()` ist der Punkt, an dem alles passiert, was mit Malverfahren zu tun hat. Sie übernimmt zwei Parameter: die aktuelle Position (in Floats, nicht in ganzen Pixeln) und ein Objekt der Klasse `KisPaintInformation`, das die Druckstufe, X- und Y-Winkel und die Bewegungsrichtung enthält. In Zukunft könnten diese Informationen erweitert werden.

    if (!m_painter->device()) return;

       KisBrush *brush = m_painter->brush();

Ein `KisBrush`-Objekt ist die Vertretung für eine Gimp-Pinsel-Datei: es ist eine Maske oder eine Reihe von Masken. Eigentlich verwenden wir hier den Pinsel nicht, außer um den hotspot unter dem Mauszeiger zu bestimmen.

    Q_ASSERT(brush);

       if (!brush) return;

       if (! brush->canPaintFor(info) )
           return;

       KisPaintDeviceSP device = m_painter->device();
       KisColorSpace * colorSpace = device->colorSpace();
       KisColor kc = m_painter->paintColor();
       kc.convertTo(colorSpace);

       KisPoint hotSpot = brush->hotSpot(info);
       KisPoint pt = pos - hotSpot;

       // Split the coordinates into integer plus fractional parts. The integer
       // is where the dab will be positioned and the fractional part determines
       // the sub-pixel positioning.
       Q_INT32 x, y;
       double xFraction, yFraction;

       splitCoordinate(pt.x(), &x, &xFraction);
       splitCoordinate(pt.y(), &y, &yFraction);

       KisPaintDeviceSP dab = new KisPaintDevice(colorSpace, "smeary dab");
       TQ_CHECK_PTR(dab);

Wir ändern die Pixel einer Maleinheit nicht direkt: stattdessen erstellen wir eine kleine Maleinheit, einen Tupfer, und fügen das der aktuellen Maleinheit hinzu.

    m_painter->setPressure(info.pressure);

Wie den Kommentaren zu entnehmen ist, wird hier der eigentliche Tupfer erstellt. In diesem Fall malen wir ein paar Linien. Wenn das Malverfahren fertig ist, werden die Länge, die Position und die Dicke der Linien von der Druckstufe und der Farbmenge äbhängig sein, und wir müssen eine harte, schmierige Ölfarbenpinsel erstellen. Dazu fehlte bisher aber die Zeit.

    // Compute the position of the tufts. The tufts are arranged in a line
       // perpendicular to the motion of the brush, i.e, the straight line between
       // the current position and the previous position.
       // The tufts are spread out through the pressure

       KisPoint previousPoint = info.movement.toKisPoint();
       KisVector2D brushVector(-previousPoint.y(), previousPoint.x());
       KisVector2D currentPointVector = KisVector2D(pos);
       brushVector.normalize();

       KisVector2D vl, vr;

       for (int i = 0; i < (NUMBER_OF_TUFTS / 2); ++i) {
           // Compute the positions on the new vector.
           vl = currentPointVector + i * brushVector;
           KisPoint pl = vl.toKisPoint();
           dab->setPixel(pl.roundX(), pl.roundY(), kc);

           vr = currentPointVector - i * brushVector;
           KisPoint pr = vr.toKisPoint();
           dab->setPixel(pr.roundX(), pr.roundY(), kc);
       }

       vr = vr - vl;
       vr.normalize();

Schleißlich kopieren wir den Tupfer in die eigentliche Maleinheit und sagen dem Painter, dass wir ein kleines Rechteck der Maleinheit beschmutzt haben.

    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);
       TQ_CHECK_PTR(op);
       return op;
    }

Das ist alles: Malverfahren sind einfach und machen Spaß.

## Ansichtenmodule

Ansichtenmodule sind die bizarrsten im Bunde: ein Ansichtenmodul ist eine normale KPart-Anwendung, die Teile einer Benutzeroberfläche und etwas Funktionalität bereitstellt. Z. B. sind die Karteikarte für das Histogramm und der "Drehen"-Dialog Ansichtenmodule.

## Import/Export-Filter

&chalk; arbeitet mit der normalen &koffice;-Dateifilter-Architektur. Es gibt eine Anleitung, etwas alt, aber dennoch nützlich, unter . Es ist warscheinlich das beste, beim Erstellen von Dateifiltern mit dem &chalk;-Team zusammen zu arbeiten, und im &koffice;-Filter zu entwickeln. Beachten Sie, dass Sie Ihren Filter testen können ohne &chalk; zu starten. Benutzen Sie dazu das Programm `koconverter`.

Filter haben zwei Seiten: importieren und exportieren. Dies sind normalerweise zwei verschiedene Module, die sich etwas Code teilen können.

Die wichtigen Einträge in `Makefile.am` sind:

    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

Ob Sie Import- oder Exportfilter rstellen, es läuft immer auf die Implementierung der folgenden Funktion hinaus:

    virtual KoFilter::ConversionStatus convert(const QCString& from, const QCString& to);

Die Einstellungen in der `.desktop`-Datei bestimmen, in welcher Richtung der Filter konvertiert:

Importieren:

    X-TDE-Export=application/x-chalk
    X-TDE-Import=image/x-xcf-gimp
    X-TDE-Weight=1
    X-TDE-Library=libchalkXXXimport
    ServiceTypes=KOfficeFilter

Exportieren:

    X-TDE-Export=image/x-xcf-gimp
    X-TDE-Import=application/x-chalk
    ServiceTypes=KOfficeFilter
    Type=Service
    X-TDE-Weight=1
    X-TDE-Library=libchalkXXXexport

Der Mimetype, der für das Beispiel gewählt wurde, ist ein Hinweis. Würden Sie bitte einen xcf-Filter implementieren?

### Importieren

Das große Problem mit Imprtfiltern ist, die Daten von dem Datenträger zu lesen. Die Vorlage, diesen Code aufzurufen, ist recht einfach:

Notitz: Wir sollten einen Weg finden, &chalk; zu ermöglichen, eine Datei offen zu halten, und Daten nur zu lesen, wenn es nötig ist, anstatt den gesamten Inhalt in die interne Vertretung der Maleinheit zu kopieren. Das ist jedoch derzeit nicht umgesetzt.

    KoFilter::ConversionStatus XXXImport::convert(const QCString&, const QCString& to)
    {
       if (to != "application/x-chalk")
           return KoFilter::BadMimeType;

       KisDoc * doc = dynamic_cast<KisDoc*>(m_chain -> outputDocument());
       KisView * view = static_cast<KisView*>(doc -> views().getFirst());

       TQString filename = m_chain -> inputFile();

       if (!doc)
           return KoFilter::CreationError;

       doc -> prepareForImport();

       if (!filename.isEmpty()) {

           KURL url(filename);

           if (url.isEmpty())
               return KoFilter::FileNotFound;

           KisImageXXXConverter ib(doc, doc -> undoAdapter());

           if (view != 0)
               view -> canvasSubject() ->  progressDisplay() -> setSubject(&ib, false, true);

           switch (ib.buildImage(url))  {
               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());
                   return KoFilter::OK;
               default:
                   break;
           }

       }
       return KoFilter::StorageCreationError;
    }

- Das soll ein Importfilter sein. Wenn er nicht zum Konvertieren eines Bildes in &chalk; aufgerufen wird, ist irgendetwas falsch.

- Die Filterkette hat für uns ein Ausgabedokument erstellt. Wir müssen es in ein `KisDocM` casten, da &chalk;-Dokumente besonderer Behandlung bedürfen. Sie sollten überprüfen, dass das Ergebnis des Casts nicht 0 ist, da, wenn er 0 ist, der Import fehlschlägt.

- Wenn wir diesen Filter aus der GUI aufrufen, versuchen wir, die Ansicht zu bekommen. Gibt es eine Ansicht, kann der Konvertierungscode versuchen, die Fortschrittsanzeige zu aktualisieren.

- Der Filter hat für uns den Dateinamen der Eingabedatei.

- `KisDoc` muss für den Import verbereitet werden. Bestimmte Einstellungen werden initialisiert und die "Rückgäning"-Funktion wird deaktiviert. Sonst könnten Sie Teilschritte des Filters rückgängig machen, was seltsames Verhalten wäre.

- Ich habe beschlossen, den eigentlichen Import-Code in eine separate Klasse auszulagern, die ich hier instanziire. Sie können Ihren ganzen Code hier hinein schreiben, was allerdings etwas chaotisch wäre.

- Mein Importeur gibt einen Statuscode zurück, den ich verwenden kann, um den Status des Importfilters zu setzen. &koffice; kümmert sich um die Anzeige von Fehlermeldungen.

- Wenn die Erstellunge von `KisImage` erfolgreich war, setzen wir das aktuelle Bild des Dokuments auf das neu erstellte Bild. Wir sind nun fertig: `return KoFilter::OK`.