In PostScript selber programmieren? – Übersicht

ThemenBeispielprogramme
Einleitung
Die Sprache
Arithmetische Operationen, Standardfunktionen
Elementare Graphikbefehle Farbkreis nach Itten
Variablen und Wertzuweisungen
Stapel-Operationen
Prozeduren, Laufanweisungen, Schleifen Grauskala
EPSF
Logische Operatoren
Felder (arrays) Vergleich zweier Farbkreise
Veränderung des Koordinatensystems
Schrift und Text digitaler Farbkreiselersatz
Umlaute und Sonderzeichen I
Umlaute und Sonderzeichen II Umlaute und Sonderzeichen
Pfade – und noch mehr Graphikbefehle
Weitere Beispiele
Einbetten von Fotos oder eingescannten Bildern Einfaches Bildchen 1
Einfaches Bildchen 2
Umwandlung jpeg nach eps
Exkurs über DSC (Dokumentenstrukturkonventionen)
Ausblick


In PostScript selber programmieren?

© Dietrich Zawischa 2003–2005

Inhaltsübersicht

Einleitung

PostScript, die Druck-Seitenbeschreibungssprache von Adobe, hat sich seit ihrer Einführung 1985 zum weltweiten Standard entwickelt. Gute Grafik- und Textverarbeitungsprogramme können PostScript-Code ausgeben, Druckereien nehmen Aufträge als Postscript-Dateien an, und im übrigen gibt es Programme, die solche Dateien in das (ebenfalls von der Firma Adobe stammende) PDF-Format (Portable Document File Format) umwandeln, so daß sie (z.B. mit Hilfe des Adobe Readers) auch auf nicht-PostScript-fähigen Druckern ausgegeben werden können. Er wird daher auch anderen so ergehen, wie es mir ergangen ist: man kommt mit PostScript in Berührung, ob man will oder nicht.

Copyright-Notiz

Damit keine Mißverständnisse aufkommem: Der Name "PostScript" ist eingetragenes Warenzeichen der Firma Adobe Systems. Adobe Systems Inc. ist Eigentümerin des Copyrights der Liste von Operatoren und der schriftlichen Spezifikation von Adobes PostScript-Programmiersprache.
Da Adobe jedoch an der weiten Verbreitung der Sprache interessiert ist, gestattet die Firma jedem,
- Programme in PostScript zu schreiben,
- Treiber zu schreiben, deren Output aus PostScript-Befehlen besteht
- Software zu schreiben, die in PostScript geschriebene Programme interpretiert,
- die durch das Copyright geschützte Liste der Kommandos in dem Ausmaß zu kopieren, wie es für die oben genannten Zwecke erforderlich ist.
Einzige Bedingung für diese allgemeine Erlaubnis ist, daß jeder, der die geschützte Liste von Befehlen auf diese Weise benutzt, einen entsprechenden Hinweis auf das Copyright gibt.

Ziele

Das eigentliche Ziel ist die Bereitstellung von kleinen Programmbeispielen, speziell von solchen, die dem Verständnis der Farbenlehre dienen können, und mit denen man durch Änderung von Parametern auch noch ein bißchen experimentieren kann. Nach dem Studium einiger Beispiele sollten Sie in der Lage sein, ähnliche Programme selbst zu schreiben.

Dies hier soll einen leichten Einstieg ermöglichen, wird aber kein ausführliches Lehrbuch oder Tutorium ersetzen. Weitere Informationen finden Sie bei Adobe selbst und an vielen anderen Stellen – die Links, die ich hier ursprünglich hatte, sind inzwischen zerbrochen, aber Suchmaschinen finden genug.

Der Ausgangspunkt

Ein typisches Grafikprogramm macht einem manches sehr bequem, anderes aber unmöglich. Um bunte Bilder herzustellen, wie ich sie in der Vorlesung zeigen wollte, reichte die vom Grafikprogramm angebotene vorgestanzte Farbpalette nicht aus. Notlösung: man erzeugt das Bild erst mit den vorhandenen Farben und versucht dann, im PostScript-Output die Stellen zu finden, wo die Farbe gesetzt wird, und kann sie dort den eigenen Wünschen anpassen. PostScript-Code ist lesbarer ASCII-Code, daher kann man mit einem beliebigen Editor darin herumfummeln.

Der Eindruck, den man bei diesem Vorgehen von PostScript bekommt, kann verheerend sein. Der vom Grafik-Programm automatisch erzeugte Code enthält viel Redundanz und wird dadurch recht umfangreich. Um dem entgegenzuwirken, werden für die häufig vorkommenden Befehle oder Befehlsgruppen einbuchstabige Kurzformen definiert, was der – im Prinzip gegebenen – Lesbarkeit sehr abträglich ist. Manchmal sieht es sogar so aus, als ob die Ausgabe mit redundantem Schrott angereichert würde, um das Lesen zu erschweren.

Ich möchte im folgenden eine Lanze für PostScript brechen. Es ist eine richtige Programmiersprache, es kennt Laufanweisungen, Schleifen, Abfragen – alles, was man so braucht. In den Beispielprogrammen werde ich Abkürzungen zugunsten der Lesbarkeit möglichst vermeiden. Das Schwergewicht wird auf der Erstellung einfacher Grafiken liegen, wobei aber, meinen Vorlieben folgend, die Farbe eine große Rolle spielt, hier wird etwas mehr Aufwand getrieben werden.

Auf meinem Rechner ist Ghostscript (GS), das ist ein PostScript-Interpreter, und Ghostview (GSview ), das ist eine sehr komfortable Benutzeroberfläche, installiert. Die haben den Vorteil, daß sie kostenlos bzw. als Shareware erhältlich sind. Kommerzielle Programme bieten möglicherweise noch zusätzlichen Komfort.

Man kann in einem Editierfenster (mit einem beliebigen Editor, der allerdings in der Lage sein sollte, Dateien mit der Endung .ps oder .eps als Textdateien zu akzeptieren) das Programm basteln und sich im Fenster von Ghostview nach jedem Abspeichern das Ergebnis (oder die Fehlermeldungen) zeigen lassen. Man kann mit Ghostscript allein auch interaktiv arbeiten, das kann anfangs zum Kennenlernen der Art, wie die Kommandos abgearbeitet werden, hilfreich sein, ist aber eher umständlich.

Die Sprache

Man muß sich vor Augen halten, daß PostScript-Code etwas ist, das zum Drucker geschickt wird um dort als Grafik ausgegeben zu werden. Der Drucker kann keine Daten-Dateien vorhalten (von den eingebauten Schriften abgesehen). Es gibt zwar Lesebefehle, wie die Daten zum Drucker kommen sollten, damit sie bei der Abarbeitung zur Verfügung stehen, weiß ich aber nicht; das hängt, wenn überhaupt möglich, sicher von der Hardware ab. Abgesehen von den Schriften müssen die Programme daher alle benötigten Daten in sich enthalten.

Wer längere Zeit mit einem Taschenrechner von Hewlett-Packard gearbeitet hat, dem ist die "umgekehrt polnische Notation" (reverse polish notation, RPN) vertraut und lieb. Auf diesen Taschenrechnern sucht man vergeblich nach der Taste mit dem Gleichheitszeichen, auch öffnende und schließende Klammern sind nicht vorhanden, statt dessen gibt es eine große Eingabetaste (ENTER).

Eine einfache Addition geht so vor sich:
14 (ENTER) 8.6 +
wird eingegeben, im Sichtfenster (vor meinem geistigen Auge sehe ich den HP35 vor mir) erscheint das Ergebnis, 22.6.
Die wichtige Organisationsstruktur ist der Stapel (auf englisch: stack). Das oberste Element des Stapels ist im Sichtfenster zu sehen, die darunter liegenden Elemente sieht man nicht. Nachdem die Zahl 14 eingegeben ist, wird sie durch (ENTER) auf dem Stapel um eine Stufe tiefer geschoben. Die nächste Eingabe, 8.6, liegt dann oben und ist im Fenster zu sehen. Durch die Taste + wird das Rechenwerk angewiesen, die obersten beiden Elemente vom Stapel zu nehmen, zu addieren und das Ergebnis als oberstes Element auf den Stapel (ins Sichtfenster) zurück zu legen.
Soll 6*(14+8.6) berechnet werden, kann man dies mit der Eingabe
6 (ENTER) 14 (ENTER) 8.6 + * erreichen.

PostScript arbeitet mit einem Operandenstapel, der alle möglichen Variablentypen aufnehmen kann. Die Plätze auf dem Stapel sind numeriert, der oberste hat die Nummer 0. Das Leerzeichen dient als Trennzeichen. Werden mehrere durch ein Leerzeichen getrennte Zahlen eingegeben, so werden sie der Reihe nach auf den Stapel gelegt.

Die Kenntnis der folgenden Tabelle ermöglicht Ihnen schon, Ghostscript anstelle eines Taschenrechners zu benutzen:

Arithmetische Operationen Standardfunktionen
addAdditionatanArcustangens
divDivisioncosCosinus
idivganzzahlige Divisionexpallg. Exponentiation a hoch b
mulMultiplikationlnnatürlicher Logarithmus
subSubtraktionlogdekadischer Logarithmus
negdreht Vorzeichen umsinSinus
sqrtQuadratwurzel

Zu beachten ist, daß der Arcustangens zwei Argumente benötigt: der Aufruf lautet
Gegenkathete Ankathete atan
und das ist sehr praktisch: -1 -1 atan = liefert ein anderes Ergebnis als 1 1 atan =.

Probieren Sie es aus! Suchen Sie in dem Ordner, in dem Ghostscript installiert ist (wahrscheinlich C:\gs\gsx.xx\bin) nach der ausführbaren Datei, die gswin32.exe heißen könnte, und lassen Sie sie ausführen. Mit dem Kommando quit (oder Fenster zu) beenden Sie die Sitzung.

Das Gleichheitszeichen = kommt ansonsten in Programmen nicht vor; = holt das oberste Element vom Stapel und gibt es auf dem Bildschirm aus. Die oben gestellte Aufgabe für den Taschenrechner löst man wie folgt:
6 14 8.6 add mul =

Weitere Funktionen:

absBetragceiling ganze Zahl >= Argument
floorganze Zahl <= Argument mod Modulo, Divisionsrest
randPseudo-Zufallszahl roundRundung

rand erzeugt eine ganzzahlige Pseudo-Zufallszahl im Bereich von 0 bis 231-1. Ansonsten gilt auch hier: ausprobieren!

Elementare Grafikbefehle

Vorbemerkung: die folgenden kleinen Beispiele sind für die Betrachtung am Bildschirm gedacht, daher sparen wir uns die für den Drucker nötigen Vorspann-Informationen.
Als zu beschreibende Seite setzen wir ein Blatt DIN A4 voraus.
Voreingestellt ist als Koordinatenursprung die linke untere Ecke des Blattes, als Längeneinheit der "bigpoint", also 1/72 Zoll. Voreingestellt ist außerdem die Zeichenfarbe Schwarz.

Wenn wir lieber einen Zentimeter als Längeneinheit haben wollen, schreiben wir:
72 2.54 div dup scale
Die Syntax für den scale-Operator ist: xSkalenfaktor ySkalenfaktor scale. Ein Zoll ist 2.54 cm, durch 72 2.54 div erhalten wir die Zahl der Punkte pro Zentimeter, die dann oben auf dem Stapel liegt. Durch dup wird das oberste Element des Stapels dupliziert, also nocheinmal auf den Stapel gelegt. Dann findet der scale-Operator die gewünschten Argumente vor.

Als nächstes verlegen wir den Koordinatenursprung in die Blattmitte:
10.5 14.85 translate

Um eine Linie zu zeichnen, müssen wir mehrere Befehle absetzen:
0.1 setlinewidth
0 0 moveto
5 2 lineto
Bis jetzt ist nichts zu sehen.
Der lineto-Operator zieht eine Linie, aber er zeichnet sie noch nicht. Dies macht erst der Befehl
stroke
Wenn Sie mit GS interaktiv arbeiten, sehen Sie jetzt immer noch nichts. Es fehlt noch die Ausgabe der "fertigen Seite" durch
showpage.

Sie werden vermutlich feststellen, daß das interaktive Arbeiten nicht besonders bequem ist, denn nach der Ausgabe durch showpage ist die Grafik gelöscht. Sie können aber Ihre Bilddatei auch mit einem Editor erstellen, mit einem möglichst kurzen Namen wie "b1" abspeichern, und dann unter GhostScript "laufen" lassen, dazu tippen Sie
(b1) run
ein.
Es ist noch bequemer, Ghostview einzusetzen. Geben Sie Ihrer Grafikdatei beim Speichern die Endung .ps, dann findet Ghostview sie leichter.

Der Grund dafür, daß lineto noch keine Linie zeichnet, liegt darin, daß man oft etwas anderes will. Mit wenigen Änderungen und Ergänzungen ergibt sich z.B. folgendes Programm:

72 2.54 div dup scale
10.5 14.85 translate
0.1 setlinewidth
0 0 moveto 5 0 lineto 5 4 lineto 0 4 lineto
closepath
1 0 0 setrgbcolor
fill

showpage

Ersetzt man fill durch stroke, dann wird . . .

Will man den geschlossenen Pfad sowohl rot ausfüllen als auch schwarz nachziehen, so ist zu bedenken, daß nach fill oder stroke der Pfad "verbraucht" ist, also verschwunden, nicht mehr bekannt. Die Abhilfe:
. . . 5 4 lineto 0 4 lineto
closepath
gsave
1 0 0 setrgbcolor fill
grestore
0 0 0 setrgbcolor stroke
showpage

gsave speichert den Graphik-Status ab, grestore stellt ihn wieder her.

Letzten Endes sollen die Bilder, die wir erzeugen wollen, in einem größeren Zusammenhang erscheinen, beispielsweise in einen Text eingebettet. Dadurch soll der Graphik-Status der Umgebung nicht verändert werden. Daher empfiehlt es sich, immer gsave als erstes und grestore als letztes Kommando zu geben, außerdem müssen diese Kommandos immer paarig auftreten, wie öffnende und schließende Klammern.

Der Befehl setrgbcolor wurde verwendet, um die Farbe festzulegen. Er ist fast selbsterklärend: die drei Farbmaßzahlen für Rot, Grün und Blau sind anzugeben; die so erzeugte Farbe hängt aber davon ab, welche Grundfarben im Ausgabegerät verwendet werden, ist also geräteabhängig. Im Fall von einfachen Grafiken braucht man sich darüber noch keine Gedanken zu machen; es soll aber darauf hingewiesen werden, daß PostScript auch die Hilfsmittel für geräteunabhängige, genaue Farbspezifikation bereitstellt.

Weitere Graphikbefehle:

BefehlFunktion Beispiel / Erläuterung
arcKreisbogenxMittelp yMittelp Radius WAnf WEnd arc
arcnKreisbogen wie oben, aber im Uhrzeigersinn
curvetoBézier-Kurve xh1 yh1 xh2 yh2 x y curveto
rcurvetoBézier-Kurve dxh1 dyh1 dxh2 dyh2 dx dy rcurveto
rlinetoDeltaX DeltaY rlineto
rmovetoDeltaX DeltaY rmoveto

Die Befehle arc und arcn ziehen, falls (durch moveto, lineto oder eine andere vorausgegangene Anweisung) ein aktueller Punkt definiert ist, als erstes eine Linie von diesem Punkt zum Beginn des Bogens.
Die Befehle curveto bis rmoveto setzen voraus, daß ein aktueller Punkt definiert ist (durch ein vorausgegangenes moveto, lineto . . .). Dieser Punkt habe die Koordinaten x0, y0.
curveto: Die Verbindungslinie von x0,y0 mit xh1,yh1 ist Tangente an die Kurve im Anfangspunkt x0,y0, entsprechend die Linie von x,y nach xh2,yh2 Tangente im Endpunkt. Je länger diese Tangentenstücke sind, desto enger schmiegt sich die Kurve an die jeweilige Tangente.

Farbkreis nach Itten Als Beispiel für die Graphikbefehle finden Sie hier den sehr schlicht programmierten Farbkreis nach Itten (Quelltext). Der Code dient außerdem als erstes Beispiel für die Verwendung von Variablen für häufig wiederkehrende Größen.

Variablen und Wertzuweisungen

Deklaration von Variablen und Variablentyp vor der Verwendung ist nicht nötig. Eine Wertzuweisung, die in der Programmiersprache Pascal durch
Radius:=3.75;
erfolgen würde, sieht hier so aus:
/Radius 3.75 def
Der Operator def nimmt den Wert 3.75 und das Wort /Radius vom Stapel und trägt dieses Wertepaar ins aktuelle "Wörterbuch" (dictionary) ein. Durch den Schrägstrich wird der Interpreter angewiesen, die folgenden Buchstaben buchstäblich (literal) zu nehmen und nicht als Anweisung oder schon definierte Variable zu interpretieren.
Soll in einem späteren Schritt der zugehörige Durchmesser berechnet werden, sieht das so aus:
/Durchmesser Radius 2 mul def
Ohne vorausgehenden Schrägstrich wird das Wort Radius als Variablennamen interpretiert, der im Wörterbuch nachgeschlagen und durch den dort gefundenen Wert ersetzt werden muß.

Stapel-Operationen

Der Stapel kann, wie wir gesehen haben, Zahlenwerte und Variablennamen aufnehmen, tatsächlich noch vieles mehr, wie wir gleich sehen werden. Es ist daher wichtig, den Stapel manipulieren zu können. Dazu dienen die folgenden Befehle:
pop entfernt das oberste Element vom Stapel.
clear leert den Stapel vollständig.
dup nimmt das oberste Element vom Stapel und legt es zweimal wieder darauf ab.
exch vertauscht die obersten beiden Elemente.
index: Der Befehl n index, wobei n die Nummer des gesuchten Elementes auf dem Stapel ist, bewirkt, daß eine Kopie vom n-ten Element oben auf den Stapel gelegt wird. Man beachte, daß das oberste Element die Nummer 0 hat!
copy: Durch n copy werden die obersten n Elemente nocheinmal auf den Stapel gelegt.
count zählt die Elemente des Operandenstapels und legt das Ergebnis oben drauf.
pstack schließlich gibt den Inhalt des Stapels auf der Standard-Ausgabeeinheit aus. Das Programm muß vollständig abgearbeitet sein (d.h. das Bild nicht mehr zu sehen), dann können Sie sich unter Ghostview nach Eingabe von M die Meldungen ansehen.

Prozeduren, Laufanweisungen, Schleifen

Schließt man eine Folge von Anweisungen, Zahlen etc. in geschlungene Klammern ein, dann wird diese Folge zu einem Element zusammengefaßt und als solches auf den Stapel gelegt. Das kann man verwenden, um Prozeduren zu definieren – man braucht bloß der Befehlsfolge einen Namen zu geben:
/Prozedurname {Befehlsfolge} def
bewirkt, daß das Schlüsselwort Prozedurname (ohne vorausgehenden Schrägstrich!) im weiteren Verlauf jedesmal durch die Befehlsfolge ersetzt wird.

Laufanweisungen haben die Form
Anfangswert Inkrement Laufgrenze {Befehlsfolge} for
d.h. der Operator for holt die obersten vier Elemente vom Stapel, die der Reihe nach (von oben nach unten) als Befehlsfolge, Laufgrenze, Inkrement und Anfangswert gedeutet werden. Bei jedem Durchlauf der Schleife wird zuerst der aktuelle Wert der Laufvariablen auf den Stapel gelegt. Dieser muß entweder verwendet oder mittels pop entfernt werden.


Beispiel: eine gleichmäßig gestufte Grauskala

Grauskala %!PS-Adobe-3.0
gsave
72 2.54 div dup scale
2.5 8.85 translate
0 0 moveto

16 1024 div dup scale

/statusante save def
    % Rettet den aktuellen Zustand aller Einstellungen
    % und Definitionen (Wörterbucheinträge)
/showpage { } def
    % macht showpage-Anweisung unschädlich
%%BeginDocument: Grauskala
Grauskala2 %!PS-Adobe-3.0 EPSF-3.0
%%BoundingBox: 0 0 1024 768
gsave
64 dup scale % Bildbreite 16 Einheiten
0 0 0.6 setrgbcolor % Hintergrund färben
0 0 moveto 16 0 lineto 0 12 rlineto -16 0 rlineto closepath fill

1 setlinewidth 3 3 moveto

0 0.1 1.001
{setgray currentpoint 0 6 rlineto stroke moveto 1 0 rmoveto}
for

grestore
showpage
%%EOF

%%EndDocument
statusante restore % Einstellungen wie vorher

showpage
grestore
%%EOF

Mit diesem Beispiel soll verschiedenes gezeigt werden:
Ich habe oben gesagt, daß ein Blatt A4 als zu beschreibende Seite vorausgesetzt wird, aber die fertigen Beispielprogramme sind nach Möglichkeit so ausgelegt, daß sie den Bildschirm im Vollbildmodus ganz ausfüllen, wenn als Bildauflösung 72 dpi (dots per inch) eingestellt ist. Um keine großen Änderungen durchführen zu müssen, habe ich den ursprünglichen Code (schwarz dargestellt) in ein Rahmenprogramm eingebettet, das die Grafik skaliert und auf einem Blatt A4 zentriert ausgibt.
Eine mit %! beginnende Kopfzeile wird üblicherweise von Druckern zur Identifikation von PostScript-Code verlangt. Falls sie nicht benötigt wird, wird sie als Kommentar überlesen. Die Angabe PS-Adobe-3.0 bezieht sich auf die Dokumentenstrukturkonventionen, nicht auf den PostScript-Level. In der dritten (roten) Zeile wird auf die Einheit Zentimeter umgestellt.
Die sechste Zeile verändert die Skala wieder, und zwar so, daß die Bildbreite 16 cm beträgt statt 1024 bp (big points), wie sie der Angabe der BoundingBox zwei Zeilen später entspräche.
Wenn die letzten beiden schwarz geschriebenen Zeilen entfernt werden, können die blau geschriebenen Zeilen entfallen, sie dienen der Sicherung vor unbeabsichtigten Änderungen durch die eingeschlossene Bilddatei.
Die Bilddatei selbst (schwarz geschriebener Code) ist als "gekapseltes Bild" (Encapsulated PostScript File, eps-File) gestaltet: die Kopfzeile deklariert dieses. Die nächste Zeile gibt die linke untere und die rechte obere Ecke eines das Bild umschließenden Rechteckes an. Daraus kann ein Textsatzprogramm die Skalierung berechnen.
Die wesentlichen Merkmale einer eps-Datei sind Kopfzeile, BoundingBox, sowie das Versprechen, den grafischen Zustand und den Operandenstapel des einbettenden Programmes unverändert zu hinterlassen.Außerdem dürfen die Zeilen nicht zu lang sein (255 Zeichen maximal). Ersteres wird durch das Paar gsave – grestore erreicht, für den Rest ist der Programmierer verantwortlich. In der Anleitung zum Aufbau von EPS-Dateien der Firma Adobe findet man zwei Prozeduren BeginEPSF und EndEPSF, die, vor und nach einem eingebetteten EPS-File aufgerufen, noch zusätzliche Sicherheit und Schutz des Rahmenprogrammes bieten. Üblicherweise werden EPS-Dateien ja nicht von Hand programmiert, sondern von einem Grafikprogramm generiert (nach der Auswahl der entsprechenden Export-Optionen "save as . . ." bzw. "speichern unter . . .").
Die neben dem unteren Bildchen stehenden drei Zeilen sind der Hauptteil des Programms: Startpunkt, Schrittweite und Obergrenze der Laufvariablen, die in diesem Fall die Helligkeit ist. Dann die Befehlsfolge in geschlungenen Klammern. setgray holt den Wert der Laufvariablen vom Stapel. currentpoint legt das aktuelle x-y-Koordinatenpaar auf den Stapel. Nachdem die von 0 6 rlineto gezogene Linie gezeichet ist, steht es dann für die Anweisung moveto zur Verfügung.
Nach dem Zurückladen der in der Variablen "statusante" abgelegten Informationen hat showpage wieder seine ursprüngliche Bedeutung.
Die letzten drei, wieder rot geschriebenen Zeilen sind das Ende des Rahmenprogramms. Dieses Rahmenprogramm kann auch für andere Bilder im eps-Format verwendet werden.


Zurück zu der durch geschlungene Klammern zusammengefaßten Befehlsfolge. Durch
n {Befehlsfolge} repeat
wird die Folge n-mal ausgeführt, durch
{Befehlsfolge} loop
beliebig oft. Zweckmäßigerweise baut man im letzten Fall eine Sequenz der Art
Abbruchbedingung {exit} if
ein: Wenn die Abbruchbedingung erfüllt ist, wird der Ausgang (exit) gewählt, siehe folgenden Abschnitt.

Logische Operatoren

Logische Variablen können die Werte true (=wahr) oder false (=falsch) annehmen. Zur Konstruktion von logischen Ausdrücken stehen folgende Operatoren bereit:

OperatorBedeutung Beispiel
andund, logisches ProduktA B and
eqgleichWert1 Wert2 eq
gegrößer oder gleich Wert1 Wert2 ge
gtgrößer
lekleiner oder gleich
ltkleiner
notnichtWert1 Wert2 eq not
oroder, logische Summe
xorentweder – oder

und für die Abfragen if und ifelse, z.B.:
A B gt {Anweisungen} if
A B gt C D eq and {Anweisungen1} {Anweisungen2} ifelse

Felder (arrays)

So, wie durch geschlungene Klammern eine Anzahl von Elementen zu einer Einheit zusammengefaßt werden, kann man auch durch die eckigen Klammern Elemente zusammenfassen. Diese Einheit wird als Feld (array) bezeichnet. Die Elemente können von verschiedenem Typ sein; ich beschränke mich auf Felder von Variablen gleichen Typs.
Die im folgenden Beispiel vorkommende Anweisung
/Ra [1 1 0 0 0 1] def
definiert ein Feld namens Ra mit sechs Elementen, wobei die Zählung links mit 0 beginnt.
Die wichtigsten Befehle zur Manipulation von Feldern sind def, get, put, aload und astore.
Wenn das Feld nicht von Anfang an bekannt ist, kann man es nicht so einfach durch eckige Klammern und Wertzuweisung definieren. Statt des obigen Beispiels kann man das gleiche Ergebnis auch durch
/Ra 6 array def
1 1 0 0 0 1 Ra astore pop
erreichen. Zunächst wird dem Namen (key) Ra ein Feld mit sechs Elementen zugeordnet. Dann werden sechs Zahlen (Nullen und Einsen) und das (leere) Feld auf den Stack gelegt. Durch den Befehl astore wird das (immer noch leere) Feld vom Stapel geholt, dann werden die vorgefundenen Zahlen den Feldelementen zugewiesen und schließlich wird das ganze Feld nocheinmal (als Einheit) auf dem Stapel abgelegt, von wo es mit pop entfernt oder anderweitig verwendet wird.
Mit
Ra 4 get
greift man auf das Element Nr. 4 zu, durch
Ra 3 0.5 put
würde dem Element Nr. 3 der neue Wert 0.5 zugewiesen.
aload schließlich legt die Elemente des angegebenen Feldes auf den Stapel und dann als oberstes das Feld selbst. Am Beispiel des oben auf zwei Arten eingeführten Feldes Ra: Eingabe von
Ra aload
würde den Stackinhalt
1 1 0 0 0 1 [1 1 0 0 0 1]
erzeugen. Wieder ist der oberste Eintrag, das Feld selbst, gegebenenfalls durch pop zu entfernen.

Farbvergleich Vergleich zweier Farbkreise:
Dieses Beispiel demonstriert die Verwendung von Feldern, Laufanweisungen und verschiedene Arten, Farben festzulegen (setrgbcolor und sethsbcolor und nach geringfügiger Änderung auch setcmykcolor), sowie den Operator rotate.

Die Programmliste ist kommentiert; hier noch einige ergänzende Anmerkungen:
Drehwinkel rotate
dreht das Koordinatensystem um den Ursprung um den angegebenen Winkel (in Grad).
Um Unsauberkeiten an den Grenzen der Farbfelder aufgrund von Rundungsfehlern zu vermeiden, wurden die Farbfelder etwas überlappend gezeichnet.
Die Zahl der Farbschritte von einer Grundfarbe zur nächsten wurde "fein" genannt. Beim Durchlaufen des Kreises wird die Farbe jeweils zwischen den benachbarten Grundfarben interpoliert, das geschieht durch die folgenden Anweisungen:

/j exch def % die Laufvariable wird j genannt
/j0 j fein idiv 6 mod def % Index der vorausgehenden Farbe
/j1 j0 1 add 6 mod def % Index der folgenden Farbe
/Farbanteil1 j fein mod fein div def
/Farbanteil0 1 Farbanteil1 sub def
Ra j0 get Farbanteil0 mul Ra j1 get Farbanteil1 mul add
Ga j0 get Farbanteil0 mul Ga j1 get Farbanteil1 mul add
Ba j0 get Farbanteil0 mul Ba j1 get Farbanteil1 mul add
setmodecolor

wobei setmodecolor durch setrgbcolor ersetzt wird, oder, nach Änderung, durch 0 setcmykcolor. setcmykcolor ist für die Darstellung auf dem Bildschirm nicht besonders nützlich, der vierte Wert "k" für Schwarz, (BlacK) ist redundant, im Beispielprogramm daher 0 gesetzt.
Beim inneren Farbkreis wird sethsbcolor verwendet; die drei Argumente Farbton (Hue), Sättigung (Saturation) und Intensität (Brightness) können jeweils zwischen 0 und 1 variieren. Während die Werte Sättigung und Intensität (fast) selbsterklärend sind, ist zu Farbton/Hue h folgendes anzumerken: h=0 kennzeichnet Rot, h=1/6 Gelb, h=1/3 Grün, h=1/2 Cyan, h=2/3 Blau, h=5/6 Magenta (Purpur), und h=1 ist wieder Rot. Zwischen diesen Werten, die sich im R'G'B'-Schema leicht darstellen lassen, werden die Maßzahlen R', G', B' linear interpoliert. Mehr darüber finden Sie im Abschnitt "Farbe für den Bildschirm".
Wir haben die fast selbsterklärenden Befehle setrgbcolor, sethsbcolor unssetcmykcolor kennengelernt. Dabei ist "Rot Grün Blau setrgbcolor" eine Zusammenfassung von zwei Befehlen: "/DeviceRGB setcolorspace Rot Grün Blau setcolor". Der Name /DeviceRGB macht die Geräteabhängigkeit deutlich. Der Befehl sethsbcolor legt ebenfalls den /DeviceRGB Farbraum zugrunde und kann daher nicht entsprechend aufgespalten werden.

Veränderung des Koordinatensystems

In den Befehlen translate, scale und rotate haben wir schon die Operatoren kennengelernt, die man verwenden kann, um geeignete Koordinatensysteme einzuführen. Das Ergebnis dieser Operationen wird vom System in der aktuellen Transformationsmatrix (current transformation matrix, CTM) abgelegt, die dazu benutzt wird, die Benutzerkoordinaten in Systemkoordinaten umzurechnen.
Hier noch ein klein wenig Hintergrundinformation dazu. Auf die (einfachen) Rechenregeln mit solchen Transformationsmatrizen soll nicht eingegangen werden. Die Umrechnung von Benutzerkoordinaten x, y in andere Koordinaten x', y' (dies können z.B. die Systemkoordinaten sein) erfolgt entsprechend den Gleichungen
x' = ax + cy + s,
y' = bx + dy + t.
Die Transformationsmatrix enthält die Transformationskoeffizienten, PostScript speichert sie in einem Feld (Array) von sechs Elementen:
[ a  b  c  d  s  t ].
Mehrere Transformationen hintereinander ausgeführt ergeben eine CTM, die sich als "Verkettung" (Matrizenprodukt) der einzelnen Transformationsmatrizen mit der ursprünglichen CTM darstellen läßt. Statt die drei obengenannten Befehle zu verwenden, kann man auch die gewünschte Transformationsmatrix angeben und durch den Befehl concat mit der aktuellen CTM verketten: die beiden Anweisungen
[ 1 0 0 1 72 36 ] concat
und
72 36 translate
haben dieselbe Wirkung, ebenso bewirken
[ 2 0 0 3 0 0 ] concat
und
2 3 scale
dasselbe, und dem Befehl
30 rotate
entspricht
[ 30 cos 30 sin 30 sin neg 30 cos 0 0 ] concat.
Meist kommt man jedoch mit den einfachen Transformationsbefehlen aus.


Schrift und Text

Die überragende Stärke von PostScript liegt im Bereich der schriftlichen Ausgabe von Text, die höchsten typographischen Anforderungen genügen kann. Jeder zu druckende Buchstabe ist letztlich als eine Folge von Graphikbefehlen zu sehen, die bewirkt, daß der Buchstabe auf das Ausgabemedium gezeichnet wird. Dazu enthält PostScript eine Fülle von speziellen Befehlen, die man nie braucht, wenn man nur ein paar Zahlen und wenig Text in eine Grafik einfügen will. Letzteres ist hier unser Ziel: Achsbeschriftungen, Überschriften und hie und da ein Wort sind nötig, mehr nicht. Daher werden wir uns hier auf die wichtigsten Befehle beschränken, die Wirkungsweise wird in Beispielen nur angedeutet.
Allerdings verlocken die Möglichkeiten zu typographischen Spielereien (wie z.B. die Überschrift "Ästhetik und Naturwissenschaft"), auch darauf werden wir zu sprechen kommen.

Ein Beispiel:

gsave
72 2.54 div dup scale
2.5 25 translate
0 0 moveto
/Helvetica findfont
1.5 scalefont
setfont
(Erster Versuch) show
(
mit Schrift) show
showpage
grestore

Neu ist nur der rotbraun geschriebene Teil. findfont sucht die Schrift mit dem auf dem Stack vorgefundenen Namen und legt sie oben auf den Stapel. (Genau genommen wird bei zusammengesetzten Objekten nur ein Zeiger auf den Stapel gelegt, das Objekt bleibt, wo es im Speicher ist oder wird bei dieser Operation in den virtuellen Speicher kopiert.) scalefont nimmt die Schrift und den Maßstabsfaktor vom Stapel und transformiert die Schrift wie gewünscht, setfont nimmt schließlich die so modifizierte Schrift wieder vom Stapel und installiert sie im aktuellen Font-Dictionary (Schriftschnitt-Wörterbuch).
In runde Klammern eingeschlossener Text wird als Zeichenkette (String) interpretiert. In Verbindung mit der durch die Definition der Schrift geleisteten Vorarbeit definiert dieser String einen komplizierten Pfad, der dann mit dem Befehl show mit der voreingestellten Farbe gefüllt wird. show ist analog den Operatoren fill oder stroke und versteht die auf diese Weise definierten Pfade. show übergibt nach Ausgabe eines Strings die neuen aktuellen Koordinaten auf den Grafik-Stack, so daß ein weiterer show-Befehl den richtigen Anfangspunkt vorfindet.
PostScript Level 2 bietet zur größeren Bequemlichkeit ein Kommando, das die drei auf die Schrift bezüglichen Befehle zu einem zusammenfaßt:

/Helvetica 1.5 selectfont
(Erster Versuch) show
(
mit Schrift) show

Farbkreiselersatz Digitaler Farbkreisel-Ersatz. (Nur eine Notlösung!) Keine Computersimulation, sondern ein echtes Experiment. Aus großer Entfernung zu betrachten, bis die Streifen verschwinden. Die Farbdaten (Farben und ihre Anteile in Prozent) am Anfang des Quellprogramms können Sie beliebig abändern.

Der Quelltext des Farbkreisel-Ersatzes diene als Beispiel für Text- und Datenausgabe. Gewählt wurde eine dicktengleiche Schrift, damit sich Text und Daten bequem kombinieren lassen. Neu ist der Operator cvs, mit dessen Hilfe Zahlen in Strings umgewandelt werden. Für den Zielstring muß vorher entsprechend Platz reserviert werden, in völliger Analogie zum Operator array geschieht dies durch den Operator string, konkret z.B. /Zeichenkettenname 8 string def, wodurch ein maximal 8 Zeichen langer String Speicherplatz und Namen erhält. Die Umwandlung erfolgt dann durch

Variablenname Zeichenkettenname cvs,

wonach der String oben auf dem Stapel liegt, aber auch die für den String reservierten Speicherplätze gefüllt sind. (Auf dem Stapel liegt in Wahrheit nur ein Zeiger auf diese Plätze.)



Umlaute und Sonderzeichen: die "kleine Lösung"

Wenn man sich auf die bis jetzt beschriebenen Befehle zur Manipulation der Zeichensätze beschränkt, dann gibt es kein ß und keine Umlaute, auch keine fremdsprachlichen Akzente (wie é, ô).
Natürlich lassen sich auch Umlaute, ß und alle denkbaren Akzente erhalten; wenn man nur einige wenige Zeichen braucht, wie das bei Grafiken meist der Fall ist, dann ist dies mit dem in PostScript Level 2 vorhandenen Befehl
glyphshow
leicht zu bewerkstelligen. Achten Sie in dem Bild des "Farbkreisel-Ersatzes" auf die vorletzte Zeile links unten: die Ausgabe des Wortes "Weiß" wurde durch
(Wei) show /germandbls glyphshow
erreicht.
Der Befehl glyphshow gestattet Zugriff auf jedes einzelne Zeichen der eingestellten Schrift unter Umgehung der Codierung. Um ihn zu verwenden, muß man die Namen der Sonderzeichen kennen. Die im Deutschen häufigeren finden Sie in der folgenden Tabelle fettgedruckt:

â /acircumflex ä /adieresis Ä /Adieresis
à /agrave ç /ccedilla Ç /Ccedilla
é /eacute É /Eacute ê /ecircumflex
ë /edieresis è /egrave /Euro
ï /idieresis ô /ocircumflex ö /odieresis
Ö /Odieresis ß /germandbls ü /udieresis
Ü /Udieresis

Die Namen der Zeichen sind weitgehend selbsterklärend, und nach dem gleichen Schema sind viele mehr gebildet: caron steht z.B. für das Häkchen, entsprechend gibt es /ccaron, /Ccaron, /scaron, /rcaron etc.
Nützliche Zeichen, die man nicht über die Tastatur erreichen kann, sind /copyright, /degree, /endash, /emdash, /ellipsis (Auslassungspunkte), /multiply, /periodcentered, /plusminus, /registered, /trademark.

Umlaute und Sonderzeichen – die "große Lösung"

Die Anweisung glyphshow greift direkt über den Namen auf das einzelne Zeichen zu; alle anderen Textausgabebefehle – wir haben hier erst show behandelt – benutzen eine Codierungstabelle (Encoding, Encoding Vector), um dem Buchstabencode den Namen des Zeichens zuzuordnen. Der von Postscript als Standard verwendete Codierungsvektor enthält die im Deutschen gebräuchlichen Umlaute nicht, läßt dafür aber ausreichend freien Platz für die in den verschiedenen Sprachen verwendeten Sonderzeichen. PostScript enthält noch einen zweiten Codierungsvektor, der, soweit ich das überblicke, mit der von Microsoft-Windows verwendeten ANSI-Codierung übereinstimmt. Die folgenden Programmzeilen zeigen, wie auf diesen umgeschaltet werden kann (funktioniert in dieser Form ab Level 2):

/Helvetica findfont
dup length dict begin
{def} forall
/Encoding ISOLatin1Encoding def
currentdict
end
/Helvetica-ISOLatin1 exch definefont
20 scalefont setfont
72 720 translate
0 0 moveto
(Schön, daß es klappt! 20 \240) show
0 -25 moveto
(\307'est la vie! L'état, \347'est moi! Grün & Weiß) show
showpage

Wesentlich sind die rot geschriebenen Zeilen: Das Font-Dictionary der Helvetica wird auf den Stapel geholt (findfont), der Stapeleintrag wird dupliziert (dup); das Duplikat wird zur Bestimmung der Zahl der Einträge verwendet (length) und ein neues, noch leeres unbenanntes Diktionär derselben Länge wird auf den Stapel gelegt (dict). Die Anweisung begin verschiebt dieses auf den Dictionary-Stack und macht es zum aktuellen, als erstes konsultierten Wörterbuch. Der oberste Eintrag im Operandenstapel ist danach wieder das Font-Dictionary der Helvetica.
Die Einträge in einem Wörterbuch sind Schlüsselwort-Wert-Paare; der Operator forall berücksichtigt dies, holt ein solches Paar nach dem anderen auf den Stapel und führt mit diesem die in der geschlungenen Klammer stehende Anweisung aus: def überträgt das Schlüsselwort und den zugehörigen Wert in das aktuelle vorhin eingerichtete Diktionär. Durch eine neue Definition wird der dem Schlüssel /Encoding zugeordnete Array anschließend überschrieben.
currentdict kopiert das neu erstellte Wörterbuch wieder auf den Operandenstapel, end entfernt es vom Dictionary-Stack. Der neue Name des abgeleiteten Fonts wird auf den Stapel gelegt, durch exch werden die obersten zwei Einträge vertauscht, so daß der folgende Operator definefont die Argumente, die er benötigt, in der richtigen Reihenfolge vorfindet. definefont korrigiert den zum Schlüssel /FID gehörigen Eintrag der Font-Identifikation, trägt den neuen Font unter dem angegebenen Namen in das Font-Register ein und legt ihn auf dem Operandenstapel zur weiteren Verwendung ab. Von dort sollte er, falls er nicht gleich weiterverwendet wird, durch pop entfernt werden. Später kann mit findfont oder selectfont darauf zugegriffen werden. (Die Codierungstabellen und auch das Vorbild für diese Zeilen findet man im "Roten Buch" von Adobe).

Ich möchte noch kurz den Fall abhandeln, daß man mit anders codierten Dateien arbeiten möchte. Als Beispiel wähle ich die Standard-Codierung, die um Umlaute und akzentuierte Zeichen so erweitert wird, daß letztere über die Tastatur ("im ASCII-Modus", also z.B. unter DOS) erzeugt werden können. Dieses Beispiel unterscheidet sich vom vorigen nur dadurch, daß ein neuer Encoding-Vektor bereitgestellt wird, ein Array mit 256 Elementen, in diesem Beispiel namens /modasciiEncoding.

Hier der Quellcode des modifizierten Programmes; die Ausgabeanweisungen lauten
0 0 moveto
(\271Schön, daß es klappt!\252       20 \240) show
0 -25 moveto
(\200'est la vie! L'état, \207'est \274 Grün & Weiß) show
und das Ergebnis sieht so aus:
Sonderzeichen
Mit diesem Beispiel wird auch gezeigt, wie der durch Oktalzahlen gegebene Code von Sonderzeichen in die auszugebenden Zeichenketten eingefügt werden kann.

Pfade – und noch mehr Graphikbefehle

Wir haben gesehen, daß durch die verschiedenen Graphikbefehle zunächst Pfade definiert werden, die anschließend nachgezogen (stroke) oder, wenn sie geschlossen sind, mit Farbe ausgefüllt werden können (fill). Geschlossene Pfade können darüber hinaus verwendet werden, um die folgenden Zeichenoperationen auf einen bestimmten Bereich zu beschränken; dazu dient der Befehl
clip.
Indem man clip und die folgenden Anweisungen zwischen gsave und grestore einschließt, kann man das Beschneiden auch wieder abstellen.
Pfade können aus mehreren getrennten Teilen bestehen. Der Befehl
closepath
schließt einen Teilpfad, indem der aktuelle Punkt durch eine gerade Strecke mit dem anfangs durch moveto erreichten Ausgangspunkt des Teilpfades verbunden wird.
Der aktuelle Pfad ist Teil des graphischen Status und kann mit gsave gespeichert und mit grestore wiederhergestellt werden.
newpath löscht den aktuellen Pfad; die Operatoren stroke und fill enthalten diesen Befehl implizit.
Auch die einzelnen Lettern, aus denen ein gesetzter Text besteht, sind zunächst Teilpfade. Die zu druckende Zeichenkette definiert in Verbindung mit der gewählten Schrift eine Folge von Teilpfaden, die dann mit dem Befehl show mit der aktuellen Farbe gefüllt werden. show ist die Zusammenfassung einer Folge von Anweisungen, deren letzte dann der Operator fill ist.
Um die Kontur der Schrift für andere Zwecke nutzen zu können, wurde der Operator charpath definiert. Dieser erwartet auf dem Stapel einen String und als oberstes eine logische Variable.
(Text) true charpath erzeugt einen Pfad (die Umrisse des in der gewählten Schrift gesetzten Wortes "Text"), der dann durch clip zur Begrenzung der folgenden Zeichenoperationen gemacht werden kann (so ist das Bild
Ästhetik und
Naturwissenschaft entstanden),
oder auch durch fill ausgefüllt;
(Text) false charpath stroke
bewirkt, daß die Umrisse der Buchstaben gezeichnet werden.

Mit
Strichdicke setlinewidth
können wir die Strichdicke setzen, das kennen wir schon; man kann aber auch festlegen, ob und wie eine Linie gestrichelt gezeichnet werden soll, dies geschieht durch setdash, wobei das gewünschte Strichmuster in einem Array angegeben wird, und durch einen Offset-Parameter der Beginn verschoben werden kann, also z.B.
[0.3 0.5] 0.1 setdash
erzeugt Linien, bei denen auf 0.3 Einheiten lange Striche 0.5 Einheiten lange Pausen folgen, wobei der erste Strich aber um 0.1 Einheiten kürzer gezeichnet wird. Durch [] 0 setdash wird eine ununterbrochene Linie eingestellt.
Wie zwei aneinandergefügte Linienstücke miteinander verbunden werden läßt sich mit
setlinejoin
beeinflussen. Voreingestellt ist 0 setlinejoin mit der Bedeutung, daß die Konturen der Linien verlängert werden, bis sie sich schneiden. 1 setlinejoin bewirkt abgerundete Ecken, 2 setlinejoin abgeschrägte Verbindungen.
Entsprechende Festlegungen für die Enden dickerer Linien lassen sich mit setlinecap treffen: 0, 1 und 2 sind als Parameter vorgesehen; 0 bedeutet, daß die Linie an den Endpunkten glatt abgeschnitten wird, 1: es werden Halbkreise angesetzt, so daß die Linie wie mit der Röhrchenfeder gezeichnet erscheint, 2: die Linie wird am Endpunkt um die halbe Strichdicke verlängert.

Sehr häufige Elemente von Graphiken sind Rechtecke. Drei einander ähnliche Befehle (Level 2) tragen dem Rechnung: rectfill, rectclip und rectstroke. Die Syntax ist
xEckpunkt yEckpunkt Breite Hoehe rectfill
oder
[xE1 yE1 B1 H1 xE2 yE2 B2 H2 . . .] rectfill,
man kann einen ganzen Schwarm von Rechtecken gleichzeitig erzeugen, indem die Parameter durch eckige Klammern zu einem Array zusammengefaßt werden. Der aktuelle Pfad wird durch diese Befehle nicht verändert!

Weitere Beispiele

Jetzt ist eigentlich schon fast alles abgehandelt, was ich zum Erstellen meiner Grafiken und Illustrationen bisher benötigt habe. Für viele der einfacheren Bilder ließen sich sicherlich auch die gängigen Grafik- oder Zeichen- und Malprogramme einsetzen; wenn aber die Ergebnisse genau berechnet werden müssen, gibt es nicht mehr viele, mit denen man das kann. Die Regenbogenbilder zum Beispiel sind auch in PostScript programmiert worden.
Das Logo des Arbeitskreises Paläontologie Hannover habe ich ursprünglich mit einem Grafikprogramm erzeugt – Dateigröße: 66 kB. Dasselbe direkt in PostScript geschrieben ergibt eine Datei von gerade einmal 635 Bytes. Auch eine Frage der Ästhetik.

APH-Logo

Einbetten von Fotos oder eingescannten Bildern

Jetzt möchte ich noch auf die Möglichkeit eingehen, Text und Graphik mit gerasterten Bildern zu kombinieren. Das habe ich für Plakate, Handzettel und ähnliches schon mehrfach verwendet. PostScript bietet mit dem image-Operator die Möglichkeit, "Pixelgraphik" zu verarbeiten. Wie aber fügt man ein als Datei vorliegendes Bild in eine PostScript-Datei ein?
Das ist im Grunde ganz einfach, denn gute Bildbearbeitungsprogramme bieten die Möglichkeit, Bilder im "Encapsulated PostScript Format", das wir oben schon kennengelernt haben, abzuspeichern, und in dieser Form können sie einfach eingefügt werden. EPS-Dateien können in TeX- (und LaTeX) Dokumente eingebunden werden, wenn diese zum Drucken nach PostScript (oder PDF) umgewandelt werden; die dazu nötigen Makropakete sind mittlerweile Standard. (Auch die gängigen Textverarbeitungsprogramme unterstützen das Einfügen von EPS-Bildern.) Die so erzeugten EPS-Dateien sind allerdings im allgemeinen recht groß.
Fotos werden gerne im platzsparenden JPEG-Format gespeichert. Falls das Bild in diesem Format (JFIF) vorliegt, dann gibt es in dem Programm jpeg2ps von Thomas Merz eine frei aus dem Internet beziehbare, gute Möglichkeit der Umwandlung. jpeg2ps erzeugt aus einer jpeg-Bilddatei eine eps-Bilddatei, die nur wenig größer ist als die Ausgangsdatei.
Wenn man allerdings so wie ich eine so erzeugte eps-Datei in ein handgestricktes PostScript-Programm einfügt, ist es zweckmäßig, die Zeilen
%%Page: 1 1
und
       showpage
zu entfernen, die im lesbaren Anfangsteil der von jpeg2ps erzeugten eps-Datei stehen. Man erhält sonst Fehlermeldungen oder nicht das gewünschte Ergebnis, wenn man nicht spezielle Vorkehrungen (wie die blau geschriebenen Zeilen in unserem eps-Beispielprogramm) trifft, um diese Zeilen zu "entschärfen".

Die Aufgabe, um die es hier geht, ist also schon gelöst, ich möchte im folgenden trotzdem noch genauer darauf eingehen und zeigen, wie man mit Hilfe eines PostScript-Programmes (und GhostScript) aus einer jpeg eine eps-Datei machen kann.

Ein einfaches Bildchen

Zum Einfügen von gerasterten Bildern stellt PostScript den image-Operator bereit. Dessen Wirkungsweise wollen wir zunächst anhand eines einfachen Schwarz-Weiß-Beispiel-Bildchens besprechen und erproben:

%!
gsave
100 100 scale
8 12 1 [8 0 0 12 0 0] < c1 80 0c 1e 1e 1e 1e 1e 1e 1e 1e 1e > image
restore
showpage

Vor dem Aufruf von image werden die benötigten Parameter auf den Stapel gelegt. Dies sind der Reihe nach:
Spaltenzahl Zeilenzahl BitsProPixel Bildtransformationsmatrix Datenquelle.
Das Beispielbildchen ist also 8 Pixel breit und 12 Pixel hoch, die Pixel sind schwarz oder weiß, daher genügt ein Bit zur Angabe. Die Bildtransformationsmatrix ist die Matrix, die das Einheitsquadrat (1 mal 1 Längeneinheit) auf das Bild abbildet (Transformationsmatrizen haben wir schon kurz besprochen), und als Datenquelle ist hier ein Hexadezimal-String, kenntlich an den spitzen Klammern, die die als Hexadezimalzahlen zu lesenden Zeichen einschließen. Leerzeichen und Zeilenvorschub werden innerhalb der spitzen Klammern ignoriert. Das Bild sieht so aus:

kleines SW-Bild

Wandelt man die Hexadezimalzahlen des Strings in Binärzahlen um, so erhält man zeilenweise:
1 1 0 0 0 0 0 1
1 0 0 0 0 0 0 0
0 0 0 0 1 1 0 0
0 0 0 1 1 1 1 0
0 0 0 1 1 1 1 0
0 0 0 1 1 1 1 0
0 0 0 1 1 1 1 0
0 0 0 1 1 1 1 0
0 0 0 1 1 1 1 0
0 0 0 1 1 1 1 0
0 0 0 1 1 1 1 0
0 0 0 1 1 1 1 0

Bilddateien von Fotos beispielsweise sind meist recht groß, diese Daten in einen String zu packen ist unpraktisch. Außer Strings sind als Datenquelle auch Prozeduren zulässig, sowie Dateien. Als Datei kommt für die Bilddaten nur die Eingabedatei in Frage. Diese ist als currentfile natürlich stets vorhanden, denn sie enthält ja das gerade verarbeitete Programm. Somit können wir das Bild auch durch die Zeilen

%!
gsave
100 100 scale
8 12 1 [8 0 0 12 0 0] currentfile image % hier folgen 12 Bytes,
% die in HTML nicht darstellbar sind
grestore
showpage

Auf das den image-Operator abschließende Leerzeichen folgt dann in 108 Bits die Bildinformation, und anschließend, nach einem Zeilenvorschub, noch die letzten zwei Anweisungen. Hier finden Sie das Progrämmchen, wenn Sie es ausprobieren wollen. Zum Editieren des Bildchens bräuchten Sie einen Hexadezimal-Editor. Gsview zeigt Ihnen das Ergebnis.
Bevor ich auf Einzelheiten der Bilddarstellung näher eingehe, möchte ich die Möglichkeiten nutzen, die PostScript bietet, um das lästige Hantieren mit Binärdaten zu vermeiden: dies sind die filter-Operatoren, die es gestatten, Dateien umzucodieren. PostScript stellt einige Standardfilter bereit. Die Syntax für ein decodierendes Filter ist im einfachsten Fall

Quelldatei Filtername filter

und als Ergebnis liegt anschließend statt der Quelldatei die umgewandelte Datei auf dem Stapel.
Ein Beispiel: ersetzen wir in dem obigen kleinen Progrämmchen currentfile durch
currentfile /ACCIIHexDecode filter ,
so können wir statt der Binärdaten wieder die hexadezimal kodierten Bytes eingeben. Diese Daten sind durch das Zeichen > abzuschließen, das vom ASCIIHexDecode-Filter als Endmarke interpretiert wird. (Leerzeichen und Zeilenvorschübe werden von diesem Filter überlesen.) Damit wird aus dem kleinen Beispielprogramm (hier der Quelltext):
%!
gsave
100 100 scale
8 12 1 [8 0 0 12 0 0] currentfile /ASCIIHexDecode filter image
C1 80 0C 1E 1E 1E 1E 1E 1E 1E 1E 1E>
grestore
showpage

In dieser Form kann die Datei mit einem beliebigen Editor bearbeitet werden.
Der image-Operator liefert, unabhängig von Zeilen- und Spaltenzahl des Bildes, als Ergebnis ein quadratisches Bild der Kantenlänge 1. Die Bilddaten werden zeilenweise eingelesen, wobei mit der untersten Zeile begonnen wird. Dies läßt sich mit Hilfe der Bildtransformationsmatrix ändern: ersetzt man sie durch [8 0 0 -12 0 12], so wird die erste gelesene Zeile zur obersten.

PostScript kennt (seit Level 2) noch eine andere Variante des image-Operators: diese wird mit nur einem Parameter auf dem Stapel aufgerufen, aber dieser eine Parameter ist ein Wörterbuch (dictionary), das alle nötigen Parameter enthält. In dem obigen kleinen Programm könnte man die Zeile, die mit "image" endet, durch folgende Zeilen ersetzen, um die gleiche Wirkung zu erzielen:
<< /ImageType 1
/Width 8
/Height 12
/ImageMatrix [8 0 0 12 0 0]
/DataSource currentfile /ASCIIHexDecode filter
/BitsPerComponent 1
/Decode [0 1]
>> image
In dieser Form ist das Kommando sehr viel leistungsfähiger.
Einige der Parameter sind uns bereits bekannt, andere fast selbsterklärend. Für ImageType ist für ein nichttransparentes Bild den Wert 1 anzugeben. Für BitsPerComponent sind die Werte 1, 2, 4, 8 und 12 zulässig. Ob es sich um ein Schwarz-Weiß- (oder Graustufen-) Bild oder um ein Farbbild handelt, erkennt der Interpreter am vorher eingestellten Farbraum. Wir haben in dem Beispiel keine Farben verwendet, daher gilt die Voreinstellung auf DeviceGray. Um ein Farbbild zu erzeugen, müßten wir vorher etwa
/DeviceRGB setcolorspace
angeben.
Decode, der Decodierungsvektor, gibt der Reihe nach für jede Komponente des Farbraumes die Grenzwerte an. Für ein Bild im RGB-Farbraum wäre entsprechend für Decode [0 1 0 1 0 1] anzugeben.
Im JPEG-Format (JFIF) ist das Bild in einem einigermaßen komplizierten Verfahren codiert. Kernstück der Umformung ist die sogenannte diskrete Cosinus-Transformation, DCT. Diese auf dem JPEG-Standard basierenden Codierungs- und Decodierungsalgorithmen sind in PostScript als Filter mit den Namen DCTEncode bzw. DCTDecode vorhanden. Das heißt, wenn wir ein vorhandenes "jpeg"-Bild mit einem kurzen Vorspann und etwas Nachspann versehen (in Analogie zu dem obigen Beispiel mit dem binär dargestellten Bildchen), dann sollte PostScript dieses Bild verarbeiten können und korrekt ausgeben. Das funktioniert auch. Nur: der Binärcode des Bildes kann wieder Ärger bereiten. Daher ist es zweckmäßig, genau wie in unserem vorigen Beispiel, den Bildinhalt noch weiter umzucodieren, und genau das tut auch das oben erwähnte Programm jpeg2ps von Thomas Merz.
In der zweiten Variante unseres Beispiels haben wir die Bitfolge als Hexadezimalzahlen verschlüsselt angegeben, für jedes Byte also zwei Zeichen (Bytes). Das hat den Vorteil, daß es relativ leicht zu entziffern ist, aber dadurch verdoppelt sich der Speicherbedarf. Da Bilddateien meist recht groß sind, empfiehlt sich die Verwendung einer platzsparenden Codierung. PostScript kennt die ASCII85-Codierung und Decodierung. Eine nach ASCII85 codierte Datei ist nur um 25% größer als die Quelldatei.
Ich habe zur Übung ein kleines Programm jpegineps.ps geschrieben, um die Möglichkeiten, Ein- und Ausgabedateien zu benutzen und durch Filter zu leiten, kennenzulernen. Dieses Programm liefert keinen gedruckten Output, sondern erzeugt aus einer Datei Bild.jpg eine Datei Bild.eps. Es ist nicht so bequem zu benutzen wie das Merzsche jpeg2ps: Bevor man es ausführen läßt, muß man die Zieldatei Bild.eps und die Zwischenspeicherdatei Hex.txt erzeugen, indem man z.B. ein einzelnes Leerzeichen mit dem Editor unter diesen beiden Namen abspeichert, und die Ausgangs-Bilddatei nach Bild.jpg kopieren. Alle Dateien müssen im gleichen Verzeichnis wir jpegineps.ps stehen, oder es muß der vollständige Pfad angegeben werden.
Das Programm ist mit knappen Kommentaren versehen; es soll hier aber nicht im Detail besprochen werden.
Ein paar Worte zum file-Operator erscheinen jedoch angebracht: file erwartet zwei Argumente vom Typ Zeichenkette (string) auf dem Stapel: den Dateinamen und den Zugriffsmodus, und öffnet dann eine Datei des angegebenen Namens, die er als oberstes auf den Stapel legt. Laut "Rotem Buch" sind sechs verschiedene Zugriffsmodi möglich. r (nur lesen), w (nur schreiben, Datei erzeugen, wenn nicht vorhanden, überschreiben, wenn vorhanden), a (nur schreiben, Datei erzeugen, wenn nicht vorhanden, neue Daten anhängen, wenn vorhanden), r+ (lesen und schreiben, Fehlermeldung, wenn Datei nicht vorhanden), w+ (lesen und schreiben, Datei erzeugen, wenn nicht vorhanden, nicht gelesene Daten gegebenenfalls überschreiben) und a+ (nur schreiben, Datei erzeugen, wenn nicht vorhanden, neue Daten anhängen, wenn vorhanden). Ich habe lange gebraucht, bis ich herausgefunden habe, daß GhostScript (zumindest so, wie ich es installiert habe), davon nur r und r+ akzeptiert, während die anderen mit der Fehlermeldung "invalid access" zum Abbruch führen.



Exkurs über DSC (Dokumentenstrukturkonventionen)

Bei einfachen kleinen Programmen für die Betrachtung am Bildschirm braucht man sich über die Strukturierung keine Gedanken zu machen. Wenn man aber eine Datei zum Drucker schickt, empfiehlt es sich, die erste Zeile mit %! beginnen zu lassen: abhängig von der Einrichtung des Druckers könnte es sonst passieren, daß das Programm nicht als PostScript interpretiert, sondern als Text ausgedruckt wird. Dies ist das erste und einfachste Beispiel für eine Dokumentstrukturanweisung.
Weitere DSC-Anweisungen haben wir bei der Besprechung des EPSF-Formats kennengelernt:
%!PS-Adobe-3.0 EPSF-3.0
%%BoundingBox: 0 0 1024 768
stand in unserem Beispiel, das mit der Zeile
%%EOF
beendet wurde.
Die DSC-Kommandozeilen beginnen mit dem Prozentzeichen und werden daher vom PostScript-Interpreter als Kommentare überlesen: Wenn alle mit %% beginnenden Zeilen entfernt werden, ändert sich im Fall des einfachen Beispielprogrammes nichts.

Es kann aber doch nützlich sein, etwas mehr über diese zweckmäßigen Konventionen zu wissen und noch ein paar Kommandos mehr zu kennen.
Ein Beispiel: Wenn man für eine Präsentation eine größere Datei – mehrere Seiten Text und Bilder – vorbereitet hat, dann kann man sie z.B. mit Hilfe von GSview ansehen und durchblättern. Ohne Verwendung von DSC kann man aber nicht zurückblättern, was oft wünschenswert ist. Um das Zurückblättern zu ermöglichen, oder sogar den Sprung zu einer beliebigen Seite, müssen die einzelnen Seiten unabhängig voneinander sein, und es müssen Seitennummern vorhanden sein.
Letzteres wird durch die Angabe von Gesamtseitenzahl, z.B.
%%Pages: 32
im Vorspann und der Seitennummer durch z.B.
%%Page: Bild2 3
(die Seite 3 hat in diesem Beispiel den Namen Bild2) am Beginn jedes Seitenaufbaues erreicht.
Unabhängigkeit der Seiten voneinander heißt, sie müssen in beliebiger Reihenfolge interpretierbar sein. Um Definitionen und Prozeduren, die auf vielen Seiten verwendet werden, nicht für jede Seite neu eingeben zu müssen, können sie in einem Prolog vereinbart werden,
%%BeginProlog
. . .
%%EndProlog

und sind dann von jeder Seite aus sichtbar.

Das Paar
%%BeginDocument: Name
und
%%EndDocument
ist uns bei der Behandlung des eps-Beispiels schon begegnet: man verwendet es für eingeschlossene Dokumente, um deren DSC unschädlich zu machen, z.B. die DSC-Information
%%EOF
die sonst das Dateiende signalisieren würde.

Wer mehr wissen will, sei auf die Original-Spezifikationen von Adobe verwiesen.



Ausblick

Ich habe hier eine Auswahl von PostScript-Anweisungen abgehandelt, die ich beim Erstellen von einfachen Grafiken (oft allerdings mit aufwendig berechneten Farben) immer wieder benütze; es soll aber darauf hingewiesen werden, daß die Sprache sehr viel umfangreicher ist und insbesondere für hier nicht angesprochene drucktechnische Detailprobleme Lösungen bietet.

Dies alles findet man im "Roten Buch" von Adobe, und dieses findet man im Netz! (pdf-Datei, ca. 5.8 MB)
Im Netz ist im übrigen auch das "Blaue Buch", Adobes PostScript Language Tutorial & Cookbook vorhanden, sowie das "Grüne Buch", in dem die Struktur der Seitenbeschreibungssprache beschrieben wird.


Zurück zur Seite "Ästhetik und Naturwissenschaft"

Zurück zur Eingangsseite


Valid HTML 4.01 Transitional CSS ist valide!