Avancerad 2D-grafik med Java 2D

Java 2D är en integrerad del av JDK som finns i diverse paket under java.awt. Exemplen på den här sidan är hämtade ur boken http://java.sun.com/products/jdk/1.4/docs/guide/2d/spec/j2d-bookTOC.doc.html och ur avsnittet om Java 2D i JavaTutorial, http://java.sun.com/docs/books/tutorial/2d/. Diverse exempelprogram finns att hämta på http://java.sun.com/products/java-media/2D/samples/, titta gärna även på http://java.sun.com/products/java-media/2D/samples/java2demo/Java2Demo-pi.html, vilket är en applet som demonstrerar Java 2D:s alla möjligheter på ett mycket bra sätt (naturligtvis finns även dess källkod). Den kräver Java Plug-in. En intrressant bok om hur man skriver flashig text hittar du här, http://developer.java.sun.com/developer/onlineTraining/Media/2DText/.

Det en här sidan beskriver bara en liten del av vad som går att göra med Java 2D och tar inte upp någonting om hur API:t egentligen processar grafik och bilder.

Rita 2D-grafik

Grunden är klassen java.awt.Graphics2D vilken ärver av java.awt.Graphics. Du får tag i ett objekt av den klassen genom att casta det Graphics-objekt du får in i metoden paintComponent() på följande sätt:
public void paintComponent(Graphics g) {
  super.paintComponent(g);
  Graphics2D g2 = (Graphics2D)g;
  ...
Det är alltså egentligen ett objekt av Graphics2D som skickas till paintComponent().

Olika geometriska former

Det finns inte en specifik metod för varje geometrisk form som ska ritas, i stället används de generella metoderna draw() och fill() vilka båda tar ett objekt av interfacet java.awt.Shape. Shape implementeras av en mängd olika klasser i paketet java.awt.geom som beskriver olika former. Dessa olika klasser har alla två inre klasser som ärver av den yttre klassen och representerar position och storlek som float respektive double. Här kommer ett exempel som ritar en linje, en båge och en ellips, LineArcEllipse.java. Godtyckliga former kan skapas med klasserna GeneralPath, vilken beskriver en linje som an följd av olika kurvor, och Area, vilken beskriver en area som en kombination av olika delareor. Se vidare dessa två exempel, GeneralPathTest.java, AreaTest.java. Det går även att rita tredje- och andragradsfunktioner med klasserna CubicCurve2D och QuadCurve2D.

Olika linjer

Interfacet java.awt.Stroke (som implementeras av java.awt.BasicStroke) definierar hur tjock en linje är, hur en linje slutar, hur två linjer möts och om en linje ska streckas. Du tilldelar ett Graphics2D-object en Stroke med metoden setStroke(). Här kommer ett exempel som manipulerar med några linjer, LineStroke.java. Observera hur Graphics2D-objektets färg ändras: ändringen påverkar endast det som ritas efter att färgen sätts.

Olika fyllningar

Nu är det äntligen dags för lite roliga färger. Förutom den tidigare kända metoden setColor() kan nu även Graphics2D-objektets bakgrundsfärg sättas med metoden setBackground(). Men framför allt, det går att ange avancerade mönster genom att tilldela en Graphics2D en Paint med netoden setPaint(). Interfacet java.awt.Paint har två olika implementationer. java.awt.GradientPaint anger två punkter med olika färger. Mellan punkterna förändras färgen succesivt. java.awt.TexturePaint fyller ett föremål med en bild som upprepas över hela föremålet. Här är ett exempel på båda två, PaintTest.java. För att göra bilden i exemplet används en BufferedImage, mer om den under avsnittet om bilder nedan.

Transformationer

Klassen java.awtAffineTransform används för att transformera en Graphics2D. De transformationer som finns färdiga är rotera, zooma, flytta och skjuva. Här är ett exempel som roterar och skjuvar, TransformTest.java.

Ritdirektiv

Det finns ett flertal olika sätt att rita ut exakt samma figur. En vanlig konflikt är om det ska gå fort att rita den eller om den ska bli snygg. Det går att styra utritningen på flrea olika sätt med hjälp av java.awt.RenderingHints. Här är ett exempel på en kurva med och utan antialiasing (vilket innebär att kantiga sidor jämnas ut). RenderingHintsTest.java

Aktiv del av ritytan

Graphics2D har en metod setClip() vilken tar en Shape som parameter. Efter att metoden anropats sker endast ritning i denna Shape, det som ritas utanför syns inte på skärmen. Metoden clip(Shape s) sätter den aktiva delen till skärningsytan av tidigare aktiv del och den Shape som skickas till metoden. Här är ett exempel, ClipTest.java.

Genomskinlighet och överritning

En Graphics2D:s alfavärde anger hur genomskinligt det som ritas är, 1.0 är helt ogenomskinligt och 0.0 är helt genomskinligt. Klassen java.awt.AlphaComposite har dels ett alfavärde, dels en regel som anger hur något ritas över. Reglerna förklaras här, http://java.sun.com/doc/books/tutorial/2d/display/compositing.html. Detta exempel anger att det senast ritade hamnar överst och att alfavärdet är 0.25, dvs tämligen genomskinligt. Observera dels hur färgerna blandas men även att både det röda och det blå blir mycket ljusare eftersom det är genomskinligt. CompositeTest.java

Texthantering

Fonter

Klassen java.awt.font.TextLayout innehåller en mängd metoder för att hantera en String, bland annat metoden draw() för att rita ut den. Klassen java.awt.Font representerar en font. Det här exemplet skriver ut lite text med olika fonter, DrawText.java, det här exemplet listar alla tillgängliga fonter, ListFonts.java. GraphicsEnvironment innehåller information om den dator applikationen körs på, till exempel tillgängliga Fonter. Nya fonter kan laddas med den statiska metoden Font.createFont(int, InputStream) .

Effekter

Roliga effekter kan med text kan åstadkommas genom att sätta aktiv del av ritytan (clipping area) till bokstäverna i texten. Det kan göras med raderna

Shape shape = layout.getOutline(
          AffineTransform.getTranslateInstance(20, 100));
g2.setClip(shape);
 

layout är ett objekt av TextLayout. Metoden getOutline() returnerar en Shape som representerar den yta som täcks av bokstäverna. Genom att skicka en AffineTransform till getOutline() kan Shapen flyttas. Gör vi inte det hamnar den ovanför ritytan. Om vi nu ritar något kommer det bara att synas i bokstäverna. Här är tre exempel. Det här fyller texten med en bild, FillTextWithImage.java (här är bilden, starry.gif), det här fyller texten med tecknet '*', FillTextWithChar.java och det här fyller texten med linjer, FillTextWithLines.java.

Ännu mer effekter kan man få om man dessutom anger en rolig Stroke, se avsnittet "Olika linjer" ovan.

Cursor

Här är ett program som sätter en cursor där användaren klickar i texten. Intressantare än att sätta en cursor är kanske att det överhuvud taget går att avgöra på vilken bokstav användaren klickat. Det blir rätt mycket kod, det intressanta är raderna
TextHitInfo currentHit = textLayout.hitTestChar(clickX, clickY);
int insertionIndex = currentHit.getInsertionIndex();
Metoden hitTestChar() returnerar ett objekt av TextHitInfo som innehåller information om en position i texten. Dess metod getInsertionIndex() returnerar ett index i texten. Raden Shape[] carets = textLayout.getCaretShapes(insertionIndex); returnerar en Shape som innehåller form och position av en cursor på rätt ställe i texten. Här är programmet, HitTestSample.java.

Bilder

Det finns en hel del stöd för bildfiltrering i Java 2D, dessutom finns det ett helt API som heter Java Advanced Imaging. Inget av detta tas dock upp här.

Double buffer

För att inte störa skärmen med det tidsödande arbetet att rita och manipulera grafik, text och bilder är det bäst att rita färdigt allting på en bild som inte syns på skärmen. Sedan är det bara att lägga ut hela den färdiga grafiken på en gång. Detta illustreras av det här programmet, DoubleBuffer.java, som börjar med skapar en java.awt.image.BufferedImage med metoden createBufferedImage(). Sedan hämtas dess Graphics2D med metoden createGraphics(), därefter ritas allting färdigt och i paintComponent() återstår så bara att rita ut den färdiga bilden.

Utskrift

AWT utskrift

I AWT ingår ett primitivt API för utskrift. Det är i och för sig relativt enkelt att använda men ganska snart vill man göra mer än vad som är möjligt. Mitt råd är därför att istället använda nedanstående API ur Java 2D.

Java 2D utskrift

Att skriva ut något går till på följande sätt
  1. Skaffa ett java.awt.print.PrinterJob genom att anropa den statiska metoden PrinterJob.getPrinterJob().
  2. Ange vilket objekt som har hand om utskriften genom att skicka en referens till det till någon av PrinterJobets metoder setPrintable(Printable p) eller setPageable(Pageable p).

    Interfacet Printable innehåller endast metoden print() vilken får ett Graphics2D och en int som representerar ett sidnummer som inparameter. Det objekt som implementerar gränssnittet ritar/skriver sidans innehåll i nyss nämnda Graphics2D och returnerar Printable.PAGE_EXISTS. Om sidan inte finns returneras Printable.NO_SUCH_PAGE. En Printable vet inte hur många sidor utskriften har.
  3. Interfacet Pageable innehåller dels en Printable, dels några fler metoder som ger ytterligare information om utskriften. En Pageable används om olika sidor ska ha olika egenskaper.

  4. Om så önskas kan två olika dialogrutor visas. Dels en om utskriften med metoden PrinterJob.printDialog(), dels en om sidformat med metoden PrinterJob.pageDialog().
  5. Starta utskriften genom att anropa PrinterJob.print().
  6. PrinterJobet kommer nu att sköta om utskriften. När det behöver få mer av det som ska skrivas ut anropar det metoden print() i den Printable som angavs i punkt två. Denna Printable ritar/skriver då angiven sida.
Här kommer ett program som skriver ut en textfil, PrintText.java (notera att en Printable inte vet hur många sidor en utskrift har) och ett som skriver ut samma grafik som visas på skärmen, PrintComponent.java.  Om Componentens utseende inte ska vara likadant när den skrivs ut som när den ritas på skärmen ska metoden printComponent() omdefinieras till att rita det som ska skrivas ut.