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