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