Do wykreślania obiektów graficznych w Javie służy wykreślacz - kontekst
graficzny - będący obiektem (abstrakcyjnej) klasy java.awt.Graphics
.
Klasa ta udostępnia metody pozwalające na rysowanie figur geometrycznych jak również
zewnętrznych obrazów.
getGraphics()
z:
java.awt.Component
java.awt.Image
wraz z podklasami,
głównie BufferedImage
(używana jako bufor ekranowy).
Po użyciu wykreślacza, jeśli nie będzie on więcej potrzebny, należy mu wydać polecenie dispose()
,
które zniszczy zasoby przez niego wykorzystywane.
getGraphics()
, ani żadnej innej z nim związanej
(getWidth()
, getHeight()
) dopóki komponent, którego dotyczy wywołanie nie będzie w pełni
zrealizowany (pack()
).
Aby to zapewnić stosuje się mechanizmy synchronizujące wątki, a także metodę
SwingUtilities.invokeLater()
.
Zamiast rysować na wykreślaczu pobranym od komponentu, wygodniej jest umieścić kod
rysujący w metodzie wykreślającej: paintComponent(Graphics g)
w Swingu
lub paint(Graphics g)
dla AWT.
Dzięki temu odświeżanie rysunku będzie następować automatycznie, ponieważ
obie metody są wywoływane na zasadzie callback-u w wątku zdarzeniowym,
gdy:
repaint()
, która wstawi do kolejki
zdarzeniowej żądanie odświeżenia komponentu
Thread.sleep()
.
Należy również unikać bloków i metod synchronizowanych.
Aby użyć metody wykreślającej komponent do wykreślania własnej grafiki należy
stworzyć podklasę klasy komponentu, w której się ją przedefiniuje. Pierwszą
instrukcją w metodzie wykreślającej musi być wywołanie jej wersji z klasy bazowej:
super.paintComponent(g)
w Swingu lub
super.paint(g)
dla AWT.
paint()
lub paintComponent()
.
Mimo, iż można rysować na dowolnym komponencie, to jednak do przedstawiania własnej
grafiki służą głównie klasy Canvas
dla AWT i JPanel
w przypadku Swinga.
Układ współrzędnych kontekstu graficznego jest zaczepiony w jego lewym-górnym rogu, tzn. jego współrzędne wynoszą [0,0] i rosną w prawo (pierwsza współrzędna) i w dół (druga). Współrzędne są umiejscowione pomiędzy pikselami urządzenia wyjściowego. Wykreślenie punktu o danych współrzędnych wyświetla najbliższy piksel leżący poniżej i po prawej stronie w stosunku do tego punktu.
w
i wysokość h
, to wypełniając go podaje się właśnie te wielkości,
podczas gdy rysując jego krawędź należy przyjąć rozmiary o jeden piksel mniejsze: w-1
, h-1
.
Point
Położenia obiektów graficznych są przeważnie reprezentowane przy pomocy obiektu klasy java.awt.Point
.
Obiekt tej klasy przechowuje dwie liczby typu int
, będące jego publicznymi składowymi (a więc
można na nich bezpośrednio operować):
x
- współrzędna poziomay
- współrzędna pionowaPoint(int x, int y)
inicjuje współrzędne podanymi wartościami,
natomiast Point()
nadaje współrzędnym domyślne wartości 0
.
Metody tej klasy umożliwiają dokonywanie prostych operacji na punktach:
void move(int x, int y)
- zmiana położeniavoid translate(int dx, int dy)
- przesunięciedouble distance(Point2D p)
- odległość od podanego punktu (metoda
odziedziczona z nadklasy java.awt.geom.Point2D
)
Dimension
Do reprezentowania rozmiarów komponentów (i nie tylko) służy klasa java.awt.Dimension
.
Obiekt tej klasy zawiera publiczne składowe:
width
- szerokośćheight
- wysokośćDimension(int width, int height)
inicjuje wymiary podanymi
wartościami, natomiast Dimension()
nadaje im domyślne wartości 0
.
setClip(Shape clip)
,
przekazując jako argument odniesienie do obiektu klasy implementującj interfejs
java.awt.Shape
. Nie wszystkie tego typu kształty są honorowane.
Wykreślanie można ograniczyć do zadanego obszaru. Punkty leżące poza tym obszarem
nie będą wykreślane, nawet jeśli rysowany obiekt z niego wystaje. Najprostszym
sposobem zdefiniowania obszaru obcianania jest metoda setClip(int x, int y, int w, int h)
.
Ogranicza ona wykreślanie do prostokąta zaczepionego w [x,y]
i wymiarach [w,h]
.
Domyślny obszar wykreślania można przywrócić poprzez
setClip(0, 0, w-1, h-1)
-
gdzie w
jest szerokością a h
wysokością pierwotnego obszaru (komponentu).
Innym sposobem jest setClip(null)
.
Kolory są obiektami klasy java.awt.Color
. Każdy kolor komponuje się z
trzech składowych: czerwonej, zielonej i niebieskiej. Oprócz tego kolory mogą być
mniej lub bardziej przezroczyste. Poziom nasycenia koloru daną składową określa
liczba typu int
z przedziału 0-255
lub typu float
z przedziału 0.0-1.0
- zależnie od użytego konstruktora.
Stopień przezroczystości reguluje składowa alpha przyjmująca wartości z
powyższych przedziałów. Kolor całkowicie przezroczysty odpowiada wartości alpha
równej 0 (lub 0.0), natomiast nieprzezroczysty odpowiada liczbie 255 (lub 1.0).
W klasie Color
są zdefiniowane statyczne składowe określające 13
najczęściej używanych kolorów:
black
,
blue
,
cyan
,
darkGray
,
gray
,
green
,
lightGray
,
magenta
,
orange
,
pink
,
red
,
white
,
yellow
,
oraz ich odpowiedniki pisane dużymi literami.
Color(int r, int g, int b)
- tworzy nieprzezroczysty kolor
(alpha==255
)
na podstawie składowych z przedziału 0-255
.
Color(int r, int g, int b, int a)
- jak wyżej, ale uwzględnia stopień
przezroczystości a
z przedziału [0..255]
.
Color(float r, float g, float b)
- jak w pierwszym przypadku, ale
r, g, b
są z przedziału [0.0 1.0]
.
Color(float r, float g, float b, float a)
- umożliwia określenie
przezroczystości: 0.0 < a < 1.0
.
Color brighter()
- zwraca jaśniejszą wersję tego koloru (this
). Tworzy nowy obiekt !Color darker()
- zwraca ciemniejszą wersję tego koloru. Tworzy nowy obiekt - jak wyżej.int getRed()
, int getGreen()
, int getBlue()
- składoweint getAlpha()
- stopień przezroczystościColor
Poniższy program demonstruje stosowanie klasy Color
, w szczególności
przezroczystość kolorów. Cztery etykiety nakładają się na siebie w ten sposób,
że prawa-dolna jest na spodzie, a lewa-górna na wierzchu.
Skrajne etykiety mają kolory tła, które są nieprzezroczystymi wersjami kolorów tła
etykiet środkowych. Kolory napisów na etykietach wewnętrznych są jaśniejszymi,
a ich tła - ciemniejszymi wersjami kolorów dużych etykiet.
Napis określa wartość czynnika alpha.
import java.awt.*; import javax.swing.*; import java.util.*; class Colors extends JFrame { int xy = 5; Random rand = new Random(); Container cp = getContentPane(); Colors() { super("Colors"); setSize(220, 240); setLocation(200, 200); setDefaultCloseOperation(EXIT_ON_CLOSE); cp.setLayout(null); Color rIntClr = new Color(rand.nextInt(256), rand.nextInt(256), rand.nextInt(256), rand.nextInt(256) ); Color rFltClr = new Color(rand.nextFloat(), rand.nextFloat(), rand.nextFloat(), rand.nextFloat() ); int ri = rIntClr.getRed(); int gi = rIntClr.getGreen(); int bi = rIntClr.getBlue(); int rf = rFltClr.getRed(); int gf = rFltClr.getGreen(); int bf = rFltClr.getBlue(); Color opI = new Color(ri, gi, bi); Color opF = new Color(rf, gf, bf); addLab(opF); addLab(rIntClr); addLab(rFltClr); addLab(opI); setVisible(true); } void addLab(Color c) { JLabel inner = new JLabel("a=" + c.getAlpha()); inner.setOpaque(true); inner.setBackground(c.darker()); inner.setForeground(c.brighter()); inner.setBorder(BorderFactory.createEtchedBorder()); inner.setHorizontalAlignment(JLabel.CENTER); inner.setBounds(10, 50, 60, 20); JLabel label = new JLabel(); label.setOpaque(true); label.setBackground(c); label.setBounds(xy, xy, 80, 80); label.setBorder(BorderFactory.createEtchedBorder()); label.setLayout(null); label.add(inner); cp.add(label); xy += 40; } public static void main(String[] args){ new Colors(); } }
java.awt.Font
.
Tworzy się je konstruktorem
Font(String name, int style, int size)
, przy czym:
name
jest
style
jest stałą (z klasy Font
) określającą styl:
PLAIN
, BOLD
, ITALIC
,
bądź sumą bitową dwu ostatnich.
size
jest rozmiarem czcionki w punktach (1 pt = 1/72 cala).
Czcionki można modyfikować przeciążonymi metodami deriveFont()
klasy
Font
, które na podstawie tej czcionki tworzą nową, o atrybutach
określonych przez parametry konkretnej wersji metody. Np. poniższa metoda tworzy
wersję wytłuszczoną czcionki font
:
Font bold = font.deriveFont(Font.BOLD)
Aby umiejscowić napis w kontekście graficznym, potrzebna jest znajomość jego rozmiarów.
Klasa FontMetrics
zawiera informacje o sposobie wykreślania konkretnych
fontów. Odniesienie do obiektu tej klasy zwraca metoda getFontMetrics()
z klasy Graphics
.
'_'
.
Wszystkie rozmiary i położenia podaje się względem tej linii (jej lewego początku).
int getAscent()
- uniesienie ponad linię bazową: odległość od linii bazowej do najwyższego punktu napisuint getDescent()
- obniżenie poniżej linii bazowej: odległość od linii bazowej do najniższego punktu napisuint getLeading()
- standardowy odstęp między wierszami w tym foncieint getHeight()
- suma trzech powyższychint stringWidth(String s)
- całkowita długość linii bazowej napisu. Rectangle2D getStringBounds(String s, Graphics g)
- zwraca prostokąt
mieszczący napis w danym kontekście graficznym.
Klasa Graphics
dostarcza metody umożliwiające wykreślanie prostych obiektów graficznych.
Bardziej złożone obiekty komponuje się przy pomocy tych elementarnych metod.
Obiekty są wykreślane aktualnie obowiązującym kolorem, który można zmienić metodą setColor(Color)
.
drawLine(int x1, int y1, int x2, int y2)
rysuje odcinek o początku
[x1, y1]
i końcu [x2, y2]
.
drawPolyLine(int[] xPoints, int[] yPoints, int nPoints)
rysuje łamaną
linię o nPoints
wierzchołkach, których współrzędne są dane w tablicach.
drawPolygon(int[] xPoints, int[] yPoints, int nPoints)
podobnie jak
poprzednia - metoda rysuje łamaną linię - jednak dodaje odcinek łączący pierwszy
punkt z ostatnim tworząc wielokąt.
fillPolygon(int[] xPoints, int[] yPoints, int nPoints)
rysuje wielokąt
wypełniając jego wnętrze aktualnym kolorem.
drawRect(int x, int y, int w, int h)
rysuje brzeg prostokąta, którego
lewy-górny róg ma współrzędne [x, y]
, szerokość wynosi w
a wysokość h
.
fillRect(int x, int y, int w, int h)
wypełnia prostokąt obowiązującym kolorem.drawRoundRect(int x, int y, int w, int h, int arcW, int arcH)
rysuje
prostokąt o zaokrąglonych brzegach - parametry arcW, arcH
specyfikują
odpowiednio poziomy i pionowy wymiar średnicy łuku zaokrąglającego rogi.
fillRoundRect(int x, int y, int w, int h, int arcW, int arcH)
wypełnia zaokrąglony prostokąt.draw3DRect(int x, int y, int w, int h, boolean raised)
rysuje prostokąt o trójwymiarowym wyglądzie
- krawędzie są cieniowane sprawiając, że prostokąt wystaje ponad powierzchnię (gdy raised == true
) lub
jest wciśnięty do wewnątrz (raised == false
).fill3DRect(int x, int y, int w, int h, boolean raised)
wypełnia prostokąt 3D.clearRect(int x, int y, int w, int h)
wyczyszcza prostokątny obszar zaczepiony
w punkcie [x,y]
i o bokach [w,h]
. Puste miejsce zostanie wypełnione kolorem tła.drawOval(int x, int y, int w, in h)
rysuje elipsę wpisaną w prostokąt, którego lewy-górny róg
ma współrzędne [x, y]
, szerokość wynosi w
a wysokość h
. Oczywiście, jeśli są one równe to
powstanie okrąg.fillOval(int x, int y, int w, in h)
wypełnia obowiązującym kolorem elipsę.Metoda
drawArc(int x, int y, int w, int h, int startAngle, int arcAngle)rysuje eliptyczny łuk wpisany w prostokąt zaczepiony w punkcie
[x,y]
i o bokach [w, h]
.
Parametr startAngle
określa położenie kątowe początku łuku a arcAngle
określa jego
rozpiętość w stopniach.
Kąty są interpretowane w ten sposób, że 0o odpowiada godzinie 3 na zegarku i rośnie w kierunku
przeciwnym do ruchu wskazówek. Wartości ujemne odpowiadają godzinom następującym po 3.
Zatem startAngle == 0
oznacza, że łuk będzie zaczynał się na godz. 3, a startAngle == 90
-
na godz. 12. Podobnie arcAngle == 360
oznacza, że łuk będzie elipsą wpisaną w ten prostokąt,
a arcAngle == -180
tworzy jej dolną połowę.
Metoda fillArc(int x, int y, int w, int h, int startAngle, int arcAngle)
wypełnia łuk kolorem.
Oprócz koloru kontekst graficzny przechowuje również obowiązujący krój pisma.
Można go zmienić metodą setFont(Font f)
, pobrać metodą getFont()
.
Wykreślając napis podaje się jako współrzędne położenie jego linii bazowej.
drawString(String str, int x, int y)
wykreśla napis str
w punkcie [x,y]
.drawChars(char[] data, int off, int len, int x, int y)
wykreśla len
znaków z tablicy
data
począwszy od pozycji off
.
Następny program ilustruje niektóre metody wykreślające klasy Graphics
. Tworzymy własny panel
dziedzicząc klasę JPanel
. Wykreślanie odbywa się w metodzie paintComponent()
,
która jest wywoływana na zasadzie callback-u zawsze, gdy komponent wymaga odrysowania.
Przedefiniowana metoda getPreferredSize()
jest potrzebna do ustalenia rozmiarów naszego komponentu
i wywoływana przez zarządcę rozkładu. Należy pamiętać o wywołaniu super.paintComponent(g)
w pierwszej instrukcji metody odrysowującej.
import java.awt.*; import javax.swing.*; class JHelloWorld extends JPanel { public void paintComponent(Graphics g){ super.paintComponent(g); g.setColor(Color.blue); g.fillRect(0, 0, 200, 200); g.setColor(Color.yellow); g.fillOval(50, 50, 100, 100); g.setColor(Color.red); g.drawArc(75, 100, 50, 33, -20, -140); g.setColor(Color.black); g.fillOval(76, 83, 8, 10); g.fillOval(116, 83, 8, 10); g.setColor(Color.orange); g.fillPolygon(new int[]{100, 95, 105}, new int[]{95, 115, 115}, 3); g.setColor(Color.green); g.setFont(new Font("Serif", Font.BOLD, 30)); g.drawString("Hello World", 3, 199); } public Dimension getPreferredSize(){ return new Dimension(200, 200); } public static void main(String[] args){ JFrame frame = new JFrame("JHelloWorld"); JPanel world = new JHelloWorld(); frame.getContentPane().add(world); frame.setLocation(200, 200); frame.pack(); frame.show(); } }
Kolorem tła jest jest kolor tła ciężkiego komponentu, na którym odbywa się wykreślanie.
Zatem może on być zależny od systemu. Po wywołaniu metody clearRect()
,
która czyści prostokątny obszar ukaże się właśnie ten kolor jako tło - co ilustruje
następny przykład. Aby mieć pewność co do koloru tła, należy wypełnić żądanym kolorem
prostokątny obszar pokrywający cały pulpit, na którym odbywa się rysowanie.
W praktyce oznacza to wywołanie metod setColor()
oraz fillRect()
na rzecz odniesienia do obiektu klasy Graphics
pozyskanego od danego komponentu.
Kolorem tła głównego okna JFrame
jest green
(widoczny
poprzez wywołanie metody clearRect()
).
Jego contentPane
ma kolor tła cyan
(marginesy).
Własny panel wstawiony do contentPane
ma kolor tła blue
.
Umieszczona w nim etykieta ma ramkę, której kolory (są dwa) powstały na podstawie
koloru tła tej etykiety. Jest on zdeterminowany przez domyślny L&F i ma wrtość
javax.swing.plaf.ColorUIResource[r=204,g=204,b=204]
.
import java.awt.*; import javax.swing.*; class BackShow extends JFrame { BackShow() { super("BackShow"); setLocation(200, 200); setDefaultCloseOperation(EXIT_ON_CLOSE); Container cp = getContentPane(); cp.setLayout(new FlowLayout()); cp.add(new BackPane()); cp.setBackground(Color.cyan); setBackground(Color.green); pack(); show(); } public static void main(String[] args) { new BackShow(); } } class BackPane extends JPanel { BackPane() { setBackground(Color.blue); setLayout(null); JLabel lab = new JLabel("JLabel"); lab.setBorder(BorderFactory.createEtchedBorder()); lab.setBounds(90, 90, 100, 100); add(lab); } public void paintComponent(Graphics g) { super.paintComponent(g); g.setColor(Color.magenta); g.fillRect(50, 50, 100, 100); g.clearRect(10, 10, 100, 100); } public Dimension getPreferredSize() { return new Dimension(200, 200); } }
setPaintMode()
- przywraca normalny sposób kolorowania (tryb domyślny)
setXORMode(Color xorClr)
- po wywołaniu tej metody kolory wykreślanych
obiektów będą się zmieniać w następujący sposób:
xorClr
xorClr
nie wprowadza żadnych zmian.xorClr
daje normalny efekt.Następny programik ilustruje tryb XOR. Pierwsze dwa kwadraty (licząc od lewego-górnego rogu) są wykreślane w trybie XOR z kolorem tła (niebieski). Pozostałe w trybie XOR z kolorem zielonym. Ponadto pierwsze cztery są rysowane kolorem białym, piąty zielonym, a pozostałe dwa niebieskim (!). Każdy z siedmiu kwadratów ma ramkę w kolorze tła, dla lepszej widoczności.
import java.awt.*; class PaintModes extends Canvas { int xy; int wh = 50; Color bgc = Color.blue; Color fgc = Color.white; Color xor = Color.green; PaintModes() { setBackground(bgc); setForeground(fgc); } public void paint(Graphics g) { super.paint(g); xy = 0; g.setXORMode(bgc); fRect(g, fgc); fRect(g, fgc); g.setXORMode(xor); fRect(g, fgc); fRect(g, fgc); fRect(g, xor); fRect(g, bgc); fRect(g, bgc); } void fRect(Graphics graph, Color c) { graph.setColor(bgc); graph.drawRect(xy, xy, wh, wh); graph.setColor(c); graph.fillRect(xy, xy, wh, wh); xy += (wh/2); } public Dimension getPreferredSize() { return new Dimension(200, 200); } public static void main(String[] args) { Frame frame = new Frame("PaintModes"); Canvas modes = new PaintModes(); frame.add(modes); frame.setLocation(300, 300); frame.pack(); frame.setVisible(true); } }
Klasa java.awt.Graphics2D
- będąc podklasą klasy Graphics
- dodaje do niej nowe możliwości. Aby się nimi posługiwać należy rzutować odniesienie
do wykreślacza typu Graphics
, na Graphics2D
.
Jest to zawsze wykonalne, ponieważ klasa wykreślacza jest bezpośrednią podklasą
klasy Graphics2D
.
Jest to podstawowa klasa do wykreślania 2-wymiarowych obiektów.
Graphics2D
posiada następujące atrybuty,
które determinują wynik wykreślania:
Graphics
Graphics
setRenderingHint(RenderingHints.Key key, Object value)
(np. włączyć antyaliasing, czyli wygładzanie czcionek lub grafiki -
o ile dana platforma to obsługuje).
Ponadto możliwe są następujące operacje wykreślające:
Klasa Graphics
dysponowała dwoma trybami nakładania kolorów: normalny
i XOR. W klasie Graphics2D
metoda setComposite(Composite comp)
pozwala na sterowanie nakładaniem się kolorów w znacznie szerszym zakresie.
Interfejs Composite
określa sposób w jaki będzie nałożony aktualny
kolor na już wykreślone piksele. Klasa AlphaComposite
implementuje ten
interfejs, dostarczając 12 stałych określających sposoby przenikania się kolorów.
getComposite()
można pobrać aktualny sposób komponowania
kolorów, co może być potrzebne do przywrócenia poprzedniego trybu.
Obiektów klasy AlphaComposite
nie tworzy się konstruktorem (bo go nie ma),
lecz używa się statycznej metody getInstance()
, która zwraca obiekt
tej klasy. Przekazuje się jej jako argumenty: alpha
- współczynnik
przenikania i (opcjonalnie) rule
- stałą określającą regułę, np.:
AlphaComposite.SRC_OVER
.
Można również użyć statycznych składowych tej klasy przechowujących odniesienia do
gotowych obiektów reprezentujących nieprzezroczyste wersje (alpha==1.0
)
każdego trybu np.: AlphaComposite.SrcOver
.
Na obrazku widzimy efekty jakie dają wszystkie 12 reguł nakładania się kolorów
dla stałej przenikania 1.0 i dla stałej przenikania 0.4. Napisy pod obrazkami są takie
jak nazwy stałych z klasy AlphaComposite
(wyjąwszy wartość przenikania).
Obrazek jest wynikiem działania poniższego programu.
Poszczególne jego fragmenty są tworzone najpierw w buforze, a następnie wykreślane w kontekście graficznym komponentu po to, by uniknąć niepożądanego wpływu koloru tła. Dysponujemy dzięki temu czystą kartką - wykreślaczem bufora.
import java.awt.*; import javax.swing.*; import java.awt.image.*; import java.awt.geom.*; class PaintModes2D extends JPanel { int rule; float alpha; String rName; PaintModes2D(String desc, int acr, float al){ setBorder(BorderFactory.createLineBorder(Color.cyan)); setBackground(Color.white); rName = desc + " " + al; rule = acr; alpha = al; } public void paintComponent(Graphics g){ super.paintComponent(g); Graphics2D g2d = (Graphics2D)g; int w = getWidth(); int h = getHeight(); BufferedImage buffImg = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); Graphics2D gbi = buffImg.createGraphics(); gbi.setColor(Color.red); gbi.setFont(new Font("Sans", Font.BOLD, 10)); gbi.drawString(rName, (w-6*rName.length())/2, getHeight()-5); gbi.setColor(Color.blue); gbi.fillRect(8, 8, 2*w/3, h/2); gbi.setComposite(AlphaComposite.getInstance(rule, alpha)); gbi.setColor(Color.green); gbi.fillOval(w/4, h/4, 2*w/3, h/2); g2d.drawImage(buffImg, null, 0, 0); } public Dimension getPreferredSize(){ return new Dimension(100, 80); } static void addMode(Container cp, String title, int mode){ cp.add(new PaintModes2D(title, mode, 1f)); cp.add(new PaintModes2D(title, mode, 0.4f)); } static void addModes(Container cp){ addMode(cp, "CLEAR", AlphaComposite.CLEAR); addMode(cp, "XOR", AlphaComposite.XOR); addMode(cp, "SRC", AlphaComposite.SRC); addMode(cp, "SRC_IN", AlphaComposite.SRC_IN); addMode(cp, "SRC_OUT", AlphaComposite.SRC_OUT); addMode(cp, "SRC_OVER", AlphaComposite.SRC_OVER); addMode(cp, "SRC_ATOP", AlphaComposite.SRC_ATOP); addMode(cp, "DST", AlphaComposite.DST); addMode(cp, "DST_IN", AlphaComposite.DST_IN); addMode(cp, "DST_OUT", AlphaComposite.DST_OUT); addMode(cp, "DST_OVER", AlphaComposite.DST_OVER); addMode(cp, "DST_ATOP", AlphaComposite.DST_ATOP); } public static void main(String[] args){ JFrame frame = new JFrame("PaintModes2D"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setLocation(200, 200); Container cp = frame.getContentPane(); cp.setLayout(new GridLayout(12, 2)); addModes(cp); frame.pack(); frame.show(); } }
Metoda addMode()
dodaje pojedynczy podobrazek dla danej reguły nakładania
kolorów i dwóch stałych przenikania: 1 i 0.4. Jest ona wywoływana w metodzie
addModes()
dla wszystkich stałych z klasy AlphaComposite
.
Metoda paintComponent()
jest odpowiedzialna za wykreślanie poszczególnych
komórek. Wywołuje metodę setComposite()
na rzecz wykreślacza (klasy
Graphics2D
), która ustala nowy tryb nakładania.
Kształty są obiektami klas implementujących interfejs java.awt.Shape
.
Można je obrysowywać metodą draw(Shape s)
lub wypełniać metodą
fill(Shape s)
- obie z klasy Graphics2D
.
Pakiet java.awt.geom
dostarcza kilku predefiniowanych kształtów:
Ellipse2D
Rectangle2D
, RoundRectangle2D
Line2D
QuadCurve2D
CubicCurve2D
Większość klas tego pakietu, reprezentujących obiekty graficzne, ma trzy warianty:
float
)double
)java.awt.Rectangle
, będąca podklasą Rectangle2D
, reprezentuje prostokąt o współrzędnych
całkowitoliczbowych (int
).
Klasa java.awt.geom.GeneralPath
jest kształtem złożonym z kilku krzywych
(stopni 1, 2, lub 3). Taką ścieżkę tworzy się konstruktorem GeneralPath()
,
a następnie buduje - dodając do niej kolejne krzywe, punkty lub inne kontury -
następującymi metodami:
moveTo(float x, float y)
- dodaje do ścieżki punkt o współrzędnych [x,y]
.lineTo(float x, float y)
- dodaje odcinek łączący ostatni punkt ścieżki z [x,y]
.quadTo(float cx, float cy, float x, float y)
- łączy ostatni punkt ścieżki z punktem [x,y]
krzywą drugiego stopnia, z punktem kontrolnym [cx, cy]
.
curveTo(float c1x, float c1y, float c2x, float c2y, float x, float y)
- łączy ostatni punkt na
ścieżce z punktem [x,y]
krzywą trzeciego stopnia o punktach kontrolnych:
[c1x,c1y]
i [c2x,c2y]
.
append(Shape s, boolean con)
- dodaje do konturu kształt s
,
i jeśli con
ma wartość true
, to zostanie on połączony
odcinkiem z ostatnim punktem na dotychczasowej ścieżce.
closePath()
- dodaje odcinek łączący ostatni punkt ścieżki z punktem określonym w ostatnim
wywołaniu moveTo()
.setWindingRule()
):
int WIND_EVEN_ODD
- poruszając się po prostej z zewnątrz w kierunku konturu, po
pierwszym przecięciu jego brzegu wchodzimy do jego wnętrza, po drugim - wychodzimy. Ogólnie: po nieparzystej
liczbie przecięć punkt na prostej znajduje się wewnątrz obszaru, po parzystej - na zewnątrz.
int WIND_NON_ZERO
- punkt jest na zewnątrz (wewnątrz) obszaru, jeśli półprosta o
początku w tym punkcie, przecina brzeg obszaru w kierunku od lewej do prawej taką samą (różną)
liczbę razy jak od prawej do lewej.
Kierunek obchodzenia konturu (jego orientacja) jest określony przez właściwy dla niego
obiekt klasy PathIterator
. Intuicyjnie - kierunek ten jest określony przez przejście od początku do końca,
kształty zamknięte zgodnie z ruchem wskazówek zegara, jednak dla bardziej złożonych konturów może to nie
być jasne.
WIND_NON_ZERO
.
Klasa Area
definiuje obszary i operacje algebraiczne, jakie można na nich wykonywać: dodawanie,
odejmowanie i przecinanie obszarów. Obiekt tej klasy tworzy się konstruktorem bezargumentowym
Area()
lub podając początkowy kształt: Area(Shape s)
, którego wnętrze będzie tworzyć ten obszar.
Bardziej złożone obszary uzyskuje się wykonując następujące operacje:
add(Area a)
- dodaje nowy obszar.subtract(Area a)
- z obszaru this
usuwa wszystkie punkty obszaru a
.intersect(Area a)
- pozostawia punkty należące jednocześnie do this
i a
.exclusiveOr(Area a)
- dodaje obszar a
, a następnie usuwa część wspólną
this
(pierwotnego) i a
.
W kolejnym programie metoda octopus()
pokazuje jak tworzyć złożone kształty. Jej pierwszy i drugi
argument określa położenie figury, a trzeci wielkość. Warto zauważyć, że elipsy dodawane jako oczy, leżą
we wnętrzu tworzonych obszarów - ale można to zmienić podając do konstruktora GeneralPath
argument GeneralPath.WIND_EVEN_ODD
.
Zastosowanie antyaliasingu do wykreślania zielonych kształtów wygładza ich kontury.
import java.awt.*; import javax.swing.*; import java.awt.geom.*; class Shapes extends JPanel { public void paintComponent(Graphics g){ super.paintComponent(g); Graphics2D g2d = (Graphics2D)g; Shape ob = octopus(60, 100, 11); g2d.setColor(Color.blue); g2d.fill(ob); g2d.setColor(Color.yellow); g2d.draw(ob); g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); Area af = new Area(octopus(130, 130, 11)); af.subtract(new Area(new Ellipse2D.Float(135, 145, 22, 11))); af.subtract(new Area(new Ellipse2D.Float(102, 145, 22, 11))); g2d.setColor(Color.green.darker().darker()); g2d.fill(af); Shape so = octopus(110, 20, 14); Area ao = new Area(so); ao.add(new Area(octopus(80, 70, 5))); ao.add(new Area(octopus(140, 70, 5))); g2d.draw(so); g2d.setColor(new Color(180, 220, 60, 150)); g2d.fill(ao); g2d.setColor(Color.black); g2d.draw(ao); } Shape octopus(int x, int y, int s){ GeneralPath gp = new GeneralPath(); gp.append(new Ellipse2D.Float(x+s, y+s, s, 2*s), false); gp.append(new Ellipse2D.Float(x-2*s, y+s, s, 2*s), false); gp.moveTo(x, y); gp.lineTo(x+s, y); gp.quadTo(x+5*s, y, x+5*s, y+5*s); gp.curveTo(x+s, y+2*s, x+3*s, y+4*s, x, y+5*s); gp.curveTo(x-3*s, y+4*s, x-s, y+2*s, x-5*s, y+5*s); gp.quadTo(x-5*s, y, x-s, y); gp.closePath(); return gp; } public Dimension getPreferredSize(){ return new Dimension(200, 200); } public static void main(String[] args){ JFrame frame = new JFrame("Shapes"); JPanel shape = new Shapes(); frame.getContentPane().add(shape); frame.setLocation(300, 300); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.pack(); frame.show(); } }
Do graficznej reprezenatacji napisów służy klasa java.awt.font.TextLayout
.
Obiekt tej klasy jest odpowiedzialny za zwymiarowanie napisu w aktualnym kontekście.
Podstawowy konstruktor
TextLayout(String s, Font f, FontRenderContext frc)
pobiera jako trzeci argument frc
odniesienie do obiektu obrazowania
czcionki. Można go uzyskać metodą z klasy Graphics2D
o nazwie
getFontRenderContext()
.
Wykreślanie napisu przy pomocy klasy TextLayout
może być wykonane na dwa sposoby:
draw(Graphics2D g, float x, float y)
- gdzie g
to kontekst graficzny a x, y
są współrzędnymi początku linii bazowej napisu.Shape getOutline(AffineTransform af)
(o przekształceniach afinicznych dalej), przekazując jako
argument null
.
W obu przypadkach może być potrzebna metoda Rectangle2D getBounds()
-
zwracająca prostokąt, w którym mieści się napis.
Przykładowy program wyświetla dwoma sposobami napisy, używając
klasy AlphaComposite
do ustalenia sposobu nakładania kolorów.
Wykreślanie w buforze jest potrzebne dla uniknięcia niepożądanej interakcji z innymi,
już wykreślonymi, kolorami kontekstu graficznego.
Użyta metoda translate(int x, int y)
z klasy Graphics
przemieszcza współrzędne wykreślacza tak, że jego początek ([0,0]
)
będzie w miejscu określonym przez argumenty [x,y]
.
Należy zwrócić uwagę na sposób pozycjonowania tekstu - różny w obu przypadkach.
import java.awt.*; import javax.swing.*; import java.awt.font.*; import java.awt.geom.*; import java.awt.image.*; class Text2D extends JPanel { public void paintComponent(Graphics g){ super.paintComponent(g); Graphics2D g2d = (Graphics2D)g; int h = getHeight(); int w = getWidth(); FontRenderContext frc = g2d.getFontRenderContext(); // napisy wykreslamy w buforze BufferedImage bimg = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); Graphics2D gbi = bimg.createGraphics(); // wykreslenie napisu "2D" Font fb = new Font("Lucida Bright", Font.BOLD, getHeight()); TextLayout tlb = new TextLayout("2D", fb, frc); // dla ustalenia polozenia napisu Rectangle2D rb = tlb.getBounds(); gbi.setColor(Color.cyan); tlb.draw(gbi, (float)(w-rb.getWidth()-rb.getX())/2, h/2+(float)rb.getHeight()/2); // wykreslenie napisu "Java" Font ff = new Font("Serif", Font.BOLD|Font.ITALIC, (int)(0.6*h)); TextLayout tlf = new TextLayout("Java", ff, frc); // dla ustalenia polozenia napisu Rectangle2D rf = tlf.getBounds(); Shape st = tlf.getOutline(null); int yPos = (int)((h-rf.getHeight())/2); int xPos = (int)((w-rf.getWidth())/2); // przemieszczenie układu współrzędnych gbi.translate(xPos-rf.getX(), yPos-rf.getY()); // ustalenie trybu nakladania kolorow w buforze gbi.setComposite(AlphaComposite.SrcOut); gbi.setColor(Color.blue); gbi.fill(st); gbi.setComposite(AlphaComposite.Src); gbi.setColor(Color.yellow); gbi.draw(st); g2d.setColor(new Color(217, 127, 255)); g2d.fillRect(0, 0, w, h); g2d.drawImage(bimg, null, 0, 0); } public Dimension getPreferredSize(){ return new Dimension(300, 200); } public static void main(String[] args){ JFrame frame = new JFrame("Text2D"); JPanel textd = new Text2D(); frame.getContentPane().add(textd); frame.setLocation(300, 300); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.pack(); frame.show(); } }
Shape
zawiera następujące metody (typu boolean
)
umożliwiające rozstrzyganie wzajemnego położenia danego obszaru względem innych:
TextLayout
udostępnia w tym celu kilka metod, zwracających
obiekt klasy TextHitInfo
.
Do pobrania prostokąta ograniczającego dany obiekt służy metoda getBounds()
.
Jest ona zdefiniowana w klasie TextLayout
, jak również w interfejsie
Shape
.
contains(Point2D p)
,
contains(double x, double y)
contains(Rectangle2D r)
,
contains(double x, double y, double w, double h)
intersects(Rectangle2D r)
,
intersects(double x, double y, double w, double h)
W klasie Graphics2d
jest metoda
hit(Rectangle r, Shape s, boolean stroke)
, która sprawdza czy
prostokąt r
przecina kontur s
.
Jeśli stroke
ma wartość false
, to jest brane
pod uwagę wnętrze kształtu s
, w przeciwnym wypadku sprawdzane jest
przecięcie jego zarysu
(wykonanego przy pomocy obowiązującego pióra) z prostokoątem r
.
W przeciwieństwie do klasy Graphics
, w Graphics2D
można
wykreślać linie o dowolnej grubości.
Przez środek pogrubionej linii przechodzi linia środkowa, której współrzędne podaje
się przy wykreślaniu.
Ponadto, oprócz linii prostych i łamanych można wykreślać krzywe określonych typów.
Stroke
.
Klasa BasicStroke
implementuje ten interfejs i dostarcza dodatkowe
metody określające grubość, kształty zagięcia, zakończenia lub przerywanie linii.
Konstruktor BasicStroke()
konstruuje pióro domyślne,
a BasicStroke(float width)
- pióro o podanej szerokości.
Pozostałe konstruktory zostaną opisane dalej.
Pióro domyślne wykreśla ciągły ślad o szerokości jednego piksela.
Metoda setStroke(Stroke s)
(w klasie Graphics2D
) ustala
nowy kształt pióra, który jest zazwyczaj obiektem klasy BasicStroke
,
natomiast metoda Stroke getStroke()
dostarcza aktualne pióro.
Linie mogą mieć trzy rodzaje zakończeń określanych stałymi klasy
BasicStroke
:
static int CAP_BUTT
- ściętestatic int CAP_ROUND
- półkolistestatic int CAP_SQUARE
- kwadratowe (domyślna)
static int JOIN_BEVEL
- spłaszczonestatic int JOIN_ROUND
- zaokrąglonestatic int JOIN_MITER
- zaostrzone (domyślne)
Konstruktor BasicStroke(float width, int cap, int join)
pozwala
ustalić sposób zakończenia i łączenia linii inny niż domyślne.
Jeśli linie są nachylone do siebie pod małym kątem a łączenie jest zaostrzone -
to może okazać się zbyt długie. Można je ograniczyć dodatkowym argumentem konstruktora,
który określi maksymalną długość zaostrzenia:
BasicStroke(float width, int cap, int join, float miterLimit)
.
Jeśli łączenie przekracza miterLimit
, to zostaje zastąpione połączeniem
spłaszczonym.
Wykreślana linia nie musi być ciągła - może być przerywana. Parametry określające sposób przerywania i moment jego rozpoczęcia podaje się w tablicy jako argument konstruktora:
BasicStroke(float w, int cap, int join, float mLim, float[] dash, float phase).Parametr
float[] dash
jest tablicą zawierającą długości kresek i
odstępów. Przerywanie linii nastąpi od miejsca określonego przez parametr
phase
(jesli phase == 0
, to od razu).
Program będzie rysował linie różnymi piórami, o różnych zakończeniach i sposobach łączenia. Każda z nich jest rysowana dwoma piórami: cienkie pokazuje linię środkową grubego.Należy zwrócić uwagę na podobieństwa i różnice pomiędzy zakończeniem ściętym (kolor magenta) i kwadratowym (kolor zielony). Linia przerywana jest wykreślana od lewego-górnego końca z przesunięciem 10 pikseli i ma zaostrzone łączenie.
import java.awt.*; import javax.swing.*; class LinesDemo extends JPanel { public void paintComponent(Graphics g){ super.paintComponent(g); Graphics2D g2d = (Graphics2D)g; drawZ(g2d, Color.green, 50, 50, 15, new BasicStroke(20f, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 10 )); drawZ(g2d, Color.blue, 90, 100, 40, new BasicStroke(5f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER, 5f, new float[]{10f, 15f}, 10f )); drawZ(g2d, Color.cyan, 60, 25, 105, new BasicStroke(30f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND )); drawZ(g2d, Color.magenta, 30, 140, 150, new BasicStroke(10f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL )); } // rysuje duza litere 'Z' void drawZ(Graphics2D g2d, Color c, int st, int sx, int sy, BasicStroke stroke){ int[] xv = new int[]{sx, sx+st, sx, sx+st}; int[] yv = new int[]{sy, sy, sy+st, sy+st}; g2d.setColor(c); g2d.setStroke(stroke); g2d.drawPolyline(xv, yv, 4); g2d.setColor(c.darker()); g2d.setStroke(new BasicStroke()); g2d.drawPolyline(xv, yv, 4); } public Dimension getPreferredSize(){ return new Dimension(200, 200); } public static void main(String[] args){ JFrame frame = new JFrame("LinesDemo"); JPanel lines = new LinesDemo(); frame.getContentPane().add(lines); frame.setLocation(200, 200); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.pack(); frame.show(); } }
Do malowania i wypełniania służą pędzle - obiekty klas implementujących interfejs
Paint
. Trzy takie klasy dostarczone przez Java API znajdują się w
pakiecie java.awt
. Odpowiadają one trzem sposobom wypełniania obszarów:
Color
,GradientPaint
,TexturePaint
.setPaint(Paint p)
, a jego aktualną
wartość można pobrać metodą: getPaint()
.
Malowanie gradientem polega na podaniu dwóch punktów P1,P2
i ich
kolorów C1,C2
. Kolory punktów leżących na odcinku [P1,P2]
będą zmieniać się od C1
do C2
.
Punkty prostej wyznaczonej przez ten odcinek mają kolory:
P1
mają kolor C1
, a punkty leżące za P2
mają kolor
C2
[P1,P2]
.
GradientPaint
są cztery konstruktory:
GradientPaint(float x1, float y1, Color c1, float x2, float y2, Color c2) GradientPaint(float x1, float y1, Color c1, float x2, float y2, Color c2, boolean ac) GradientPaint(Point p1, Color c1, Point p2, Color c2) GradientPaint(Point p1, Color c1, Point p2, Color c2, boolean ac)Pierwszy i trzeci konstruują domyślny gradient acykliczny, drugi i czwarty pozwalają ustalić acykliczność.
Malowanie teksturą polega na powielaniu obrazu graficznego we wszystkich kierunkach. Konstruktor pędzla teksturowego
TexturePaint(BufferedImage img, Rectangle2D rec)pobiera jako argumenty powielany obraz (który powinien być możliwie mały) i punkt jego zaczepienia w kontekście graficznym. Obraz może być tworzony dynamicznie poprzez rysowanie na wykreślaczu pozyskanym od obiektu
img
, ale może też być ikoną wczytaną z pliku (a następnie
odrysowaną na tym wykreślaczu). Argument rec
określa obszar wewnątrz
kontekstu graficznego (tego, którego dotyczy zmiana pędzla).
Zostanie na nim odrysowany obraz dostarczony przez argument img
,
a następnie powielony na tym wykreślaczu we wszystkich kierunkach.
Położenie prostokąta rec
wpływa na przesunięcie tekstury w
kontekście graficznym, natomiast jego rozmiary określają rozmiar wynikowy pojedynczego
obrazu i w związku z tym umożliwiają - jeśli są różne od rozmiarów tego obrazu -
jego skalowanie.
Poniższy programik ilustruje sposób użycia gradientów (cykliczny i acykliczny), oraz tekstur utworzonych z pliku, bądź dynamicznie, poprzez rysowanie na wykreślaczu.
import java.awt.*; import javax.swing.*; import java.awt.image.*; import java.awt.geom.*; class PaintShow extends JPanel { ImageIcon imic; Image img; PaintShow(String icName){ imic = new ImageIcon(icName); img = imic.getImage(); } public void paintComponent(Graphics g){ super.paintComponent(g); Graphics2D g2d = (Graphics2D)g; int w = getWidth(); int h = getHeight(); Paint paint; paint = new GradientPaint(w/7, h/7, Color.red, w/3, h/3, Color.green); g2d.setPaint(paint); g2d.fillRect(0, 0, w/2, h/2); paint = new GradientPaint(w*2/3, h*2/3, Color.yellow, w*3/4, h*3/4, Color.blue, true); g2d.setPaint(paint); g2d.fillRect(w/2, h/2, w/2, h/2); BufferedImage bimg; Graphics imgr; Rectangle rect; int iw = 15; int ih = 15; bimg = new BufferedImage(iw, ih, BufferedImage.TYPE_INT_RGB); imgr = bimg.createGraphics(); imgr.setColor(Color.magenta); imgr.fillRect(0, 0, iw-1, ih-1); imgr.setColor(Color.cyan); imgr.fillOval(0, 0, iw-2, ih-2); rect = new Rectangle(iw/2, h/2+ih/2, iw, ih); paint = new TexturePaint(bimg, rect); g2d.setPaint(paint); g2d.fillRect(0, h/2, w/2, h/2); iw = imic.getIconWidth(); ih = imic.getIconHeight(); bimg = (BufferedImage)this.createImage(iw, ih); imgr = bimg.getGraphics(); imgr.drawImage(img, 0, 0, null); rect = new Rectangle(w/2, 0, iw/2, ih/2); // skalowanie paint = new TexturePaint(bimg, rect); g2d.setPaint(paint); g2d.fillRect(w/2, 0, w/2, h/2); } public Dimension getPreferredSize(){ return new Dimension(200, 200); } public static void main(String[] args){ JFrame frame = new JFrame("PaintShow"); JPanel paint = new PaintShow(args[0]); frame.getContentPane().add(paint); frame.setLocation(300, 300); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.pack(); frame.show(); } }
Jako argument wywołania trzeba przekazać nazwę pliku z ikonką.
Każdy obiekt graficzny może zostać poddany afinicznemu przekształceniu, które jest
obiektem klasy java.awt.geom.AffineTransform
.
Obiekty przekształcenia można konstruować podając jako argument konstruktora jego macierz.
Jednak w większości przypadków wygodniej jest wykorzystać konstruktor bezparametrowy AffineTransform()
i składać przekształcenie wywołując metody będące przekształceniami elementarnymi:
void translate(double x, double y)
- przesunięcie o wektor [x, y]
void scale(double x, double y)
- rozciąganie o współczynnikach x
i y
,
dla ujemnych argumentów jest złożone z symetrią
void rotate(double t, double x, double y)
- obrót o kąt t
względem punktu [x,y]
void shear(double x, double y)
- przekształcenie nożycowe -
złożenie obrotu z rozciąganiem
Innym sposobem jest wykorzystanie statycznych metod getXxxInstance(...)
,
(gdzie Xxx
jest nazwą jednego z powyższych przekształceń) które zwracają
gotowy obiekt .
Każdy kontekst graficzny ma obowiązujące przekształcenie afiniczne, które jest aplikowane do jego
układu współrzędnych przed wykreślaniem. Domyślnie jest to identyczność. Można je zmienić metodą
setTransform(AffineTransform af)
(ale potem koniecznie trzeba przywrócić oryginalne, uzyskane
metodą getTransform()
), albo składać z przekształceń elementarnych metodami
rotate()
, scale()
, shear()
, translate()
- odpowiednikami metod z klasy
AffineTransform
. Drugi sposób jest preferowany, ponieważ podmiana obowiązującego przekształcenia
może wprowadzić w błąd zarządców rozkładów, którzy z niego korzystają.
Napisy można poddawać przekształceniom bezpośrednio - nie korzystając z przekształcania kontekstu graficznego.
Umożliwia to metoda Font deriveFont(AffineTransform af)
z klasy Font
, jak i wspomniana wcześniej
metoda Shape getOutline(AffineTransform af)
z klasy TextLayout
. Pierwsza dostarcza przekształconą
wersję czcionki, druga - napisu.
Kolejny obrazek wraz z programem ilustrują przekształcanie afiniczne kontekstu
graficznego. Najpierw jest on przesuwany, aby punkt [0,0]
znalazł się
na środku.Następnie zostaje odbity względem osi poziomej (przy pomocy skalowania o
współczynniku -1), aby współrzędne pionowe rosły w górę. Potem wykreślany jest
układ współrzędnych, koło i kwadrat, które mają pokazywać jak działają kolejne przekształcenia.
import java.awt.*; import javax.swing.*; import java.awt.font.*; import java.awt.geom.*; class Affine extends JPanel { public void paintComponent(Graphics g){ super.paintComponent(g); Graphics2D g2d = (Graphics2D)g; g2d.translate(100, 100); g2d.scale(1, -1); drawAxes(g, new Color(10, 10, 200)); g2d.shear(0.25, 0.25); drawAxes(g, new Color(10, 200, 10)); g2d.rotate(Math.PI/3); drawAxes(g, new Color(200, 10, 10)); } void drawAxes(Graphics g, Color c){ g.setColor(c); g.drawLine(-100, 0, 100, 0); g.drawLine(0, 100, 0, -100); g.setColor(c.brighter()); g.drawRect(20, 20, 50, 50); g.setColor(c.darker()); g.fillOval(-70, 20, 50, 50); } public Dimension getPreferredSize(){ return new Dimension(200, 200); } public static void main(String[] args){ JFrame frame = new JFrame("AffineTransform"); JPanel affine = new Affine(); frame.getContentPane().add(affine); frame.setLocation(300, 300); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.pack(); frame.show(); } }
Oprócz rysowania własnych obrazków istnieje możliwość wykreślania obrazów zawartych
w plikach graficznych typu GIF, JPEG lub PNG.
Obrazy są obiektami klasy java.awt.Image
, a właściwie jej podklas -
ponieważ jest to klasa abstrakcyjna.
java.awt.Toolkit
:
Image createImage(String fileName)
- tworzy nowy obrazek z pliku fileName
Image createImage(URL url)
- tworzy obrazek z pliku umiejscowionego w url
getImage(String fileName)
, getImage(URL url)
- buforowane warianty powyższych metod.
Aktualny przybornik (obiekt klasy Toolkit
) przechowuje obrazki tworzone tymi metodami w pamięci
podręcznej aby przyspieszyć kolejne wywołania tych metod z identycznym argumentami (fileName
lub url
).
Uzyskanie w ten sposób odniesienia nie gwarantuje, że obrazek jest od razu w całości załadowany do pamięci operacyjnej i gotowy do wyświetlenia. Jeśli trzeba - ładowanie zostanie automatycznie dokończone w osobnym wątku (systemowym).
Odniesienie do właściwego obiektu klasy Toolkit
można uzyskać jej statyczną metodą:
Toolkit.getDefaultToolkit()
, albo metodą getToolkit()
z klasy Component
- a więc dostępną w każdym komponencie.
javax.swing.ImageIcon
konstruktorem:
ImageIcon(String fileName)
- podając nazwę pliku lub,ImageIcon(URL url)
- podając jego lokalizacjęImage getImage()
.
Po utworzeniu obiektu ImageIcon
obrazek będzie znajdował się w
pamięci operacyjnej. Jeśli ładowanie obrazu trwa powoli, to wywołanie tego
konstruktora może zuważalnie wstrzymać wykonanie wątku.
W takich przypadkach lepiej użyć metod klasy Toolkit
.
getImage()
zwróci wartość różną od null
.
getImage(URL url)
z klasy
java.applet.Applet
(nie działa ona do momentu uzyskania przez aplet
pełnego kontekstu, zatem nie można jej wywoływać
w konstruktorze apletu, ani też w inicjatorach jego składowych).
drawImage()
z klasy Graphics
,
które wykreślają załadowany fragment obrazka w całości lub podaną jego część,
ewentualnie skalując go.
drawImage()
z klasy Graphics2D
,
które do metody odziedziczonych z Graphics
dodają możliwość
przekształcenia afinicznego obrazka oraz poddania go filtrowaniu.
paintIcon(Component c, Graphics g, int x, int y)
z klasy
ImageIcon
- wykreśla ona obrazek zaczepiony w punkcie
[x,y]
kontekstu graficznego g
.
Metody tej nie należy wywoływać, ponieważ robi to VM poprzez callback.
Można ją natomiast przedefiniować dostarczając w ten sposób ikonę wykreślaną przez kod javy.
Wszystkie metody drawImage()
wymagają podania jako jednego z argumentów
odniesienia do obiektu typu ImageObserver
, który będzie doglądał procesu
ładowania obrazu. W praktyce podaje się this
odnoszące się do komponentu,
na którym odbywa się wykreślanie. Podobną rolę gra pierwszy argument
w paintIcon()
.
Jeśli szybkość ładowania obrazka jest mała w stosunku do jego rozmiarów, to należy wykorzystać obserwator ładowania. Pozwoli on zaczekać (wstrzymać wykonanie wątku) do momentu, gdy obrazek będzie załadowany w odpowiedniej części lub w całości. Dodatkowo można uzyskiwać informacje o postępach ładowania.
ImageObserver
Interfejs java.awt.image.ImageObserver
zawiera metodę
imageUpdate()
, która jest wywoływana (poprzez wywołania zwrotne), gdy
pojawi się jakaś nowa informacja dotycząca obrazka - np. załadowano kolejny fragment.
Obiekty tego typu przekazuje się przeważnie jako argumenty metody
drawImage()
w celu asynchronicznego wykreślenia pozostałej części obrazka,
niedostępnej w chwili jej wywołania. Klasa Component
implementuje ten
interfejs, więc każdy komponent jest obserwatorem ładowania.
Zaimplementowana metoda imageUpdate()
powoduje odświeżenie komponentu
(repaint()
), gdy tylko kolejna porcja obrazka zostanie dostarczona -
skutkuje to dorysowaniem kolejnej jego części.
Metoda boolean imageUpdate(Image img, int flags, int x, int y, int w, int h)
ma zwracać true
, jeśli kolejne doładowania będą potrzebne i
false
w przeciwnym przypadku - gdy uzyskano potrzebną informację.
Argument img
jest odniesieniem do obserwowanego obrazka,
a flags
dostarcza informacji o postępie ładowania, które można
odczytać używając stałych interfejsu ImageObserver
.
Interpretacja pozostałych argumentów zależy od aktualnego stanu określonego
przez flags
.
MediaTracker
Inny - prostszy - sposób oczekiwania na ładowanie udostępnia klasa
java.awt.MediaTracker
. Umożliwia ona jednoczesne śledzenie postępu
ładowania wielu obrazków. Obiekt tej klasy tworzy się konstruktorem
MediaTracker(Component c)
- podając jako argument komponent,
na którym zostaną wyświetlone obrazki. Po uzyskaniu obiektu dodaje się kolejne
obrazki metodą addImage(Image img, int id)
każdemu przypisując
identyfikator id
definiujący ich grupę.
Metodą waitForAll()
rozpoczyna się ładowanie wszystkich obrazków,
a po ich załadowaniu następuje wyjście z metody. Ładowanie grupy (być może
jednoelementowej) następuje po wywołaniu waitForId(int id)
.
Można również sprawdzać stan ładowania metodami checkAll()
,
checkId()
, statusAll()
, statusId()
.
Klasa ImageIcon
wykorzystuje obiekt klasy MediaTracker
do śledzenia postępu ładowania obrazów - ikon będących obiektami tej klasy.
W kilku przypadkach może być użyteczne umieszczenie obrazu w buforze -
obiekcie klasy BufferedImage
, która zresztą jest jedyną znaną podklasą
abstrakcyjnej klasy Image
: