Manipulando Imagens com Thumbnailator no Java
A manipulação de imagens é uma tarefa comum em aplicações modernas, seja para redimensionar fotos de perfil, gerar miniaturas de produtos ou aplicar marcas d’água. No ecossistema Java, a biblioteca Thumbnailator se destaca por sua simplicidade e eficiência, oferecendo uma interface fluente para realizar operações complexas com poucas linhas de código.
Instalação
Para integrar o Thumbnailator ao seu projeto Maven, adicione a seguinte dependência ao arquivo pom.xml:
<dependency>
<groupId>net.coobird</groupId>
<artifactId>thumbnailator</artifactId>
<version>0.4.20</version>
</dependency>
Redimensionando uma Imagem
Redimensionar imagens é uma das operações mais frequentes na manipulação de imagens. Com o Thumbnailator, esse processo se torna simples: basta carregar a imagem utilizando o método of, definir as dimensões desejadas (largura e altura) com o método size e, em seguida, usar toFile para salvar o resultado no local desejado.
try {
Thumbnails.of(new File("images/castelo.jpg"))
.size(150, 150)
.toFile(new File("images/castelo-small.jpg"));
} catch (IOException e) {
e.printStackTrace();
}
O método of possui várias sobrecargas que permitem receber diferentes tipos de parâmetros, como uma String representando o caminho do arquivo, um objeto InputStream ou até mesmo uma URL.
Thumbnails.of("images/castelo.jpg")
.size(150, 150)
.toFile("images/castelo-string.jpg");
InputStream input = new FileInputStream("images/castelo.jpg");
Thumbnails.of(input)
.size(150, 150)
.toFile("images/castelo-input-stream.jpg");
URL url = new URL("https://botecodigital.dev.br/exemplos/thumbnailator/castelo.jpg");
Thumbnails.of(url)
.size(150, 150)
.toFile("images/castelo-url.jpg");
O método size preserva o aspecto da imagem, ou seja, mantém suas proporções originais. Isso garante que a imagem se ajuste dentro das dimensões especificadas, sem distorções. Por esse motivo, uma das dimensões (largura ou altura) pode ficar menor do que o valor informado.
Se quisermos forçar a imagem a ter dimensões específicas, podemos utilizar o método forceSize. No entanto, é importante lembrar que isso pode resultar em distorções, como alongamento ou achatamento da imagem, já que as proporções originais não serão preservadas.
Thumbnails.of("images/castelo.jpg")
.forceSize(150, 150)
.toFile("images/castelo-force.jpg");
Nos exemplos, utilizamos o método toFile para salvar a imagem redimensionada diretamente em um arquivo. No entanto, também é possível utilizar o método toOutputStream para enviar a imagem redimensionada diretamente para um OutputStream, como em casos de envio pela web.
OutputStream os = new FileOutputStream("images/castelo-outputstream.png");
Thumbnails.of("images/castelo.jpg")
.size(150, 150)
.outputFormat("png")
.toOutputStream(os);
No exemplo acima, utilizamos um FileOutputStream apenas para demonstrar, mas o mesmo procedimento pode ser aplicado a qualquer outro tipo de OutputStream. Após carregar a imagem que será redimensionada, usamos o método size para definir as dimensões e, em seguida, outputFormat para especificar o formato da imagem. Por fim, utilizamos toOutputStream, informando o OutputStream de destino para onde a imagem será gravada.
Recortando uma imagem
Muitas vezes, é necessário gerar uma imagem em um tamanho específico que não respeita as proporções da imagem original, mas sem causar distorções. Para isso, podemos utilizar o método crop, que permite recortar a imagem. Esse método recebe como parâmetro a posição do corte, definida pelo enum Positions, que oferece as seguintes opções: TOP_LEFT, TOP_CENTER, TOP_RIGHT, CENTER_LEFT, CENTER, CENTER_RIGHT, BOTTOM_LEFT, BOTTOM_CENTER e BOTTOM_RIGHT.
Thumbnails.of("images/castelo.jpg")
.crop(Positions.CENTER)
.size(200, 200)
.toFile("images/castelo-crop.jpg");

Outra forma de recortar uma imagem é definindo manualmente uma região de recorte. Para isso, utilizamos o método sourceRegion, que possui diversas sobrecargas e pode receber, entre outros parâmetros, as coordenadas x1, y1 (ponto inicial) e x2, y2 (ponto final) que delimitam a área da imagem que desejamos recortar.
Thumbnails.of("images/castelo.jpg")
.sourceRegion(100, 100, 200, 200) // x1 = 100, y1 = 100, x2 = 200, y2=200
.size(300, 300)
.toFile("images/castelo-crop2.jpg");
Marca D’Água – Watermark
Outro recurso oferecido pelo Thumbnailator é a adição de marca d’água de forma simples e prática. Para isso, utilizamos o método watermark, que recebe a imagem da marca d’água como um objeto BufferedImage.
BufferedImage watermarker = ImageIO.read(new File("images/duke.png"));
Thumbnails.of("images/castelo.jpg")
.size(200, 200)
.watermark(watermarker)
.toFile("images/castelo-watermark.jpg");
Também é possível ajustar a opacidade da marca d’água e definir sua posição na imagem. Para isso, passamos como parâmetros a posição desejada usando o enum Positions, a imagem da marca d’água e um valor do tipo float que representa o nível de transparência.
BufferedImage watermarker = ImageIO.read(new File("images/duke.png"));
Thumbnails.of("images/castelo.jpg")
.size(200, 200)
.watermark(Positions.BOTTOM_RIGHT, watermarker , 0.4f)
.toFile("images/castelo-watermark.jpg");
Se a imagem da marca d’água não estiver nas dimensões desejadas, podemos redimensioná-la utilizando o próprio Thumbnailator e, em seguida, gerar um objeto BufferedImage por meio do método asBufferedImage.
BufferedImage watermarker = Thumbnails.of( ImageIO.read( new File("images/duke.png") ) )
.size(75, 75)
.asBufferedImage();
Thumbnails.of("images/castelo.jpg")
.size(400, 350)
.watermark(Positions.BOTTOM_RIGHT, watermarker , 0.75f)
toFile("images/castelo-watermark.jpg");
Rotacionando uma imagem
Podemos rotacionar uma imagem usando o método rotate, que recebe o ângulo em graus para a rotação. Valores positivos giram a imagem no sentido horário, enquanto valores negativos aplicam a rotação no sentido anti-horário.
Thumbnails.of("images/castelo.jpg")
.size(300, 300)
.rotate(-90)
.toFile("images/castelo-rotate.jpg");
Adicionando Filtros
Outros recursos podem ser incorporados por meio de filtros. A biblioteca já oferece alguns filtros prontos e também permite que criemos os nossos próprios, implementando a interface ImageFilter. Depois de criar um objeto que implementa essa interface, podemos adicioná-lo utilizando o método addFilter.
Adicionando Texto
Para adicionar um texto a uma imagem, criamos um objeto Caption(implementa ImageFilter), que recebe como parâmetros o texto a ser inserido, um objeto Font, um objeto Color, a posição onde o texto será colocado (definida pelo enum Positions) e os insets (margens internas). Após criar o objeto, adicionamos esse filtro utilizando o método addFilter.
Caption caption = new Caption(
"Thumbnailator",
new Font("Verdana", Font.BOLD, 14),
Color.WHITE,
Positions.BOTTOM_CENTER,
10
);
Thumbnails.of("images/castelo.jpg")
.size(300, 300)
.addFilter( caption )
.toFile("images/castelo-caption.jpg");
Obs: É possível informar um valor de transparência logo após especificar a cor no objeto Caption.
Caption caption = new Caption(
"Thumbnailator",
new Font("Verdana", Font.BOLD, 14),
Color.WHITE,
0.5f, // transparência
Positions.BOTTOM_CENTER,
10
);
Colorize
Podemos aplicar uma coloração a uma imagem usando o filtro Colorize, que recebe uma cor específica e um valor float indicando o nível de transparência.
Colorize colorize = new Colorize( Color.decode("#0000FF") , 0.5f);
Thumbnails.of("images/castelo.jpg")
.size(300, 300)
.addFilter( colorize )
.toFile("images/castelo-colorize.jpg");
Flip – Virar imagem
Podemos inverter uma imagem horizontalmente ou verticalmente utilizando o filtro Flip. Ele oferece dois atributos estáticos para facilitar essa operação: Flip.VERTICAL e Flip.HORIZONTAL, cujos nomes já indicam claramente o tipo de inversão aplicada.
Thumbnails.of("images/castelo.jpg")
.size(300, 300)
.addFilter( Flip.VERTICAL )
.toFile("images/castelo-flip.jpg");
Opacidade/Transparência
Alteramos a transparência de uma imagem utilizando o filtro Transparency, que aceita um valor float representando o nível de opacidade. É importante ressaltar que, se a imagem original não suportar transparência, devemos alterar seu tipo para permitir o canal alpha, utilizando o método imageType com o valor BufferedImage.TYPE_INT_ARGB. Além disso, é necessário definir o formato de saída para um que suporte transparência, como PNG, configurando isso pelo método outputFormat com o valor "png".
Thumbnails.of("images/castelo.jpg")
.size(300, 300)
.outputFormat("png")
.imageType(BufferedImage.TYPE_INT_ARGB)
.addFilter(new Transparency(0.5f))
.toFile("images/castelo-transparency.png");
Escala de Cinza
O Thumbnailator não oferece um filtro nativo para converter imagens em escala de cinza, mas podemos criar nosso próprio filtro implementando a interface ImageFilter, conforme explicado anteriormente.
public class Greyscale implements ImageFilter{
private ColorConvertOp op;
public Greyscale(){
op = new ColorConvertOp(
ColorSpace.getInstance(ColorSpace.CS_GRAY), null
);
}
@Override
public BufferedImage apply(BufferedImage img) {
return this.op.filter(img, null);
}
}
A interface ImageFilter possui o método apply, que deve ser implementado. Esse método recebe como parâmetro um objeto BufferedImage, que representa a imagem original, e deve retornar um BufferedImage com o filtro aplicado. No nosso exemplo, utilizamos a classe ColorConvertOp para converter a imagem original para escala de cinza.
Thumbnails.of("images/castelo.jpg")
.size(300, 300)
.addFilter(new Greyscale())
.toFile("images/castelo-grayscale.jpg");
Processamento em lote
O Thumbnailator facilita o processamento de múltiplas imagens de maneira bastante simples. O método of, que usamos para carregar imagens, também aceita um array dos mesmos tipos mencionados anteriormente, permitindo assim a entrada de várias imagens ao mesmo tempo. Além disso, é possível utilizar métodos como fromFiles, fromFilenames e outros para carregar imagens a partir de um Iterable.
Para definir a saída, utilizamos o método toFiles, que recebe como primeiro parâmetro um objeto File representando o diretório de destino onde os arquivos serão salvos, e como segundo parâmetro uma função de renomeação, permitindo os arquivos serem salvos com outro nome. Essa função pode ser fornecida pela classe Rename, que disponibiliza atributos estáticos para adicionar prefixos ou sulfixos como: NO_CHANGE, PREFIX_DOT_THUMBNAIL, PREFIX_HYPHEN_THUMBNAIL, SUFFIX_DOT_THUMBNAIL e SUFFIX_HYPHEN_THUMBNAIL.
File dst = new File("images/thumbs");
File org = new File("images/origin");
Thumbnails.of( org.listFiles() )
.size(100, 100)
.toFiles(dst, Rename.SUFFIX_HYPHEN_THUMBNAIL);
Esta foi uma introdução à biblioteca Thumbnailator, que, embora não seja uma ferramenta extensa para manipulação de imagens, oferece recursos simples e práticos para as operações mais comuns do dia a dia, atendendo a grande parte das necessidades básicas. T++
