Seminararbeit

Werbung
Seminararbeit
zum Thema:
Bildverarbeitung mit Java2D gegen Java Advanced Imaging
Betreuender Professor: Link, Norbert
Autor: Winkler, Bernd
MatrikelNr.: 008952
Inhalt:
1. Vorwort
1.1. Was ist Bildverarbeitung
1.2. Einsatzgebiete
1.3. Warum Bildverarbeitung mit Java
2. Java2D
2.1. Typen und Operanden für simple Anwendungen
2.2. Anzeigen eines gespeicherten Bildes
2.3. Zeichnen eines Bildes
2.4. Bildobjekte
2.4.1. Bildklassen (BufferedImage, RenderedImage, ...)
2.4.2. Die Farbmodelle
2.4.3. Das Raster
2.4.4. Erzeugen eines Bildobjekts
2.4.5. Pixeldatenbereiche auslesen
2.5. Operationen für Bildobjekte
2.5.1. Operatoren
2.5.2. Rendering Hints
2.5.3. Transformationen im Grafikkontext
2.5.4. Bildfilter
2.6. Hilfsmittel im Umgang mit Bildern
2.7. Applikationserstellung mit Java2D
2.8. Übersicht
3. Java Advanced Imaging
3.1. Typen und Operanden für simple Anwendungen
3.2. Anzeigen eines gespeicherten Bildes
3.3. Verzahnung mit Java2D
3.4. Bildobjekte
3.4.1. Aufbau der Bildklassen
3.4.2. Erzeugen von Bildobjekten
3.4.3. Kombinieren von Bildobjekten
3.4.4. Der Parameterblock
3.4.5. Das Raster
3.5. Operationen auf den Bildobjekten
3.6. Hilfsmittel im Umgang mit Bildern
3.7. Applikationserstellung mit JAI
3.8. Übersicht
4. Gegenüberstellung beider Frameworks
5. Quellen
Seite 2 von 50
1. Vorwort
1.1. Was ist Bildverarbeitung
Mit der zunehmender Rechenleistung von Computern und der dadurch geschaffenen Möglichkeit
Bilder auf einem Monitor anzuzeigen oder gar zu analysieren sind viele Anwendungen machbar
geworden. In diesen Anwendungen werden Bilder gespeichert, gelesen, verändert, analysiert, angezeigt und gedruckt. Gerade in der Bildanalyse wird die Komplexität und Leistungsanforderung
des Themas deutlich. Die Anforderungen, die die Bildverarbeitung an eine Entwicklungsumgebung
stellt können immens sein, da hierbei möglicherweise mehrmals auf die Daten jedes Pixels zugegriffen wird. Ein Beispiel, das dies Verdeutlicht: ein Bild der Größe 1024x768 besteht aus 95232
Pixel. Angenommen jeder Zugriff auf ein Pixel im Speicher dauert 3 ns und unsere Applikation
lädt und schreibt und lädt, so dauert diese gesamte Operation 857088 ns, also 0.85 sec. Das kann
durchaus in Ordnung sein, außer, wenn diese Zeit für jeden Frame der Darstellung in einem Scrollfenster vergeht. Die Anforderungen der Bildmanipulationsmöglichkeit fordern zudem
Schnittstellen, die ein Anwenden von mathematischen Operatoren auf das Bild zulassen. Diese
Operatoren werden auf die Bilddaten mittels Klassen, von bereits bestehenden Frameworks der
Entwicklungsumgebung, angewandt um das Bild beispielsweise zu drehen, zu skalieren oder auch
zu De-/Komprimieren oder in seiner Farbgebung zu verändern.. Das Bild und die Änderungen
müssen dem User sichtbar gemacht werden, was ebenfalls zu den Aufgaben des Frameworks
gehört, dafür Klassen für entsprechende Objekte bereit zu stellen. Von Anwendungen der
Bildanalyse wurden noch weitere Operatoren gefordert, die komplexe mathematische
Zusammenhänge auf dem Bild sichtbar zu machen ermöglichen. Die dadurch geschaffenen
Entwicklungsmöglichkeiten sind die, der heute in der Industrie im bildverarbeiteden
Zusammenhang verwendeten Frameworks.
1.2. Einsatzgebiete
Bildverarbeitende Systeme werden heute in der Industrie in verschiedensten Bereichen eingesetzt.
Häufig findet man Bild analysierende Systeme zusammen mit Bild anzeigenden Systemen im
Einsatz. Diese Applikationen werden beispielsweise zur Abstandsmessung (Selbststeuerndes
Auto), Objektpositionierung (Fließbandfertigung), Objektverfolgung (Überwachungskameras),
Objektidentifikation (Biometrie) oder Qualitätsprüfung (Produktion) verwendet. Reine Bild
anzeigende Applikationen, die meist auch Funktionen zum Drucken des selbigen bieten, finden
ihre Anwendung in der Bildbestandsverwaltung (Werbeindustrie oder Produktionspläneverwaltung), Bildaufbereitung (z.B. Gamma-Wert Korrektur) und Bilderzeugung (Zeichenprogramme). Die dritte und letzte Gruppe von Anwendungen bei denen Bilder verarbeitet werden,
bilden die Applikationen, die Bilder laden und anzeigen, auch wenn das nicht ihr Hauptzweck ist.
Gemeint sind fast alle Applikationen, denn diese Fähigkeit besitzt bereits jedes Programm, das ein
Icon oder ein Logo verwendet. Bei modernen Anwendungen wird meist eine Vielzahl von kleinen
Bildern verwendet, beginnend mit den Toolbars über die Pfeile auf Navigationsbuttons bis zum
eigenen Mauszeiger.
Seite 3 von 50
1.3 Warum Bildverarbeitung mit Java
Java bietet einige Vorteile, die andere Programmiersprachen so nicht bieten. Zum einen braucht ein
fertig kompiliertes Java-Programm relativ wenig Speicherplatz, kann also schnell durch das
Internet übertragen werden, zum anderen sind Java-Files hoch portabel, soll heißen, sie laufen auf
den meisten Betriebsystemen ohne Probleme. Web-Applikationen, die mit Bildern arbeiten, sind
durchaus in Java programmierbar. Auch wäre im Zusammenhang mit dem Web eine
Bildverarbeitungssoftware vorstellbar, bei der die komplexe Operation von einer anderen
Programmiersprache übernommen wird und Java nur zum Anzeigen dient. Möglicherweise in
diesem Zusammenhang eine Client-Server-Architektur. Zum Anzeigen von Bildern ist Java auf
jeden Fall besten geeignet, erstens, da Bildanzeige-Widgets vorhanden sind und leicht selbst
geschrieben werden. Zweitens, da die Programmierung in Java-Code erfolgt und
dementsprechende Vorteile geniest, beispielsweise Laufzeitfehler statt Systemabsturz oder auch,
dass der Code nur einmal kompiliert werden muss. Ob Java für die tatsächliche Verarbeitung von
Bildern brauchbar ist (die wesentlich Rechenzeit intensiver ist), ist fraglich und kommt auf die
Aufgabe an. Wenn es um das Skalieren, Verschieben oder das ändern des Farbmodels bzw. das
ändern der Zuordnung von Farben von Bildern geht, ist Java genauso gut wie jede andere Sprache.
Wie alles in Java läuft es subjektiv gesehen jedoch ab einer gewissen Anzahl von
durchzuführenden Operationen auch in der Bildverarbeitung langsam. Es ist dann mehr an der
Kunst des Programmierers, die Rechenzeit auf inaktive Momente zu legen, um die Laufzeit zu
verbessern.
2. Java2D
Java2D ist ein eigenständiges Framework zur Verarbeitung von Bildern und Grafiken. Seit dem
JDK 1.2 ist Java2D ein Teil der API des Java SDK's. Die Version, die mit der Version 1.4 des
Standard Development Kit's ausgeliefert wird ist relativ leistungsfähig, verglichen mit den
Leistungen in seinem Anfangsstadium. Das Java2D API beinhaltet 2D Grafik-, Text- und
Bildkapazitäten durch eine Erweiterung des Abstract Windowing Toolkit (AWT). Das Java2D API
stellt folgendes zur Verfügung:






Einheitliches Rendermodel für Grafik anzeigende wie für Grafik druckende Geräte
(WYSIWYG)
Große Anzahl von geometrischen Primitiven, wie Kurven, Rechtecke und Ellipsen und
Mechanismen um ein geometrisches Objekt virtuell zu rendern.
Methoden zur Überlappungserkennung von geometrischen Objekten, Texten und Bildern.
Ein Verknüpfungsmodel das zugriff ermöglicht, wie die Farbwerte überlappender Pixel
miteinander verrechnet werden.
Erweiterte Farbunterstützung für ein erleichtertes Farbmanagement
Unterstützung komplexe Dokumente zu drucken.
Seite 4 von 50
2.1. Typen und Operanden für simple Anwendungen
java.awt.Graphics
Java AWT Grafikkontext
java.awt.JComponent
Klasse, deren Objekt ein Bild tragen wird und das auf einem
Fenster angezeigt werden kann.
java.awt.geom.AffineTransform
Affine Transformation, die beispielsweise eine Skalierung
erlaubt.
java.awt.image.BufferedImage
Klasse deren Objekte die Bilddaten im Speicher halten.
javax.swing.JFrame
Klasse, die ein Fensterobjekt liefert, auf dem die Anwendung
sichtbar wird.
2.2. Anzeigen eines gespeicherten Bildes
Das anzeigen eines gespeicherten Bildes erfordert nur zwei Arbeitsschritte und ist deshalb auch als
Quelltext leicht zu begreifen.
1. Laden des Bildes von der Festplatte
2. Ablegen des Bildes in einer anzeigenden Komponente auf einem sichtbaren Fenster
.
.
.
public JLabel getImageComponent(){
JLabel label = new JLabel();
label.setIcon(new ImageIcon("pfad/bilddateiname.typ"));
return label;
}
.
.
.
public void buildFrame(){
JFrame frame = new JFrame("Bildanzeige");
frame.setBounds(0,0,800,600);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(getImageComponent());
frame.setVisible(true);
}
.
Seite 5 von 50
.
.
Dies ist eine extreme simple Lösung die Java2D benutzt um das Bild anzuzeigen. JLabel erbt von
java.awt.Component, die die Methode public void paint(Graphics g) abstrakt deklariert.
JLabel besitzt eine inline deklarierte Klasse AccessibleJLabel, die sich eines AccessibleContextes
bedient, um der JComponent, die sich in der Erbfolge von Component zu JLabel befindet, auf
AWT-Art mitzuteilen, dass es ein Bild zu zeichnen hat. Um ein Bild anzuzeigen brauch man aber
im Allgemeinen nicht die Fähigkeiten eines JLabels um ein Bild zu präsentieren, es sei denn, die
Laufzeit und Speicheranforderungen sind an dieser Stelle nicht relevant. Sonst aber bietet die
Lösung wenig Fähigkeiten zur Erweiterung, im Sinne der Bildverarbeitung. Deshalb hier noch ein
Beispiel, welches wesentlich Allgemeiner gehalten ist und so ein höheres Entwicklungspotential
bietet. In diesem Beispiel ist die Klasse, die beschrieben wird, die Komponente, die auf das Fenster
gelegt wird.
public class MyComp extends JComponent {
private Image image;
public MyComp(ImageIcon icon){
super();
image = icon.getImage();
}
public void paint(Graphics g){
g.drawImage(image,0,0,this);
}
}
Die Methode buildFrame() könnte dann folgendermaßen aussehen:
public void buildFrame(){
JFrame frame = new JFrame("Bildanzeige");
frame.setBounds(0,0,800,600);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new MyComp(new ImageIcon(“pfad/name.typ”);
frame.setVisible(true);
}
Das Beispiel bietet vor allem durch den direkten Zugang auf den Grafikkontext hohe
Erweiterbarkeit. In den Objekten des Grafikkontextes kann beliebig gezeichnet oder Bilder
eingeladen werden. Außerdem wird von der Klasse die Anwendung von Transformations-, Fontund Colorobjekte unterstützt. Der Grafikkontext ist die Grundlage von Java2D.
Seite 6 von 50
2.3. Zeichnen eines Bildes
Bilder zu zeichnen ist beispielsweise bei Anwendungen nötig, die Ergebnisse auf Bildern
einblenden, beispielsweise der Text über einem Bild eines Formulars, oder bei Anwendungen, die
ein Overlay über ein anderes Bild oder eine Eingabemaske legen. In Java2D sind
java.awt.Graphics und java.awt.Graphics2D die grundlegenden Klassen, die Zeichenoperationen
durchführen. Hier Beispiele für Bilder die mit einem Objekt dieser Klasse gezeichnet wurden.
Die Objekte von Graphics lassen sich durch eine paint-Methode oder ein Bild gewinnen.
public void paint(Graphics g){
.
.
.
}
bzw.
Image image = loadImage();
Graphics g = image.getGraphics();
Das Zeichnen in die Grafikkontexte beider Methoden unterliegt den gleichen Regeln, bis auf die
Tatsache, dass die Darstellung der Pixel von den in ein Bild gezeichneten Grafikobjekten an den
Koordinatenachsen clipt. Dies ist bei Graphics2D ausgeprägter, da sie von Graphics erbt und es
erweitert. Durch casten wird aus dem erhaltenen Graphics-Objekt ein Graphics2D-Objekt:
Graphics2D g2d = (Graphics2D) g;
Die Ansammlung von Zustandattributen verbunden mit einem Graphics2D-Objekt wird als der
Graphics2D-Rendering-Kontext bezeichnet. Eine Grafik oder ein Bild mit dem Grafikkontext zu
zeichnen und diese Zustandsattribute dabei darauf anzuwenden heißt Rendern. Auf der nächsten
Seite werden diese Attribute des Grafikkontextes im einzelnen genauer Beschrieben:
Seite 7 von 50
Der Pen-Style (reines Linien zeichnen) wird durch das Stroke-Attribut beeinflusst.
Damit wird festgelegt, wie dick die nächsten zu renderden Linien sind oder auch in
welchem Abstand sie gestrichelt gezeichnet werden soll. Des Weiteren beschreibt
dieses Attribut wie die Linienenden aussehen. Die Linienfarbe wird jedoch durch die
aktuelle Zeichenfarbe beschrieben.
Der Fill-Style (flächenfüllendes Zeichnen) wird durch die aktuelle Zeichenfarbe
bestimmt, sofern kein Userspezifisches Pattern-Attribut gesetzt wurde. Dieses
erlaubt Füllen mit reiner Farbe, nach Gradienten oder mit Pattern.
Der Composite-Style wird durch das gesetzte Composite-Attribut bestimmt.
Dieses beschreibt eine Vorschrift, wie mit den Farbwerten zweier Pixel mit gleichen
Darstellungskoordinaten der Farbwert des entsprechenden Darstellungspunktes
errechnet wird.
Es ist durch das Transform-Attribut möglich den gesamten Grafikkontext einer
affinen Transformation zu unterwerfen, wodurch eine automatische Umrechnung
zwischen User-Space- und Device-Space-Koordinaten möglich wird. Zusätzliche
Translationen, Skalierungen, Scherungen und Rotationen können ebenfalls noch
damit Eingerechnet werden.
Durch einfügen eines beliebigen geometrischen Objekts kann das Clip-Attribut
gesetzt werden, so das die Darstellung von Pixel außerhalb der Aussenkante des
Objekts ausbleibt.
Das Font-Attribut wird verwendet um Zeichenketten in Glyphen umzuwandeln. Es
ist ausgestattet mit Schriftart, -größe und –stiel (Bold und/oder Italic).
Das Rendering Hints-Attribut beschreibt, die bei der Darstellung verwendete
Abwägung zwischen Qualität und Geschwindigkeit. Mit diesem Attribut kann man
Darstellungsfeatures wie beispielsweise Antialiasing, Dithering oder auch
Interpolationsart einstellen.
Die durch den Kontext generierte Grafik wird durch diese Attribute beeinflusst. Die Attribute
lassen sich mit folgenden Methoden setzten:
g2d.setClip(myClip);
g2d.setTransform(myAffineTransform);
g2d.setFont(myFont);
g2d.setColor(myColor);
g2d.setStroke(myStroke);
g2d.setPaint(myPaint);
g2d.setComposite(myComposite);
//Rechteck
<- x, y, Breite, Höhe
//3x2 Matrix
<- AffineTransform
//Schriftart
<- Font
//Zeichenfarbe <- Color
//Linienstiel <- Stroke
//Textur
<- Paint
//Überlappungsvorschrift
Seite 8 von 50
Die Zeichenoperationen heißen alle draw...(...) bzw. fill...(...). Hier ein Beispiel für die
Anwendung, in dem ein Vermessungsraster-Bild generiert wird. Da die Klasse
java.awt.Image abstrakt ist, wird in diesem Beispiel ein Objekt einer von Image erbenden
Klasse für das Bild verwendet.
public BufferedImage createImage(){
BufferedImage bi = new BufferedImage(200,
200,
BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = (Graphics2D) bi.getGraphics();
g2d.setColor(Color.black) ;
for( int i = 0 ; i < bi.getWidth(); i++ ){
if( i % 5 == 0 )
g2d.drawLine(i,0,i,10);
if( i % 10 == 0 )
g2d.drawLine(i,0,i,20);
if( i % 50 == 0 )
g2d.drawString(new String().valueOf(i),i,25);
}
for( int i = 0 ; i < bi.getHeight(); i++ ){
if( i % 5 == 0 )
g2d.drawLine(0,i,10,i);
if( i % 10 == 0 )
g2d.drawLine(0,i,20,i);
if( i % 50 == 0 ){
g2d.drawString(new String().valueOf(i),25,i);
g2d.drawOval(-i,-i,i*2,i*2);
}
}
return bi;
}
Die Abbildung zeigt den durch den darüber stehenden Quelltext erzeugte Bildinhalt. Die weißen
Flächen erhalten ihre Farbe durch den Hintergrund, im gezeichneten Bild befinden sich dort Pixel
mit einem Alphawert von 0, also volltransparent.
Seite 9 von 50
Ein anderes Beispiel erzeugt eine ein Bild, dass man beispielsweise als Grundlage für ein
Werbebanner verwenden kann.
public BufferedImage createImage(){
//Neues RGB-Image erzeugen
BufferedImage bi = new BufferedImage(400,//Breite
100,//Höhe
BufferedImage.TYPE_INT_RGB);
//Objekt des Grafikkontextes
Graphics2D g2d = (Graphics2D) bi.getGraphics();
//In der Schleife wird der Hintergrund erzeugt
for( int i = 0 ; i < bi.getHeight() ; i++ ){
int a = (int) (i/bi.getHeight()) * 255;//a ist ein Zähler im
//Interval 0 – 255
//
R G
B
Color color = new Color( a, 50, 255-a );
}
g2d.setColor(color);
g2d.drawLine(0,i,bi.getWidth(),i);//Horizontale Linie auf
//der Höhe i rendern
//Jetzt gehts um den Schriftzug
g2d.setColor(Color.white);//Farbe der Aufschrift
AffineTransform aftShear = new AffineTransform();
aftShear.setToShear(-0.5,0.0);//Affine Transformation für Scherung
//in negativer x-Richtung
g2d.setTransform(aftShear);
//Transformation wird gesetzt
g2d.setFont(new Font("Comic Sans MS",Font.BOLD,80));//Schriftart
//
Aufschrift
x
y
g2d.drawString( "Kauf Was" , 40, 80);//Font-Objekt wird gerendert
return bi; //Bild zurückgeben
}
Abbildung zeigt den Bildinhalt, der durch den darüber stehenden Quelltext erzeugt wird
2.4 Bildobjekte
In Bildobjekten sind die Bild repräsentierenden Daten organisiert. Die Werte jedes Pixels sind in
einem Raster-Objekt gespeichert bzw. werden dorthin eingeladen, wobei das Bild an
Bildbeobachter Informationen über den Ladestatus abgeben kann. Das Bildobjekt hält Methoden,
die Informationen über das Bild abgeben bereit, wie beispielsweise Breite und Höhe oder
Farbmodel. Außerdem ermöglicht dem Benutzer Zugriff auf die Bilddaten mittels eines
Grafikkontextes und dem Raster.
Seite 10 von 50
2.4.1. Bildklassen
Image
Die abstrakte Klasse java.awt.Image bildet die Grundlage für jede andere grafische Bilder
repräsentierende Klasse.
VolatileImage
Die abstrakte Klasse java.awt.image.VolatileImage erklärt ein Bild, das jederzeit
seinen Inhalt aufgrund äußerer Umstände verlieren kann. Die kann beispielsweise durch das
Betriebssystem oder andere Anwendungen auftreten, liegt also außerhalb der Steuerungen dieses
Programms. Durch seine Möglichkeiten der Hardwarebeschleunigung kann ein VolatileImage
auf einigen Plattformen signifikante Performance-Vorteile genießen.
RenderedImage
java.awt.image.RenderedImage ist das allgemeine Interface für Objekte die Bilder in
Form von Rastern beinhalten oder produzieren können. Die Bilddaten können entweder als
einzelner Bildteil (so genanntes Tile) oder als Array aus Bildteilen gespeichert/produziert
werden.
WritableRenderedImage
Das in java.awt.image enthaltene Interface erbt von RenderedImage und erweitert diese
durch das Anbieten der Möglichkeit interessierte Objekte über Bildteilproduktionsereignissen in
Kenntnis zu setzten. Alle interessierten Objekte müssen das Interface TileObserver
implementieren und sich als solche in dem WritableRenderedImage eintragen.
BufferedImage
Das ist ein Bild, das vielseitig genutzt werden kann. Zum einen kann es zum Double Buffering
benutzt werden, dadurch, dass sich sämtliche Daten aus ihm Abfragen lassen und somit leicht eine
Kopie erzeugt werden kann. Zum anderen kann in diese Bild gezeichnet werden. Bei alle dem
unterstützt das BufferedImage verschiedenste Farbtiefen und Farbbänder. Die Klasse erbt von
Image, implementiert WritableRenderedImage und managet ein Bild im Speicher. Sie
bietet Methoden zum speichern, interpretieren und rendern der Pixeldaten. Ein Objekt der Klasse
java.awt.image.BufferedImage ist im Wesentlichen ein Bild mit einem zugreifbaren
Datenpuffer. Ein BufferedImage hat ein Farbmodel und ein Raster von Bilddaten.
Abbildung zeigt die, durch die Objekthierarchie entstandenen, zugriffsbereiten Datenobjekte eines
BufferedImage. Ein Raster-Objekt hat ein SampleModel- und ein DataBuffer-Objekt
und ein ColorModel-Objekt ein ColorSpace-Objekt. Raster und ColorModel sind
Objekte in BufferedImage.
Seite 11 von 50
2.4.2 Die Farbmodelle
Die abstrakte Klasse java.awt.image.ColorModel kapselt die Methoden für das
Übersetzen eines Pixelwertes in einzelne Farbkomponenten, wie beispielsweise die Rot-, Grün-,
Blau- (und Alpha-) Komponente. Dies ist immer dann nötig, wenn ein Bild angezeigt, gedruckt
oder auf ein anderes Bild gezeichnet wird. Als Argumente für Zu- oder Rückholwerte von den
Methoden dieser Kategorie, werden Pixel als 32-bit Integer oder als Array von primitiven Typen
dargestellt. Sollte ein Bild verwendet werden dessen Pixeldaten nicht aus einzelnen Integerwerten
bestehen so wird eine IllegalArgumentException geworfen, falls eine Methode aufgerufen
wird, die auf Integerwerten arbeitet. Die Anzahl, Anordnung und Interpretation von
Farbkomponenten für ein ColorModel ist in einem ColorSpace-Objekt spezifiziert. Ein
Farbmodel das mit Pixeldaten benutzt wird, die keine Alpha-Werte enthalten, setzt alle Pixel auf
undurchsichtig, also mit einem Alpha-Wert von 1.0.
Die verschiedenen Arten, mit denen die Farbwerte gespeichert werden sind:
DataBuffer.TYPE_BYTE
DataBuffer.TYPE_INT
DataBuffer.TYPE_USHORT
DataBuffer.TYPE_SHORT
DataBuffer.TYPE_FLOAT
DataBuffer.TYPE_DOUBLE
Alle diese Typen bezeichnen den so genannten Transfertyp. Die höchste Performance erreicht man,
wenn man ein Farbmodel mit einem der ersten drei Typen verwendet. Hinzu kommt, dass einige
Bildfilter-Operationen die letzten drei Farbmodelle nicht unterstützen.
In dem Raster eines BufferedImage und dem SampleModel des Rasters müssen die
Transfertypen mit dem des Farbmodels übereinstimmen. Genauso muss die Anzahl der Elemente
des Arrays, das einen Pixelwert repräsentiert, für Raster und SampleModel mit dem des
ColorModels des BufferedImage identisch sein.
Das im ColorModel enthaltene Objekt der Klasse java.awt.color.ColorSpace wird
von dem ColorModel benutzt um den Farbraum zu erfahren, auf den sich ein Color-, Image-,
BufferedImage- oder einem GraphicsDevice-Objekt bezieht. Es besitzt außerdem
Methoden um zwischen RGB- und CIEXYZ-Farbräumen umzurechnen. Der CIEXYZ-Farbraum
ist so definiert, dass sich mit seiner Hilfe zwischen zwei Farben von beliebigen Farbräumen mit
hoher Genauigkeit umgerechnet werden kann.
Um bei der Bildverarbeitung eine noch höhere Performance und weniger Speicherauslastung zu
erreichen kann anstatt eines BufferedImages mit einem ColorModel mit einem
PackedColorModel oder einem IndexColorModel versehen werden.
PackedColorModel speichert alle Samples von einem Pixel in einem 32-bit Integer. Das bringt
nur bei Bildern, die vom Grafikkontext gezeichnet wurden und viele grafische Objekte mit
vielen Überlappungen beinhalten einen Vorteil.
IndexColorModel hingegen speichert in einer Tabelle für jeden möglichen Farbwert einen 32bit Integer Wert der einem ARGB-Wert entspricht. Dadurch lässt sich bei s/w-Bildern eine
Kompression von ca. 32:1 erzielen ohne zusätzlichen Rechenaufwand erzielen.
Seite 12 von 50
2.4.3 Das Raster
Die Klasse java.awt.image.Raster beschreibt ein 2-Dimensionales Array von einer
rechteckigen Anordnung von Pixel. Das Raster kapselt außer einem ColorModel einen
DataBuffer, der die Sample-Daten speichert, und ein SampleModel, das beschreibt, wie ein
gesuchter Sample-Wert von dem DataBuffer extrahiert wird und mithilfe des ColorModel in
einen Farbwert für die gesuchte Position im 2D-Koordinatensystem geliefert wird.
Die von Raster erbende Klasse WritableRaster erweitert diese um einen schreibenden
Zugriff auf die Pixeldaten in SampleModel und DataBuffer.
Von der Klasse SampleModel von der in einem Raster ein Objekt existiert, werden die
Samples der Pixel eines Bildes gehalten und lese- bzw. schreibender Zugriff darauf ermöglicht.
Eine Sample sind Daten für ein Band eines Bildes und ein Band besteht aus allen Samples von
einem bestimmtem Transfertyp eines Bildes.
Beispielsweise könnte ein Pixel drei Samples enthalten, die seinen roten, grünen und blauen
Bestandteil darstellen. Es gibt drei Bänder im Bild, das dieses Pixel enthält. Ein Band besteht aus
allen roten Samples von allen Pixels im Bild. Das zweite Band besteht aus allen grünen sowie das
dritte aus allen blauen Samples besteht. Das Pixel kann in den verschiedenen Formaten gespeichert
werden, z.B. können alle Samples von einem bestimmten Band oder alle Samples von einem
bestimmten Pixel kontinuierlich gespeichert werden.
Die Klasse DataBuffer existiert, um eine oder mehr Daten-Array's zu verwalten. Jedes Array
im DataBuffer wird als eine Bank angesehen. Die Klasse stellt dazu verschiedene
Zugriffsmethoden für die Bänke zur Verfügung, die ein Ein- und Auslesen ermöglichen. Diese
Methoden sind für verschiedene Typen von Bänken vorhanden. Die Bänke bekommen beim
erzeugen diesen Typ zugewiesen. Er beschreibt die Art von Daten, die in der Bank gespeichert
werden. Diese Typen sind:
TYPE_BYTE
TYPE_INT
TYPE_USHORT
TYPE_SHORT
TYPE_DOUBLE
TYPE_FLOAT
TYPE_UNDEFINED
Letzterer bietet jedoch weniger Performance als die darüber stehenden Typen und wird von Java
2D beim Laden von Bildern nicht verwendet.
Hier ein Beispiel, was durch das Verständnis des Rasters ermöglicht wird. Von einem Bild sollen
die einzelnen Farbbänder herausgefiltert und angezeigt werden. Dazu werden die Farbsamples aus
dem Raster die ein Band repräsentieren in ein Array aus Integerwerten kopieren und dieses dann
wieder in ein Raster eines neuen Bildes einfügen. Das Raster wiederum holt und schreibt diese
Daten mittels dem SampleModel und dem DataBuffer.
Seite 13 von 50
Zunächst die Routine, die ein Array von Integerwerten aus einem BufferedImage über das
Raster extrahiert. Das Raster benutzt dazu sein SampleModel um ein Farbband zu landen.
Dieses wiederum befragt den DataBuffer, der alle Daten des Bildes Band für Band gespeichert
hat. Die Daten aus dem DataBuffer für das entsprechende Band werden von dem
SampleModel an das Raster weitergereicht und dort mit Hilfe des ColorModels bzw.
dessen ColorSpace in Farbwerte interpretiert. Zurück kommt ein Array aus Integerwerten mit
zahlen zwischen 0 und 255 in diesem Fall, wo der Transfertyp INT_RGB als Grundvoraussetzung
angenommen wird.
private getIntegerBand(BufferedImage image, int bandNr){
int[] iArray
=
new int[ image.getWidth() * image.getHeight() ];
image.getSamples(0,
0,
image.getWidth(),
image.getHeight(),
bandNr,
iArray);
//X-Koordinate von Links Oben
//Y-Koordinate von Links Oben
//Breite des Bereichs
//Höhe des Bereichs
//gewünschtes Band
//speichert Ergebnis
return iArray;
}
Und die Routine, die aus einem Array von Integerwerten einem Bild zurück transformiert um das
Ergebnis der Routine getIntegerBand(...) sichtbar zu machen:
private BufferedImage getImageVisible(int width, int bandNr, int[] iArray){
int height
=
(int) ( iArray.length / width );
BufferedImage bi = new BufferedImage( width,
height,
BufferedImage.TYPE_INT_RGB);
bi.getRaster().setSamples( 0, 0, width, height, bandNr, iArray);
return bi;
}
Seite 14 von 50
Hier ein Beispielbild auf das die beiden Routinen losgelassen wurden.
Das Orginalbild
Das Rote Farbband wird als Array zurückgegeben bei
bandNr = 0
Das Grüne Farbband wird zurückgegeben, wenn
bandNr = 1
Und für das blaue Farbband gilt
bandNr = 2
Zwischen dem Aufruf der beiden Routinen kann das Array beliebig verändert werden. Die
Laufzeiten betragen übrigens für die verwendete Routine getSamples(…) dieses im Original
640x480 großen Bildes 40ms und für die hier verwendete Routine setSamples(…) beträgt sie
50ms. Sollten eine dieser Routinen direkt in einer paint-Methode verwendet werden, so sollte nicht
mehr viel Rechenzeit nebenher verbraucht werden, da die paint-Methode sich sehr stark auf die
Laufzeit des Programms auswirkt.
Seite 15 von 50
2.4.4 Erzeugen eines Bildobjekts
Bildobjekte der verschiedenen Klassen zu erzeugen ist im Prinzip einfach, wie auch das Objekt so
zu konfigurieren, dass die optimalen Voraussetzungen für die weitere Verarbeitung gewährleistet
sind. Die Auswahl beschränkt sich bei Java2D meist auf das BufferedImage, da dieses die
meisten Konfigurationen zulässt. Ein Image kann man nur erhalten, wenn man davon erbt oder
ein davon erbendes Objekt hält bzw. es von einer Factory-Klasse bezieht, da die Klasse abstrakt ist.
Ebenso wie ein Objekt von VolatileImage oder ein Objekt das das Interface
RenderedImage implementiert. Bei einem BufferedImage arbeiten die Methoden die
transfertyp- oder farbmodelspezifische Daten behandeln meist mit einem Eingabeparameter
imageType. Die hier einzusetzenden Werte können von der Klasse BufferedImage bezogen
werden, die diese mit statischen Variablen erklärt. Hier einige Beispiele:
BufferedImage.TYPE_INT_RGB
BufferedImage.TYPE_USHORT_GRAY
BufferedImage.TYPE_BYTE_BINARY
BufferedImage.TYPE_4BYTE_ABGR
Deutlich zu sehen ist der Transfertyp und das Farbmodel, das benutzt wird. Die einfachste Art ein
leeres BufferedImage zu gewinnen ist folgender Aufruf:
... new BufferedImage( int_width,
int_height,
BufferedImage.TYPE_3BYTE_BGR );
Für den Fall, dass ein indiziertes oder ein binäres Farbmodel verwendet werden soll, dient speziell
dieser Konstruktor:
... new BufferedImage( int_width,
int_height,
BufferedImage.TYPE_BYTE_INDEXED,
myIndexColorModel );
Dabei ist myIndexColorModel ein Objekt der Klasse IndexColorModel, dem Bits pro Pixel
und Farbband - Farbwert Referenzlisten übergeben werden, um es zu erzeugen.
2.4.5 Pixeldatenbereiche auslesen
Um das Auslesen von Pixelbereichen zu erleichtern, ist in der Klasse BufferedImage eine
Methode getSubimage(int x, int y, int w, int h) implementiert. Wird etwas
anderes als ein achsenparalleler rechteckiger Bildausschnitt gewünscht, so muss direkt auf das
Raster, das SampleModel und den DataBuffer zugegriffen werden. Beispielsweise mit:
bufferedImage.getRaster().getSample(x,y,band);
bufferedImage.getRaster().getPixel(x,y,iArray);
Seite 16 von 50
2.5. Operationen für Bildobjekten
Um Funktionen wie Zoomen, Drehen, Glätten, Schärfen usw. anzubieten werden Operatoren
benötigt, die mathematische Funktionen auf das Bild anwenden und die Bilddaten dadurch in
gewünschter Richtung verändern bzw. ein neues Bild zurückgeben. In Java2D gibt es einige
grundlegende Operatoren, die Standardoperationen ausführen, doch durch den Zugriff auf die
Pixeldaten durch das Raster ist eine Erweiterung durchaus möglich. Diese Operatoren sind Teil
des Java2D Immediate Mode Models, dem entsprechend Änderungen auf Grafiken sofort sichtbar
sind.
Aufgerufen werden diese Operationen beim Rendern des Bildes, also beim laden des Bildes in den
Grafikkontext, in das man es zeichnen will. In Graphics2D gibt es eine Methode um ein
BufferedImage mit einem BufferedImageOp einzufügen. BufferedImageOp ist es, von dem die
Operatoren in Java2D erben. Diese Operatoren sind:
AffineTransformOp
Wendet eine 3x2 Matrix
auf das Bild an
(Skalierung, Scherung,
Drehung, Translation).
Im Beispielbild ist eine
Scherung in x um 0.2.
ColorConvertOp
Wechselt das
ColorSpace-Objekt
und ändert die
Bilddaten entsprechend
ab. Im Bild zu sehn ist
das Ergebnis, nachdem
ein Grau-Farbraum
eingesetzt wurde.
Seite 17 von 50
ConvolveOp
Dieser Operator wendet
einen Gradienten auf
das Bild an. Dies kann
zum Glätten und
Schärfen des Bildes
verwendet werden.
Gradient im Beispiel:
0.2
0.4
0.2
LookupOp
0.4
0.8
0.4
0.2
0.4
0.2
Operator zur Änderung
der Zuordnung von
Sample-Werten zu
Farbwerten. Im Beispiel
wurde die Zuordnung
des roten und blauen
Bandes um 56 geshiftet:
if(sampleWert < 200)
sampleWert += 56;
else
sampleWert -= 200;
RescaleOp
Skaliert die einzelnen
Farbbänder. Entweder
alle gleich oder alle
verschieden. Im
Beispiel:
rot
grün
blau
alpha
*
*
*
*
1.0
0.5
1.5
1.0
Seite 18 von 50
2.5.2. Rendering Hints
Um die Qualität der zu rendernden Objekte festzulegen dient in Java2D ein Objekt der Klasse
java.awt.RenderingHints. Dieses Objekt enthält eine Reihe von Schlüssel-Wert-Paaren
von denen der Grafikkontext auf die Parameter verschiedene Grafik glättende Operationen
schließt. Alle Schlüssel und alle möglichen Werte sind in RenderingHints als öffentliche
statische Konstanten deklariert, wodurch es einfach ist, ein Objekt von RenderingHints zu
erzeugen. Dem Objekt müssen nur diejenigen Werte gesetzt werden, die nicht Standard sind, die
restlichen, nicht im Objekt enthaltenen Attribute, werden durch den Grafikkontext komplettiert.
Ein solches Objekt von RenderingHints wird zeichnenden Grafikkontexten und Operatoren
mitgegeben. Hier die Liste aller der Schlüssel-Wert Paare:
KEY_ALPHA_INTERPOLATION
VALUE_ALPHA_INTERPOLATION_DEFAULT
VALUE_ALPHA_INTERPOLATION_QUALITY
VALUE_ALPHA_INTERPOLATION_SPEED
KEY_ANTIALIASING
VALUE_ANTIALIAS_DEFAULT
VALUE_ANTIALIAS_OFF
VALUE_ANTIALIAS_ON
KEY_COLOR_RENDERING
VALUE_COLOR_RENDER_DEFAULT
VALUE_COLOR_RENDER_QUALITY
VALUE_COLOR_RENDER_SPEED
KEY_DITHERING
VALUE_DITHER_DEFAULT
VALUE_DITHER_DISABLE
VALUE_DITHER_ENABLE
KEY_FRACTIONALMETRICS
VALUE_FRACTIONALMETRICS_DEFAULT
VALUE_FRACTIONALMETRICS_OFF
VALUE_FRACTIONALMETRICS_ON
KEY_INTERPOLATION
VALUE_INTERPOLATION_BICUBIC
VALUE_INTERPOLATION_BILINEAR
VALUE_INTERPOLATION_NEAREST_NEIGHBOR
KEY_RENDERING
VALUE_RENDER_DEFAULT
VALUE_RENDER_QUALITY
VALUE_RENDER_SPEED
KEY_STROKE_CONTROL
VALUE_STROKE_DEFAULT
VALUE_STROKE_NORMALIZE
VALUE_STROKE_PURE
KEY_TEXT_ANTIALIASING
VALUE_TEXT_ANTIALIAS_DEFAULT
VALUE_TEXT_ANTIALIAS_OFF
VALUE_TEXT_ANTIALIAS_ON
Seite 19 von 50
2.5.3. Transformationen im Grafikkontext
Um eine affine Transformation auf ein grafisches Objekt anzuwenden gibt es verschiedene Wege.
Einer wurde hier bereits aufgezeigt, mit dem AffineTransformOp. Für alle grafischen Objekte
die gerendert werden, gilt aber trotzdem die Transformation, die als Attribut des Grafikkontextes
gesetzt ist. Diese übersetzt die Koordinaten von allen grafischen Objekten von dem Benutzer- zum
Gerätekoordinatensystem. Um diese Transformation zu setzten wird der Methode
setTransform() ein Objekt der Klasse java.awt.geom.AffineTransform übergeben,
welche ebenfalls dem Java2D Framework angehört. Ein Objekt dieser Klasse kann recht vielseitig
verwendet werden:
AffineTransform aft = new AffineTransform();
Das Objekt aft enthält nun eine Einheitsmatrix. Mit oder ohne eine Veränderung der Daten der
enthaltenen Matrize, kann das Objekt nun beispielsweise von einem AffineTransformOp
verwendet werden.
AffineTransformOp op = new AffineTransformOp( aft, myRenderingHints);
Das setzten der Umrechnung von Benutzer- zu Gerätekoordinaten:
( (Graphics2D) g ).setTransform( aft );
Das Zeichen eines Bildes:
( (Graphics2D) g ).drawImage(image,aft,this);
Und das Zeichnen eines geometrischen Objekts:
Shape shape = aft. createTransformedShape(myRectangle);
((Graphics2D)g).draw(shape);
Hierbei wird zunächst ein virtuelles rendern durchgeführt, d.h. das Ergebnis existiert, ob es
gezeichnet wurde oder nicht in dem Objekt shape. Die Klasse java.awt.Shape ist die
Grundlage von jedem geometrischen Objekt und das Ergebnis der Transformation ist auf jeden Fall
wieder eine solche Shape.
2.5.4. Bildfilter
Die Bildfilterklassen die von Java2D angeboten werden, sind Teil der Producer – Consumer –
Paradigmen wie sie mit dem AWT-Push-Model verwirklicht werden. Die angebotenen Bildfilter
werden einem ImageProducer mitgegeben, nachdem ihnen einer der bereits angesprochenen
Operatoren übergeben wurde. Anschließend wird dem ImageProducer ein oder mehrere
interessierte ImageConsumer mitgeteilt, sprich diejenigen, die von der Erfüllung des
Produktionsauftrages benachrichtigt werden und das Ergebnis der Produktion erhalten. Die von
ImageProducer erbende Klasse java.awt.image.FilteredImageSource ist für
Bildfilter ausgelegt. Ihr wird beim erzeugen ein Objekt der Klasse ImageFilter übergeben, von
dem die verschiedenen Bildfilterklassen Java2D's erben. Ein FilteredImageSource ist auch
ein ImageConsumer. Dadurch ist es möglich eine Pipeline zu bilden. Ein Objekt von
Seite 20 von 50
FilteredImageSource kann als interessierten ImageConsumer ein weiteres
FilteredImageSource-Objekt besitzen, dessen ImageConsumer wiederum ein weiteres
FilteredImageSource-Objekt ist. Die möglichen Bandfilter, die so einem Objekt gegeben
werden sind:
AreaAveragingScaleFilter bedient sich eines internen Transformationsoperators um das
Bild in seinen räumlichen Ausdehnungen um die gesetzten
Faktoren zu skalieren. Im Gegensatz zu dem
ReplicateScaleFilter benutz diese Klasse "nearest
neighbor" Interpolation und andere mathematische
Skalierungsfunktionen.
BufferedImageFilter
bietet die Möglichkeit jeden beliebigen Operator auf ein Bild
anzuwenden.
CropImageFilter
wird zum gewinnen eines Teilbildes verwendet.
ReplicateScaleFilter
bedient sich eines internen Transformationsoperators um das
Bild in seinen räumlichen Ausdehnungen um die gesetzten
Faktoren zu skalieren.
RGBImageFilter
Filter, der das Farbmodel eines Bildes auf ein RGBFarbmodel setzt und alle nötigen Änderungen vornimmt.
Die Verwendung ist Relativ simpel. Zunächst braucht man einen Objekt dessen Klasse
ImageConsumer implementiert. Im nun folgenden Beispiel ist das myImageConsumer.
BufferedImageFilter bif = new BufferedImageFilter(myBufferedImageOp);
FilteredImageSource ifs =
new FilteredImageSource( image.getSource() , bif);
Das Objekt der Klasse FilteredImageSource ist ein ImageProducer und führt als solcher
die Operation, die der Operator myBufferedImageOp beschreibt, erst dann durch, wenn dies in
Auftrag gegeben wird:
ifs.startProduction(myImageConsumer);
Dadurch wird die Anwendung des Operators im Filter auf das Bild ausgelöst. Während bzw. nach
Abschluss der Produktion werden die entsprechenden Methoden in myImageConsumer vom
FilteredImageSource aufgerufen, durch die der ImageConsumer das ProduktionsErgebnis erhält.
Seite 21 von 50
Hilfsmittel im Umgang mit Bildern
java.awt.Toolkit
Liefert ganz allgemeine Hilfestellungen. Auf Bilder
und Grafik bezogen kann das Toolkit ein Bild von
Platte laden und Informationen über Bildschirmgröße
und -auflösung geben.
java.awt.image.ImageProducer
Jedes von Image erbende Objekt besitzt einen
ImageProducer. Dieser hält eine Referenz auf die
Bilddaten und kann diese verändern bzw. ein
verändertes Abbild von ihnen produzieren. Dabei teilt
ImageConsumern seinen Produktionsstatus mit und
liefert das Produktionsergebnis bei letzterem ab.
java.awt.image.ImageConsumer
Ein an dem Ergebnis einer Produktion eines
ImageProducers
interessiertes
Objekt
implementiert diese Klasse und alle dazu gehörigen
Methoden. Während der Produktion des Bildes
werden
dann
diese
Methoden
von
dem
ImageProducer
aufgerufen,
wodurch
der
ImageConsumer Informationen über Breite und
Höhe, Farbmodel, Pixeldaten usw. erhält.
java.awt.image.ImageObserver
Ein Objekt dieser Klasse wird an die verschiedenen
Methoden des Bildes übergeben. Immer, wenn eine
Änderung im Ladezustand des Bildes auftritt. Benachrichtigungen treffen beispielsweise ein, wenn nur
Breite und Höhe über das Bild verfügbar sind oder
wenn bereits einige Pixeldaten vorhanden sind. Der
ImageObserver erhält bei jeder Benachrichtigung
das
bisher
bekannte
Bild.
Durch
einen
ImageObserver wird es möglich, das bisher
Bekannte eines Bildes, noch während dieses erzeugt
oder geladen wird, zu verarbeiten. Beispielsweise
kann man die Breite und Höhe des Bildes bereits in
die Berechnungen einfließen lassen, noch bevor das
Bild vollständig da ist. Anzumerken ist, dass jede
Component ein ImageObserver ist.
java.awt.MediaTracker
Ein MediaTracker-Objekt hat die Fähigkeit
mehrere Bilder zu verwalten und auf diese zu warten,
bis sie fertig produziert oder geladen sind. Einer der
Einsatzzwecke ist es einen Kurzfilm bestehend aus
mehreren Bilder anzeigen zu können, ohne sich allzu
viel sorgen um den Ladestatus der Bilder machen zu
müssen.
Seite 22 von 50
java.awt.image.PixelGrabber
Klasse um einzelne Pixel bzw. Pixelbereiche aus
einem Bild zu extrahieren. Wegen der starken
Laufzeitansprüche dieser Klasse ist davon abzuraten
und stattdessen auf das Raster zuzugreifen.
2.7. Applikationserstellung mit Java2D
Die erste Frage, die man sich stellen sollte, wenn man mit Bildern Arbeiten will, ist, was man mit
den Bildern machen will. Es folgen nun kurze abschnitte die Programmcode nach nutzen sortieren.
Das ganze wird von einer Beispielapplikation abgeschlossen, in der eine von JComponent
erbende Klasse ausformuliert wird. Diese Komponente bietet die Schnittstellen, die benötigt
werden um mittels einer GUI die einfachsten Operationen zu unterstützen.
Im Allgemeinen will man zunächst ein Bild von der Platte laden, sofern man es nicht über einen
InputStream oder etwas Ähnliches erhält. Dafür gibt es u.a. folgende Möglichkeiten:
Vorweg zunächst:
String path = new String("pfad\\datei.typ");
Dann entweder
Image image = new ImageIcon(path).getImage();
oder
Image image = Toolkit.getDefaultToolkit().createImage(path);
oder
Applet applet = new Applet();
Image image = applet.getImage(applet.getDocumentBase(),path);
Meistens wird nachdem das Bild geladen wurde, das Bild angezeigt, z.B. auf einem JButton.
JButton button = new JButton(new ImageIcon(image));
Im Prinzip kann jede Component ein Bild anzeigen, da sie eine paint-Methode besitzt. Jedoch
stellen nicht alle von Component erbenden Klassen Schnittstellen zum Einladen von Bildern zur
Verfügung. Um eine Component zu haben, die in der Lage ist, das Bild schnell anzuzeigen,
empfiehlt sich, von Component, besser Container oder noch besser JComponent zu erben
und eine eigene paint-Methode zu implementieren.
public void paint(Graphics g){
super.paint(g);
g.drawImage(image,0,0,myImageObserver);
}
Seite 23 von 50
Um eine Transformation auf das Bild anzuwenden, also beispielsweise das Bild in seiner Größe
zu ändern, gibt es folgende Möglichkeiten:
Zunächst wird vorausgesetzt:
AffineTransform aft = new AffineTransform();
aft.setToScale(0.5,0.5);
Dann kann skaliert werden mit:
g.drawImage(bufferedImage,aft,myImageObserver);
oder
AffineTransformOp aftOp = new AffineTransformOp(aft,myRenderingHints);
aftOp.filter(bufferedImageSource,bufferedImageDestination);
bzw. kann dieser Operator auch anders verwendet werden. Voraussetzung ist, dass man einen
ImageConsumer besitzt, der das Ergebnis richtig verarbeitet und anzeigt.
BufferedImageFilter filter = new BufferedImageFilter(op);
FilteredImageSource producer =
new FilteredImageSource(bufferedImage.getSource(),filter);
producer.startProduction(myImageConsumer);
Ebenso wie der Operator AffineTransformOp auf das Bild angewendet wurde können auch alle
anderen Operatoren auf das Bild angewendet werden.
Eine Bilddatenanalyse kann aufgrund des direkten Zugriffs auf die Pixeldaten durch das Raster
implementiert werden. Fertige Klassen zu diesem Thema gibt es in Java2D nicht.
int width = bufferedImage.getWidth();
int height = bufferedImage.getHeight();
int[] pixel = new int[width*height];
pixel = bufferedImage.getRaster().getPixels(0,0,width,height,
bandNr,
pixel);
Beispielprogramm:
public class MyComponent extends JComponent implements ImageConsumer {
private BufferedImage
bufferedImage;//Anzuzeigendes Bild
private BufferedImageOp bufferedImageOp;//Operator auf Bild
private int
loadStatus;//siehe imageComplete(...)
/***********************************************************************/
/** Standardkonstruktor
**********************************************************************/
public MyComponent(){
super();
}
/***********************************************************************/
/** Setzt ein neues Bild in den Component ein, das dieser anzeigen soll.
*
* @param bi Neues Bild das auf der Component angezeigt werden soll.
**********************************************************************/
Seite 24 von 50
public void setImage(BufferedImage bi){
buffferedImage = bi;
repaint();
}
/***********************************************************************/
/** Lädt einen neuen Operator in diese Komponente ein der auf das Bild
*
* @param op Irgendein Operator
**********************************************************************/
public void setOperator(BufferedImageOp op){
bufferedImageOp = op;
}
/***********************************************************************/
/** Zeichenroutine, die den Component zusammen mit dessen Inhalt
* in den Grafikkontext g zeichnet.
*
* @param g Objekt des Grafikkontextes
**********************************************************************/
public void paint(Graphics g){
super.paint(g);
if( bi != null || //Nur wenn Bild fertig produziert ist
loadStatus != ImageConsumer. TOPDOWNLEFTRIGHT){
if( bufferedImageOp == null ){
g.drawImage(bufferedImage,0,0,this);
}
else {// Wenn ein Operator gesetzt wurde
((Graphics2D)g).drawImage(bufferedImage,
bufferedImageOp,
this);
}
}
}
/***********************************************************************/
/** Durch Aufruf der Methode wird dem ImageConsumer mitgeteilt, wie weit
* die Produktion des Bildes ist
*
* @param status siehe statische Konstanten in ImageConsumer
**********************************************************************/
public void imageComplete(int status){
loadStatus = status;
if( status = ImageConsumer. TOPDOWNLEFTRIGHT )
repaint();
}
Seite 25 von 50
/***********************************************************************/
/** Kann auch als allgemeine Schnittstelle verwendet werden um
* das Farbmodel des Bildes zu ändern. Ein ColorConvertOp
* wird benutzt um das Orginalbild durch eine Kopie des
* Bildes mit geändertem Farbmodel zu ersetzen.
*
* @param cm Neues Farbmodel des Bildes
**********************************************************************/
public void setColorModel(ColorModel cm){
if( cm != bufferedImage.getColorModel() ){
RenderingHints renderingHints = new RenderingHints(
RenderingHints.KEY_COLOR_RENDERING,
RenderingHints.VALUE_COLOR_RENDER_QUALITY);
ColorConvertOp op = new ColorConvertOp(renderingHints);
bufferedImage = op.createCompatibleDestImage(
bufferedImage,
cm);
}
}
/***********************************************************************/
/** Sobald die räumlichen Ausdehnungen des Bildes bekannt sind, kann
* ein neues BufferedImage erzeugt werden. Dessen Transfertyp und
* Farbmodel wird später mit setColorModel() umgeändert.
*
* @param width Breite die das gerade Produzierte Bild besitzt.
* @param height Höhe die das gerade Produzierte Bild besitzt.
**********************************************************************/
public void setDimensions(int width, int height){
bufferedImage = new BufferedImage(width,
height,
BufferedImage.TYPE_INT_ARGB);
}
/***********************************************************************/
/** Nicht unterstützt. Falls Unterstützung gewünscht, hintflag ist
* eine Bit-Maske, deren Aufschlüsselung die RenderingsHints,
* mit denen bufferedImage in den Grafikkontext gezeichnet wird,
* ergibt.
*
* @param hintflag 32-bit Bitmaske, die die einzustellenden
*
RenderingHints repräsentiert.
**********************************************************************/
public void setHints(int hintflag){
}
/***********************************************************************/
/** Falls eine Unterstützung erwünscht ist, muss das Array in die
* Sample-Werte des Rasters von bufferedImage gesetzt werden.
**********************************************************************/
public void setPixels(int x, int y, int w, int h,
ColorModel model, byte[] pixels,
int off, int scansize){
throw new UnsupportedOperationException("Nicht unterstützt");
}
Seite 26 von 50
/***********************************************************************/
/** Erhält von einem ImageProducer das Ergebnis der Produktion in Form
* eines Array's aus 32-bit Integerwerten. Aus diesem Array wird
* mithilfe der restlichen Daten das Bild gefüllt, indem jeder 32-bit
* Wert in vier 8-bit Bestandteile zerlegt wird. Die dadurch
* entstehenden vier Array's werden dann in bufferedImage geladen.
*
* @param x Linke Kante des einzufügenden Bereichs im
*
User-Koordinatensystem.
* @param y Obere Kante des einzufügenden Bereichs im
*
User-Koordinatensystem.
* @param w Breite des einzufügenden Bereichs im
*
User-Koordinatensystem.
* @param h Höhe des einzufügenden Bereichs im User-Koordinatensystem.
* @param model Farbmodel, das für bufferedImage gilt.
* @param pixels Pixeldaten als 32-bit Integer (4 mögliche Bänder)
* @param offset Startwert, um das Array aus 32-bit Integerwerten zu
*
durchlaufen.
* @param scansize Länge jeder Pixelzeile.
*
**********************************************************************/
public void setPixels(int x, int y, int w, int h,
ColorModel model, int[] pixels,
int offset, int scansize){
int[]
int[]
int[]
int[]
red
green
blue
alpha
=
=
=
=
new
new
new
new
int[pixels.length];//Rotes Band
int[pixels.length];//Grünes Band
int[pixels.length];//Blaues Band
int[pixels.length];//Alpha Band
/* Der Wert eines Pixels an der Stelle i besteht
aus einer 32-bit Zahl. Beispielsweise entsprechen
die untersten 8-bit dem Pixelwert im Roten Band.
Dem entsprechend wird jeder Wert im Array Pixels
seinen vier Bänder aufgesplited.
*/
for( int i = offset; i < pixel.length; i++ ){
alpha[i]
blue[i]
green[i]
red[i]
=
=
=
=
(pixels[i] >> 24) & 0xFF;
(pixels[i] >> 16) & 0xFF;
(pixels[i] >> 8) & 0xFF;
(pixels[i]
) & 0xFF;
}
/* Sollte es sich um ein anderes Farbmodel als bereits
in bufferedImage befindlich handeln, so wird dieses
in bufferedImage gesetzt. */
setColorModel(cm);
//Extrahierte Farbbänder warden in das Raster gesetzt.
bufferedImage.getRaster().setSamples(x,y,w,h,0,red);
bufferedImage.getRaster().setSamples(x,y,w,h,1,green);
bufferedImage.getRaster().setSamples(x,y,w,h,2,blue);
bufferedImage.getRaster().setSamples(x,y,w,h,3,alpha);
//Die entstandenen Änderungen müssen noch angezeigt werden.
repaint();
}
Seite 27 von 50
/***********************************************************************/
/** Bekommt bei der Bildproduktion ggf. Bildkommentare und
* -beschreibungen gesetzt, sofern vorhanden.
*
* @param props Tabelle mit Eigenschaften des Bildes.
**********************************************************************/
public void setProperties(Hashtable props){
}
/***********************************************************************/
}
2.8 Übersicht
AffineTransform
Bild anzeigen
Bild laden
Bild skalieren
BufferedImage
BufferedImageOp
Clipping
ColorModel
ColorSpace
Composite
Farbmodel
Füllen von Flächen
Grafikkontext
Graphics
Graphics2D
Image
ImageProducer
ImageConsumer
ImageObserver
Komponenten
MediacTracker
Operatoren
PixelGrabber
Raster
RenderingHints
Rendern
Toolkit
…………………………………………………………………..20
…………………………………………………………………..23
…………………………………………………………………..23
……………………………………………………………….20,24
……………………………………………………………….11,12
…………………………………………………………………..17
……………………………………………………………………8
…………………………………………………………………..12
…………………………………………………………………..12
……………………………………………………………………8
…………………………………………………………………..12
……………………………………………………………………8
………………………………………………………………..7-10
………………………………………………………………..7-10
……………………………………………………………......7-10
…………………………………………………………………..11
…………………………………………………………………..22
…………………………………………………………………..22
…………………………………………………………………..22
………………………………………………………………24-28
…………………………………………………………………..22
……………………………………………………………….17,18
…………………………………………………………………..23
…………………………………………………………………..13
…………………………………………………………………..19
……………………………………………………………………7
…………………………………………………………………..22
Seite 28 von 50
3. Java Advanced Imaging
Java Advanced Imaging (JAI) API erweitert die Java AWT Plattform indem sie gestattet
verfeinerte leistungsstarke Bildverarbeitung in Java Applets und Applikationen anzuwenden. JAI
ist ein Bündel aus Klassen die Bildverarbeitungsfunktionalität zur Verfügung stellen, das sowohl
über die Fähigkeiten von Java2D als auch der Java Foundation Klassen hinausgeht, obwohl es zu
diesen API's voll Kompatibel ist. Es implementiert einen für die Bildverarbeitung grundlegenden
Teil, wie Bildteile (Tile), Region des Interesses (Region of Interest) und verzögerte Ausführung
von Operationen. JAI offeriert außerdem noch ein Set aus Bildverarbeitungs-Operatoren, wie
beispielsweise Punkt-, Flächen oder Frequenzoperatoren. Grundsätzlich versucht JAI auf die
Bedürfnisse aller Bildverarbeitenden Applikationen einzugehen und diese zu erfüllen. Die API ist
extrem erweiterbar gehalten und gestattet, dass neue Operatoren eingeführt werden, die genauso
arbeiten als wären sie ein nativer Teil.
3.1. Typen und Operanden für simple Anwendungen
Fabrikklasse für Operationen, die
durch statische Methoden der
javax.media.jai.JAI
Klasse und deren Parametern
erzeugt werden.
Bildklasse deren Objekte
javax.media.jai.PlanarImage
Bilddaten im Speicher verwaltet.
Von java.awt.ScrollPane erbende
Klasse die dieses um die nötigen
javax.media.jai.widget.ScrollingImagePanel
Schnittstellen für JAI-Bedürfnisse
erweitert.
javax.swing.JFrame
Fenster
2.2. Anzeigen eines gespeicherten Bildes
Die folgenden Methoden zeigen zunächst das Laden eines Bildes und dann das einbinden von
diesem in eine GUI.
public RenderedOp loadImage(String path){
return JAI.create("fileload",path);
}
public void showImage(String path){
JFrame frame = new JFrame( "Bildanzeige - JAI" );
frame.setBounds(100,100,800,600);
frame.getContentPane().add(
new ScrollingImagePanel( loadImage(path), 0, 0 );
frame.setVisible(true);
}
Seite 29 von 50
Das gewünschte Bild wird in einem Scrollpane auf einem Fenster angezeigt. Die Klasse JAI
enthält viele verschiedene statische create-Methoden, die immer zuerst einen String übergeben,
um zu entscheiden was zu tun ist und einen RenderedOp, der Bilddaten verwaltet, zurückgibt. In
das ScrollingImagePanel kann jedes RenderedImage geladen werden (auch ein BufferedImage ist
ein RenderedImage).
3.3. Verzahnung mit Java2D
JAI setzt hauptsächlich auf den abstrakten Definitionen in dem Java2D API auf. Im Allgemeinen
werden alle Mechanismen die Renderable und Rendered Bilder, Pixel-Samples und
Datenverwaltung in JAI ausformuliert. Im Folgenden sind die Hauptpunkte dieses
Zusammenhangs aufgelistet:

Die Interfaces RenderableImage und RenderedImage die im Java2D API definiert werden,
werden von JAI benutzt um darauf weitere Schichten aufzubauen. JAI erlaubt das erstellen
und manipulieren gerichteter azyklischer Graphen aus Objekten die dieses Interface
implementieren.

Die Klasse TiledImage implementiert das Interface WritableRenderedImage und
kann ein Fliesen-Gitter, also ein 2D-Array, aus Raster Objekten beinhalten. Anders als in
einem BufferedImage des Java2D API benötigt eine TiledImage kein ColorModel für
photometrische Interpretationen seines Bildinhaltes.

Die Operator Objekte sind in JAI beträchtlich höher entwickelt als in dem Java2D API. Die
Klasse OpImage, von der alle Operatoren erben, unterstützt vielseitig die Erweiterung zu
neuen Operatoren, die in Java2D nur schwer hätten realisiert werden können. Ein Grund
dafür ist, dass JAI mit einem Registry-Mechanismus arbeitet, der die Selektion von
Operationen auf ein RenderedImage automatisiert.

Die Klassen des Java2D API SampleModel, DataBuffer und Raster wurden in JAI
ohne Veränderung übernommen, mit Ausnahme das doubles und floats nun als
Transfertyp zugelassen sind, zusätzlich zu byte, short und int.
3.4. Bildobjekte
3.4.1. Aufbau von Bildklassen
JAI unterstützt drei auf Java2D aufsetzende Modelle nach denen die Bildklassen ausgelegt werden.



Das Producer/Consumer (push) Model – Basis AWT Bilder verarbeitendes Model
Das "immediate mode" Model – Weiterentwickeltes AWT Bilder verarbeitendes Model.
Das Pipeline (pull) Model – JAI Model
Das JAI Model gliedert sich in zwei Schichten, die verschiedene Bildverarbeitungsmechanismen in
Applikationen ermöglichen:
Seite 30 von 50
Der Rendered Layer
Bildklassen und Operatoren im Rendered Layer sind Kontextspezifisch. Merkmal
dieser Schicht ist, dass das Wort rendered in den Namen der Klassen und
Interfaces enthalten ist. Ein RenderedImage ist ein Bild das gerenderet wurde um
den Bedürfnissen des Kontextes zu entsprechen. Rendered Layer Operatoren können
ebenfalls zu Ketten geformt werden. Sie nehmen kontextspezifische Parameter. Die
Rendered Schicht ist dazu entworfen mit der Renderable Schicht zusammen zu
arbeiten. Sie ist in Quellen und Operatoren für die geräteabhängige Repräsentation
von Bildern oder Gerendertem enthalten. Die Rendered Schicht ist primär durch das
Interface
RenderedImage.
Bildursprünge,
wie
beispielsweise
ein
BufferedImage, implementieren dieses Interface. Operatoren dieser Schicht sind
einfach RenderedImages, die andere RenderedImages als Quelle nehmen.
Deshalb werden Ketten in diesem Layer auf die gleiche art konstruiert wie die, des
Renderable Layers. JAI's Version von RenderedImage ist PlanarImage.
Instanzen der Klasse RenderedOp sind die Knoten in einem Render Graphen. Dieser
wird aus Rendered Objektknoten zusammengebaut, die von den Instanzen von
RenderedOp, kann aber auch von jeder anderen von PlanarImage erbenden
Klasse, repräsentiert werden. Hier ein praktisches Beispiel für diesen Zusammenbau:
.
.
.
RenderedOp image0 = JAI.create("fileload",path1);
RenderedOp image1 = JAI.create("fileload",path2);
RenderedOp image2 = JAI.create("add",image0,image1);
.
.
.
Der Renderable Layer
Darunter ist eine, vom Renderprozess unabhängige, Schicht zu verstehen. Merkmal
dieser Schicht ist, dass alle Interfaces und Klassen das Wort renderable im
Namen tragen. Sie bietet Bildquellen die problemlos mehrfach und in verschiedenen
Kontexten verwendet werden können. Die Renderable-Schicht bietet außerdem
Bildoperatoren die render-unabhängige Parameter nehmen. Diese Operatoren können
untereinander referenziert werden wodurch sich Pipelines bilden lassen. Der
Grundgedanke dieser Schicht ist es, ein "Pull"-Model zu verwirklichen. Dieses
Funktioniert, indem man bildlich gesprochen am Ende einer solchen Pipeline aus
Operatoren anfängt das Bild durch die Operatorenkette zu sich heran zu ziehen,
beispielsweise zu einer Anzeige oder einer Datei. Das heißt die Anfrage nach dem
Bild und dessen Produktion wird auf Empfängerseite gestartet. Solche Anfragen sind
Kontextabhängig (genauso wie Geräteabhängig) wodurch die Operatorenkette sich
dem Kontext angepasst. Nur die Daten für den Kontext werden produziert. Die
Seite 31 von 50
Strukturen die sich nach dem Renderable Layer richten sind "lightweight",
verbrauchen also weniger Speicherplatz. Sie gehen nie direkt mit Pixelberechnungen
um. Vielmehr bedienen sich diese Strukturen Operator-Objekte aus dem Rendered
Layer. Dies ist Möglich, da die Operator-Klassen des Rendered Layer ein Interface
namens ContextualRenderedImageFactory Implementiert, das es erlaubt,
diese an verschiedene Kontexte anzupassen. Dadurch, dass die Rendered Schicht
Operatoren vollständig beschreibt ist es möglich die Intelligenz beider Schichten in
einer einzigen Klasse zu lagern. Das erleichtert das Schreiben neuer Operatoren und
von Erweiterungen der benutzen Architektur ungemein.
Der Renderable Layer wird hauptsächlich durch das RenderableImage-Interface
definiert. Jede Klasse die dieses Interface implementiert ist eine renderable
Bildquelle, von der erwartet wird, dass sie sich einem RenderContext anpasst.
RenderableImages werden in einem, vom Benutzer definiertem
Koordinatensystem gehalten. Eine der Hauptfunktionen des RenderContext ist es,
eine Umrechnung zwischen Benutzerkoordinaten und Gerätkoordinaten für das
Rendering zu spezifizieren. Eine Kette in diesem Layer ist eine Kette aus
RenderableImages oder vielmehr eine Kette aus RenderableImageOps (eine
Klasse, die RenderableImage implementiert) an deren Anfang ein
RenderableImage steht. Aus RenderableImages wird in Applikationen
normalerweise ein Render Graph aufgestellt. Meist geschieht dies mit Klassen, die
RenderableImage implementieren, wie beispielsweise RenderableOp. Die
Wurzel eines solchen Graphen ist immer ein RenderableImage-Objekt.
Die Grafik zeigt den Fall, dass von dem Benutzer ein Aufruf von Graphics2D.drawImage() ausgelöst
wurde. Ein Renderkontext wird erzeugt und benutzt um die getImage()-Methode des Renderable
Operators aufzurufen. Ein Rendered Operator wird, um die eigentliche Pixelberechnungen vorzunehmen,
erzeugt und mit der Quelle und dem Ziel des Renderable Operators verbunden. Außerdem erhält der
Rendered Operator einen Klon des Parameterblocks des Renderable Operators. Die Pixeldaten fließen
dann durch den Rendered Operator zu Graphics2D. Die Renderable Operatorenkette bleibt verfügbar um
wieder zu rendern, wenn seine getImage()-Methode aufgerufen wird.
Seite 32 von 50
Im Folgenden sind die Bildklassen erklärt, die dieses Model bedienen:
PlanarImage
ist die Hauptklasse um 2D-Bilder zu beschreiben. Sie implementiert
das Interface RenderedImage aus dem Java2D API. Das Interface
RenderedImage beschreibt ein unterteilbares (Tiled), nur-lese (also
read only) Bild, das aus Pixel besteht, die in einem SampleModel
und einem DataBuffer beschreiben werden. Jede Bildteil (Tile) ist
ein Rechteck mit identischen Dimensionen, das über ein Gitternetz
platziert wurde. Alle Bildteile (Tiles) teilen sich das gleiche
SampleModel.
Zusätzlich
zu
den
Fähigkeiten
des
RenderedImages, behält PlanarImage die Verbindung zwischen
Bildquelle und –ziel von Objekten in einem Render Graphen bei. Da
Knoten dieses Graphen bidirektional verbunden sind, braucht der
Garbage Collector Hilfe um festzustellen, ob ein solches Objekt nicht
mehr referenziert wird. Diese Aufgabe wird ebenfalls von
PlanarImage übernommen (indem dieses sich dem Weak
References API aus Java 2 bedient).
TiledImage
erklärt ein Bild, dass mehrere so genannte Tiles beinhaltet. Tiles,
wörtlich übersetzt Fliese, sind das Ergebnis regelmäßiger Aufteilung
der Bilddaten, wie durch ein Gitternetz, also Teilbilder. Dieses Gitter
kann an jeder beliebigen Stelle der Gesamtfläche liegen. Nach der
Erzeugung einer Instanz ist das Gitternet eines TiledImages leer.
Wird dann ein Tile verlangt so wird es mit Daten des QuellenPlanarImages initialisiert. Anschließen kann der Inhalt geändert
werden. Die Bildquelle kann ebenfalls für alle oder einen Teil der Tiles
geändert werden, indem die set-Methoden benutzt werden, die von
TiledImage zur Verfügung gestellt werden. Insbesondere kann eine
willkürliche Region des Interesses ("region of interest" - ROI) mit den
Daten gefüllt werden, die von einer Quellen-PlanarImage stammen
werden. Eine weitere Eigenschaft von TiledImages ist, dass sie mit
der Methode createGraphics() einen Java2D Grafikkontext
Graphics2D zurückgeben, mit dem in das TiledImage gezeichnet
werden kann. TiledImage erbt von PlanarImage und
implementiert WriteableRenderedImage.
ImageOp
Von dieser Klasse erben alle Klassen die auf Bildern Operationen
durchführen können. Die Klasse selbst erbt von PlanarImage.
ImageOp
überschreibt
die
abstrakte
Methode
RenderedImage.getTile() um die Möglichkeit zu schaffen,
darauf Operationen durchzuführen.
RenderableOp
ist eine Klasse die lightweight eine Operation im renderbaren Raum
definiert. Objekte dieser Klasser werden normalerweise erzeugt durch
einen Aufruf von JAI.createRenderable()und sind danach frei
editierbar. Ein RenderableOp erbt von RenderableImage und
kann deshalb nach seinen renderunabhängigen Dimensionen
Seite 33 von 50
befragt werden. Wenn ein RenderableOp rendert benutzt er
eine
OperationRegistry
um
die
eine
passende
ContextualRenderedImageFactory zu lokalisieren. Diese
liefert die Umrechnung von Benutzerkoordinatensystem zu
Gerätekoordinaten.
RenderedOp
Diese Klasse ist, genauso wie RenderableOp, in ihrer Architektur
lightweigt. Objekte dieser Klasse speichern den Namen einer
Operation und jeweils ein Objekt von ParameterBlock und
RenderingHints, die die Operation beschreiben. Objekte dieser
Klasse sind Knoten in einem Rendergraphen. Man bringt einen
RenderedOp dazu zu Rendern, indem man eine seiner Methoden von
RenderedImage aufruft. Dabei wird dann eine Kette aus
OpImages erzeugt, die ähnlich der ist, die an dem RenderedOp
hängt. Möglicherweise variiert die Anzahl der Knoten etwas, da beim
Renderprozess u.a. Knoten zusammenzufassen, durch das erkennen
von Patterns, und Knoten zu expandieren, sprich durch ein Pattern von
Knoten zu ersetzten. Dieser Vorgang wird von dem RenderedOp mit
Hilfe einer RenderedImageFactory durchgeführt. Dieses
benötigt die OperationRegistry des RenderedOp, die den
Vorgang steuert. Anhand von Schnittstellen für das verändern oder
ersetzten der OperationRegistry ist es dem Entwickler möglich,
in diesen Teil des Renderns einzugreifen, beispielsweise um eigene
Operatoren, also von OpImage erbende Objekte, aufzurufen.
3.4.2. Erzeugen von Bildobjekten
Die statischen Methoden der Klasse JAI helfen dem Entwickler Operationen, wie beispielsweise
das erzeugen einer neuen Bildquelle zu beschreiben bzw. sofort durchzuführen. Einer der
grundsätzlichsten Bilddatentypen in JAI ist ein Objekt der Klasse PlanarImage. Angenommen,
dass path ein String ist, der einen Bildpfad beschreibt, wird dieses folgendermaßen erzeugt:
PlanarImage image = JAI.create("fileload",path);
Das funktioniert so, obwohl eigentlich ein Objekt der Klasse RenderedOp zurückkommt. Ob
das Ergebnis des Ladens eines Bildes in ein reines Bildobjekt oder ein Rendergraphknotenobjekt
ist im allgemeinen für die Applikation nicht wichtig, da diese Operation sofort ausgeführt wird.
Sollte die Anwendung jedoch beispielsweise an den Header-Daten der Bilddatei interessiert sein
stellt JAI erstens verschiedene Streams und Codecs zur Verfügung. Die Streams sind auf den aus
dem AWT API bekannten java.io.InputStream aufgebaut und können entsprechend
verwendet werden. Die zur Verfügung stehenden Codec's die Codierung wie Decodierung
erlauben umfassen folgende Formate:
Seite 34 von 50
BMP
Microsoft Bitmap Bild Format
FPX
FlashPix Format
GIF
Graphics Interchange Format
JPEG
Entwickelt von der Joint Photographic Experts Group
PNG
Portable Network Graphics
PNM
Portable aNy Map Datei Format. Beinhaltet PBM, PGM und PPM
TIFF
Tag Image File Format
Aus einem RenderedOp bzw. einem PlanarImage kann man einen RenderableOp
gewinnen und damit die Überleitung zu einer Applikation, die auf Renderable aufbaut,
konstruieren:
ParameterBlock pb = new ParameterBlock();
pb.addSource(image);
RenderableOp renderableImage = JAI.createRenderable("renderable",pb);
Ein solches RenderedImage ließe sich nun weiter zu einem TiledImage. Dieses besitzt
einen Konstruktor, dem entweder das Rechteck beschrieben wird, in dem sich das Bild
befindet, wo sich das Fliesengitter befindet und welches SampleModel und ColorModel
zugrunde liegt, oder ein RenderedImage übergeben.
3.4.3. Kombinieren von Bildobjekten
Der Rendergraph
JAI versucht dem Entwickler die Applikationserstellung zu erleichtern, indem es mit
seinem Rendered und Renderable Layer den Aufbau von Operationsgraphen zulässt.
Das bedeutet, die Operationen, anstatt sie sofort auf die Bilddaten anzuwenden, sie
zunächst zurück zuhalten und erst zum Zeitpunkt des Renderns auf diese
anzuwenden. Diese Graphen werden aus Knotenobjekten zusammengesetzt, die sich
wie ein Bild und ein Operator verhalten. Solche Objekte sind in den beiden Schichten
beispielsweise RenderedOp und RenderableOp. Ein Beispiel in dem pbxObjekte Instanzen von ParamterBlock sind, die beschreiben, wie die Bilder
entstehen:
RenderedOp im0 = JAI.create("const",pb1);
RenderedOp im1 = JAI.create("const",pb2);
im1 = JAI.create("add",im0,im1);
Seite 35 von 50
Wenn nun im1 angezeigt wird, enthält es im0 und das Bild, mit dem im1 initialisiert
wurde, übereinandergelegt. In im1 befindet sich aber tatsächlich nur eine
Operationsbeschreibung für das Addieren von zwei Bildern und zwei Referenzen auf
jeweils einen weiteren Knoten, der dieses Bild auf Anfrage liefert. Dieser Aufbau
wird nachfolgend Visualisiert:
3.4.4. Der Parameterblock
In dem JAI API wird im Allgemeinen eine Operation durch einen Namen selektiert und durch
Parameter beschrieben. Operationen haben zumeist eine Bildquelle (Source) und, entsprechend der
Operation, noch einige Parameter. Um eine Operation zu beschreiben, wird ein Objekt der Klasse
ParameterBlock benutzt um diese Parameter zu übergeben. Im Beispiel soll das Bild um 90°
gedreht werden:
ParameterBlock pb = new ParameterBlock();
pb.addSource(myPlanarImage);
// Bildquelle; Vorher geladenes Bild
pb.add(0.0f);
// X-Koordinate des Drehpunkts
pb.add(0.0f);
// Y-Koordinate des Drehpunkts
pb.add((float)Math.toRadiant(90));
// Drehwinkel
RenderedOp rotatedImage = JAI.create("rotate",pb);//erzeugt neuen Knoten
Die Reihenfolge solcher Parameter ist Operationsspezifisch, mit Ausnahme von der Bildquelle,
die in der Reihenfolge keine Rolle spielt. JAI.create() erzeugt einen neuen
Operationsknoten, dessen ParameterBlock Objekt das an die Klasse übergebene ist. Die
Klassen RenderedOp oder RenderableOp besitzen ihrerseits Schnittstellen um auf das so
gesetzte ParameterBlock Objekt von Instanzen dieser Klassen zu zugreifen und zu
verändern.
Seite 36 von 50
3.5. Operationen auf den Bildobjekten
In JAI sind eine Vielzahl von Operatoren bekannt, die von einfachen Ladeoperationen bis zur
diskreten Fourier-Transformation reichen. Um den Rahmen dieser Arbeit nicht zu sprengen
befindet sich im Folgenden nur eine Auflistung von Operatoren, die in JAI bereits verwirklicht
sind. Generell kann man die Operatoren von JAI in die Folgenden Kategorien gliedern:









Punkt Operatoren
Flächen Operatoren
Geometrische Operatoren
Farbquantisierende Operatoren
Datei Operatoren
Frequenz Operatoren
Statistische Operatoren
Kanten extraktions Operatoren
Weitere Operatoren
Punkt Operatoren
funktionieren, auf dem Prinzip das ein Eingangspixelwert über eine Operation berechnet einen
Ausgangspixelwert ergibt. Die Summe aller Ausgangspixel ist das Ergebnisbild. Punktoperatoren
verändern niemals die Lage eines Punktes.
Absolute
Berechnet den mathematischen absoluten Betrag der
Pixelwerte einer Rendered oder Renderable Bildquelle
Add
Addiert die Pixelwerte zweier Rendered oder Renderable
Bildquellen
AddCollection
Addiert die Pixelwerte einiger Rendered oder Renderable
Bildquellen
AddConst
Addiert zu einer Rendered oder Renderable Bildquelle in
jedes Band einen konstanten Wert.
And
Führt mit den korrespondierenden Pixelwerten von zwei
Rendered oder Renderable Bildquellen eine Logische
AND-Operation auf Bit-Ebene durch.
AndConst
Nimmt die Pixelwerte einer Rendered oder Renderable
Bildquelle bitweise UND mit einer Konstanten pro Band.
BandCombine
Nimmt eine Rendered oder Renderable Bildquelle und
berechnet darauf mehrere beliebige Kombinationen der
Bänder, die mittels einer Matrix spezifiziert sind.
BandSelect
Entnimmt einer Rendered oder Renderable Bildquelle
eine gewählte Anzahl von Bändern und kopiert sie in ein
neues Bild.
Seite 37 von 50
Clamp
Setzt alle Pixelwerte einer Rendered oder Renderable
Bildquelle die unter einer unteren Wert liegen auf diese
Untergrenze und alle Pixelwerte die eine Obergrenze
übersteigen auf deren Wert. Andere Pixelwerte werden
nicht verändert.
ColorConvert
Nimmt eine Rendered oder Renderable Bildquelle und
vollbringt eine Pixel zu Pixel Farbkonvertierung der
Pixeldaten.
Composite
Kombiniert zwei Rendered oder Renderable Bildquellen
über deren Alpha-Werte.
Constant
Divide
DivideByConst
DivideComplex
DivideIntoConst
Nimmt eine Rendered oder Renderable Bildquelle um
eine Multiband Tiled Rendered Bildquelle zu erzeugen,
in dem alle Pixel pro Band den gleichen Farbwert
besitzen.
Dividiert die Werte der Pixel, Band für Band, zweier
Rendered oder Renderable Bildquellen voneinander und
erzeugt aus den Ergebnissen der Division einen neuen
Bild.
Dividiert von den Pixelwerten jedes Bandes einer
Rendered oder Renderable Bildquelle einen konstanten
Wert.
Dividiert die komplexen Werte der Pixel, Band für Band,
zweier Rendered oder Renderable Bildquellen
voneinander und erzeugt aus den Ergebnissen der
Division einen neuen Bild.
Nimmt ein Array aus Konstanten und eine Rendered oder
Renderable Bildquelle um die zu dem jeweiligen Band
korrespondierenden Konstanten von den dorten
Pixelwerten zu dividieren und aus dem Ergebnis ein
neues Bild zu erzeugen.
Exp
Nimmt eine Rendered oder Renderable Bildquelle um die
Exp-Funktion auf jeden Pixelwert jedes Bandes
anzuwenden, also neuer Pixelwert ist gleich 10Quellpixelwert
Invert
Negiert die Pixelwerte einer Rendered oder Renderable
Bildquelle
Log
Berechnet den natürlichen Logarithmus auf die
Pixelwerte der einzelnen Bänder einer Rendered oder
Renderable Bildquelle.
Lookup
Nimmt eine Rendered oder Renderable Bildquelle und
eine Lookup Tabelle die auf das Bild angewandt wird.
MatchCDF
Nimmt eine Rendered oder Renderable Bildquelle und
berechnet die Pixelwerte so um, dass sie möglichst nahe
an eine gegebene Kumulative Distributions Funktion
(CDF; Histogram) heranreichen.
Seite 38 von 50
Max
Min
Kombiniert zwei Rendered oder Renderable Bildquellen
indem der größere Pixelwert an der korrespondierenden
Position und Band beider Bildquellen ins neue Bild
gelangt.
Kombiniert zwei Rendered oder Renderable Bildquellen
indem der kleiner Pixelwert an der korrespondierenden
Position und Band beider Bildquellen ins neue Bild
gelangt.
Multiply
Multipliziert die korrespondierenden Pixelwerte von
zwei Rendered oder Renderable Bildquellen.
MultiplyComplex
Multipliziert komplex die korrespondierenden Pixelwerte
von zwei Rendered oder Renderable Bildquellen.
MultiplyConst
Multipliziert die Pixelwerte von einer Rendered oder
Renderable Bildquelle und einem Array aus Konstanten.
Die Indizes des Arrays entsprechen dabei der Zuordnung
der Konstanten zu den Bändern des Bildes.
Not
Führt mit den Pixelwerten einer Rendered oder
Renderable Bildquelle eine Logische NOT-Operation auf
Bit-Ebene durch.
Or
Führt mit den korrespondierenden Pixelwerten von zwei
Rendered oder Renderable Bildquellen eine Logische
OR-Operation auf Bit-Ebene durch.
OrConst
Führt mit den Pixelwerten einer Rendered oder
Renderable Bildquelle und einem Array aus Konstanten
eine Logische OR-Operation auf Bit-Ebene durch. Die
Indizes des Arrays entsprechen dabei der Zuordnung der
Konstanten zu den Bändern der Bildquelle.
Overlay
Legt eine zweite Rendered oder Renderable Bildquelle
über eine erste. Das bedeutet, dass nur dort, wo die
Alphapixelwerte der zweiten Bildquelle nicht 100%
betragen das erste Bild zu sehn ist.
Pattern
Definiert aus einer Rendered oder Renderable Bildquelle
ein sich wiederholendes Muster.
Piecewise
Rescale
Subtract
Führt eine abschnittsweise lineare Mittelwertbildung auf
den Pixelwerten einer Rendered oder Renderable
Bildquelle.
Führt die Pixelwerte in eine neue Skala über. Dabei
werden die Pixelwerte jedes Bandes mit jeweils einer
Konstanten multipliziert und anschließend mit einer
Weiteren addiert.
Subtrahiert die korrespondierenden Pixelwerte des
entsprechenden Bandes zweier Rendered oder
Renderable Bildquellen. Bild zwei wird von Bild eins
abgezogen.
Seite 39 von 50
SubtractConst
Subtrahiert von den Pixelwerten jedes Bandes einer
Rendered oder Renderable Bildquelle jeweils eine
Konstante.
SubtractFromConst
Subtrahiert von einem Array aus Konstanten jeweils die
Pixelwerte des korrespondierenden Bandes.
Threshold
Setzt alle Pixelwerte einer Rendered oder Renderable
Bildquelle die in zwischen zwei bestimmte Werte Fallen
auf einen weiteren konstanten Wert.
Xor
Führt mit den korrespondierenden Pixelwerten von zwei
Rendered oder Renderable Bildquellen eine Logische
XOR-Operation auf Bit-Ebene durch.
XorConst
Führt mit den Pixelwerten einer Rendered oder
Renderable Bildquelle und einem Array aus Konstanten
eine Logische XOR-Operation auf Bit-Ebene durch. Die
Indizes des Arrays entsprechen dabei der Zuordnung der
Konstanten zu den Bändern der Bildquelle.
Flächen Operatoren
sind geometrische Transformationen die eine Umpositionierung von Pixel als Ergebnis haben.
Durch die Verwendung von mathematischen Transformationen werden die Pixel des
Eingangsbildes von ihrer X- und Y-Koordinaten zu neuen Koordinaten im Ausgangsbild bewegt.
Grundsätzlich gibt es zwei Arten von Flächen Operatoren: linear und nicht linear. Lineare
Operationen sind beispielsweise Translationen, Rotationen oder Skalierungen. Nicht lineare
Operationen sind Warp Transformationen.
Border
Fügt einer Rendered Bildquelle einen Rahmen hinzu.
BoxFilter
Berechnet die Intensität eines jeden Pixel jedes Bandes einer
Rendered Bildquelle neu, indem aus einem Umgebenden
Rechteck der Mittelwert gebildet wird.
Convolve
Wendet einen Gradienten (Kernel) auf das Bild an, indem die
Pixelwerte der entsprechenden Umgebungspixel mit den
korrespondierenden Werten des Kernels multipliziert werden.
Crop
MedianFilter
Clipt das Bild einer Rendered Bildquelle an einem angegebenen
Rechteck.
Filtert aus einer Rendered Bildquelle mittels eines nichtlinearen Filters isolierte Linien oder Pixel ohne das Gesamtbild
zu verändern.
Seite 40 von 50
Geometrische Operationen
Erlauben das ändern der Lage, Größe oder Form eines Bildes.
Affine
Wendet eine affine Transformation auf eine Rendered oder
Renderable Bildquelle an, ggf. mit Filter (z.B. Interpolation).
Rotate
Rotiert eine Rendered oder Renderable Bildquelle um einen
gegeben Punkt mit einem in Radiant spezifizierten Winkel
Scale
Nimmt eine Veränderung der räumlichen Ausdehnungen der
Rendered oder Renderable Bildquelle vor.
Shear
Schert eine Rendered Bildquelle in horizontaler oder
vertikaler Richtung.
Translate
Verschiebt eine Rendered oder Renderable Bildquelle im 2DRaum
Transpose
Spiegelt eine Rendered oder Renderable Bildquelle punkt oder
an der Winkelhalbierenden. Ermöglicht auch das Drehen des
Bildes um reine 90° Winkel.
Warp
Führt eine Warptransformation auf einer Rendered Bildquelle
durch, ggf. mit Filter (z.B. Interpolation).
Farbquantisierungs Operatoren
auch bekannt als Dithering, wird oft benutzt um die Quantisierungsfehler gering zu halten, wenn
Operationen auf monochrome Bilder angewendet werden, die weniger als 8-Bit Farbtiefe
besitzten, oder auf Farbbilder, mit weniger als 24-Bit. Im JAI API sind bereits zwei verschiedene
Dithering-Operatoren vorhanden:
ErrorDiffusion
Führt auf den Pixelwerte einer Rendered Bildquelle eine
Farbquantisierung durch. Dabei werden für die Pixelwerte der
nächste Farbwert in einer gegebenen Lookup Tabelle gesucht.
Diese Tabelle ist konfigurierbar. Der bei diesem Vorgang
entstehende Quantisierungsfehler wird nach unten rechts
"diffused", also diffundiert. Das bedeutet der Quantisierungsfehler wird in den dortigen Pixelwert hineingerechnet, in der
Hoffnung, den dort entstehenden Auszugleichen. Über die
ErrorDiffusion Operation werden die besten Ergebnisse
erzielt.
OrderedDither
Führt auf den Pixelwerte einer Rendered Bildquelle eine
Farbquantisierung durch. Dabei werden für die Pixelwerte der
nächste Farbwert in einem gegebenen 3D-Farbraum gesucht
und anschließend geshiftet, um einen aus einer gegebenen
Dithering-Maske bestimmten Wert.
Seite 41 von 50
Datei Operatoren
Mit ihnen werden Dateien gelesen und geschrieben.
AWTImage
Erzeugt aus einem Objekt der Klasse java.awt.Image eine
Rendered Bildquelle.
BMP
Liest einen BMP Eingangsdatenstrom
Encode
Schreibt eine Rendered Bildquelle in einen gegebenen
OutputStream in einem vereinbarten Format in dem die
bereitgestellten Codierungsparameter benutzt werden.
FileLoad
Liest ein Bild aus einer Datei
FileStore
Schreibt eine Rendered Bildquelle in eine gegebene Datei in
einem festgelegten Format, indem die gegebenen
Codierungsparameter benutz werden.
Format
Ändert das Format einer Rendered oder Renderable Bildquelle.
Diese Operation setzt die Pixelwerte in einen gegebenen
Datentyp um, ersetzt SampleModel und ColorModel und
restrukturiert das Layout des Tilegitters.
FPX
Liest ein Bild aus einem FlashPix Stream
GIF
Liest ein Bild aus einem GIF Stream
IIP
IIPResolution
Bietet Client-Seitige Unterstützung für das Internet Imaging
Protocol (IIP) für Rendered und Renderable Bildquellen. Die
Operation erzeugt ein RenderedImage oder
RenderableImage das seine Daten von einem IIP Server
erhält.
Bietet Client-Seitige Unterstützung für das Internet Imaging
Protocol (IIP) für eine Rendered Bildquelle. Die Operation
erzeugt ein RenderedImage mit einer gewünschten
Auflösung, das die Daten beinhaltet, die von einem IIP Server
geliefert werden.
JPEG
Liest ein Bild aus einem JPEG (JFIF) Stream
PNG
Liest ein Bild aus einem PNG (Version 1.1) Stream
PNM
Liest ein Bild aus einer PNM-Datei. Dies beinhaltet auch PBM,
PGM und PPM Bilder aus ASCII und Roh-Format. Es speichert
die Bilddaten in ein festgelegtes SampleModel.
Stream
Produziert ein Bild durch das Dekodieren eines
SeekableStream. Die Formate die dabei akzeptiert werden
sind durch die Klasse
com.sun.media.jai.codec.ImageCodec registriert.
TIFF
Liest TIFF 6.0 Daten aus einem SeekableStream.
URL
Erzeugt ein Bild aus den Daten die durch den Uniform Resource
Locator (URL) geliefert werden.
Seite 42 von 50
Frequenz Operatoren
Werden benutzt um von seiner räumlich basierenden Form zu einer Frequenz basierenden Form zu
übersetzen und dort Operationen darauf durchzuführen. Die meißten Frequenz Operationen
basieren auf der Fourier Transformation. Die inversen Transformationen können benutzt werden,
um ein Frequenz basierendes Bild wieder zurück zu einem räumlich orientiertem zu berechnen.
Conjugate
Negiert die Imaginären Anteile von komplexen Pixelwerten in
einer Rendered oder Renderable Bildquelle.
DCT
Wendet eine zweidimensionale diskrete Cosinus
Transformation auf jedes einzelne Band einer Rendered oder
Renderable Bildquelle an.
DFT
Wendet eine diskrete Fourier Transformation auf eine
Rendered oder Renderable Bildquelle an.
IDCT
Wendet eine zweidimensionale inverse diskrete Cosinus
Transformation auf jedes einzelne Band einer Rendered oder
Renderable Bildquelle an.
IDFT
Wendet eine inverse diskrete Fourier Transformation auf eine
Rendered oder Renderable Bildquelle an.
ImageFunction
Generiert aus beschreibenden Funktionen (Objekte die das
Interface ImageFunction implementieren) eine neues Bild.
Magnitude
Berechnet die Magnitude für jedes Pixel jedes Bandes einer
Rendered oder Renderable Bildquelle welche komplexe
Pixeldaten enthält.
MagnitudeSquared
Berechnet Magnitude2 für jedes Pixel jedes Bandes einer
Rendered oder Renderable Bildquelle welche komplexe
Pixeldaten enthält.
PeriodicShift
Erzeugt ein neues Bild, aus einer Rendered oder Renderable
Bildquelle, das in X oder/und Y Richtung geshiftet wurde. Das
bedeutet, dass das Originalbild im erzeugten Bild mit einem
Offset erscheint und an den Bildgrenzen des Erzeugten Bildes
clipt. Vor diesem Offset an dem das Originalbild beginnt,
befindet sich dieser geclipte Teil. Durch Shiften in X- und YRichtung entstehen so vier Teile des Orginalbildes, durch die
Zusammen das gesamte Orginalbild sichtbar ist, nur als solches
schwerer optisch Erkennbar ist.
Phase
Nimmt ein komplexe Daten enthaltende Rendered oder
Renderable Bildquelle und berechnet den Phasenwinkel zu
jedem Pixelwert zu berechnen. Dieser Winkel errechnet sich
mittels arctan2 von den einzelnen Pixelwerten des ersten
Bandes zu den korrespondierenden des zweiten Bandes.
PolarToComplex
Erzeugt aus zwei Rendered oder Renderable Bildquellen ein
Bild, das komplexe Pixelwerte besitzt, indem die Magnitude(
Modulo ) und die Phase der korrespondierenden komplexen
Pixel genommen werden.
Seite 43 von 50
Statistische Operatoren
stellen die Mittel zur Verfügung den Inhalt von Bildern zu analysieren.
Extrema
Findet auf einer Rendered Bildquelle in einer bestimmten
Region den minimalen und maximalen Pixelwert für jedes
Band. Die Bilddaten bleiben durch diese Operation unberührt.
Nach der Operation lässt sich deren Ergebnis über die
Methode getProperty("extrema") von dem erzeugten
Bild abfragen.
Histogram
Erzeugt aus einer Region einer Rendered Bildquelle ein
Histogram. Wenn man die Methode
getProperty("histogram")aufruft, erhält man ein
Objekt der Klasse javax.media.jai.Histogram, in der
sich die Ergebnisse der Operation befinden.
Mean
Sucht in einer Region einer Rendered Bildquelle das Mittel
aller Pixel für jedes Band heraus. Die Bilddaten bleiben davon
unberührt. Das Ergebnis der Operation kann mittels der
Operation getProperty("mean") des erzeugten
Bildobjektes abgefragt werden.
Kanten Extraktions Operatoren
es gibt in JAI nur einen Operator der zur Kantenextraktion dient. Dieser wendet einen Gradienten
der Klasse KernelJAI auf eine Region bzw. das ganze Bild an, wodurch Kanten zum Vorschein
kommen.
GradientMagnitude
Wendet auf eine Rendered Bildquelle auf das ganze Bild oder
nur eine Region einen Gradienten in zwei orthogonale
Richtungen an.
Weitere Operatoren
Dieser Operator fällt in keine der darüber genannten Kategorien.
Renderable
Produziert aus einem Rendered Bildqulle ein
RenderableImage, bestehend aus einer Pyramide aus
RenderedImages mit progressiv abnehmenden
Auflösungen.
Seite 44 von 50
3.6. Hilfsmittel im Umgang mit Bildern
javax.media.jai.JAI
stellt eine Reihe von statischen Methoden zur Verfügung um
neue Rendergraphen zu erschaffen. Von ihr kann keine Instanz
erzeugt werden, da es nur zum Verwalten dieser Methoden
dient. Der Großteil der Methoden der Klasse werden zum
erzeugen von RenderedImages verwendet. Dabei nehmen
sie
einen
Operationsnamen,
ein
Objekt
von
ParameterBlock und eines von RenderingHints als
Parameter. Andere Methoden zum erzeugen eines
RenderedImages benutzt die ihnen übergebenen
Parametern
zum
Konfigurieren
eines
neuen
ParamterBlocks. Diese Methoden heißen alle create.
Zum erzeugen eines RenderableImages gibt es nur eine
Methode: createRenderable. Diese nimmt ebenfalls die
drei Grundparameter: Operationsname, ParameterBlockund RenderingHints-Objekt. JAI besitzt ausserdem noch
eine Methode namens createCollection, die ebenfalls
mit den Grundparametern aufgerufen wird. Diese erzeugt ein
Objekt der Klasse java.util.Collection in dem eine
Ansammlung von PlanarImages verwaltet wird.
javax.media.jai.widget.ScrollingImagePanel
Bildanzeigendes Scrollfenster, mit Viewport. Instanzierung wie
bei JScrollPane oder ScrollPane. Von letzterem erbt diese
Klasse und erweitert es auf JAI Bedürfnisse, so das
RenderedImages und RenderableImages bereits beim Aufruf
des Konstruktors keine Probleme bereiten und von den
Objekten der Klasse angezeigt werden. Da die Klasse
java.awt.ScrollPane erweitert und ist die heavyweight und nicht
doppelt puffernd.
java.media.jai.widget.ImageCanvas
Diese Klasse erzeugt Objekte, die einfache Anzeigeflächen für
RenderedImages repräsentieren. Die Klasse erbt von
java.awt.Canvas und kann deshalb in jedem Kontext
verwendet werden in dem es um ein Canvas geht. Das
Canvas sendet Events aus, wenn sich seine Größe ändert oder
ein Tile seiner enthaltenen Bildquelle abgefragt wird.
Seite 45 von 50
3.7. Applikationserstellung mit JAI
Das folgende Beispiel zeigt eine javax.swing.JComponent, die (wie auch in dem Beispiel
von Java2D) ein Bild mit dazugehörigen Operationen versieht. Die Klasse ist extrem erweiterbar
und bietet für manche Anwendung eine gute Grundlage. Mit der Methode loadImage()
übergibt man einen Pfad an dem sich ein Bild befindet. Anschließend kann man mit der Methode
addOperation() den Namen einer Operation zusammen mit einem ParameterBlockObjekt, welches nur die grundlegenden Parameter der Operation enthalten muss, in ein Objekt der
Klasse eingeladen werden. Dort wird dann daraus ein RenderedOp erzeugt, der an die bisherigen
Operationen angehängt wird. Durch diverse Erweiterung der Schnittstellen könnte man
beispielsweise die Operatoren verwalten, beispielsweise diverse Parameter ändern.
implements
implements
implements
implements
implements
implements
implements
implements
implements
implements
java.awt.Dimension;
java.awt.Graphics;
java.awt.Graphics2D;
java.awt.geom.AffineTransform;
java.awt.image.renderable.ParameterBlock;
java.util.ArrayList;
javax.swing.JComponent;
javax.media.jai.InterpolationNearest;
javax.media.jai.JAI;
javax.media.jai.RenderedOp;
public class JAITest extends JComponent{
private ArrayList
operatorList;
/***********************************************************************/
/** Konstruktor in dem eine neue Operatorenliste vorbereitet wird.
**********************************************************************/
public JAITest() {
operatorList = new ArrayList();
}
/***********************************************************************/
/** Setzt die Liste der Operatoren zurück und lädt ein neues Bild als
* Bildladeoperator in diese Liste ein. Anschließend wird
* <code>{@link #repaint()}</code> aufgerufen wodurch das Ergebnis
* dieses Operators in den Grafikkontext gerendert wird. Kurz: Das
* Bild, das sich an dem übergebenen Pfad befindet wird geladen
* und angezeigt.
*
* @param path Pfad des neuen anzuzeigenden Bildes
**********************************************************************/
public void loadImage(String path){
operatorList.clear();
operatorList.add(JAI.create("fileload",path));
repaint();
}
Seite 46 von 50
/***********************************************************************/
/** Erzeugt einen neuen Operator der mit den bisherigen verknüpft wird.
* Dieser Operator ist dann derjenige, dessen Ergebnis dann mit dem
* anschließenden Aufruf der <code>paint()</code>-Methode dann
* gezeichnet wird.
* <p>
* Ein Beispiel für das Erzeugen einer neuen Operation:<br><br><code>
*
* <blockquote>
*
ParameterBlock pb = new ParameterBlock();
*
pb.add(0.0f);
//X-Koordinate des Drehpunktes
*
pb.add(0.0f);
//Y-Koordinate des Drehpunktes
*
pb.add((float)Math.toRadians(45)); //Winkel um dem gereht wird.
*
myJAIComponent.addOperation("rotate",pb);
* </blockquote></code>
*
* @param opName Name der Operation die auf das gesetzte Bild
*
zusätzlich ausgeführt werden soll.
* @param pb
ParameterBlock, in dem nur die Parameter der Operation
*
ohne Interpolation übergeben wird. Quelle und
*
Nearest Neighbour Interpolation werden automatisch
*
hinzugefügt.
**********************************************************************/
public void addOperation(String opName, ParameterBlock pb){
pb.addSource((RenderedOp)
operatorList.get(operatorList.size()-1));
pb.add(new InterpolationNearest());
operatorList.add(JAI.create(opName,pb));
repaint();
}
/***********************************************************************/
/** Zeichnet diese Component und deren Inhalt (falls vorhanden).
* Dabei wird der letzte Operationsknoten angezeigt. In ihm befindet
* sich das Operationsergebnis aller Operationen, die nacheinander
* ausgeführt wurden.
*
* @param g Objekt des Grafikkontextes von dem darüber liegenden
*
Container
**********************************************************************/
public void paint(Graphics g){
super.paint(g);
if( operatorList.size() > 0 ){
RenderedOp shownImage = (RenderedOp)
operatorList.get(operatorList.size()-1);
Graphics2D g2d = (Graphics2D) g;
/* Zeichnet das Bild mit der Umrechnung zwischen
Bildkoordinatensystem und Benutzerkoordinatensystem. In diesem Fall eine Einheitsmatrix.
*/
g2d.drawRenderedImage(shownImage,
new AffineTransform());
}
}
Seite 47 von 50
/***********************************************************************/
/** Gibt die Größe dieser Component an den Layout-Manager. Diese Größe
* errechnet sich über den letzten Operationsknoten. Die Dimensionen
* des von ihm errechneten Bildes wird zurückgegeben. Voraussetzung
* dafür ist, dass mindestens ein Operationsknoten vorhanden ist.
*
* @return Größe in Pixel der Component.
**********************************************************************/
public Dimension getPreferredSize(){
if( operatorList.size() > 0 ){
RenderedOp shownImage = (RenderedOp)
operatorList.get(operatorList.size()-1);
//Höhe und Breite des letzten Renderzustandes.
return new Dimension(shownImage.getWidth(),
shownImage.getHeight());
}
else {//Falls noch kein Bild geladen wurde
return super.getPreferredSize();
}
}
/***********************************************************************/
}
3.8. Übersicht
Rendergraph
Renderable Layer
Rendered Layer
ParameterBlock
Bildklassen
Operatoren
JAI
RenderedOp
RenderableOp
……………………………………………………………………..35,36
……………………………………………………………………..31,32
………………………………………………………………………...31
………………………………………………………………………...36
……………………………………………………………………..33,34
…………………………………………………………………….37-44
………………………………………………………………………...45
………………………………………………………………………...34
……………………………………………………………………..33,34
Seite 48 von 50
4. Gegenüberstellung beider Frameworks
Grundsätzlich sind beide API's in Applikationen verwendbar. Es liegt an der Art der Anwendung
die zu Programmieren ist, für welches Framework man sich entscheidet. Java2D ist für kleine
Aufgaben bestens geeignet. Sei es für das Zeichnen von Icons, Statistiken oder auch das anzeigen
von Bildern. Sofern auf dem Bild keine komplexeren Operationen ausgeführt werden, erhält man
von Java2D sowie im Produktionserzeugnis als auch in der Laufzeit befriedigende Ergebnisse. JAI
ist wie auch Java2D noch in der Entwicklung, weswegen man von Zeit zu Zeit auf Fehler in beiden
API's stößt. JAI ist im Gegensatz zu Java2D besser strukturiert und besser zu Verwalten. Ein
Grund für JAI's hohe Erweiterbarkeit. Bei komplexen mathematischen Operationen braucht aber
auch JAI die eine oder andere Sekunde an Laufzeit für die Berechnung. Aus laufzeittechnischen
Gründen gibt eigentlich keinen Grund zu JAI zu wechseln. JAI baut auf Java2D und AWT auf und
ist von der Laufzeit geringfügig schneller. Seinen Vorteil in der Geschwindigkeit erhält JAI nur
durch einige schnellere Algorithmen und Anwendungen die JAI's Organisationsstruktur den
Modellen gerecht bedienen. Die Parameter von Operationen bleiben austauschbar, Bildquellen
können von mehreren Threads bzw. Prozessen gleichzeitig genutzt werden und Berechnungen auf
dem Bild erfolgen nur dann, wenn sie auch gebraucht werden. Dies alles müsste man sich bei
Java2D erst schaffen um Vergleichbar zu werden. Hier nun eine Gegenüberstellung von einigen
Eckpunkten beider API's:
Java2D
JAI
Immediate Mode, Push
sehr gut (Läuft mit JRE, also
kostenlos)
Immediate Mode, Push, Pull
gut (JRE + zusätzliche Pakete,
also kostenlos)
wenig, besitzt hauptsächlich
Basisoperatoren
mittelmäßig, beispielsweise
keine Spur von Wavelet,
Blobanalyse, opt. Fluß,
Tracking oder auch keinen
morphologische Operatoren
befriedigend
Hoch, da man recht schnell auf
Pixelwerten zurückgeht um den
Nachteil von wenigen
Operatoren auszugleichen
versucht.
sehr gut
Mittel. Code ist gut lesbar und
sehr gut strukturierbar, jedoch
für Erweiterungen müssen
meist einige Klassen
implementiert werden.
Multi-Threading
muss zusätzlich Implementiert
werden
Multi-Threading-Fähig
Unterstützte Dateitypen
GIF, BMP, JPEG, TIFF
BMP, FPX, GIF, JPEG,
PNG, PNM, TIFF
Bildverarbeitende Modelle
Verfügbarkeit
Angebotene Operatoren
Erweiterbarkeit
Komplexität des Codes
Nun gehört es zu den von Sun erklärten Zielen in Zukunft in JAI noch mehr Operatoren anzubieten
und die Bilder noch schneller zu verarbeiten. Auch an Java2D wird noch gearbeitet um es schneller
und Leistungsfähiger zu machen. Für geringe Aufgaben sind beide Frameworks geeignet und
leisten gute Arbeit. Verglichen mit C++ und dessen Bildverarbeitungsklassen hat Java2D wie auch
JAI keine Chance von der Geschwindigkeit. Von der Anzahl der Operatoren gewinnt dort JAI,
sofern man keine Zusatzpakete für C++ installiert. Vergleicht man die angebotenen Operatoren
von Java2D und JAI mit Open-Source Bibliotheken wie Halcon oder OpenCV, ziehen ebenfalls
beide Java API's den Kürzeren. Will man aber Java benutzen, so stellt sich JAI als eine gute
Erweiterung zu Java2D heraus und kann im Einzelfall für Teile von Anwendungen interessant sein.
Seite 49 von 50
5. Quellen:
Sun Microsystems: Java 2DTM API WHITE PAPER
http://java.sun.com/products/java-media/2D/whitepaper.html
Sun Microsystems: Java 2DTM API DATASHEET
http://java.sun.com/products/java-media/2D/datasheet.html
Sun Microsystems: Java 2DTM API Homepage
http://java.sun.com/products/java-media/2D/index.html
Sun Microsystems: Java 2DTM API Specification
http://java.sun.com/j2se/1.4/docs/guide/2d/spec.html
Sun Microsystems: Programming in Java Advanced Imaging
http://java.sun.com/products/java-media/jai/forDevelopers/jai1_0_1guide-unc/JAITOC.fm.html
Sun Microsystems: Java Advanced Imaging API Summary
http://java.sun.com/products/java-media/jai/forDevelopers/jai1_0_1guide-unc/
API-summary.doc.html
Sun Microsystems: Java Advanced Imaging API Dokumentation
http://java.sun.com/products/java-media/jai/forDevelopers/jai-apidocs/index.html
Seite 50 von 50
Herunterladen