Academia.eduAcademia.edu

Manipulação e desenho em componentes com Java2D

2007, 2a Jornada da Produção Científica da Educação Profissional e Tecnológica da Região Centro Oeste - Educação Profissional: Ensino e Pesquisa para Ações Sustentáveis

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.

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