Filtro de bandeira para imagens

Devido ao artigo "Quebrando CAPTCHAs" recebi dezenas de e-mails querendo mais informações de como eu fiz. Um dos pontos mais questionados foi a respeito do filtro de bandeira utilizado. Então resolvi disponibilizar o filtro para quem tiver interesse.

O filtro ainda está na sua versão alpha 😀 e ainda precisa de várias melhorias como a remoção de variáveis desnecessárias, passar oas variáveis da função cosseno por parâmetros, remoção da matrix de conversão (não é necessária) entre outros. Quem quiser colaborar é só avisar.

O primeiro passo foi criar uma interface para implementação de Filtros, pois estou utilizando essa mesma interface para outros filtros.

package br.com.thiagovespa.image.utils.filter;

import java.awt.image.BufferedImage;

/**
 * Interface à ser utilizada em todos os filtros de imagem
 *
 * @author Thiago Galbiatti Vespa - <a
 *         href="mailto:thiago@thiagovespa.com.br">thiago@thiagovespa.com.br</a>
 * @version 1.0
 *
 */
public interface ImageFilter {
	/**
	 * Aplica o filtro à imagem
	 * @param src Imagem original
	 * @return Imagem com o filtro aplicado
	 */
	public BufferedImage applyTo(BufferedImage src);
}

Após isso criei a implementação do filtro de bandeira. Lembrando novamente que esse é apenas um primeiro protótipo, há algumas melhorias a serem feitas.

package br.com.thiagovespa.image.utils.filter;

import java.awt.image.BufferedImage;
import java.util.Arrays;

/**
 * Versão inicial do filtro de bandeira. Licenciado sob GPL:
 * http://www.gnu.org/licenses/gpl.html
 * 
 * @author Thiago Galbiatti Vespa - <a
 *         href="mailto:thiago@thiagovespa.com.br">thiago@thiagovespa.com.br</a>
 * @version 0.6
 * 
 */
public class FlagFilter implements ImageFilter {

	private boolean x;
	private boolean y;

	/**
	 * Construtor padrão. O efeito é aplicado no eixo x e y
	 */
	public FlagFilter() {
		this.x = true;
		this.y = true;
	}

	/**
	 * Construtor que especifica onde aplicar o filtro
	 * 
	 * @param x
	 *            se verdadeiro aplica no eixo x
	 * @param y
	 *            se verdadeiro aplica no eixo y
	 */
	public FlagFilter(boolean x, boolean y) {
		this.x = x;
		this.y = y;
	}

	@Override
	public BufferedImage applyTo(BufferedImage src) {

		int w = src.getWidth();
		int h = src.getHeight();

		// TODO: Remover uso de matrizes desnecessárias
		int[][] resultMatrix = new int[w][h];

		// Copia para matriz
		for (int i = 0; i < w; i++) {
			for (int j = 0; j < h; j++) {
				int value = src.getRGB(i, j);
				resultMatrix[i][j] = value;
			}
		}

		int[] transformY = new int[w];

		if (this.y) {

			// Cosseno horizontal
			for (int i = 0; i < w; i++) {
				// TODO: Colocar variáveis como parâmetro
				double x = (i / (double) (w - 1)) * 3 * Math.PI;
				double valor = 0.27 * (((Math.cos(x) + 1) * ((double) (h - 1) / 2.0)));
				transformY[i] = (int) valor;
			}
		}
		MinMaxReturn increment = getNormValue(transformY);

		int[] transformX = new int[h + increment.getDelta()];

		int[][] transformMatrix = new int[(w)][h + increment.getDelta()];

		for (int i = 0; i < w; i++) {
			Arrays.fill(transformMatrix[i], 0xffffff);
			for (int j = 0; j < h; j++) {
				int desloc = (j - transformY[i]);
				transformMatrix[i][increment.getSum() + desloc] = resultMatrix[i][j];
			}
		}
		if (this.x) {

			// Cosseno vertical
			for (int i = 0; i < h + increment.getDelta(); i++) {
				// TODO: Colocar variáveis como parâmetro
				double y = (i / (double) (h - 1)) * 1.5 * Math.PI + 16;
				double valor = (((2 * Math.cos(y) + 1) * ((double) (w - 1) / 2.0)) * 0.07) + 7;
				transformX[i] = (int) valor;
			}
		}
		MinMaxReturn incrementX = getNormValue(transformX);

		int[][] transform2Matrix = new int[(w + incrementX.getDelta())][h
				+ increment.getDelta()];

		for (int i = 0; i < transform2Matrix.length; i++) {
			Arrays.fill(transform2Matrix[i], 0xffffff);
		}

		for (int j = h + increment.getDelta() - 1; j >= 0; j--) {

			for (int i = w - 1; i >= 0; i--) {
				int desloc = (i - transformX[j]);
				transform2Matrix[incrementX.max + desloc][j] = transformMatrix[i][j];
			}
		}

		int[] resultImage = new int[(w + incrementX.getDelta())
				* (h + increment.getDelta())];

		// Matrix de conversão
		for (int i = 0; i < transform2Matrix.length; i++) {
			for (int j = 0; j < transform2Matrix[0].length; j++) {
				resultImage[j * (transform2Matrix.length) + i] = transform2Matrix[i][j];
			}
		}
		BufferedImage dest = new BufferedImage(transform2Matrix.length,
				transform2Matrix[0].length, BufferedImage.TYPE_INT_RGB);

		dest.setRGB(0, 0, transform2Matrix.length, transform2Matrix[0].length,
				resultImage, 0, transform2Matrix.length);

		return dest;
	}

	class MinMaxReturn {
		int min;
		int max;

		int getDelta() {
			return max - min;
		}

		int getSum() {
			return max + min;
		}
	}

	// TODO: Colocar esse método em classe utilitária
	private MinMaxReturn getNormValue(int[] numbers) {
		MinMaxReturn ret = new MinMaxReturn();
		ret.max = numbers[0];
		ret.min = numbers[0];
		for (int i = 1; i < numbers.length; i++) {
			if (numbers[i] > ret.max) {
				ret.max = numbers[i];
			}
			if (numbers[i] < ret.min) {
				ret.min = numbers[i];
			}
		}

		return ret;
	}

}

Agora para utilizar é só instanciar o filtro e chamar o método applyTo passando a imagem a ser alterada.

ImageFilter flagFilter = new FlagFilter(true,true);
BufferedImage flaggedImage = flagFilter.applyTo(ci.getImage());

Fiz outro teste com a bandeira do Brasil.

Bandeira do Brasil
Bandeira do Brasil

Aplicando somente ao eixo x temos o seguinte:

FlagFilter - Eixo X
FlagFilter - Eixo X

Aplicando somente ao eixo y temos o seguinte:

FlagFilter - Eixo Y
FlagFilter - Eixo Y

E aplicando aos dois eixos temos o seguinte:

FlagFilter - Eixo X e Y
FlagFilter - Eixo X e Y

Agora é só utilizar no seu projeto e se fizer algum melhoria no código é só me avisar que atualizo o post.

Sobre: Thiago Galbiatti Vespa

Thiago Galbiatti Vespa é mestre em Ciências da Computação e Matemática Computacional pela USP e bacharel em Ciências da Computação pela UNESP. Coordenador de projetos do JavaNoroeste, membro do JCP (Java Community Process), consultor Oracle, arquiteto de software de empresas de médio e grande porte, palestrante de vários eventos e colaborador de projetos open source. Possui as certificações: Oracle Certified Master, Java EE 5 Enterprise Architect – Step 1, 2 and 3; Oracle WebCenter Portal 11g Certified Implementation Specialist; Oracle Service Oriented Architecture Infrastructure Implementation Certified Expert; Oracle Certified Professional, Java EE 5 Web Services Developer; Oracle Certified Expert, NetBeans Integrated Development Environment 6.1 Programmer; Oracle Certified Professional, Java Programmer; Oracle Certified Associate, Java SE 5/SE 6