Funktionale Bilder Seminar Programmiersprachen und Programmiersysteme“ ” Arbeitsgruppe Programmiersprachen und Übersetzerkonstruktion, Institut für Informatik, Technische Fakultät, Christian-Albrechts-Universität zu Kiel. Seminarbeitrag von Torsten Krause, Sommersemester 2010. Basierend auf Conal Elliott, Functional Images“. ” Betreuung durch Prof. Dr. Michael Hanus. 2 Inhaltsverzeichnis 1 Einführung 4 2 Hintergrund 2.1 Nichtfunktionale Bilder . . . 2.1.1 Rastergrafiken . . . . 2.1.2 Vektorgrafiken . . . . 2.1.3 Prozedurale Texturen 2.2 Funktionale Bilder . . . . . . 2.2.1 Polymorphie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 5 5 6 7 7 8 3 Funktionale Bildbeschreibung mit Pan 3.1 Schwarzweißbilder, Regionen und Bildkomposition 3.2 Graustufenbilder, Farbverläufe und Interpolation . 3.2.1 Rastergrafiken einbinden . . . . . . . . . . . 3.3 Punkt- und Bildtransformationen . . . . . . . . . . 3.3.1 Polare Transformationen . . . . . . . . . . . 3.4 Animationen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 11 13 15 18 21 22 4 Pan als eingebettete Sprache 4.1 Weitere Entwicklung . . . . . . . . . . . . . . . . . . . . . . . . . . 23 24 5 Fazit 25 6 Literatur 26 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 1 Einführung Der Grundgedanke hinter funktionalen Bildern besteht darin, Bilder als Funktionen über einem unendlichen, kontinuierlichen, zweidimensionalen Raum aufzufassen. Hier sollen im folgenden Kapitel einige der üblichen Ansätze der Bildbeschreibung dargestellt werden. Dabei wird erwähnt, welche Konzepte bei einer funktionalen Bildbeschreibung verwendet werden können. Des weiteren wird die von Conal Elliott in seinem Artikel Functional Images“ beschriebene Sprache Pan“ vor” ” gestellt. Hierbei handelt es sich um eine in Haskell implementierte, eingebettete, anwendungsspezifische Sprache zur funktionalen Bidbeschreibung. Bei der Implementierung von Pan wurde besonderes Augenmerk auf eine effiziente Ausführung gelegt. Nachdem in Kapitel 3 die Sprachmittel von Pan ausführlich beschrieben werden, wird im vierten Kapitel kurz auf wichtigsten Eigenheiten der Implementierung eingegangen. Eine weitere, bildbeschreibende, funktionale Sprache wird die von Jerzy Karczmarczuk in seinem Artikel Functional Approach to Texture Generation“ beschriebe” ne, in Clean eingebette Sprache Clastic“. Sie teilt sich mit Pan das Konzept ” der Bilder als Funktionen über einem unendlichen, kontinuierlichen, zweidimensionalen Raum, ihr Schwerpunkt liegt aber in der generierung von Texturen, also speziellen Bildern, die z.B. als Oberflächenfarbe für 3D-Objekte in Computergrafiksystemen verwendet werden. Hier wird der Schwerpunkt auf fortgeschrittene Ideen wie periodische Wiederholungen und Rauschen als Quelle für Bildeffekte gelegt. Die grundlegenden Konzepte sind allerdings die selben, die auch Pan verwendet, so dass dise problemlos am Beispiel von Pan gezeigt werden können. 4 2 Hintergrund 2.1 Nichtfunktionale Bilder Bilder lassen sich am Computer in vielfältiger Weise beschreiben und darstellen. Die gänigsten Beschreibungsformen sind Raster- und Vektorgrafiken. 2.1.1 Rastergrafiken Die einfachste Form der Darstellung ist ein endliches, zweidimensionales Feld das mit Farbinformationen gefüllt wird. Handelt es sich bei der Farbinformation lediglich um Wahrheitswerte, so kann damit ein Schwarzweißbild dargestellt werden. In diesem Fall nennt man ein solches Feld eine Bitmap“. Die ist ein Begriff ” der sich mittlerweile auch im Falle anderer Farbinformationen durchgesetzt hat. Beispielsweise könnte mit Farbinformationen aus dem Wertebereich {0, . . . , 255} eine 8-Bit-Graustufen-Bitmap gefüllt werden. Analog könnte man ein Graustufenbild auch mit kontinuierlichen Werten aus dem Intervall [0, 1] darstellen. Eine farbige Bitmap kann man mit Rot-Gelb-Blauen (RGB) Farbinformationen aus dem dreidimensionalen Intervall [0, 1]3 befüllen. Derartige Felder werden auch als Rastergrafik“ bezeichnet. ” In einer Bitmap wird ein Bild also über eine endliche Menge von Farbinformationen an fest vorgegebenen Positionen, die den Feldeinträgen entsprechen, definiert. Für jede Position muss explizit ein Farbwert angegeben werden. Transformationen sind nicht möglich. Will man ein Bitmap ändern, so muss mit einem externen Programm eine neue Bitmap erstellt und für jede Position ein neuer Farbwert errechnet werden. Aufgrund der einfachen Datenstruktur hinter einer Bitmap eignet sich diese Darstellung allerdings sehr gut, um Bilder zu speichern. Es existiert eine Fülle von Dateiformaten in denen solche Bitmaps abgespeichert werden können. Sie eigenen sich besonders zur Ausgabe auf gerasterten“ Ausgabegeräten wie Monitoren oder ” Druckern. Die obige Feststellung, dass verschiedene Arten von Bildern als Bitmaps über unterschiedliche Arten von Farbinformationen aufgefasst werden können, kann in einer Bildbeschreibungssprache besonders elegant ausgenutzt werden, wenn diese Sprache polymorph ist. 5 2 Hintergrund 2.1.2 Vektorgrafiken Ein anderer Ansatz der Bilddarstellung in Computern sind die sogenannten Vektorgrafiken. Hierbei werden Bilder als Folge von einfachen Zeichenoperationen dargestellt. Solche Zeichenoperationen können beispielsweise einzelne Linien und (geschlossene) Linienzüge, Kreise und Ellipsen, Rechtecke und andere Vielecke oder Pfade sein. Alle diese Zeichenoperationen nehmen kontinuierliche Koordinaten als Parameter. Sie können auch mit weiteren Parametern, etwa der Linienstärke und -farbe, ergänzt werden. Will man eine Vektorgrafik transformieren, so muss mit einem externen Programm lediglich die Folge von Zeichenoperationen manipuliert werden. Um eine Vektorgrafik zu speichern muss die Folge von Zeichenoperationen geeignet kodiert werden. Ein Beispiel ist das XML-basierte, also baumartige Format Scalable Vector Graphics (SVG). Wird in einem Knoten zum Beispiel die aktuelle Zeichenfarbe geändert, so hat dies Auswirkung auf alle darunterliegenden Knoten. Zur Interpretation einer SVG-Grafk ist also wenigstens ein Kellerautomat notwendig. Eine Vektorgrafik lässt sich auf einem Ausgabegerät mit kontinuierlichen Koordinaten, wie einem Plotter, direkt ausgeben. Für eine Ausgabe auf gerasterten Geräten muss die Vektorgrafik selbst gerastert werden, dass heisst ein Interpreter, der eine diskrete Rasterkoordinate und die Vektorgrafik als Eingabe bekommt, führt die Folge von Zeichenoperationen aus und entscheidet, welcher Farbwert an der übergebenen Koordinate gesetzt wird. Pfade werden in Vektorgrafiken häufig durch Bézierkurven oder Splines dargestellt. Dabei handelt es sich um parametrisierte Beschreibungen von Kurven, also Funktionen. Die letzten beiden Feststellungen motivieren es, Bilder nicht als zu interpretierende Folge von Zeichenoperationen anzusehen, sondern direkt als Funktionen zu modellieren. Ein weiteres Beispiel für ein Vektorgrafiksystem ist die Sprache Postscript (PS). Hierbei handelt es sich um eine sogenannte Seitenbeschreibungssprache, also um eine Sprache, die das Aussehen einer (zu druckenden) Seite, im weiteren Sinne also eines Bildes, beschreibt. Eine Postscript-Datei ist dabei ein Programm, das auf einer abstrakten, zustandsbehafteten Kellermaschine ausgeführt wird. Diese ist zusätzlich mit einer Zeichentabelle ausgestattet, wodurch Turingvollständigkeit und damit ein universeller Beschreibungsmechanismus erreicht wird. Postscriptprogramme werden als Folgen von Anweisungen in Präfixnotation geschrieben, die den Zustand der Kellermaschine modifizieren. Konzeptionell ist Postscript also eine imperative Sprache. 6 2.2 Funktionale Bilder 2.1.3 Prozedurale Texturen In der Computergrafik werden Bilder, mit denen die Oberflächen von dreidimensionalen Objekten als ortsabhängige Farbe“ eingefärbt werden, als Textur“ be” ” zeichnet. Der Vorgang, eine Oberfläche mit einer Textur zu überziehen, wird als texture mapping“ bezeichnet. ” Früher war es üblich, die dazu verwendeten Texturen als Rastergrafiken zu speichern und zum texture mapping in den Grafikspeicher zu laden. In heutigen Grafikanwendungen ist dies wegen des umfangreichen Speicherbedarfs oft nicht mehr praktikabel. Texturen werden daher vermehrt zur Laufzeit berechnet. Man spricht in diesem Zusammenhang von prozeduralen Texturen“ oder shadern“. ” ” Ein shader ist eine Rechenvorschrift im mathematischen Sinne, die Koordinaten als Eingabe bekommt und einen Farbwert als Ausgabe liefert, also (vereinfacht) ein Abbildung R2 7→ [0, 1]3 . Shader werden in sogenannten Shadersprachen geschrieben. Diese können beispielsweise Assemblersprachen sein, wie etwa die ARB – OpenGL Assembly Lan” guage“ die verwendet wird, um die ebenfalls shader“ genannten Hardwareein” heiten moderner Grafikprozessoren anzusteuern. Andere Vertreter sind eher C” artig“ wie die OpenGL Shading Language“ die nach ARB übersetzt wird oder ” die RenderMan Shading Language“ (RSL) die Teil der RenderMan Interface ” ” Specification“ der Firma PIXAR ist. Auch wenn Programme der typischen Vertreter der Shadersprachen als Anweisungsfolgen notiert werden, sind sie Seiteneffektfrei. Es gibt also auch keinen Programmstatus und damit fallen sie weder unter die typische Definition einer imperativen, noch einer deklarativen Programmiersprache. 2.2 Funktionale Bilder In der Beschreibung der nichtfunktionalen Bilder hat sich gezeigt, dass es durchaus sinnvoll sein kann, Bilder direkt als Funktionen darzustellen. Verschiedene Arten von Bildern können dann gleichartig behandelt werden, wenn Polymorphie unterstützt wird. Wenn der Grundgedanke hinter funktionalen Bildern – wie in der Einleitung erwähnt – darin besteht, Bilder als Funktionen über einem unendlichen, kontinuierlichen, zweidimensionalen Raum aufzufassen, dann ist ein Bild in einer funktionalen, polymorphen Sprache nichts weiter als eine Abbildung des Typs type Image α = Point → α. Hierbei ist Point der Typ von Koordinaten im betrachteten Raum. Für Bilder sind dies zweidimensionale kartesische Koordinaten 7 2 Hintergrund type Point = (Float, Float) oder Polarkoordinaten type PolarPoint = (Float, Float). Sinnigerweise bietet man dann Funktionen fromPolar :: PolarPoint → Point und toPolar :: Point → PolarPoint an. Übliche Operationen auf Bildern sind die folgenden: ◦ Komposition: Erstellen neuer Bilder aus vorhandenen Bildern. Da Bilder Funktionen sind, entspricht dies einer Funktionskomposition. ◦ Transformation: Die Veränderung der Lage oder Form eines Bildes kann durch eine Funktion realisiert werden, die die Koordinaten modifiziert, bevor auf diese das Bild appliziert wird. ◦ Interpolation und Gradienten: Farbverläufe zwischen fest gewählten Bezugspunkten. 2.2.1 Polymorphie Wie in Abschnitt 2.1 erwähnt, kann es sich anbieten Polymorphie auszunutzen, um verschiedene Arten von Bildern auf gleiche Art verarbeiten zu können. Der obige Typ Image α ist daher im Zielbereich polymorph bezüglich der Farbinformation gestaltet. Als konkrete Bildtypen bieten sich beispielsweise die folgenden an: ◦ Image Bool: Verwendet man die Wahrheitswerte als Farbinformation können damit einerseits Schwarzweißbilder definiert werden. Andererseits können hiermit auch die Bereiche definiert werden, in denen ein Bild dargestellt werden soll. Ein derariges Bild wirkt dann als Filter“ für ein anderes Bild. ” Um dies zu verdeutlichen kann man ein Typsynonym type Region = Image Bool einführen. Solche Regionen können dann für die Bildkomposition verwendet werden, indem man aus einfachen Regionen komplexe Regionen erstellt und anschließend ein anderes Bild mit solchen komplexeren Regionen filtert. Da eine Region als Menge von Bildpunkten aufgefasst werden kann, in denen gerade nicht weggefiltert wird, können zum Erstellen komplexerer Regionen die üblichen Mengenoperationen wie Schnitt, Vereinigung oder Komplement verwendet werden. ◦ Image [0, 1]: Verwendet man als Farbinformation relle Werte aus dem Intervall [0, 1], so lassen sich damit beispielsweise Bilder in Graustufen darstellen. Diese können ähnlich wie Regionen als Filter auf andere Bilder angewandt werden. Hierbei wird die Graustufe als Maß dafür verwendet, wie viel von 8 2.2 Funktionale Bilder dem zu filternden Bild verwendet wird. Hiermit kann also ein Bild ausgeblendet werden. Verwendet man diese Technik mehrfach, kann ein Bild mit einem anderen gemischt werden. Handelt es sich dabei um vollflächige, einfarbige Bilder, so lässt sich mit einer solchen Filterung ein Farbverlauf beschreiben. Handelt es sich um Bilder, die nur an diskreten Punkten definiert sind, beispielsweise um eine aus einer Datei geladene Rastergrafik, so kann der Zwischenraum mit geeigneten Farbverläufen interpoliert werden. ◦ Image [0, 1]k : Verwendet man als Farbinformation relle Werte aus dem mehrdimensionalen Intervall [0, 1]k , können hiermit farbige Bilder definiert werden. Das k muss dabei je nach verwendetem Farbraum gewählt werden. Mit k = 3 lassen sich RGB-Bilder beschreiben. Mit k = 4 lassen sich BRGABilder beschreiben. Dabei handelt es sich um RGB-Bilder, die um einen zusätzlichen Wert, den sogenannten Alphakanal“, erweitert sind. Dieser ” wird als Transparenzwert interpretiert. Üblich ist es, die Farben so anzugeben, dass kein Wert der drei Farbkanäle höher ist, als der Wert des Alphakanals (premultiplied alpha). Die völlig transparente Farbe ist dann eindeutig als transparent :: Image [0, 1]4 transparent = (0,0,0,0) gegeben. Andere Grundfarben sind beispielsweise white, black, red :: Image [0, 1]4 white = (0,0,0,1) black = (1,1,1,1) red = (0,0,1,1). Das hier betrachtete, funktionale Bildsystem Pan ist nur im Zielbereich Polymorph. Polymorphie kann aber auch im Wertebereich sinnvoll sein. Beispielsweise könnte man Point × Time als Definitionsbereich für animierte Bilder verwenden, oder einen eindimensionalen Definitionsbereich verwenden um mit den selben funktionalen Mitteln auch Audiosignale zu definieren. 9 3 Funktionale Bildbeschreibung mit Pan In Pan werden einige Typsynonyme für die bisher beschriebenen Dinge verwendet. Für die [0, 1]-Intervalle wird type Frac = Float benutzt, wobei die Zusicherung, dass die Werte tatsächlich nur im [0, 1]-Intervall liegen zur Schnittstellendefinition gehört und nicht im Programcode selbst geprüft wird. Für BRGA-Farben existiert der Typ type Colour = (Frac, Frac, Frac, Frac) und für farbige Bilder der Typ type ImageC = Image Colour. Es werden einige Liftingoperationen generell definiert, um andere Operationen damit einfach ausdrücken zu können. Beispielsweise kann mit lift3 :: (α → β → γ → δ) → (p → α) → (p → β) → (p → γ) → (p → δ) lift3 h f1 f2 f3 p = h (f1 p) (f2 p) (f3 p) eine Funktion condR definiert werden, mit der in Abhängigkeit einer Region eines von zwei Bildern angezeigt wird: condR :: Region → Image α → Image α → Image α condR = lift3 (λ a b c → if a then b else c) Die Funktion const kann als nullstelliges Lifting betrachtet werden, mit dem beispielsweise das komplett leere Bild emptyI :: ImageC emptyI = const transparent oder andere vollflächige, einfarbige Bilder definiert werden können: whiteI, blackI, redI :: ImageC whiteI = const white blackI = const black redI = const red 10 3.1 Schwarzweißbilder, Regionen und Bildkomposition Abbildung 3.1: disk1 3.1 Schwarzweißbilder, Regionen und Bildkomposition Schwarzweißbilder können als Regionen interpretiert werden. Diese können als Punktmengen aufgefasst werden. Eine einfache Region ist eispielsweise disk :: Float → Region disk r p = distO p < r disk1 :: Region disk1 p = disk 1 wobei distO den Abstand zwischen einem gegebenen Punkt und dem Nullpunkt der Zeichenebene berechnet. Hier zeigt sich ein Vorteil funktionaler Bilder, denn disk kann über einen Parameter variiert werden. Abbildung 3.1 zeigt die Region disk1. Mit einer solchen Region und condR kann dann ein einfaches Bild erstellt werden. Beispielsweise zeigt Abbildung 3.2 das Bild, das als ybDisk :: imageC ybDisk = condR disk1 blueI yellowI definiert ist. Eine etwas kompliziertere Region ist beispielsweise altRingsR :: Region altRingsR p = even ⌊ distO p ⌋ Diese ist in Abbildung 3.3 zu sehen. Hier zeigt sich ein weiterer Vorteil funktio- 11 3 Funktionale Bildbeschreibung mit Pan Abbildung 3.2: ybDisk naler Bilder, denn altRingsR ist unendlich groß. Nun wäre es sicher mühsam für jede Form, die eine Region haben kann, eine entsprechende boolsche Funktion zu schreiben. Pan besitzt daher Funktionen für die üblichen Mengenoperationen, um aus einfachen Regionen neue Regionen zu kombinieren. universeR, emptyR :: Region compR :: Region → Region (∩), (∪), (⊕), (\) :: Region → Region → Region universeR = const True emptyR = const False compR = lift1 not (∩) = lift2 and (∪) = lift2 or (⊕) = lift2 6= r \ r ′ = r ∩ compR r ′ Die Abbildungen 3.4 und 3.5 zeigen zwei komplexere Regionen, die wie folgt definiert sind: ringR :: Region ringR = disk1 \ disk 0.5 complexR :: Region 12 3.2 Graustufenbilder, Farbverläufe und Interpolation Abbildung 3.3: altRingsR complexR (x, y) = altRingsR (x + 2, y) ⊕ altRingsR (x − 2, y) Ein wichtiger Spezialfall für die Anwendung von Regionen ist das Zurückführen der prinzipiell unendlich großen funktionalen Bilder auf einen endlichen Bereich. In vielen Grafiksystemen wird hier eine rechteckige Boundingbox verwendet, die definiert, in welchem Bereich das Bild angezeigt wird. Bei funktionalen Bildern ist man hier nicht auf eine rechteckige Form beschränkt. Das Einschränken eines Bildes auf einen endlichen Bereich geschieht einfach mit einer endlichen Region, über die mittels crop alles Außenliegende durch das leere Bild ersetzt wird. crop :: Region → ImageC → ImageC crop r i =cond r i emptyI 3.2 Graustufenbilder, Farbverläufe und Interpolation Regionen haben dan Nachteil harter Kanten. Oft ist ein solches aussehen aber nicht gewünscht. Um einen weichen Übergang zu bekommen muss man von Schwarzweißzu Graustufenbildern übergehen. Beispielsweise können die alternierenden Ringe aus Bild 3.3 mit sanften Übergängen folgendermaßen definiert werden: waves :: image Frac waves p = (1 + cos(π × distO p)) / 2 13 3 Funktionale Bildbeschreibung mit Pan Abbildung 3.4: ringR Abbildung 3.6 zeigt das so entstehende Wellenmuster. Ein solcher Graustufenverlauf kann nun als Grundlage für einen Farbverlauf genutzt werden, indem der Graustufenwert benutzt wird, um zwischen zwei Farben zu interpolieren. Dazu wird in Pan eine Funktion lerpI verwendet, die folgendermaßen definiert ist: lerpC :: Frac → Colour → Colour → Colour lerpC w (b1 , g1 , r1 , a1 ) (b2 , g2 , r2 , a2 ) = (h b1 b2 , h g1 g2 , h r1 r2 , h a1 a2 ) where h x1 x2 = w × x1 + (1 − w) × x2 lerpI :: Image Frac → ImageC → ImageC → ImageC lerpI = lift3 lerpC Abbilddung 3.7 zeigt das Bild ybWaves :: ImageC ybWaves = lerpI waves blueI yellowI Die Funktion lerpI interpoliert linear zwischen zwei Bildern und wird punktweise auf die Funktion lerpC zurückgeführt, die in Abhängigkeit eines Frac-Wertes zwischen zwei Farben interpoliert. Der vorher definierte Graustufenverlauf ist die Quelle für die Frac-Werte, mit denen lerpC aufgerufen wird. Eine ähnliche Funktion ist overI mit der zwei Bilder übereinandergelegt werden können (overlay). Hier wird kein zusätzlicher Graustufenwert benötigt. Wieviel 14 3.2 Graustufenbilder, Farbverläufe und Interpolation Abbildung 3.5: complexR vom unteren Bild zu sehen ist, hängt lediglich vom Transparenzwert des darüberliegenden Bildes ab. Auch overI ist punktweise mittels einer weiteren Funktion definiert. Die Funktion overC berechnet das Übereinanderlegen für zwei Farben. overC :: Colour → Colour → Colour (b1 , g1 , r1 , a1 ) ‘overC‘ (b2 , g2 , r2 , a2 ) = (h b1 b2 , h g1 g2 , h r1 r2 , h a1 a2 ) where h x1 x2 = × x1 + (1 − a1 ) × x2 overI :: ImageC → ImageC → ImageC overI = lift2 overC Abbildung 3.8 zeigt eine einfache Überlagerung. Das entsprechende Bild ist wie folgt definiert: ybWavesRed :: ImageC ybWavesRed = overI (const (0,0,0.5,0.5)) ybWaves 3.2.1 Rastergrafiken einbinden Im Gegensatz zu anderen Grafiksystemen, die selbst rasterbasiert sind, ist es nicht trivial, Rastergrafiken als funktionales Bild zu laden. Während Rastergrafiken begrenzt und ortsdiskret sind, sind funktionale Bilder prinzipiell unbegrenzt groß und haben einen kontinuirlichen Definitionsbereich. Dennoch unterstützt Pan das 15 3 Funktionale Bildbeschreibung mit Pan Abbildung 3.6: waves Einbinden von Rastergrafiken. Die interne Darstellung geschieht über den Datentypkonstruktor data Array2 α = Array2 Int Int ((Int,Int) → α) Generell können also Rastergrafiken über verschiedene Wertebereiche eingebunden werden. Typischerweise wird dies aber Colour sein. Der Konstruktor Array2 nimmt neben der Größe des geladenen Bildes in x- und y-Richtung eine Funktion auf, die diskrete Punkte in den entsprechenden Wertebereich abbildet. Eine Rastergrafik Array2 n m f ist also für alle x-Werte 0 ≤ x < n und alle y-Werte 0 ≤ y < m definiert und f (x, y) liefert den Farbwert an der entsprechenden Position. Für jedes unterstützte Bildformat muss eine entsprechende Funktion bereitgestellt werden, die das Format (z.B. aus einer Datei geladen) lesen kann und einen entsprechenden Array2 α-Wert zurückgibt. Hier wurde ein Funktionstyp und kein Array-Datentyp gewählt, um das Bild nicht tatsächlich als Array laden zu müssen, sondern nur nach Notwendigkeit die Farbinformationen zu laden. Um nun in den unbeschränkten Wertebereich eines funktionalen Bildes zu gelangen, werden lediglich alle außerhalb der Rastergrafik liegenden Punkte mittels crop auf die leere Grafik zurückgeführt. Eine entsprechende Region wird mit den Bildgrößeninformationen und der Funktion inBounds generiert: inBounds :: Int → Int → Region inBounds w h (x, y) = 16 3.2 Graustufenbilder, Farbverläufe und Interpolation Abbildung 3.7: ybWaves 0 ≤ x ∧ x ≤ fromInt (w − 1) ∧ 0 ≤ y ∧ y ≤ fromInt (h − 1) Um in den kontinuierlichen Wertebereich zu gelangen, werden die Farbinformationen für alle Punkte, die nicht einem der diskreten Punkte des Bildes entsprechen, bilinear zwischen den vier umgebenden Punkten interpoliert: bilerpC :: Colour → Colour → Colour → Colour → (Frac, Frac)→ Colour bilerpC ll lr ul ur (wx, wy) = lerpC wy (lerpC wx ll lr) (lerpC wx ul ur) bilerpArray2 :: ((Int,Int) → Colour) → ImageC bilerpArray2 f (x, y) = let i=⌊x⌋ j=⌊y⌋ wx = x − fromInt i wy = y − fromInt j in bilerpC (f (i, j )) (f (i + 1, j )) (f (i, j + 1)) (f (i + 1, j + 1)) (wx, wy) Die bilineare Interpolation bilerpC wird auf mehrere Anwendungen der eindimen- 17 3 Funktionale Bildbeschreibung mit Pan Abbildung 3.8: ybWavesRed sionalen Interpolation lerpC zurückgeführt. Die Funktion bilerpArray2 berechnet zu einem Punkt innerhalb des Bildes die vier umliegenden Punkte der Rastergrafiken und führt die bilineare Interpolation mit den entsprechenden Farbwerten aus der Rastergrafik durch. Abbildung 3.9 zeigt eine solche bilineare Interpolation aus vier verschiedenen Farbwerten: testBilerpC :: ImageC testBilerpC = bilerpC black red blue white Um nun eine komplette Rastergrafik zu rekonstruieren werden die vorhandenen Funktionen zusammengesetzt: reconstruct :: Array2 Colour → ImageC reconstruct (Array2 n m f ) = crop (inBounds n m) (bilerpArray2 f ) 3.3 Punkt- und Bildtransformationen Die üblichen Bildtransformationen in der Copmutergrafik sind Rotation, Translation, Skalierung, Scheerung und Projektion. Alle diese werden normalerweise als Matrix dargestellt. Mehrere Transformationen werden mittels Matrixmultiplikation kombiniert und ihre Anwendung entspricht der Multiplikation der Transformationsmatrix mit einem Punkt. Die Matrixform wird hier als kompakte Darstellung für lineare, affine oder projektive Abbildungen verwendet. In der funktionalen 18 3.3 Punkt- und Bildtransformationen Abbildung 3.9: testBilerpC Bildbeschreibung ist ein solcher Umweg“ über Matrizen aber unnötig. In Pan ” werden die entsprechenden Operationen direkt als Punkttransformation definiert. Für Transformationen wird wieder ein Typsynonym eingeführt. type Transform = Point → Point Einige Beispiele für Punkttransformationen sind die folgenden Funktionen: translateP, scaleP :: (Float, Float) → Transform translateP (dx, dy) (x, y) = (x + dx, y + dy) scaleP (sx, sy) (x, y) = (x × sx, y × sy) rotateP :: Float → Transform rotateP θ (x, y) = (x × cos θ − y × sin θ, y × cos θ + x × sin θ) Ideal wäre es, wenn man eine beliebige Punkttransformation trans mit einem Bild image kombinieren könnte, beispielsweise als Hintereinanderausführung image . trans. Hier werden aber zunächst die Punkte transformiert und dann von dem Bild eingefärbt. Skaliert man ein Bild mit dem Vektor (2, 2), so wird (x, y) zunächst auf (2 · x, 2 · y) abgebildet und bekommt dann den Farbwert des Bildes an dieser Stelle. (image . scaleP (2, 2)) (x, y) = image (2 · x, 2 · y) Das heißt an der Stelle (x, y) wir der Punkt angezeigt, der vorher bei (2 · x, 2 · y) lag. Das Bild wird also nicht um den Faktor 2 vergrößert, sondern verkleinert. Dies 19 3 Funktionale Bildbeschreibung mit Pan entspricht aber kaum der Intuition beim Anwenden von scaleP (2, 2). Dies liegt daran, dass die Transformation vor dem Bild auf die Punkte angewandt wird. Was eigentlich der Intuition entspräche wäre der Zusammenhang (image . scaleP (2, 2)) (x, y) = image ( 21 · x, 1 2 · x) Hieran erkennt man, dass eine beliebige Punkttransformation trans mit einem Bild image kombiniert werden können, indem image . trans −1 berechnet wird. Die Umkehrung kann allerdings nicht vom Programmiersystem bestimmt werden und muss auch nicht immer existieren, andererseits wird die Originalabbildung trans gar nicht benötigt, so dass es genügt, die Inverse anzugeben. Für die oben angegebenen affinen Transformationen kann die Inverse auf die ursprünlichen Punkttransformation zurückgeführt werden. Da nun immer das Bild bekannt sein muss, um image . trans −1 zu definieren, können nur noch Bildtransformationen angegeben werden. Diese werden in Pan als Filter bezeichnet. type Filter α = Image α → Image α type FilterC = Filter Colour translate, scale :: (Float, Float) → Filter α translate (dx, dy) (x, y) im = im . translateP (− dx, − dy) scale (sx, sy) (x, y) im = im . scaleP (− sx, − sy) rotate :: Float → Filter α rotate θ (x, y) im = im . rotateP (− θ) Ein Vorteil der Darstellung eines Filters als Funktion ist es, dass man nicht auf lineare, affine und projektive Abbildungen beschränkt ist. Filter müssen nicht einmal invertierbar sein, aber wenn sie es sind, ist es oft sinnvoll, zunächst die inverse Transformation für Punkte zu definieren und den Filter darauf zurückzuführen. Der folgende Filter rotiert Punkte in Abhängigkeit ihrer Entfernung zum Nullpunkt. Der zusätzliche Parameter gibt an, bei welchem Abstand eine vollständige Umdrehung erreicht werden soll. swirlP :: Float → Transform swirlP r p = rotate (distO p × 2 × π / r) p swirl :: Float → Filter α swirl r p = im . swirlP (− r) Abbildung 3.10 zeigt diesen Filter angewandt auf eine einfache Region, die einen vertikalen Streifen darstellt. vstrip :: Region vstrip (x, y) = | x | ≤ 1 2 vstripSwirl :: Region vstripSwirl = swirl 1 vstrip 20 3.3 Punkt- und Bildtransformationen Abbildung 3.10: vstripSwirl An diesem Beispiel zeigt sich erneut der Vorteil polymorpher Definitionen, denn die Filter können problemlos auf Regionen und farbige Bilder angewandt werden. 3.3.1 Polare Transformationen In der Definition von swirlP fällt auf, dass diese Transformation nur den Winkel eines Punktes modifiziert, nicht aber dessen Abstand zum Nullpunkt. Es ist also ggf. günstiger Transformationen in Polarkoordinaten zu definiere. Um dies komfortabel berwerkstelligen zu können bietet Pan die Funktion polarXf : polarxf :: Transform → Transform polarxf t = fromPloar . t . toPolar Hiermit kann swirlP auch durch eine Transformation für Punkte in Polarkoordinaten definiert werden: swirlP :: Float → Transform swirlP r p = (λ (ρ, θ) → (ρ, θ + ρ × 2 × π / r)) 21 3 Funktionale Bildbeschreibung mit Pan Abbildung 3.11: swirlT 3.4 Animationen Pan unterstützt neben statischen Bildern auch Animationen. Hier werden die gleichen Konzepte benutzt wie bei der Darstellung von Bildern. Animationen sind zeitabhängige Bilder. Die Zeitkoordinate ist prinzipiell unbeschränkt und kontinuierlich. Dies motiviert die folgenden beiden Typsynonyme: type Time = Float type Anim α = Time → Image α Um eine Animation zu definieren muss man lediglich eine entsprechende Funktion definieren. Mit der Region, die die positive Halbebene in x-Richtung darstellt, und der swirl-Funktion, kann dann beispielsweise das in Abbildung 3.11 gezeigte, zeitabhängige Bild erstellt werden. xPos :: Region xPos (x, y) = x > 0 swirlT :: Anim Bool swirlT = (λ t → swirl (t2 ) xPos) 22 4 Pan als eingebettete Sprache Die von Conal Elliott beschriebene, domänenspezifische Sprache Pan wurde von ihm in Haskell implementiert und bis etwa 2000 aktiv gepflegt. Pan ist insofern auch eine eingebettete Sprache, als das Haskell hier nicht nur um Methoden der Bildbeschreibung erweitert wurde, sondern auch als Übersetzer verwendet wird, um in Pan beschriebene Bilder für verschiedenen Zielsysteme zu übersetzen. Hierzu zählt unter anderem das Erzeugen von Standalone Programmen, die ein Bild anzeigen können, Code der im Webbrowser Internet Explorer“ ausgeführt werden ” kann und Plugincode für das Programm Adobe PhotoShop. Alle Varianten basieren auf DirectX zur Visualisierung und waren daher nur für Microsoft Windows verfügbar. Pan legt den Schwerpunkt auf die funktionale Beschreibung von Bilden und deren effizienten Darstellung. Um eine effiziente Darstellung zu gewährleisten, werden die funktionalen Bilder zunächst mit einem optimierenden Compiler nach C++, welches als Zwischensprache dient, übersetzt. In einem weiteren Übersetzungsvorgang wird dann Code für das jeweilige Zielsystem erzeugt. Optimierender Compiler heißt hier, dass nicht eifach ein Haskellübersetzer wie der GHC zum Einsatz kommt. Das PanSystem hat stattdessen einen eigenen Haskell-Compiler, der für funktionale Bilder optimiert ist. In Pan geschriebene Bilder werden also nicht einfach als HaskellSprachkonstrukte verwendet. Hier unterscheidet sich Pan von vielen anderen eingebetteten Sprachen, die eher eine API für die Hostsprache darstellen und zur Laufzeit interpretiert werden. Dies hat den Nachteil, dass nicht mehr alle Sprachkonstrukte der Hostsprache in der anwendungsspezifischen Sprache zur Verfügung stehen. Im vorherigen Kapitel wurde beispielsweise weder Rekursion noch der Listendatentyp verwendet, daher ist hierauf beim Design des Übersetzers auch nicht besondere Rücksicht zu nehmen. Der GHC wird nicht als Übersetzer verwendet, da die Autoren von Pan etliche Optimierungen manuell gefunden und zunächst mit den rewrite rules des GHC implementiert hatten. Diese kollidierten aber mit den sonstigen Optimierungen des GHC und brachten daher nicht die gewünschten Verbesserungen. Im Pan-System wird daher der Haskellcode der funktionalen Bilder zunächst in einen Syntaxbaum umgewandelt, wie es auch ein Interpreter tun würde. Auf diesem wird während des Erstellens eine Bottom-Up-Analyse durchgeführt, um die gewünschten Optimierungen zu realisieren. 23 4 Pan als eingebettete Sprache Ein großer Vorteil dieser Methode ist, dass der Syntaxbaum für jedes darzustellende Bild erzeugt wird und nicht mehr modifiziert werden muss. Dies wird folgendermaßen zur Performanceverbesserung genutzt: Bei einem interpretierendem System muss für jeden Punkt, der angezeigt werden soll, ein Funktionsaufruf stattfinden, der den Farbwert aus der Bildfunktion ermittelt (i.A. zwei ineinandergeschachtelte for“-Schleifen). Im Pan-System liegt diese Funktion bereits als Syntaxbaum vor. ” Dieser wird in den Code, der die beiden Schleifen realisiert, eingesetzt, welcher damit partiell ausgewertet werden kann. Hier kommt insbesonder loop-unrolling“ ” zum Tragen. Erst der so optimierte Code wird dann nach C++ übersetzt. 4.1 Weitere Entwicklung Wie erwähnt wurde das Pan-System nur bis ins Jahr 2000 gepflegt. Die Sprache Pan wurde mehrfach neuimplementiert. Eine Implementierung Pan#“ portierte ” das Pan-System inklusive Compiler nach .NET“. Diese Version benutzte nicht ” Haskell, sondern eine eigene, Haskell-ähnliche Spache als Hostsprache. Pan wurde geringfügig erweitert und mit Pan# konnten auch Audiosignale (d.h. Bilder“ mit ” eindimensionelem Definitionsbereich) beschrieben werden. Eine weitere, bis etwa 2005 aktiv gepflegte Neuimplementierung ist Panic“; eben” falls für Haskell. Panic verwendet zur Bilddarstellung das Toolkit wxWidgets“ ” und ist damit Plattform- und Betriebssystemunabhängig. Wie die Orginalimplementierung ist Panic eine eingebettete Sprache, bei der die funktionalen Bilder nicht nur interpretiert werden, sondern übersetzt werden. Hier kommt allerdings der Haskell-Übersetzer GHC zum Einsatz. Der Quelltext eines funktional beschriebenes Bildes wird geladen und an den GHC zur Üersetzung übergeben. Das dabei entstehenden Haskellmodul wird dann dynamisch geladen und an wxWidgets übergeben. Eine Methode effect innerhalb des erzeugten Moduls wird von wxWidgets aufgerufen, um das eigentliche Bild zu erzeugen. 24 5 Fazit Bei der Darstellung von Bilder als Funktionen über einem unendlichen, kontinuierlichen, zweidimensionalen Raum eignen sich naturgemäß funktionale Programmiersprachen wie Haskell als Beschreibungssprache. Es werden zwar kaum höhere Konzepte der funktionalen Sprache wie Rekursion genutzt, da fast alle Aspekte funktionaler Bilder auf einfache Funktionskompositionen zurückgeführt werden können, aber hier bieten funktionale Sprachen sauberere Konzepte als andere Sprachklassen. Der größte Unterschied zwische funktionalen Bildern und den klassischen Beschreibungsformaten Raster- und Vektorgrafik ist jedoch, dass funktionale Bilder direkt programmiert werden, wohingegen die anderen Formate nur zur Speicherung verwendet werden. Derartige Bilder werden i.A. in WYSIWYG1 -Editoren per ” Mausklick“ erstellt. Hierin mag auch der Grund dafür liegen, dass keine der PanImplementierungen dauerhaft gepflegt wurde und sich auch kein anderes, funktionales Bildbeschreibungssystem durchgesetzt hat. Auch wenn funktionale Bilder konzeptionell schön seien mögen, so werden Bilder doch eher mit dem Auge“ er” stellt. Erstaunlicherweise werden von Pan auch kaum Konzepte der Vektorgrafiken (Linienzüge, Bézierkurven) unterstützt, obwohl dies eigentlich naheliegend wäre. Die vergleichsweise eher junge Disziplin der prozeduralen Texturen mag am ehesten ein Anwendungsgebiet für funktionale Bilder sein, denn diese müssen algorithmisch beschrieben werden und profitieren stark von Parametrisierbarkeit. Die Standards, die sich hier bisher durchgesetzt haben, sind allerdings eher Hardwarenah und damit auch weit von funktionalen Bildern entfernt. 1 What you see is what you get. 25 6 Literatur (1) http://conal.net/papers/functional-images/ (2) Conal Elliott. Functional Images. Aus Jeremy Gibbons, Oege de Moor (Herausgeber), The Fun of Programming Cornerstones of Computing. Palgrave, 2003. (3) Conal Elliott, Sigbjørn Finne, Oege de Moor, Compiling Embedded Languages. Aus Journal of Functional Programming, 13(2), 2003. (4) Jerzy Karczmarczuk. Functional approach to texture generation. Aus Shriram Krishnamurthi, C. R. Ramakrishnan (Herausgeber), Practical Aspects of Declarative Languages, volume 2257 of Lecture Notes in Computer Science. Springer, 2002. (5) http://www.haskell.org/haskellwiki/Applications_and_libraries/Graphics#Pan 26