Ogni componente Java possiede il metodo:
IN AWT, il metodo paint e' "monolitico".
In Swing, il metodo paint e' implementato invocando nell'ordine i seguenti tre metodi:
Il secondo dei tre metodi e' dovuto al fatto che in Swing un componente puo' avere bordi.
L'ultimo dei tre metodi e' reso necessario dal fatto che componenti Swing sono lightweight.
Per personalizzare la grafica di un componente, occorre definire una sottoclasse con una nuova implementazione del metodo paint (in AWT) o paintComponent (in Swing).
Scegliere la classe di componente adatta (es Button), in modo da ereditare tutti i comportamenti previsti da tale classe. Esempi:
Ridefinire il metodo paint o paintComponent inserendovi il codice per compiere il disegnamento voluto. Chiamare sempre super.paint o super.paintComponent all'inizio in modo da effettuare anche tutto il redisegmanento previsto di default (es. pulitura dello sfondo).
Il metodo paint / paintComponent ha come argomento un oggetto di classe Graphics che viene passato automaticamente dal sistema.
void paint/paintComponent (Graphics g) { .... }
Tutto il disegno si fa chiamando metodi della classe Graphics sull'oggetto g.
La classe Graphics contiene:
In realta' l'oggetto g che viene passato dal sistema a paint non e' semplicemente di classe Graphics, ma in particolare e' della sottoclasse Graphics2D.
Essendo di classe Graphics2D (sottoclasse), g e' anche di
classe Graphics (superclasse), e normalmente si usa
come se fosse di classe Graphics
Se voglio vederlo come oggetto di classe Graphics2D
(per poter vedere le funzionalita' avanzate), devo fare
una conversione esplicita (cast):
Graphics2D g2 = (Graphics2D)g;dopo di che in g2 ho sempre g ma visto come oggetto di classe Graphics2D
Noi vedremo le funzionalita' di Graphics2D.
Quello che si disegna dipende da:
Esempio:
Posso esprimere le mie primitive (linee, poligoni) in un sistema di
coordinate numeri reali tra -1 ed 1,
e poi visualizzarle in una finestra di dimensioni che voglio.
Se la finestra e' di 300x200 pixel,
cio' significa che i pixel dell'immagine raster generata saranno in
un sistema di coordinate intere x tra 0 e 300, y tra 0 e 200.
Nel definire le primitive non mi devo preoccupare di quanto
sara' grande la finestra di visualizzazione, a questo ci penseranno
le trasformazioni.
Ogni componente ha un sistema di coordinate intere (in pixel) con origine (0,0) in alto a sinistra e punto in basso a destra di coordinate (d.width-1,d.height-1) dove Dimension d = getSize() sono le dimensioni in pixel del componente. Le coordinate x crescono da sinistra a destra, le coordinate y crescono dall'alto verso il basso.
In Swing devo tenere conto che parte del rettangolo della componente
puo' essere occupata dal bordo.
Insets i = getInsets() ritorna informazioni sullo spessore
del bordo.
La parte di rettangolo libera dal bordo ha angolo in alto a sinistra
(i.left,i.top) e in basso a destra
(d.width-i.right-1,d.height-i.bottom-1).
Le coordinate sono infinitamente sottili e collocate tra un pixel
e l'altro.
La punta scrivente traccia la linea sul pixel immediatamente
a destra e in basso.
Cio' significa che, se traccio un rettangolo pieno e il suo contorno
vuoto dando le stesse coordinate, il contorno
si estende una riga di pixel in piu' in basso e una
colonna di pixel a destra rispetto al rettangolo pieno.
Sistema di coordinate logico in cui il programmatore esprime le primitive da disegnare.
Una trasformazione affine determina come portare le primitive da User space a Device space.
Di default la trasformazione e' l'identita' (nessuna trasformazione,
i due spazi di coordinate coincidono).
Quindi se la finestra e' di 300x200 pixel io devo definire primitive che
abbiano coordinate entro questi limiti, le (parti di) primitive che cadono
fuori non saranno visibili.
Se l'utente cambia dimensioni alla finestra, la scena puo' rimenere
decentrata e/o in parte tagliata fuori.
Posso invece impostare trasformazioni personalizzate:
traslazioni, rotazioni, scalature e shear.
In questo modo definisco le primitive con coordinate entro limiti che
decido io, poi affido alle trasformazioni il compito di "farle stare"
nella finestra, in base alle dimensioni correnti di questa.
Nota: Graphics2D ha trasformazioni e quindi distinzione tra device space e user space. Invece Graphics non ha trasformazioni e devo esprimere le primitive direttamente in device space.
Attributi pittorici ("come" lo posso disegnare):
Quando due primitive vanno a sovrapporsi sulla stessa area della finestra, quale delle due "vince"? Lo stabilisce il modo di composizione del colore:
Di norma il perimetro di clipping e' la finestra: cio' che ha coordinate che eccedono i limiti delle coordinate di device e' tagliato via e non si vede.
Posso specificare perimetri di clipping (clipping path) aggiuntivi. Allora anche cio' che cade dentro la finestra ma fuori dal perimetro di clipping sara' tagliato.
Metodi per disegnare direttamente una figura. I parametri sono interi esprimenti numero di pixel, cioe' le primitive vanno fornite direttamente in device space.
Dimension d = getSize(); g.drawImage (image, 0,0, d.width,d.height, this);
Il colore con cui disegno si legge con Color getColor() e si imposta con setColor(Color c).
Un colore si crea specificando le componenti RGB con new Color(r,g,b) dove r,g,b sono interi tra 0 e 255 oppure float tra 0.0 e 1.0. La classe Color ha anche costanti per i colori piu' comuni: Color.black, Color.white, Color.red, Color.green...
Il tratto e' il modo di tracciamento delle linee. Si legge con Stroke getStroke() e si imposta con setStroke(Stroke s).
Il tratto e' implementato dalla classe BasicStroke, e puo' essere:
La trama e' il modo di riempimento delle aree. Si legge con Paint getPaint() e si imposta con setPaint(Paint p).
La trama p puo' essere:
La font e' il tipo di carattere usato per tracciare le stringhe. Si legge e si imposta con Font getFont() e setFont(Font f).
Una font si crea con: new Font (nome, stile, grandezza) dove
GraphicsEnvironment genv = GraphicsEnvironment.getLocalGraphicsEnvironment(); Font[] f = genv.getAllFonts();
Pannello con grafica personalizzato:
ExDraw1.java.
La funzione paintComponent riempe un rettangolo sfumato,
vi disegna dentro un fumetto con scritto grande "Ciao!",
traccia con tratto spesso tre linee alla base del fumetto.
La classe Shape fornisce nelle sue sottoclassi vari tipi di forme geometriche che posso disegnare usando i metodi della classe Graphics2D:
Sottoclassi di shape:
Le shape sono contenute nel package java.awt.geom, che occorre percio' importare.
Alcune delle figure di cui sopra corrispondono a funzioni draw/fill
gia' viste (es: drawShape di un Rectangle2D equivale a drawRect).
Altre sono nuove.
In piu' e' possibile esprimere le coordinate con float e double.
La possibilita' di disegnare le shape esiste solo in Graphics2D, non in Graphics. Le coordinate possono essere numeri reali perche' Graphics2D ammette coordinate logiche diverse da quelle fisiche (cioe' dai pixel).
L'esempio ExDraw1.java
visto prima disegna sempre la scena in 300x200 pixel.
Invece l'esempio
ExDraw2.java
disegna la scena sempre in tutta la finestra,
anche quando l'utente la deforma.
Fa uso di trasformazioni che vedremo dopo.
La classe Area prevede le operazioni add, subtract, intersect, exclusiveOr con argomento un'altra area. Ogni shape puo' essere trasformata in un'area. Posso costruire una forma complessa aggiungendo, sottraendo, intersecando e facendo l'OR esclusivo di forme semplici.
Esempio:
Mela con morso ottenuta unendo due ellissi e sottraendone un altro:
MelaMorsa.java
Ellipse2D.Double sinistra, destra, morso; Area a; sinistra = new Ellipse2D.Double(); sinistra.setFrame(20, 20, 60, 80); destra = new Ellipse2D.Double(); destra.setFrame(40, 20, 60, 80); morso = new Ellipse2D.Double(); morso.setFrame(80, 70, 60, 60); a = new Area(); a.add( new Area(sinistra) ); a.add( new Area(destra) ); a.subtract( new Area(morso) );
Cosa si vede | Cosa c'e sotto |
![]() |
![]() |
Graphics2D contiene una trasformazione affine che e' usata per traslare, ruotare, scalare e deformare le primitive durante la loro traduzione da user space a device space.
La trasformazione e' un oggetto di classe AffineTransform. Internamente e' implementata come una matrice 3x3 (in coordinate omogenee).
Posso creare un una nuova trasformazione con:
Oppure modificare la trasformazione corrente concatenandone un'altra con i metodi di Graphics2D:
Posso ottenere qualsiasi trasformazione componendo queste.
Importante: la composizione delle matrici avviene moltiplicando la nuova matrice a destra di quella corrente, quindi PRIMA viene eseguita la trasformazione corrispondente alla NUOVA matrice e DOPO la trasformazione corrispondente alla VECCHIA matrice.
In pratica le trasformazioni sono eseguite IN ORDINE INVERSO a come le specifico nel codice.
Esempio:
Parto dalla matrice identita' I.
g2.translate(...); matrice = I T = T dove T = matrice di traslazione
g2.rotate(...); matrice = T R dove R = matrice di rotazione
Risultato: nell'ordine prima ruoto poi traslo i punti
Rotazione attorno a un punto C = (xC,yC) diverso da origine:
La trasformazione va cambiata solo momentaneamente durante l'esecuzione di paint, alla fine del codice di paint bisogna ripristinare la trasformazione che c'era prima. Altrimenti si potrebbero avere effetti inaspettati. Per fare cio':
Animazione = cambiamento dinamico della grafica mostrata su un componente.
Ad intervalli regolari (gestiti con un timer) oppure in seguito ad azioni dell'utente si ridisegna il componente cambiandone la grafica.
Di norma il metodo paint e' chiamato dal sistema, attraverso canali suoi, quando il sistema ritiene che il componente vada ridisegnato (es. quando la finestra che lo contiene e' mappata o torna visibile dopo essere stata oscurata da altre, quando viene ridimensionata...).
L'animazione rende necessario ridisegnare anche in casi diversi da quelli previsti dal sistema.
Il programma NON DEVE chiamare direttamente paint! Per provocare da programma il ridisegnamento del componente bisogna usare il metodo:
Siccome la gestione della coda e' asincrona, puo' capitare che piu' chiamate a repaint vengano collassate in una sola chiamata a paint.
L'animazione si ottiene cambiando alcuni parametri usati dentro la funzione paint e poi invocando repaint.
Pannello con cerchio che scorre avanti e indietro per un pannello:
MuoviCerchio.java
Una variabile contiene l'ascissa corrente ed e' usata da paint per
disegnare il cerchio.
C'e' un timer che ogni tanti millisecondi cambia il valore della
variabile e invoca repaint.
Vi sono due tipi di azioni interessanti che l'utente puo' compiere sull'area grafica:
Si catturano associando al pannello gli event listener opportuni.
Nell'esempio associo un MouseListener che nel suo metodo mouseClicked compia l'operazione corrispondente. Le coordinate del punto cliccato si ottengono chiamando getX() e getY() sull'evento MouseEvent.
Come sopra, ma in piu' richiedono un modo per conoscere che primitiva e' disegnata nel punto dove e' avvenuto il click.
E' necessario aver disegnato le figure che vogliamo rendere sensibili al click usando oggetti di classe shape ed il metodo fill(Shape s) della classe Graphics2D.
Si scorre la lista delle shape che sono state disegnate (e' necessario averle memorizzate) e si chiede a ognuna se e' stata interessata dal click, usando i metodi della classe Shape:
Programma che permette di disegnare cerchi e di cancellarli: ClickCircle.java
Un frame con in alto due bottoni radio "insert" e "delete" per scegliere la modalita' corrente. In basso un'area di disegno.
I cerchi presenti sono tenuti in un array di oggetti di classe Ellipse2D. La reazione al click del mouse e' gestita associando un mouse listener al pannello, che implementa mouseClicked cosi':