Pintando no Java 2D

Propaganda
Pintando no Java 2D
Escrito por Vinícius Godoy de Mendonça - Última atualização Sex, 31 de Dezembro de 2010 03:34
Usando o Java 2D, podemos desenhar e pintar diretamente, via código. A API fornece uma
série de formas básicas, chamadas primitivas e, que servem como tijolos na construção de
formas mais completas. Além disso, ele ainda fornece algumas classes para o desenho livre,
através de traços e curvas, bem como opções de borda e preenchimento. Nesse artigo, vamos
explicar em maiores detalhes esse tipo de desenho.
Desenhando primitivas
O Java 2D fornece uma série de classes, que permitem o desenho das seguintes primitivas:
pontos, linhas, curvas, elipses e retângulos. Todas elas fazem parte do pacote java.awt.geom .
Todas essas classes tem como ancestral comum a interface
Shape
, que representa uma forma geométrica qualquer. Qualquer classe que implemente a interface
Shape, é capaz de:
- Dizer qual é o seu bounding box: Ou seja, fornecer uma área retangular onde a forma
está exatamente inscrita;
- Dizer se a forma contém um determinado ponto: e, nesse caso, formas abertas são
consideradas fechadas, pontos nas bordas são considerados dentro da forma;
- Dizer se a forma intercepta um determinado retângulo;
- Fornecer um objeto do tipo PathIterator : Que descreve como desenhar a forma,
linha-a-linha, curva-a-curva;
O objeto Graphics2D é responsável por efetivamente fazer o desenho. Desenhamos usando os
métodos draw(Shape) , que desenha somente as bordas, ou fill(Shape) , para o desenho da
forma pintada. Além de usar as classes descritas acima, ele tamb
Technorati Marcas:
ém fornece métodos para o desenho direto dessas formas, como drawRect , fillRect , drawLi
ne
.
As classes criadas pelas Sun também vêm com dois tipos de precisão, Float e Double. Para
não encher a API de classes de nome similar como FloatPoint2D e DoublePoint2D, a Sun usou
uma técnica um tanto exótica. Definiu a classe principal Point2D como abstrata, e criou duas
classes internas estáticas públicas, chamadas Float e Double. Portanto, para a criação de uma
primitiva, usamos a seguinte construção:
1 / 10
Pintando no Java 2D
Escrito por Vinícius Godoy de Mendonça - Última atualização Sex, 31 de Dezembro de 2010 03:34
//Precisão de float (32 bits) Point2D pontoFloat = new Point2D.Float(10,10);
doubles (64 bits) Point2D pontoDouble = new Point2D.Double(10,10);
//Precisão de
Para jogos, geralmente não é necessário usar uma precisão maior que a de um float.
Pontos (Point2D)
A classe Point2D representa um ponto (x,y), no espaço coordenado. Além das versões
Point2D.Float e Point2D.Double, o Java ainda fornece a classe Point, para a precisão de
inteiros. O construtor padrão de todas as classes, criam um ponto na origem, a coordenada
(0,0). É possível alterar a coordenada de um ponto através do método setLocation. Esse
método aceita tanto novos valores de x e y, quanto um ponto como parâmetro, cujas
coordenadas serão copiadas. Através da classe Point2D também é possível calcular a
distância entre dois pontos.
Note que como Point2D não é descrito através de um caminho, já que é apenas um pontinho,
ele não é filho de Shape2D.
Linhas (Line2D)
A classe Line2D representa um segmento de reta. Ela é definida por 2 pontos (x1,
y1)-(x2,y2). Ela possui vários métodos interessantes para testar interseção de uma reta com
outra, de uma reta com um ponto, distância entre um ponto e o segmento de reta, ou mesmo
um indicador se um determinado ponto está sobre ou abaixo da reta em questão. Ironicamente,
não há um método para obter o tamanho de um segmento de reta, mas isso pode ser
facilmente obtido através da classe Point2D. A maior parte dos métodos em distância também
pode retornar a distância ao quadrado, muito mais rápida de ser calculada.
// desenha uma linha com precisão de Doubles Line2D linha = new Line2D.Double(x1, y1,
x2, y2); g2.draw(linha); Curvas Quadráticas (QuadCurve2D)
2 / 10
Pintando no Java 2D
Escrito por Vinícius Godoy de Mendonça - Última atualização Sex, 31 de Dezembro de 2010 03:34
oA
paramétrica
onde
curva.
é,
ou
classe
aAcurva
para
classe
QuadCurve2D
subdividi-la.
de
no
começa
uma
espaço
possui
curva,
ediversos
O
coordenado
termina,
implementa
método
de euma
métodos
esetCurve()
só
um
(x,
avez.
ponto
interface
y).
Curvas
de
é usado
como
controle,
Shape
quadráticas
método
para
e que
representa
definir
define
para
são
formadas
definir
onde
3uma
pontos
ocurva
fica
quão
por
necessários
a quadrática
“barriga”
dois
plana
pontos,
a curva
da
para
//desenho
//
Define
Cria
uma
suas
nova
coordenadas
curva
com
a
precisão
desenha
deúteis,
q.setCurve(x1,
floats
QuadCurve2D
y1,
ctrlx,
qos
=
ctrly,
new
x2,
QuadCurve2D.Float();
y2);
g2.draw(q);
Curvas Cúbicas (CubicCurve2D)
Similar a curva anterior, mas a classe CubicCurve2D possui dois pontos de controle, ao invés
de apenas 1. Defini-la é muito similar a curva quadrática, diferindo apenas pela presença do
segundo ponto de controle, por exemplo:
// Cria uma nova CubicCurve2D.Double CubicCurve2D c = new CubicCurve2D.Double(); //
Define suas coordenadas e a desenha c.setCurve(x1, y1, ctrlx1, ctrly1,
ctrlx2, ctrly2, x2, y2);
g2.draw(c);
Retângulos (Rectangle2D e RoundRectangle2D)
Retângulos derivam da classe RectangularShape , que implementa a interface Shape e
adiciona alguns métodos úteis, como métodos para pegar os pontos coordenados, o
bouding box
do retângulo, entre outras coisas. Rectangular shape define todas as formas que são
desenhadas com base numa forma retangular, inclusive formas não necessariamente
retangulares, como a elipse. Temos duas classes para retângulos, cada uma nas suas versões
Float e Double, são elas a
Rectangle2D
ea
RoundRectangle2D
. Como você já deve ter deduzido, a única diferença de uma classe para outra é que a
RoundRectangle possui bordas arredondadas. Além disso, o Java também disponibiliza a
3 / 10
Pintando no Java 2D
Escrito por Vinícius Godoy de Mendonça - Última atualização Sex, 31 de Dezembro de 2010 03:34
classe
Rectangle
, para retângulos com precisão inteira.
A criação de um retângulo é simples, basta fornecer as coordenadas x e y do canto superior
esquerdo do retângulo, além de sua largura e altura.
// Cria e desenha um retângulo 2D com precisão double g2.draw(new
Rectangle2D.Double(x, y,
rectwidth,
rectheight));
No caso do retângulo com bordas arredondadas, é necessário também fornecer a largura e a
altura do arco das bordas.
// Cria e desenha um retângulo de bordas arredondadas // com precisão Double
g2.draw(new RoundRectangle2D.Double(x, y,
rectwidth,
rectheight,
10, 10)); Elipses
A classe Ellipse2D representa uma eclipse inscrita num retângulo, por isso, é também filha de
RectangularShape. O retângulo em si não é desenhado. A forma de desenhar uma elipse, é
exatamente igual a de um retângulo. Um quadrado, portanto, desenha uma circunferência.
// Desenha uma elipse com precisão double g2.draw(new Ellipse2D.Double(x, y,
rectwidth,
rectheight)); Arcos
Para desenhar apenas uma parte de uma elipse, usamos a classe Arc2D .
4 / 10
Pintando no Java 2D
Escrito por Vinícius Godoy de Mendonça - Última atualização Sex, 31 de Dezembro de 2010 03:34
É possível desenhar três tipos de arcos:
- OPEN: Sem uma linha conectando os pontos finais do segmento de arco;
- CHORD: Com uma linha reta (corda), ligando os pontos finais do segmento de arco;
- PIE: Com segmentos de reta do início do segmento do arco até o centro da elipse
completa, e desse ponto até o fim do segmento de arco.
É possível criar um arco a partir de coordenadas, pontos, da cópia de outros arcos ou a partir
de seu centro, usando o método setArcByCenter. O exemplo abaixo usa o construtor padrão,
definindo o arco como se faria para um retângulo ou elipse:
// Desenha um arco aberto com precisão Float g2.draw(new Arc2D.Float(x, y,
rectwidth,
rectheight,
90, 135,
Arc2D.OPEN));
Formas arbitrárias
Para criar formas mais complexas do que as descritas até então, tais como estrelas,
hexágonos ou naves alienígenas, usa-se uma classe chamada GeneralPath . Essa classe
também implementa Shape, e representa uma forma qualquer formada por uma série de linhas
e curvas, quadráticas ou cúbicas.
Para construir um GeneralPath, basta chamar o construtor padrão, e então chamar um dos
seguintes métodos:
- moveTo(float x, float y): Move o ponto atual para o ponto fornecido;
- lineTo(float x, float y): Adiciona um segmento de reta ao path atual;
- quadTo(float ctrlx, float ctrly, float x2, floaty2): Adiciona um segmento de curva quadrática
ao caminho atual;
- curveTo(float ctrlx1, float ctrly1, float ctrlx2, float ctrly2, float x3, floaty3): Adiciona um
segmento de curva cúbica ao caminho atual;
- closePath(): Liga o primeiro e o último ponto do caminho com uma linha reta.
5 / 10
Pintando no Java 2D
Escrito por Vinícius Godoy de Mendonça - Última atualização Sex, 31 de Dezembro de 2010 03:34
O exemplo a seguir, mostra como desenhar um polígono usando o GeneralPath:
// desenha um polígono usando GeneralPath int x1Points[] = {0, 100, 0, 100}; int y1Points[]
= {0, 50, 50, 0}; GeneralPath polygon =
new
GeneralPath(GeneralPath.WIND_EVEN_ODD, x1Points.length); polygon.moveTo(x1Points[0],
y1Points[0]); for (int index = 1; index Resultando no seguinte polígono:
Também é possível inserir caminhos específicos ao final do seu GeneralPath através do
método append. Você não é obrigado a fechar o polígono com closePath().
Definindo as bordas e preenchimento
Vamos ver agora como definir a intensidade e o tipo do pincel nas bordas (Stroke) e o
preenchimento das formas (
Fill
). Fazemos isso alterando o contexto gráfico antes da pintura. Como visto
no nosso artigo anterior
, antes de alterar o contexto gráfico, é necessário copia-lo para que essas mesmas
características não sejam aplicadas a futuras (e indesejadas) partes do programa.
Bordas (Stroke)
O desenho das bordas é definido pela interface Stroke , e o Java já fornece uma
implementação padrão dessa interface na classe
BasicStroke
. Depois de configurado, chama-se o método setStroke da classe Graphics2D, para adotar o
novo padrão de desenho nas bordas. Podemos configurar várias propriedades, sendo as
principais:
- Espessura da linha (line width): Representa o tamanho da linha, medido
6 / 10
Pintando no Java 2D
Escrito por Vinícius Godoy de Mendonça - Última atualização Sex, 31 de Dezembro de 2010 03:34
perpendicularmente à sua trajetória. A largura da linha é medido como um valor em float e, no
sistema de coordenadas padrão, representa 1/72 polegadas (0,035cm).
- O estilo da junção (join style): Ou seja, a decoração aplicada quando dos segmentos de
reta se encontram. A classe basic stroke suporta três tipos:
- O estilo do final da linha (end cap stype): Usado quando uma linha acaba. O BasicStroke
suporta os seguintes estilos:
- O estilo do tracejado (dash style): Que representa um padrão de opacidade e
transparência, a ser usado em torno de toda a linha. Ele é definido através de um array, onde
0s representa um traço e 1 um espaço. Além dele, temos também a
dash phase, que
representa o estilo de tracejado usado no início da linha.
O exemplo abaixo, desenha um retângulo tracejado:
final static float dash1[] = {10.0f};
final static BasicStroke dashed = new BasicStroke(1.0f,
BasicStroke.CAP_BUTT,
BasicStroke.JOIN_MITER,
10.0f, dash1, 0.0f);
g2.setStroke(dashed); g2.draw(new RoundRectangle2D.Double(x, y,
rectWidth,
rectHeight,
10, 10));
Resultando em:
7 / 10
Pintando no Java 2D
Escrito por Vinícius Godoy de Mendonça - Última atualização Sex, 31 de Dezembro de 2010 03:34
Preenchimento (Fill)
Padrões de pintura são definidos pelo atributo paint, do Graphics2D. Da mesma forma que
fizemos com o Stroke, criamos um objeto que deriva da interface Paint e então o fornecemos
para o método setPaint, do graphics.
As classes Color , GradientPaint e TexturePaint implementam essa interface.
Para criar uma textura em degradée, você define uma posição inicial, uma final, as cores inicial
e final. O gradiente muda proporcionalmente da cor inicial até a final, ao longo do segmento de
reta definido pelas posições passadas. Por exemplo:
// fill Ellipse2D.Double redtowhite = new GradientPaint(0,0,color.WHITE,100, 0,color.BLUE);
g2.setPaint(redtowhite); g2.fill (new Ellipse2D.Double(0, 0, 100, 50));
Observe o desenho explicativo:
O TexturePaint permite que você aplique um padrão, definido por uma imagem. Usa-se um
retângulo de âncora, que diz qual é o tamanho que essa textura será aplicado no destino. A
imagem a seguir demonstra o uso da técnica:
8 / 10
Pintando no Java 2D
Escrito por Vinícius Godoy de Mendonça - Última atualização Sex, 31 de Dezembro de 2010 03:34
Outras considerações
O texto desse tutorial foi fortemente baseado (para não dizer, praticamente uma tradução) do t
utorial oficial da Sun
. Os códigos fontes foram, efetivamente, copiados de lá, assim como as imagens, que foram
apenas traduzidas. Não vi porque alterar muita coisa, ou mudar as explicações, já que elas
eram claras, diretas e de uma fonte mais confiável impossível. Apenas complementei algumas
coisas, que ficaram obscuras no tutorial original.
Desenhar via código é uma atividade mais lenta que simplesmente pintar imagens prontas,
especialmente quando se usa padrões complexos de pintura ou suavização. Entretanto, muitos
jogos simples, como o Arkanoid, ou Tetris, podem ser diretamente pintados, sem uma
penalidade de performance perceptível.
A grande vantagem é que as classes criadas para fazer o desenho ocupam muito menos
espaço do que o desenho em si, se estivesse gravado em arquivos de imagem (jpg, png ou
gifs) dentro do seu .jar. Por isso, essa técnica pode ser muito interessante em applets. Elas
também não perdem resolução se ampliadas ou reduzidas. Outra opção, que veremos no
futuro, é gerar esse desenho num buffer de pintura (definido por uma BufferedImage) e depois
apenas desenhar esse buffer. Isso pode penalizar o usuário com um maior consumo de
memória e um tempo de loading maior, mas que poderá ser menor do que esperar o download
de todas as imagens do jogo, além de ter excelente performance durante o game em si.
Desafio
9 / 10
Pintando no Java 2D
Escrito por Vinícius Godoy de Mendonça - Última atualização Sex, 31 de Dezembro de 2010 03:34
Você consegue, utilizando o que foi visto aqui, desenhar o fantasma do Pacman? Tente fazer
isso inicialmente utilizando as primitivas em seqüência, e depois com a classe GeneralPath.
Faça curvas suaves, e não pixel art. A resposta para esse desafio veremos na semana que
vem.
10 / 10
Download