Digitale Bildeffekte erfordern meist komplizierte Mathematik. Wirkungsvolle Manipulationen sind jedoch auch mit einfachen Verfahren möglich. Dieser Artikel stellt die Algorithmen vor und implementiert zugleich ein Bildbearbeitungsprogramm mit Qt-Oberfläche.
Digitalkameras sind heute für jedermann erschwinglich. Leider gelingen die Bilder nicht immer wie gewünscht. Oft führt der Weg zum perfekten Bild über digitale Retuschewerkzeuge wie Photoshop oder Gimp. Der Artikel zeigt, wie man entsprechende Bildbearbeitungsfunktionen selbst programmiert und in eigene Projekte integriert: die Korrektur von Helligkeit und Kontrast sowie die Tonwertspreizung. Zwei Dialoge mit je zwei Schiebereglern erlauben es Anwendern, Helligkeit und Kontrast einzustellen und eine Tonwertkorrektur vorzunehmen. Statt in einer verkleinerten Vorschau sieht der Benutzer die Änderungen gleich am Originalbild.
Digitale Bilder bestehen aus einem Raster von Bildpunkten, die Pixel (von Pictures Element) genannt werden. Digitale Kameras oder Scanner speichern für jedes Pixel eine Farbe. Dazu stehen wiederum mehrere Farbmodelle zur Verfügung. Bei Bildern für Computermonitore ist das RGB-Farbmodell üblich.
Ein Pixel besteht aus drei Komponenten, den Lichtgrundfarben Rot, Grün und Blau. Alle drei Kanalwerte liegen als Zahlen im Bild vor. Die Farbe des Pixels entsteht durch additive Farbmischung. Beispielsweise hat ein rotes Pixel in diesem Farbmodell die drei Kanalwerte Rot mit 100 Prozent, Grün 0 Prozent, Blau 0 Prozent, ein schwarzes Pixel (0, 0, 0), ein weißes Pixel (100, 100, 100).
Farbtiefe und Komponenten
Gängige RGB-Farbbilder liegen in einer Farbtiefe von 24 Bit pro Pixel vor. Da sich die Farbtiefe pro Pixel auf drei Kanäle verteilt, stehen für jeden der drei Kanäle 8 Bits zur Verfügung. Das entspricht 256 Helligkeitsstufen pro Kanal. Der maximale Wert beträgt 255 und nicht etwa 256, da der Wertebereich bei null beginnt.
Der Begriff Filter stammt von farbigen oder anders behandelten Gläsern, die man vor Kameraobjektive schraubt. Nutzt der Fotograf bei einer Aufnahme einen Filter, passieren die Lichtstrahlen erst den Filter, bevor sie in das Objektiv eintreten. Dadurch ändert sich die Lichtqualität. Ein Beispiel ist der Skylight- Filter: Landschaftsaufnahmen leiden oft unter einem Blaustich. Ein Tageslicht-Filter ist rötlich getönt und gleicht dadurch diesen Fehler aus.
Filter und Bildpunktoperationen
Filter in der digitalen Bildbearbeitung ahmen solche Prinzipien nach oder gehen sogar darüber hinaus. Ein echter Filter nutzt die physikalischen Eigenschaften behandelten Glases. Da ein digitales Bild als Folge von Zahlen vorliegt, nutzt ein digitaler Filter die Mathematik für das Verändern der Farben. Ein digitaler Filter ist also eine Rechenregel, um ein Quellpixel in ein Zielpixel umzurechnen. Dazu rechnet er für jedes Pixel drei neue Kanalwerte aus. Denkbar sind sowohl Filter, die nur die Kanalwerte des Quellpixels zur Berechnung der Kanalwerte des Zielpixels benötigen, als auch Filter, die zusätzlich Kanalwerte anderer Pixel benutzten. Verwischeffekte (Blur) etwa benutzen die Kanalwerte der Pixel aus der näheren Umgebung.
Nutzt ein Filter tatsächlich nur die Kanalwerte eines Quellpixels spricht man von einer Bildpunktoperation. Die Veränderung von Helligkeit und Kontrast und die Tonwertspreizung sind zum Beispiel Bildpunktoperation. Für die Tonwertspreizung und sogar bei der Korrektur von Helligkeit und Kontrast lässt sich jeder Farbkanal eines Zielpixels unabhängig von den anderen Kanälen des Quellpixels berechnen. Das zugrunde liegende mathematische Modell ist nicht sehr kompliziert.
Effekte für bessere Bilder
Kontrast ist definiert als Helligkeitsunterschied. Ein Bild mit hohem Kontrast enthält überwiegend sehr helle und sehr dunkle Farben, dafür wenige mittelhelle Töne. Es wirkt silhouettenartig, zeigt also eher die Umrisse von Objekten. Bei schwachem Kontrast fehlt es an sehr hellen und sehr dunklen Farben, dafür gibt es viele mittelhelle Farben. Das Bild wirkt flau.
Die Kanalwerte eines digitalen Bildes mit hohem Kontrast sind überwiegend deutlich größer oder deutlich kleiner als die Mitte des Wertebereichs: Liegt ein RGB-Bild in einer Farbtiefe von 24 Bit vor, gibt es kaum Pixel mit Kanalwerten um 128, dafür viele Pixel mit deutlich niedrigeren und deutlich höheren Kanalwerten. Bei einem solchen Bild mit schwachem Kontrast liegen die Kanalwerte der Pixel überwiegend im mittleren Bereich.
Um den Kontrast zu erhöhen, erhalten dunkle, also kleine Kanalwerte, noch kleinere Werte. Helle, große Kanalwerte bekommen noch größere Werte zugewiesen. Entsprechend verhält es sich mit der Helligkeit: Um ein Bild aufzuhellen, ist lediglich jedem Kanalwert ein größerer Wert zuzuweisen.
Mathematische Funktionen ordnen einer Zahl (Argument) eine andere Zahl zu (Funktionswert). Der Funktionswert ergibt sich aus einem Term, in dem das Argument als Variable vorkommt. Für die Filterfunktion benötigt man einen Funktionsterm, der mit Hilfe von zwei Parametern (den Werten der beiden Schieberegler für Helligkeit und für Kontrast) die Kanalwerte verändert.
Ausgangspunkt ist eine Funktionsgleichung, die einen Kanalwert nicht verändert, sondern ihn unverändert zurückgibt. Der Graph einer solchen Funktion entspricht der ersten Winkelhalbierenden der Koordinatenachsen. Das ist eine Gerade mit der Steigung 1, die den Ursprung und die beiden Stützpunkte P<->1<$> (0, 0) und P<->2<$> (255, 255) enthält (Abbildung 1). Dieser Filter entspricht einem Fensterglas, das alle Lichtstrahlen ungehindert passieren lässt.

Abbildung 1: Die neutrale Funktion (rot) weist dem Argument 192 (blau) den Funktionswert 192 (türkis) zu. Die x-Achse entspricht dem Quellkanalwert, die y-Achse dem resultierenden Zielkanalwert.
Helligkeit
Ordnet die Funktion jedem Kanalwert einen größeren, also helleren Wert zu, entspricht das einer Verschiebung der Geraden nach oben. Bei kleineren, dunkleren Kanalwerten liegt die Gerade weiter unten. Der Anwender gibt die gewünschte Helligkeitsänderung mit einem Schieberegler ein. Um das Bild aufzuhellen, stellt er einen positiven, um es abzudunkeln einen negativen Wert ein.
Kanalwerte, die bereits den Maximalwert besitzen, können nicht noch heller werden. Deshalb bekommt jeder Funktionswert, der über dem größten erlaubten Kanalwert 255 liegt, genau diesen Maximalwert zugewiesen. Nehmen zwei verschiedene Kanalwerte aufgrund der Berechnung diesen Wert an, geht die Information verloren, welcher der beiden Kanalwerte ursprünglich heller war. Das Gleiche gilt auch für das Abdunkeln und den Kontrast.
Da eine Gerade bereits durch zwei Punkte eindeutig definiert ist, reicht es, beide entsprechend zu verändern, um später die Geradengleichung zu berechnen. Dazu bieten sich die beiden Stützpunkte P<->1<$> (0, 0) und P<->2<$> (255, 255) an. Beide um den Wert h des Schiebereglers in vertikaler Richtung verschoben ergeben zwei Stützpunkte der gewünschten Geradenfunktion. Für diese gilt deshalb vorläufig: P<->1<$> (0, 0+h), P<->2<$> (255, 255+h), siehe Abbildung 2.

Abbildung 2: Die Gerade ist um 64 nach oben verschoben und bereits auf maximal 255 begrenzt. Die Funktion weist dem Argument 128 den Funktionswert 192 zu, die Farbe wird heller.
Kontrast
Um den Kontrast zu vergrößern, sollen wie beschrieben helle Kanalwerte noch heller, im Diagramm also nach oben verschoben, dunkle Werte noch dunkler, also nach unten verschoben werden. Der erste Stützpunkt P<->1<$> liegt im dunklen Bereich. Die Verschiebung dieses Punktes nach unten macht dunkle Kanalwerte noch dunkler. Der zweite Stützpunkt P<->2<$> liegt im hellen Bereich, nach oben verschoben werden alle hellen Kanalwerte noch heller (Abbildung 3).
Für die endgültigen Stützpunkte gilt P<->1<$> (0, 0+h-c) und P<->2<$> (255, 255+h+c). Wieder kann es passieren, dass Funktionswerte außerhalb des gültigen Wertebereichs liegen. Das Programm muss die Anpassungen also begrenzen.
Die Gleichung der Geraden besteht aus der Steigung m mit dem Argument als Faktor und dem y-Achsen-Abschnitt b: y = máx + b. Um die konkrete Funktionsgleichung zu erhalten, muss man die beiden Variablen m und b finden. Für m ist dabei die Punktsteigungsform der Geraden nützlich:

Abbildung 3: Diese Funktion weist dem Argument 192 den größeren Wert 211, dem Argument 96 aber den kleineren Wert 32 zu. Die Kurve ist am oberen und unteren Ende beschnitten.
Werte nur einmal ausrechnen
Die Geradengleichung für alle drei Kanäle jedes Pixels zu berechnen führt zu einer sehr großen Zahl von Funktionsaufrufen. Da es aber insgesamt lediglich 256 mögliche Werte für einen Kanal gibt, bietet es sich viel einfacher an, sie vorher auszurechnen und die Ergebnisse in einer Wertetabelle zu speichern. Dadurch fallen nur 256 Funktionsaufrufe an und die Operationen pro Pixel beschränken sich auf das dreimaliges Auslesen eines Index.
Als Container für die Wertetabelle eignet sich das »val-array« aus der Standard Template Library an (siehe Listing 1, Zeile 2). Dieser sehr leichte Container empfiehlt sich immer dann, wenn es um das Aufbewahren einer festen Anzahl von Werten geht.
Fenster, Dialoge und Regler
Das Projekt besteht aus vier Klassen, die auf der Qt-Bibliothek[1] aufbauen: »MainWindow«, »ContrastDialog«, »ValueDialog« und »CanvasView«. Die Klasse »ContrastDialog« stellt den Dialog mit den beiden Schiebereglern für Helligkeit und Kontrast dar. Bewegt der Benutzer einen der beiden QSlider, ruft er einen Qt-Slot auf, der die Wertetabelle neu berechnet und per Qt-Signal weiterschickt (Listing 1, Zeile 12).
|
Listing 1: |
|---|
01 void ContrastDialog::calcCurve() {
02 std::valarray<int> curve;
03 curve.resize(256);
04 QPoint p1(0 , 0 + brightnessSlider->value() - contrastSlider->value());
05 QPoint p2(255,255 + brightnessSlider->value() + contrastSlider->value());
06
07 double m = (p1.y() - p2.y()) / (double)(p1.x() - p2.x());
08
09 for (int i = 0; i < 256; ++i) {
10 curve[i] = QMIN(255, QMAX(0, int(i * m) + p1.y()));
11 }
12 emit curveChanged(curve);
13 }
|
Eine Strategie, um ein großes Bild beherrschbar im Speicher zu halten, ist das Zerteilen in kleine, meist quadratische Kacheln (Tiles). Das bringt eine Reihe von Vorteilen: Ein Filter kann bereits während der Verarbeitung des Gesamtbilds Kachel für Kachel bearbeiten und dabei Änderungen anzeigen. Außerdem müssen nur die veränderten Kacheln gesichert werden, um eine Änderung rückgängig machen zu können.
Die verwendete Kachelgröße ist ein Kompromiss zwischen Speicherbedarf und Verwaltungsaufwand: Sind die Kacheln sehr groß, sind pro Stück auch sehr viele Pixel zu speichern. Sind die Kacheln zu klein, wird der Verwaltungsaufwand relativ groß. Gimp[2] zum Beispiel verwendet per Default Kacheln mit 64 mal 64 Pixeln. Bei sehr großen Bildern müssen sich nicht alle Kacheln im Speicher befinden, sondern nur diejenigen, die das Programm gerade anzeigt oder bearbeitet.
Speicherklassen für Bilder
Qt bringt zwei Klassen mit, die Bilder im Speicher repräsentieren können: »QImage« und »QPixmap«. Die QPixmaps sind sehr schnell zu zeichnen, aber man kann ihre Pixel nicht direkt auslesen oder setzen. Änderungen an einem QPixmap sind nur durch einen QPainter möglich. Im Gegensatz dazu kann der Programmierer bei einem QImage direkt Pixel auslesen und schreiben.
Dafür ist das Zeichnen von QImages langsam, da ein solches dafür erst in ein QPixmap konvertiert werden muss. Auch wenn QPainter direkt Methoden zum Zeichnen von QImages bereithält, merkt der Anwender, dass Qt intern das Q-Image erst konvertiert: Ein QImage zu zeichnen dauert deutlich länger, als ein QPixmap anzuzeigen.
Um viele kleine Objekte auf dem Bildschirm darzustellen, von denen ein Großteil unsichtbar ist, gibt es in Qt das QCanvas. Darauf optimiert, nur wirklich geänderte Bereiche neu zu zeichnen, bietet es sich an, um eine große Anzahl von Kacheln aufzunehmen.
Qt bringt mehrere Klassen mit, um Objekte im Canvas anzuzeigen. Für eine Kachel eignet sich am ehesten das »QCanvasSprite«, das QPixmaps oder Animationen aus mehreren QPixmaps anzeigen kann. Um die Konvertierung von QImage nach QPixmap bei jedem Zeichenvorgang zu sparen, ist es sinnvoll, von jeder Kachel eine schnell anzeigbare Version in Form eines »QCanvasPixmapArrays« vorzuhalten.
Sprites und Kacheln
Beim Laden eines Bildes muss man zuerst feststellen, wie viele Kacheln in horizontaler und vertikaler Richtung es benötigt. Dazu teilt man die Breite und die Höhe des Bildes durch die Kantenlänge einer Kachel. Ergibt die Division einen Rest, muss der Programmierer in der entsprechenden Dimension noch eine Kachel mehr zur Verfügung stellen. Durch die Multiplikation beider Werte ergibt sich die Zahl der insgesamt benötigten Kacheln.
Beim QCanvas-Ansatz sind für jede Kachel drei Datenstrukturen nötig: ein »QImage« mit den Pixeldaten (Datenkachel), ein »QCanvasSprite« für die Position und ein »QCanvasPixmapArray« für die schnelle Darstellung (sichtbare Kachel). Die Funktion zum Laden eines Bildes bestimmt die Position jeder Kachel, kopiert aus dem kompletten Bild einen kachelgroßen Bereich an der Kachelposition in die Datenkachel und schreibt eine Kopie davon als QPixmap in die sichtbare Kachel.
Änderungen übernehmen
Beim Speichern des bearbeiteten Bildes muss das Programm die Kacheln wieder zu einem kompletten Bild zusammensetzen. Eine Methode zum Speichern erzeugt dafür zunächst ein QImage mit der fertigen Größe und setzt den Inhalt der Kacheln an ihrer Position in das große Bild ein. Dabei hilft die Qt-Methode »bitBlt()«, die Teile eines QImage in ein anderes kopiert. Eine Qt-Anwendung arbeitet Ereignisse im Mainloop (der Hauptschleife) ab. Das ist typisch für Programme, die nicht nur von oben nach unten durchlaufen, sondern auf Ereignisse (Events) reagieren. Der Mainloop in Qt verbirgt sich hinter dem Aufruf »qApp->exec()«.

Abbildung 4: Der Dialog zum Einstellen der Tonwertspreizung im Qt-Designer. Im rechten Fenster zeigt er die Signalhandler der GUI-Elemente, darunter die Methode »valueChanged()«.
Das Fenstersystem (bei gängigen Linux-Distributionen ist das X11) sendet Ereignisse wie Mausklicks, -bewegungen und Tastatureingaben an die Qt-Anwendung. Dort werden die Ereignisse in einer Schlange (Queue) zwischengespeichert. Solange die Schleife läuft, arbeitet sie eintreffende Ereignisse ab und leitet sie an die entsprechenden Objekte weiter.
Ereignisverarbeitung
Bewegt der Benutzer beispielsweise die Maus bei gedrückter Maustaste über einem QSlider, empfängt die Anwendung ein Ereignis, das der Mainloop an den QSlider übermittelt. Der QSlider ruft die Methode »mouseMoveEvent(QMouseEvent*)« auf.
In der Implementation dieser Methode sendet der QSlider« das Signal »valueChanged(int)« (Abbildung 4). Das Signal ruft alle Slots auf, die mit ihm verbunden sind. Erst nach der Ausführung aller Slots ist das Ereignis komplett verarbeitet und das nächste kommt dran. Dauert die Ausführung zu lange, bemerkt der Benutzer ein Stocken in der Bedienung der Software, da das von ihm gerade ausgelöste Ereignis warten muss. Deshalb sollte die GUI-Anwendung Ereignisse zügig verarbeiten.
Eine andere Möglichkeit, eine Anwendung reaktionsbereit zu halten, sind Threads. Dabei ruft man eine Funktion in einem Thread so auf, dass sie die aufrufende Funktion nicht blockiert, sondern sofort zurückkehrt. Ab diesem Zeitpunkt laufen aufgerufene und aufrufende Methode parallel. Greifen beide gleichzeitig auf Daten im Programm zu, kommt es zu Problemen, denen mit passenden Synchronisationsmechanismen zu begegnen ist. Das Beispiel in diesem Artikel benutzt einen einfachen Ansatz ohne Threads.
Bearbeiten im Hintergrund
Der Filter soll das Bild schon verändern, während der Anwender noch den QSlider bewegt. Alle Kacheln auf einmal bei dem Ereignis “Bewegen eines QSlider” zu bearbeiten bringt besonders auf langsamen Rechnern das Programm zum Stehen. Wird die anfallende Arbeit auf mehrere Durchläufe des Mainloop verteilt, kann das Programm die Ereignisse schneller verarbeiten.
Besitzt das Intervall der Timerklasse »QTimer« den Wert »0«, setzt der Timer sein Signal »timeout()« bei allen Durchläufen des Mainloop. Es bietet sich an, dieses Signal mit einem Slot zu verbinden, der nur einen Teil der Arbeit verrichtet. Verteilt sich die Arbeit auf fünf Teilaufgaben, muss der Slot wissen, welche davon er erledigen soll. Dazu ist eine Variable erforderlich, die die Nummer der anstehenden Teilaufgabe enthält. Als Teilaufgabe bei der Bildbearbeitung bietet sich eine Kachel an. Bewegt der Benutzer den Schieberegler, muss das Programm also entsprechende Vorkehrungen treffen, damit der Slot Kachel für Kachel abarbeiten kann:
- Die Wertetabelle, die der Slot als Argument erhält,
zwischenspeichern, da sie sowohl außerhalb des Slots als auch
nach der Abarbeitung des Slots weiter gebraucht wird. - Die Nummer der zu bearbeitenden Kachel auf null setzen.
- Den Timer starten.
Bearbeiten einer Kachel
Der Slot schreibt für jedes Pixel der Ausgangskachel ein Pixel in ein vorläufiges QImage. Die Kanalwerte der Pixel entnimmt das Programm der vorher berechneten Wertetabelle. Danach ersetzt der Slot nur die sichtbare Kachel durch das vorläufige QImage und zeigt sie durch »update()« des QCanvas an (siehe Listing 2, Zeile 13).
Nach jedem Durchlauf erhöht der Slot die Nummer der zu bearbeitenden Kachel, damit beim nächsten Durchlauf die nächste Kachel drankommt. Außerdem überprüft der Slot noch, ob bereits die letzte Kachel bearbeitet wurde, und stoppt in diesem Fall den Timer (siehe Listing 2, Zeile 16). <@99 L_Dreieck (E)>E
|
Listing 2: Slot: Bearbeite eine |
|---|
01 void CanvasView::processOneTile() {
02 QImage timg(TILESIZE,TILESIZE,32);
03 for (int y = 0; y < TILESIZE; ++y) {
04 for (int x = 0; x < TILESIZE; ++x) {
05 QRgb pixel = m_tiles[m_tileToProcess].pixel(x,y);
06 timg.setPixel(x,y, qRgb(m_lastTable[qRed(pixel)],
07 m_lastTable[qGreen(pixel)],m_lastTable[qBlue(pixel)]
08 ));
09 }
10 }
11 m_sequences[m_tileToProcess]->setImage(0, new QCanvasPixmap(timg));
12 m_sprites[m_tileToProcess]->setSequence(m_sequences[m_tileToProcess]);
13 canvas()->update();
14 ++m_tileToProcess;
15 if (m_tileToProcess == m_tiles.size()) {
16 m_tileTimer->stop();
17 }
18 }
|
Entschließt sich der Anwender dazu, die Änderungen zu übernehmen, muss das Programm den Filter auch auf die Datenkacheln anwenden. Dazu enthält es eine Methode »commit()«, die – wie Listing 2 demonstriert – alle Kanalwerte jedes Pixels bearbeitet. Im Gegensatz zu diesem Listing schreibt es aber nicht ein vorläufiges QImage, sondern direkt die Datenkachel.

Abbildung 5: Tonwertspreizung: Der ursprüngliche Helligkeitsbereich von 40 bis 190 wird auf den kompletten Bereich von 0 bis 255 abgebildet.
Entscheidet sich der Anwender dazu, die Änderungen nicht zu übernehmen, muss das Programm die ursprüngliche Version der Kachel wiederherstellen. Dazu schreibt die Methode »rollback()« in die sichtbare Kachel wieder eine Kopie der Datenkachel.

Abbildung 6: Das selbst geschriebene Bildbearbeitungsprogramm im Einsatz. Mit den beiden Schiebereglern lassen sich Helligkeit und Kontrast einstellen.
Tonwertspreizung
Digitale Bilder nutzen selten den gesamten Wertebereich zwischen 0 und 255 voll aus. Bei dunklen Bildern kommen kaum Kanalwerte um 255 vor, bei sehr hellen Bildern wird man selten Kanalwerte im unteren Bereich um 0 finden. Bei der Tonwertkorrektur wählt der Anwender einen Ausschnitt aus dem möglichen Wertebereich, den ein Algorithmus dann auf den gesamten möglichen Wertebereich streckt.
Kommen in einem Bild beispielsweise bevorzugt Kanalwerte zwischen 40 und 190 vor, kann es sinnvoll sein, nur diesen Ausschnitt aus dem Wertebereich zu verwenden und auf den gesamten möglichen Wertebereich zu spreizen (siehe Abbildung 5).
Ein Funktionsterm, der diese Spreizung vornimmt, benötigt als Eingabewerte den Anfang A und das Ende E des gewünschten Wertebereichs. Wieder reicht als einfaches mathematisches Modell die Funktion einer Geraden aus. Der Ansatz für den Funktionsterm führt – wie oben beschrieben – über zwei Stützpunkte.
Die Funktion soll dem Kanalwert A die Zahl 0 zuweisen und E den Wert 255. Daraus ergibt sich für die beiden Punkte: P<->1<$> (A, 0), P<->2<$> (E, 255). Wie beim Aufstellen des Funktionsterms für Helligkeit und Kontrast ist auch hier die Steigung der Geraden der Differenzenquotient dieser beiden Koordinatenpaare. Da keiner der beiden Punkte auf der y-Achse liegt, kann der y-Achsenabschnitt aber leider nicht einfach abgelesen werden, sondern muss erst durch Umformen der Geradengleichung nach b bestimmt werden.
Das komplette Listing liegt auf dem Server des Linux-Magazins[3]. Abbildung 6 zeigt das kompilierte Programm bei der Arbeit. Die durchgeführten Bildänderungen lassen sich über das »Datei«-Menü in der Bilddatei speichern.
Fazit
Mit wenigen Mitteln lassen sich eindrucksvolle Bildeffekte erzielen. Die einfachen Filterfunktionen für Helligkeit und Kontrast bilden das Fundament moderner Bildbearbeitung. Die vorgestellten Filteralgorithmen sind überschaubar, trotzdem ist das Resultat ansehnlich. Farbkorrekturen sind ähnlich aufgebaut wie die hier implementierten Funktionen (siehe auch[4]).
Gibt man dem Benutzer ein Werkzeug an die Hand, bei dem die anzuwendende Funktion frei definierbar ist, etwa durch Interpolation einer Kurve aus einigen gesetzten Punkten, sind viele weitere Korrektur- und Verfremdungsmöglichkeiten machbar. Das KDE-Bildbearbeitungsprogramm Krita[5] versucht mit ähnlichen Qt-Techniken wie den beschriebenen gegen den Spitzenreiter Gimp anzutreten. (ofr)
|
Infos |
|---|
|
[1] Qt-Bibliothek: [http://doc.trolltech.com/3.3/] [2] Gimp-Entwickler-Information: [http://developer.gimp.org] [3] Kompletter Quellcode: [https://www.linux-magazin.de/Service/Listings/2005/03/Qt/] [4] Techniken der Bildbearbeitung: [http://de.selfhtml.org/grafik/techniken.htm] [5] Krita: [http://www.koffice.org/krita] |
|
Der Autor |
|---|
|
Axel Jäger besucht in Frankfurt am Main ein Oberstufengymnasium mit dem Ziel, diesen Sommer die allgemeine Hochschulreife zu erlangen. Daneben arbeitet er beim deutschen Trolltech-Partner Basyskom und kümmert sich in seiner Freizeit um Qtforum.org. |





