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
- Skaffa ett java.awt.print.PrinterJob genom att anropa den
statiska metoden PrinterJob.getPrinterJob().
- 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.
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.
- Om så önskas kan två olika dialogrutor visas.
Dels en om utskriften med metoden PrinterJob.printDialog(), dels
en om sidformat med metoden PrinterJob.pageDialog().
- Starta utskriften genom att anropa PrinterJob.print().
- 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.