>

Parte 2: Transformações e filtros

Processamento de imagens e recursos de transformações

Paloma Sol

Na primeira parte desta série, na Edição 13, vimos como utilizar o Java 2D para criar formas básicas, analisamos o processo de renderização com diferentes estilos de cores e linhas, e mostramos o suporte à manipulação de imagens e efeitos de transparência. Nesta segunda parte vamos abordar transformações geométricas e filtros sobre imagens e veremos alguns fundamentos matemáticos em que essas operações se baseiam.

Transformações geométricas

A API Java 2D define as transformações geométricas de translação, rotação, escala e distorção/inclinação (shear), que podem ser aplicadas a figuras, imagens e textos. Transformações são realizadas alterando-se o atributo do tipo java.awt.geom.AffineTransform no contexto gráfico Graphics2D.

Dois métodos podem ser usados para aplicar transformações: setTransform() redefine o atributo AffineTransform desconsiderando as transformações armazenadas no Graphics2D, e transform() aplica a nova transformação de forma acumulativa (matematicamente, este método multiplica a matriz da nova transformação pela matriz de transformação armazenada no contexto gráfico).

 

Nota: Translação, rotação, escala e distorção são chamadas "transformações afins" (affine transformations); elas preservam o paralelismo das linhas, mas não seus comprimentos ou ângulos. As transformações que não causam distorções (translação e rotação) são às vezes chamadas de "transformações de corpo rígido".

 

Veja mais sobre transformações na Tabela 1 e exemplos de código no quadro "Transforme o Duke". O quadro "A matemática da transformação" explica como são representadas e calculadas as transformações.

Nosso primeiro exemplo mostra um painel onde é possível desenhar três tipos de gráficos: figuras geométricas, textos ou imagens (veja a Figura 1). Sobre esses gráficos, podemos aplicar diversas transformações clicando nos botões da interface. O código resumido está na Listagem 1.

Observe que a transformação fica armazenada no Graphics2D, e tem efeito independente do tipo de gráfico renderizado. Por isso, se você aplicar várias transformações a um texto, digamos, e depois selecionar uma figura, esta será renderizada com as mesmas transformações (acumuladas no contexto gráfico). Esse comportamento permite que um desenho complexo seja transformado “nos bastidores”, apenas aplicando transformações no Graphics2D, e depois renderizando o gráfico transformado de uma só vez.

 

Nota: Para executar o exemplo, é preciso incluir no classpath o caminho dos ícones e imagens utilizados. Isso pode ser feito na sua IDE, ou diretamente através da linha de comando. Caso você utilize diretamente o JSDK, use o seguinte comando para executar o exemplo já com o classpath configurado (fornecendo o caminho das imagens no seu sistema):

java com.javamagazine.java2d.PanelTransformacoes –cp caminho_imagens

Filtros sobre imagens

O Java 2D permite aplicar filtros sobre imagens, no estilo do Adobe Photoshop, Gimp e outras ferramentas gráficas. Usando filtros, é possível obter efeitos bastante interessantes e úteis, como suavização, realce de contornos, e ajustes de brilho e de cores.

O processamento se baseia na transformação dos pixels da imagem através de cálculos matemáticos. Esses cálculos podem envolver as cores dos pixels de origem e de seus vizinhos,  parâmetros adicionais, ou uma combinação destes.

Operações sobre imagens são de responsabilidade das classes que implementam a interface java.awt.image.BufferedImageOp. O Java 2D já oferece algumas implementações, por exemplo, ConvolveOp e LookupOp.

O exemplo da Listagem 2 ilustra os filtros apresentados nos tópicos a seguir. A janela mostra a imagem original e a imagem de destino, além de duas ComboBoxes, uma para seleção da imagem a ser processada, e outra para escolha do filtro (veja a Figura 2). É possível também carregar imagens do sistema de arquivos, selecionando a opção "Outras" na primeira lista. Os filtros são acumulativos; para voltar à imagem original clique no botão Resetar.

Observe que o exemplo usa a classe Java2DUtil, apresentada na parte 1. O código completo desta e de outras classes do exemplo pode ser baixado do site da Java Magazine.

Convolução

Operações de convolução permitem obter uma ampla gama de efeitos, como suavização, aumento de nitidez e realce de contornos. Os pixels da imagem de destino são calculados com base na cor do pixel correspondente na imagem de origem e nas cores dos pixels adjacentes. A combinação é especificada usando um kernel (matematicamente, uma matriz de coeficientes). Definindo kernels diferentes, é possível obter uma grande variedade de efeitos.

O trecho de código a seguir mostra a construção de um kernel para um filtro de suavização (blur). A matriz tem todos os elementos com valor 1/9.

 

float N = 1.0f/9.0f;

float[] matrizSuavizacao = {

  N, N, N,

  N, N, N,

  N, N, N

};

 

Kernel kernelSuavizacao =

   new Kernel(3, 3, matrizSuavizao)

BufferedImageOp suavizacaoOp =

   new ConvolveOp(kernelSuavizacao);

 

Para aplicar este filtro a uma imagem utilizamos o método filter() da classe ConvolveOp:

 

imagem = suavizacaoOp.filter(imagem, null);

 

O efeito de suavização ocorre porque a cor do novo pixel é o resultado da média das cores dos pixels original e das cores de seus vizinhos (com pesos iguais), o que deixa as cores dos pixels adjacentes mais próximas entre si, na imagem resultante. A imagem original e a resultante depois de aplicado o filtro de suavização são mostradas na Figura 2.

 

Convolução aplicada

Se você tem acesso ao Adobe® Photoshop®, pode usar o comando Filter|Other>Custom para explorar o efeito de kernels diferentes. Uma caixa de diálogo permite a definição de filtros de convolução (embora essa terminologia não seja usada no produto), alterando-se coeficientes em campos de texto:

 


Filtros personalizados no Adobe Photoshop 7.0

 

Outro efeito que pode ser obtido com a mesma técnica é o edge detection. O seguinte exemplo define um kernel para esse efeito e o aplica a uma imagem:

 

float[] matrizEdge = {

   0.0f, -2.0f,  0.0f,

  -2.0f,  9.0f, -2.0f,

   0.0f, -2.0f,  0.0f

};

 

Kernel kernelEdge = new Kernel(3, 3, matrizEdge)

BufferedImageOp edgeOp = new ...

Quer ler esse conteúdo completo? Tenha acesso completo