Manipulação e desenho em componentes com Java2D
Discente: Victor Williams Stafusa da Silva (1)
Orientador: João Paulo Delgado Preti (2)
(1) Especialista, Departamento de Área de Informática (DAI), Centro Federal de Educação Tecnológica – CEFET/MT, Cuiabá – MT,
[email protected]
(2) Mestre, Professor, Departamento de Área de Informática (DAI), Centro Federal de Educação Tecnológica – CEFET/MT,
[email protected]
RESUMO: Embora consolidada e de fundamental
importância, a forma utilizada pelo AWT no Java para
desenhar os componentes visuais é ainda pouco
conhecida por alguns programadores, o que implica em
sérios problemas quando estes se deparam com esta
área. Tendo em vista isto, este artigo desmistifica o
mecanismo de renderização de componentes do AWT e
explica as técnicas utilizadas para que sejam feitos os
desenhos de figuras geométricas, textos e bitmaps no
Java2D, sendo empregado ainda os efeitos de
antialiasing, degradês, texturas e AffineTransforms.
PALAVRAS-CHAVE: AWT. EDT. Java2D.
1. INTRODUÇÃO
O desenho e a manipulação de imagens no
Java2D é uma interessante e rica área, porém ainda
pouco conhecida entre grande parte dos
programadores na linguagem Java. Grande parte
do desconhecimento e dos erros cometidos neste
meio vem do fato de que o processo de
renderização de interfaces gráficas do AWT é
pouco conhecido e muito vulnerável a erros de
codificação. Por isto, neste presente artigo, tal
processo é explicado com o objetivo de trazer luz a
esta questão.
Para efetuar o desenho de gráficos em Java,
faz-se necessário o uso das classes Graphics e
Graphics2D que estão no centro do processo de
desenho do Java2D. São explanadas ainda as
técnicas utilizadas na renderização de textos,
imagens e figuras geométricas diversas e também
de efeitos visuais tais como antialiasing, degradês
e texturas, além de AffineTransforms, utilizados
para realizar transformações em imagens.
2. FUNDAMENTAÇÃO TEÓRICA
A
principal
referência
utilizada
no
desenvolvimento deste presente artigo é a
documentação oficial do Java [01], que detalha
todas as classes e os métodos existentes no Java 6
SE e é tipicamente a principal e a primeira fonte de
informações utilizadas no desenvolvimento em na
plataforma Java.
O processo de funcionamento da EDT - Event
Dispatch Thread e as formas de interagir com ela
são detalhados em [02], [03], [04] e [05]. A forma
como a EDT desenha os componentes na tela é
explicada em [06].
As técnicas utilizadas para desenhar-se figuras
geométricas, bordas, textos e texturas pelo Java2D
são explicadas em [07], [08], [09] e [10].
Técnicas utilizadas para leitura, escrita e
manipulação de imagens no Java2D são explicadas
em [08], [09], [10], [11] e [12].
O uso da classe AffineTransform, que é usada
pelo Java2D para realizar transformações em
imagens é detalhada por [07].
3. MATERIAIS E MÉTODOS
O presente artigo foi desenvolvido e embasado
a partir de pesquisa bibliográfica realizada na
internet em sites de conteúdo confiável e na
documentação oficial do Java 6 SE [01].
Durante a pesquisa, os códigos-fontes foram
sendo desenvolvidos, compilados e testados de
acordo com o demonstrado na bibliografia. Este
processo também auxiliou no processo de
classificação e filtragem da bibliografia.
Utilizou-se a distribuição padrão do JDK 1.6.0
SE da Sun na escrita dos códigos e na compilação.
4. DESENVOLVIMENTO
4.1 – CONHECENDO E INTERAGINDO COM A EDT
Em aplicações AWT/Swing, é criado pelo
AWT uma thread denominada EDT - Event
Dispatch Thread. Esta thread é responsável por
renderizar os componentes e também por gerenciar
seus eventos (tais como ActionEvent, MouseEvent,
KeyEvent, entre outros). Os eventos são colocados
em uma fila e processados um a um em modo
FIFO [02]. O nome da EDT (tal como retornado
pelo método getName da classe Thread e visível
em stacktraces), normalmente é “AWTEventQueue-0”.
De acordo com [03] e [04], na criação de
aplicações AWT/Swing, existem quatro regras que
não podem ser quebradas em relação a EDT, e
violá-las pode resultar em degradação do
desempenho da aplicação, deadlocks, condições de
corrida entre a EDT e outras threads, artefatos
visuais no desenho da tela (tais como áreas não
desenhadas ou sujas), travamentos e “engasgos” na
aplicação e consumo desnecessário de CPU, dentre
outros possíveis problemas. Estas regras são:
1. Apenas a EDT pode manipular
diretamente o estado visual (UI) de componentes.
2. Não executar operações demoradas ou
sujeitas a esperas e bloqueios na EDT.
3. Não alterar o estado visual de um
componente enquanto este é desenhado.
4. Evitar ao máximo adquirir bloqueios de
sincronização (locks) na EDT.
Seguir estas regras com a EDT não é fácil, e
para ajudar nisso, a classe EventQueue do pacote
java.awt, que constitui a fila de eventos do AWT,
contém os métodos invokeLater e invokeAndWait,
ambos com um parâmetro formal Runnable [04] e
[05]. O método invokeLater insere um Runnable
na fila de eventos do AWT para ser executado pela
EDT. O método invokeAndWait é semelhante,
porém ele espera a EDT terminar de executar o
Runnable antes de continuar.
Colocando o código que deve ser executado
dentro da EDT em um Runnable de invokeLater e
invokeAndWait, fica muito mais fácil seguir as
regras para trabalhar-se com a EDT. Por exemplo,
o seguinte código opera componentes visuais fora
da EDT, quebrando então a primeira regra da EDT:
public void metodo() {
// Operações fora da EDT.
componente.setVisible(true);
// Mais operações fora da EDT.
}
Para corrigi-lo, pode-se deixá-lo assim:
public void metodo() {
// Operações fora da EDT.
EventQueue.invokeLater(new Runnable() {
public void run() {
componente.setVisible(true);
}
});
// Mais operações fora da EDT.
}
O uso de invokeLater é preferível ao invés de
invokeAndWait porque o invokeLater não faz a
thread invocadora esperar, o que contribui com o
desempenho do sistema e evita deadlocks. Mas por
outro lado, invokeAndWait garante que a execução
só prosseguirá quando as operações a serem
efetuadas estiverem completas. É importante
ressaltar que invokeAndWait não pode ser
invocado de dentro da EDT, pois isso faria a EDT
esperar por si mesma.
Na classe EventQueue também existe o
método isEventDispatchThread que é útil para
determinar se a thread corrente é a EDT e com isso
é possível criar códigos que se comportem de um
jeito se estiverem na EDT ou de outro em caso
contrário.
É importante notar-se que a classe
javax.swing.SwingUtilities também tem os
métodos
invokeLater,
invokeAndWait
e
isEventDispatchThread. Tais métodos apenas
invocam os correspondentes em EventQueue.
4.2 – O MECANISMO DE DESENHO DO AWT E DO
SWING
De acordo com [06], a renderização de cada
componente visual é feita pela EDT no método
paint de cada componente. Tal método é definido
na classe java.awt.Component, que é superclasse
de todos os componentes AWT e Swing. O método
paint tem como parâmetro formal um objeto
java.awt.Graphics, que é onde o desenho deve ser
renderizado. Este objeto Graphics é invalidado
após o término do método paint e por isso ele não
deve ser guardado para uso futuro.
O método paint é invocado pelo AWT sempre
que um componente necessita ser redesenhado,
sendo isso feito na EDT. É desaconselhável
invocar o método paint diretamente. Para se forçar
o redesenho de um componente, é recomendado
invocar-se um dos métodos sobrecarregados
repaint.
Em componentes Swing (subclasses de
javax.swing.JComponent), o método paint invoca
três outros métodos: paintComponent, paintBorder
e paintChildren, nesta ordem (todos com o
parâmetro Graphics). O método paintComponent
desenha o conteúdo do componente, o método
paintBorder desenha a borda do componente e o
método paintChildren os subcomponentes.
Normalmente, em componentes Swing, não
deve ser sobrescrito o método paint, e sim o
método paintComponent, pois isso evita o
problema de não serem desenhados os
subcomponentes e a borda. A implementação de
paintComponent deve, antes tudo, invocar a
implementação da superclasse, pois ela redesenha
o fundo e os detalhes da UI. Não invocar a
implementação de paintComponent da superclasse
pode causar o surgimento de artefatos visuais no
desenho.
Existem outros métodos relacionados ao
desenho dos componentes, tais como update,
paintImmediately, paintAll e getGraphics. Porém
estes métodos quase nunca devem ser invocados
diretamente, sendo usados internamente pelo
método repaint ou por outros métodos de desenho.
4.3 – DESENHANDO FORMAS GEOMÉTRICAS E
TEXTOS COM GRAPHICS
Os métodos paint e paintComponent têm como
parâmetro formal um objeto da classe
java.awt.Graphics, porém o parâmetro real passado
pelo AWT sempre é um objeto da classe
java.awt.Graphics2D, que é uma subclasse de
Graphics. Graphics2D proporciona um conjunto
maior de métodos, e para se utilizá-los, basta fazer
um cast do parâmetro Graphics em um Graphics2D
[07] e [08].
Dentre os métodos de Graphics e Graphics2D,
há vários usados para renderizar figuras
geométricas primitivas, em especial os métodos
com os prefixos “draw” e “fill”.
É importante ser notado que o método
drawRect, que desenha um retângulo, tem quatro
parâmetros, que são, respectivamente as
coordenadas X e Y do canto superior esquerdo do
retângulo, a largura e a altura. Porém o método
análogo fillRect tem um significado um pouco
diferente para a largura e a altura, que em fillRect é
interpretada como tendo um pixel a menos. Esta
diferença também existe entre os métodos
drawOval e fillOval e drawRoundRect e
fillRoundRect [01].
Todas as operações de desenhos geométricos
usam a cor ou a textura corrente do objeto
Graphics. No caso de cores, esta é definida através
do método setColor e pode ser obtida pelo método
getColor e no caso de texturas com os métodos
setPaint e getPaint. Desta forma para se desenhar
um retângulo preenchido verde, por exemplo, usase primeiro o método setColor para se definir a cor
para verde e em seguida o método fillRect, que
desenhará o retângulo.
De acordo com [07], [08] e [09], um outro
meio de se renderizar formas geométricas é por
meio de classes que implementam a interface
java.awt.Shape. Estas classes definem formas
geométricas que fornecem meios de se obter
precisão de subpixels e oferecem a possibilidade
de se escrever classes personalizadas que
constituam formas geométricas específicas.
Para se utilizar um Shape, é necessário
primeiro instanciar a implementação desejada (que
é a figura geométrica) e passá-lo como parâmetro
para os métodos draw e/ou fill da classe
Graphics2D [01]. O método draw desenha a borda
do Shape e o método fill o interior (neste caso os
dois métodos geram uma figura com formato
idêntico). A classe Exemplo (a seguir) demonstra
um exemplo disso e o resultado obtido é mostrado
na figura 1.
import
import
import
import
import
import
import
import
java.awt.*;
java.awt.geom.*;
java.awt.image.*;
java.io.*;
javax.swing.*;
javax.imageio.ImageIO;
static java.awt.RenderingHints.*;
static java.awt.BasicStroke.*;
import static
javax.swing.WindowConstants.*;
import static
java.awt.MultipleGradientPaint.
CycleMethod.*;
public class Exemplo extends JFrame {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
new Exemplo();
}
});
}
private static final RenderingHints
hints = new RenderingHints(null);
static {
hints.put(KEY_ALPHA_INTERPOLATION,
VALUE_ALPHA_INTERPOLATION_QUALITY);
hints.put(KEY_ANTIALIASING,
VALUE_ANTIALIAS_ON);
hints.put(KEY_COLOR_RENDERING,
VALUE_COLOR_RENDER_QUALITY);
hints.put(KEY_INTERPOLATION,
VALUE_INTERPOLATION_BICUBIC);
hints.put(KEY_STROKE_CONTROL,
VALUE_STROKE_NORMALIZE);
}
private JPanel panel = new JPanel() {
public void
paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
//g2.setRenderingHints(hints); //***
g2.setColor(Color.GREEN);
g2.fillRect(15, 20, 40, 40);
Ellipse2D e = new Ellipse2D.Double(
30, 100, 20, 15);
g2.fill(e);
g2.setColor(Color.RED);
g2.drawOval(45, 25, 40, 30);
g2.draw(e);
g2.setColor(Color.BLUE);
Rectangle2D rect =
new Rectangle2D.Double(
20, 20, 100, 100);
g2.draw(rect);
g2.drawRect(23, 23, 40, 40);
g2.drawLine(10, 10, 70, 50);
g2.setColor(Color.MAGENTA);
Shape curvaQuad =
new QuadCurve2D.Double(
80, 20, 95, 50, 120, 30);
Shape curvaCub =
new CubicCurve2D.Double(80, 70,
95, 90, 110, 60, 125, 90);
Shape linha = Line2D.Double(
80, 10, 115, 30);
g2.draw(linha);
g2.draw(curvaQuad);
g2.draw(curvaCub);
g2.drawString("Hello World",
50, 100);
g2.setFont(new Font(
"Times New Roman",
Font.BOLD | Font.ITALIC, 14));
g2.drawString("Hello World",
50, 120);
adequados. As opções do RenderingHints
representam escolhas entre desempenho e
qualidade, ou efeitos que podem ser ativados ou
não. As chaves válidas de RenderingHints são os
campos estáticos com o prefixo “KEY” e os
valores são os campos estáticos com o prefixo
“VALUE”. Para cada chave há um conjunto de
valores válidos.
Voltando à classe Exemplo, o RenderingHints
é mantido em uma variável estática e preenchido
em um bloco de inicialização da classe, de forma a
estar pronto para ser usado no programa.
}
};
public Exemplo() {
final int LARG = 300, ALT = 300;
this.setSize(LARG, ALT);
this.setTitle("Exemplo");
this.setVisible(true);
Insets ins = this.getInsets();
this.setSize(
LARG + ins.left + ins.right,
ALT + ins.top + ins.bottom);
this.add(panel, BorderLayout.CENTER);
this.setDefaultCloseOperation(
DISPOSE_ON_CLOSE);
}
}
A figura 1 mostra a tela gerada pela classe
Exemplo. Nesta classe são instanciados alguns
tipos de Shapes: Rectangle2D, Ellipse2D,
QuadCurve2D, CubicCurve2D e Line2D. Estas são
classes abstratas do pacote java.awt.geom e cada
uma delas têm duas classes aninhadas concretas
Double e Float que herdam da classe externa e
definem a precisão requerida no cálculo [01].
Vários outros Shapes também estão no pacote
java.awt.geom.
O método drawString é responsável por
desenhar textos. Outros métodos como drawChars
também podem ser usados. O método setFont
define a fonte a ser usada e getFont obtém ela [01],
[08] e [09].
A figura 2, semelhante à figura 1, mostra uma
tela muito parecida, porém com o efeito de
antialiasing. A tela da figura 2 é obtida ao
descomentar-se a linha marcada com “***” na
classe Exemplo. O efeito de antialiasing é feito
por meio da classe RenderingHints do pacote
java.awt, que é um Map contendo opções de
renderização. RenderingHints tem um conjunto de
campos estáticos que são usados como chaves e
outro que são os valores [01], [07], [08] e [09].
Para se utilizar um RenderingHints, basta
instanciá-lo e acrescentar os pares chave/valor
Figura 1
Figura 2
4.4 – BORDAS
Até o momento todas as figuras desenhadas
têm sempre o mesmo tipo de borda, contínua e de
1 pixel de largura.
Para se definir outros tipos de borda, é
utilizada a interface Stroke [01], [07], [08] e [09].
A única implementação presente no Java 6 SE é a
classe BasicStroke. Tanto Stroke quanto
BasicStroke estão no pacote java.awt. Para utilizar,
analisemos este exemplo:
Shape a1 = new Arc2D.Double(
150.0, 150.0, 70.0, 70.0, 0.0, 125.0,
Arc2D.CHORD);
Shape a2 = new Arc2D.Double(
205.0, 150.0, 70.0, 70.0, 0.0, 125.0,
Arc2D.PIE);
Shape a3 = new Arc2D.Double(
260.0, 150.0, 70.0, 70.0, 0.0, 125.0,
Arc2D.OPEN);
float[] dx = {11f, 11f, 7f, 7f};
Stroke s0 = g2.getStroke();
Stroke s1 = new BasicStroke(6,
CAP_BUTT, JOIN_BEVEL);
Stroke s2 = new BasicStroke(6,
CAP_ROUND, JOIN_ROUND);
Stroke s3 = new BasicStroke(6,
CAP_SQUARE, JOIN_MITER);
Stroke s4 = new BasicStroke(6,
CAP_BUTT, JOIN_BEVEL, 10f, dx, 3f);
Stroke s5 = new BasicStroke(6,
CAP_ROUND, JOIN_ROUND, 10f, dx, 3f);
Stroke s6 = new BasicStroke(6,
CAP_SQUARE, JOIN_MITER, 10f, dx, 3f);
g2.setColor(Color.BLUE);
g2.setStroke(s1); // ###
g2.draw(a1); g2.draw(a2); g2.draw(a3);
g2.setColor(Color.RED);
g2.setStroke(s0);
g2.draw(a1); g2.draw(a2); g2.draw(a3);
Neste código são instanciados seis objetos
Stroke e três Shapes da classe Arc2D.Double. No
construtor de cada BasicStroke, o primeiro
parâmetro especifica a largura da borda em pixels,
o segundo o formato das pontas e o terceiro o
formato dos ângulos. As constantes com o prefixo
“CAP” e “JOIN” são definidas na classe
BasicStroke.
CAP_BUTT
JOIN_BEVEL
CAP_ROUND
JOIN_ROUND
CAP_SQUARE
JOIN_MITER
padrão do tracejado, (um traço de 11 pixels, uma
lacuna de 11 pixels, um traço de 7 pixels e uma
lacuna de 7 pixels). O sexto parâmetro indica a
fase inicial do padrão de tracejado e o quarto
parâmetro serve para indicar o tamanho máximo
em pixels das pontas quando JOIN_MITER é
usado, de forma que quando ângulos muito agudos
são formados, as pontas não fiquem muito longas
[01].
4.5 – MANIPULAÇÃO DE TEXTURAS
Um importante recurso que existe no Java2D
para o desenho, é a utilização de Paints, que
permitem o emprego de degradês no lugar de cores
simples [07]. Por exemplo, observemos estas
linhas de código:
Ellipse2D sh1 =
new Ellipse2D.Double(80, 55, 20, 50);
g2.setPaint(new GradientPaint(
45, 20, Color.YELLOW,
48, 24, Color.BLUE, true));
g2.fill(sh1);
CAP_BUTT
JOIN_BEVEL
Point2D p1 = new Point2D.Float(0, 0);
Point2D p2 = new Point2D.Float(50, 38);
float[] d = {0.0f, 0.4f, 0.6f, 1f};
CAP_ROUND
JOIN_ROUND
Color[] c = {Color.RED, Color.GREEN,
Color.YELLOW, Color.RED};
CAP_SQUARE
JOIN_MITER
Figura 3
As constantes “CAP” são relativas ao formato
da extremidade das linhas, onde CAP_BUTT
significa que a extremidade é reta, CAP_ROUND
que a extremidade é redonda e CAP_SQUARE que
a extremidade é quadrada. As constantes “JOIN”
se referem ao formato dos ângulos. JOIN_BEVEL
especifica que a parte externa do ângulo será
cortada em linha reta, JOIN_MITER especifica
que a parte externa do ângulo não será cortada e
JOIN_ROUND especifica que a parte externa do
ângulo será arredondada. Ao se experimentar o
código com os seis tipos de Strokes instanciados (e
alterando-se a linha marcada com “###” de
acordo), o resultado é o da figura 3.
Os últimos 3 desenhos da figura 3 são
resultados dos últimos três Strokes, que como se
vê, são tracejados. Este efeito de tracejado é obtido
ao se invocar o construtor adequado de
BasicStroke, no caso com 6 parâmetros. Os três
primeiros parâmetros são os mesmos já explanados
anteriormente. O quinto é um array contendo o
g2.setPaint(new LinearGradientPaint(
p1, p2, d, c, REPEAT));
g2.fill(new Ellipse2D.Double(
10, 125, 80, 50));
Point2D p3 = new Point2D.Float(30, 190);
Point2D p4 = new Point2D.Float(35, 201);
g2.setPaint(new RadialGradientPaint(
p3, 25f, p4, d, c, REFLECT));
g2.fill(new Rectangle2D.Double(
10, 180, 40, 50));
g2.fill(new Rectangle2D.Double(
55, 180, 40, 50));
Estas linhas criam figuras com texturas de
degradês. Acrescentando-as ao final do método
paintComponent na classe Exemplo, o resultado
obtido será o exibido na figura 4.
No código que gerou o resultado da figura 4, o
objeto GradientPaint gera o degradê azul e amarelo
da elipse da direita na figura 4. O objeto
LinearGradientPaint gera o degradê da elipse
verde, amarela e vermelha e o objeto
RadialGradientPaint gera o degradê dos dois
retângulos. Todas estas classes Paints estão no
pacote java.awt [01].
GradientPaint representa um gradiente de duas
cores e recebe nos seus construtores as duas cores
em questão e dois pontos de referência. Os pontos
de referência definem a inclinação da textura e a
distância das linhas de mesma cor. Os construtores
também podem receber um parâmetro indicando se
a textura é cíclica (ou seja, se fica se repetindo)
[01].
LinearGradientPaint é uma generalização de
GradientPaint que cria um degradê entre várias
cores diferentes. Para tal, os construtores de
LinearGradientPaint recebem um array com as
cores, um array com as distâncias entre as cores e
os dois pontos de referência (tal como em
GradientPaint). As distâncias entre as cores estão
em uma escala de 0 a 1, onde 0 é o primeiro ponto
de referência, 1 é o segundo ponto e valores
intermediários são posições intermediárias, que
assim denotam diferentes intensidades nos
degradês entre uma cor e outra (valores de
distâncias mais próximos formam degradês mais
bruscos do que os mais afastados) [01].
RadialGradientPaint cria uma textura radial e
seu construtor tem parâmetros semelhantes à
LinearGradientPaint. Porém, os pontos de
referência de RadialGradientPaint têm significado
diferente e se referem ao centro da textura e ao
foco (que pode ser omitido, caso em que se torna
igual ao centro). Também é recebido o raio da
textura [01].
Figura 5
Figura 4
Figura 6
Os pontos de referência nas texturas são
globais, ou seja, não dependem de onde serão
aplicados. Desta forma, os dois retângulos da
figura 4, que embora tenham a mesma textura e o
mesmo tamanho, têm desenhos bastante diferentes
pelo fato de mostrarem partes diferentes da mesma
textura [01].
Ambas as classes LinearGradientPaint e
RadialGradientPaint têm como parâmetro formal
no construtor
um
objeto CycleMethod.
CycleMethod é um enum interno da classe
MultipleGradientPaint do pacote java.awt. Tal
enum tem três valores: NO_CYCLE que indica
que a textura não se repete; REPEAT que indica
que a textura se repete sempre da primeira cor à
última e; REFLECT que indica que a textura se
repete alternadamente ora da primeira cor à última
e ora da última à primeira de trás para frente [01].
4.6 – MANIPULAÇÃO DE IMAGENS
Imagens em Java são representadas pela classe
Image do pacote java.awt.image. Image é uma
classe abstrata, mas BufferedImage, que é
subclasse de Image, é a implementação mais usada
no Java2D [08], [09] e [10].
É possível ler-se uma imagem a partir de um
arquivo por meio de um dos métodos read da
classe javax.imageio.ImageIO. Por exemplo, o
código abaixo faz isso [01] e [11]:
BufferedImage figura = ImageIO.read(
new File("figura.bmp"));
Neste código a imagem “figura.bmp” é lida e o
objeto figura é a imagem retornada. Se a figura não
for encontrada um java.io.IOException será
lançado.
De modo semelhante, o método write de
ImageIO é usado para salvar figuras no disco:
ImageIO.write(figura, "bmp",
new File("figura.bmp"));
Neste código a imagem “figura” é escrita no
disco em formato BMP.
Para desenhar as figuras em Graphics, basta se
utilizar um dos métodos drawImage:
g2.drawImage(figura, 145, 10, null);
g2.drawImage(figura, 165, 15, null);
g2.drawImage(figura, 185, 20, null);
A imagem resultante é mostrada na figura 5.
Note que ela é opaca, ou seja, não contém espaços
por onde seja possível ver o que há por trás dela.
Isso ocorre porque o formato BMP padrão não tem
pixels transparentes. Para se resolver isso, é
possível se utilizar outro formato tal como GIF ou
PNG que permite utilizar-se pixels transparentes,
ou então definir uma cor específica para ser os
pixels transparentes, o que pode ser feito com o
uso de um método auxiliar [12]:
public static BufferedImage
maskImg(BufferedImage im, int mask) {
BufferedImage novo =
new BufferedImage(im.getWidth(),
im.getHeight(),
BufferedImage.TYPE_INT_ARGB);
for (int x = 0; x < im.getWidth(); x++) {
for (int y = 0;
y < im.getHeight(); y++) {
int pixel = im.getRGB(x, y);
if ((pixel & 0x00ffffff) == mask)
pixel = 0x00ffffff;
novo.setRGB(x, y, pixel);
}
}
return novo;
}
Este método (maskImg) cria um novo
BufferedImage com base no que foi recebido no
parâmetro. São usados os métodos getWidth e
getHeight de BufferedImage para se obter o
tamanho da imagem. O construtor de
BufferedImage também tem um parâmetro que diz
que tipo de imagem será construída. No caso,
TYPE_INT_ARGB significa que os pixels são
representados por ints com 8 bits para o alpha, 8
para o vermelho, 8 para o verde e 8 para o azul,
nesta ordem. O valor alpha indica um valor de
transparência, onde 0 é transparente, 255 é opaco e
valores intermediários são translucentes. Após
criada a nova imagem, os pixels da imagem
original são iterados e lidos com o método getRGB
e então escritos na imagem nova com o método
setRGB (estes métodos permitem manipular a
imagem pixel a pixel). Para se utilizar o método
maskImg, basta acrescentar esta linha antes dos
métodos drawImage:
figura = maskImg(figura, 0x0000ffff);
Esta linha invoca o método maskImg,
passando como parâmetros a figura e a cor a ser
substituída por transparente, no caso o azul ciano
(00 alpha, 00 vermelho, FF verde e FF azul). O
resultado é exibido na figura 6.
É possível criar-se uma textura com desenho
de imagens [07]. Para isso basta ser utilizada a
classe TexturePaint do pacote java.awt. Esta classe
tem um único construtor que recebe dois
parâmetros, o primeiro é a imagem e o segundo é
um retângulo que define as dimensões da textura.
Por exemplo, acrescentando essas linhas ao
programa Exemplo, obtemos o resultado da figura
7:
g2.setPaint(new TexturePaint(figura,
new Rectangle(0, 0, 15, 25)));
g2.fill(new Ellipse2D.Double(
30, 125, 80, 70));
Observe que a figura 7 é semelhante a figura 4,
porém foi desenhada por cima mais uma elipse
utilizando a textura feita com a figura. O fato de
ser possível ver os objetos por trás desta nova
elipse mostra que ela é uma textura com áreas
transparentes.
Figura 7
Figura 8
Por fim, um método muito útil de
BufferedImage é o método getGraphics, que
possibilita
desenhar-se
diretamente
no
BufferedImage ao se desenhar no Graphics dele.
Este método é vantajoso por poder ser usado
mesmo em ambientes sem gráficos e não necessitar
ser executado na EDT.
4.7 – AFFINETRANSFORMS
Conforme [07], a classe AffineTransform do
pacote java.awt.geom é uma classe que representa
uma matriz de transformação linear e possui
operações de cisalhamento (shear), translação
(translate), rotação (rotate) e escala (scale). Os
métodos
estáticos
getRotateInstance,
getScaleInstance,
getTranslateInstance
e
getShearInstance da classe AffineTransform
podem ser usados para obter-se instâncias e o
método concatenate pode ser usado para combinar
AffineTransforms.
Para aplicar um AffineTransform a um Shape,
é
possível
se
utilizar
o
método
createTransformedShape, que retorna um novo
Shape
correspondente
ao
Shape
antigo
transformado.
O objeto Graphics tem associado a ele um
AffineTransform que define o espaço vetorial
sobre o qual os gráficos são renderizados. Esse
AffineTransform é acessível por meio dos métodos
getTransform e setTransform. Por exemplo, se for
inserido o seguinte trecho antes do código que gera
a figura 4:
AffineTransform af1 = g2.getTransform();
g2.setTransform(AffineTransform.
getShearInstance(.4, .1));
E depois do código este outro trecho:
g2.setTransform(af1);
O resultado obtido é o da figura 8. Nestes
trechos, o AffineTransform é sobrescrito com um
novo AffineTransform de cisalhamento, sendo o
original é salvo em uma variável e restaurado no
final para que os desenhos subseqüentes não sejam
afetados.
5. RESULTADOS E DISCUSSÕES
A análise pessoal que o autor faz sobre o
Java2D é que possui muitos pontos positivos,
sendo e este pode ser usado para diversas
finalidades na programação Java, tais como a
criação de jogos e animações, criação de captchas,
mapas e outras figuras complexas e também para a
personalização de componentes Swing com efeitos
tais como degradês e marcas d’água.
Há também alguns pequenos pontos negativos
do Java2D: A classe RenderingHints por exemplo,
poderia ser implementada de uma forma bem mais
simples e intuitiva se ao invés de chaves e valores
de um Map, fossem utilizadas propriedades
acessíveis por métodos getters e setters, tal como
qualquer classe JavaBean normal.
Outros pontos negativos que podem ser citados
é a diferença de um pixel na largura e na altura dos
métodos com prefixo draw e fill da classe Graphics
e principalmente a complexidade inerente ao
multithreading que a manipulação da EDT impõe
em lugares onde normalmente não deveria haver
preocupação em relação a threads (embora isso não
seja uma característica do Java2D, e sim do AWT).
Pessoalmente, decidi escrever sobre Java2D
por que quis fazer um trabalho diferente e único,
tendo em vista que há poucas fontes de pesquisa
relativas a tal assunto disponíveis atualmente. A
realização desta pesquisa foi muito enriquecedora e
gratificante.
6. CONCLUSÃO
Explicado o funcionamento da EDT e
analisado o mecanismo de renderização dos
componentes, visa-se que com isso possam ser
compreendidas de melhor forma o métodos
utilizado pelo AWT para a renderização de
componentes.
Foram abordadas as técnicas utilizadas para
desenharem-se figuras geométricas e textos no
objeto Graphics do Java, sendo aplicadas técnicas
de antialiasing para melhorar a qualidade da
imagem. Foram vistos os métodos utilizados pelo
Java para se carregarem imagens com áreas
transparentes e se criarem texturas com imagens e
degradês, além de bordas especiais e a utilização
de transformações lineares no objeto Graphics por
meio da classe AffineTransform.
7. REFERÊNCIAS BIBLIOGRÁFICAS
[01] SUN MICROSYSTEMS. JavaTM 2 Platform,
Standard Edition, 6 API Specification.
Disponível em
<http://java.sun.com/javase/6/docs/api/index.h
tml>. Acesso em 08 ago. 2007
[02] ZUKOWSKI, John. Swing Threading and the
Event-Dispatch Thread. Disponível em
<http://www.javaworld.com/javaworld/jw-082007/jw-08-swingthreading.html>. Acesso em
26 set. 2007
[03] GROUCHNIKOV, Kirill. Debugging Swing.
Disponível em
<http://today.java.net/pub/a/today/2007/08/30/
debugging-swing.html>. Acesso em 26 set.
2007
[04] SUN MICROSYSTEMS. Swing’s Thread
Policy. Disponível em
<http://java.sun.com/javase/6/docs/api/javax/s
wing/package-summary.html#threading>.
Acesso em 08 ago. 2007
[05] MULLER, Hans;.WALRATH, Kathy.
Threads and Swing. Disponível em
<http://java.sun.com/products/jfc/tsc/articles/t
hreads/threads1.html>. Acesso em 11 set.
2007
[06] FOWLER, Amy. Painting in AWT and
Swing. Disponível em
<http://java.sun.com/products/jfc/tsc/articles/p
ainting/index.html>. Acesso em 22 set. 2007
[07] ECKSTEIN, Robert. Learning Java2D, Part
1. Disponível em
<http://java.sun.com/developer/technicalArticl
es/GUI/java2d/java2dpart1.html>. Acesso em
08 ago. 2007
[08] SUN MICROSYSTEMS. 2D Graphics.
Disponível em
<http://java.sun.com/docs/books/tutorial/2d/in
dex.html>. Acesso em 08 ago. 2007
[09] SUN MICROSYSTEMS. Programmer's
Guide to the JavaTM 2D API. Disponível em
<http://java.sun.com/javase/6/docs/technotes/
guides/2d/spec/j2d-bookTOC.html>. Acesso
em 08 ago. 2007
[10] ECKSTEIN, Robert. Learning Java2D, Part
2. Disponível em
<http://java.sun.com/developer/technicalArticl
es/GUI/java2d/java2dpart2.html>. Acesso em
08 ago. 2007
[11] SUN MICROSYSTEMS. JavaTM Image I/O
API Guide. Disponível em
<http://java.sun.com/javase/6/docs/technotes/
guides/imageio/spec/title.fm.html>. Acesso
em 08 ago. 2007
[12] RAFKIND, Jon (kazzmir). Re: masked blit
equivalent in Java. Disponível em <
http://www.allegro.cc/forums/thread/592551>
. Acesso em 08 ago. 2007