Creare thumbnail in Java usando ImageMagick

Java thumbnail, generare immagini scalate con ImageMagick e JavaLa creazione di thumbnail è una di quelle operazioni ricorrenti che gli sviluppatori devono affrontare continuamente. L’obiettivo può essere raggiunto in diversi modi, Java, ASP, PHP, Ruby, praticamente ogni linguaggio ha delle classi o dei metodi che, a partire da un’immagine, sono in grado di realizzarne una versione scalata.

Per quanto riguarda il Java spesso si passa da classi del package java.awt o javax.imagesio che su sistemi headless e con schede video praticamente inesistenti (caso frequente su di un server remoto amministrato via SSH) hanno prestazioni di livello insufficiente. Quello di cui tratta questo post è la possibilità di usare uno strumento altamente performante come ImageMagick, chiamando direttamente un metodo statico di una classe Java. Chiaramente tutto funziona se e solo se nel PATH della macchina è già disponibile il comando convert (che fa parte del tool ImageMagick, una suite di comandi di image editing disponibile per Linux, Windows, Mac). Le prestazioni della classe sotto migliorano se oltre a convert è presente anche il comando identify. Questa seconda condizione non è verificata di default su Mac OS X, per cui la libreria prevede un workaround per ovviare a questa assenza senza perdere in performance.

Sui sistemi Linux (Debian, Red Hat e CentOS sicuramente), il pacchetto ImageMagick fa parte dei pacchetti base per cui per installarlo basta usare il proprio package manager preferito od uno qualsiasi dei metodi da shell. Su CentOS l’operazione si riduce ad esempio a digitare;


sudo yum install imagemagick

Una volta che i pacchetti sono installati è possibile utilizzare which per verificare che gli eseguibili siano effettivamente nel PATH. In particolare:

# Obbligatorio/mandatory
which convert
# Opzionale/optional
which identify

ImageMagick API da IM4Java

Non appena ci siamo assicurati di avere IM nel PATH il passo successivo è scaricare la libreria IM4Java da http://im4java.sourceforge.net/. Questa libreria non è altro che una traduzione Object Oriented delle chiamate da shell di alcuni dei tool che compongono la suite ImageMagick. La libreria andrà inserita fra le dipendenze del vostro progetto, visto cha la classe che vi fornirò sotto utilizza alcune classi inserite in quel jar.

ELbuild ImageMagickUtil.java

La classe ImageMagickUtil.java consente la creazione di thumbnail di qualsiasi dimensione a partire da un immagine, png, jpg, gif, bmp. In generale il codice capisce qual è l’estensione del file ed utilizza il metodo più indicato per la trasformazione. Il comando utilizzato è IM convert, e la logica è quella di utilizzare l’altezza e la larghezza fornite in input come una sorta di bounding box, ridimensionando la dimensione originale che più si discosta dal corrispettivo parametro  scalato e mantenendo le proporzioni per ciò che concerne l’aspetto generale dell’immagine. In nessun caso quindi questa classe provoca una distorsione delle proporzioni originali dell’immagine.

Questa classe inoltre evita l’upscaling dell’immagine, ovvero quel fenomeno di distorsione che si ha ridimensionando l’immagine ad un formato più grande di quello originale. Gli effetti dell’ingrandimento di una foto digitali sono evidenti già per upscaling del 10%, sia in termini di perdita di risoluzione che di resa a video dell’immagine. Il codice quindi esegue un controllo e, se si sta tentando di ridimensionare ad un formato più grande, restituisce l’immagine nel formato originale loggando l’evento attraverso la classe java.util.Logger.

La classe prova ad utilizzare il comando identify per capire quali sono le dimensioni del file originale. Purtroppo identify manca su alcuni sistemi dove convert è disponibile (es. nella versione binary ready di IM su Mac OS X); per questo motivo la classe raccoglie l’eccezione generata dall’assenza del comando e utilizza un metodo fallback che sfrutta il package java.imageio. Soluzione meno performante ma comunque che non degrada la resa del thumbnail visto che viene utilizzata solo per sapere quali sono altezza e larghezza dell’immagine in ingresso.

Supponiamo quindi di voler ridimensionare il file “img_orginale.png” ad una dimensione di 800 x 600 pixel e di voler salvare il risultato sul file “img_thum.png”.  Una volta inclusa la classe ImageMagickUtil.java nel vostro progetto non dovrete far altro che chiamare il metodo:


ImageMagickUtil.resize("img_originale.png",800,600,"img_thumb.png");

Ovviamente il percorso del file deve essere il path assoluto (o relativo dalla posizione del vostro jar) per cui dovrete tener conto di questo nel passare i nomi al metodo di ImageMagickUtil.java

Codice sorgente ImageMagickUtil.java – Java thumbnail

Ecco sotto il codice della classe, pronto per essere usato. Se volete scaricare il file lo trovate sul nostro sito al link:

http://elbuild.com/elblog_media/ImageMagickUtil.java

/**
 * ELbuild, sviluppo web application, siti web, ecommerce e app mobile.
 * Outsourcing Java, PHP.
 * P.zza Monteoliveto 6a 51100 Pistoia
 * http://www.elbuild.it
 */
package elbuild.imgutil;

import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.stream.FileImageInputStream;
import javax.imageio.stream.ImageInputStream;
import org.im4java.core.*;

/**
 *
 * @author Luca Adamo, ELbuild
 */
public class ImageMagickUtil {

/**
 * This method is the most important one of this class. It gives the user
 * the opportunity of specifyng an input path, a target path and two values
 * representing the desired height and width for the scaled image.
 */
 public static void resize(String fname, int w, int h, String out) {
 Boolean enabled = true;
 int wImg = 0;
 int hImg = 0;
 try {
 Info im = new Info(fname, true);
 hImg = im.getImageHeight();
 wImg = im.getImageWidth();
 enabled = (hImg > h || wImg > w);
 } catch (InfoException ex) {
 /**
 * ELbuild fallback algorithm.
 * If there's no 'identify' command use the awt package classes to
 * determine the original image size.
 */
 Logger.getLogger(ImageMagickUtil.class.getName()).log(Level.SEVERE, null, ex);
 ImageInfo imInfo = getImageDim(fname,w,h);
 wImg = imInfo.getWidth();
 hImg = imInfo.getHeight();
 enabled = imInfo.isEnabled();
 }

// create command
 ConvertCmd cmd = new ConvertCmd();
 IMOperation op = new IMOperation();
 op.addImage(fname);

if (enabled) {
 // create the operation, add images and operators/options
 op.resize(w, h);
 op.addImage(out);
 } else {
 // if the image is to little to be scaled we should log and give back
 // the original version
 Logger.getLogger(ImageMagickUtil.class.getName()).log(Level.WARNING, "Image {0} is too little to be resized to ({1},{2}). Simply copying the image.", new Object[]{fname, w, h});
 op.resize(wImg, hImg);
 op.addImage(out);
 }

try {

// execute the operation
 cmd.run(op);
 } catch (IOException ex) {
 Logger.getLogger(ImageMagickUtil.class.getName()).log(Level.SEVERE, null, ex);
 } catch (InterruptedException ex) {
 Logger.getLogger(ImageMagickUtil.class.getName()).log(Level.SEVERE, null, ex);
 } catch (IM4JavaException ex) {
 Logger.getLogger(ImageMagickUtil.class.getName()).log(Level.SEVERE, null, ex);
 }
 }

/**
 * Fallback method used to gather width and height without passing from
 * javax.awt
 */
 private static ImageInfo getImageDim(final String path, int wMax, int hMax) {
 ImageInfo result = new ImageInfo();
 String suffix = getFileSuffix(path);
 Iterator<ImageReader> iter = ImageIO.getImageReadersBySuffix(suffix);
 if (iter.hasNext()) {
 ImageReader reader = iter.next();
 try {
 ImageInputStream stream = new FileImageInputStream(new File(path));
 reader.setInput(stream);
 result.setWidth(reader.getWidth(reader.getMinIndex()));
 result.setHeight(reader.getHeight(reader.getMinIndex()));
 result.setEnabled(result.getWidth() > wMax || result.getHeight() > hMax);
 } catch (IOException e) {
 Logger.getLogger(ImageMagickUtil.class.getName()).log(Level.SEVERE, e.getMessage());
 } finally {
 reader.dispose();
 }
 } else {
 Logger.getLogger(ImageMagickUtil.class.getName()).log(Level.SEVERE, "No reader found for given format: {0}", suffix);
 }
 return result;
 }

/**
 * Dummy utility method that determines the original file suffix
 */
 private static String getFileSuffix(final String path) {
 String result = null;
 if (path != null) {
 result = "";
 if (path.lastIndexOf('.') != -1) {
 result = path.substring(path.lastIndexOf('.'));
 if (result.startsWith(".")) {
 result = result.substring(1);
 }
 }
 }
 return result;
 }

 /**
 * Inner class used as a partial replacement for Dimension
 */
 private static class ImageInfo {
 private int width;
 private int height;
 private boolean enabled;

public ImageInfo(){
 enabled = true;
 }
 /**
 * @return the width
 */
 public int getWidth() {
 return width;
 }

/**
 * @param aWidth the width to set
 */
 public void setWidth(int aWidth) {
 width = aWidth;
 }

/**
 * @return the height
 */
 public int getHeight() {
 return height;
 }

/**
 * @param aHeight the height to set
 */
 public void setHeight(int aHeight) {
 height = aHeight;
 }

/**
 * @return the enabled
 */
 public boolean isEnabled() {
 return enabled;
 }

/**
 * @param aEnabled the enabled to set
 */
 public void setEnabled(boolean aEnabled) {
 enabled = aEnabled;
 }
 }
}

Autore: Luca Adamo

Luca Adamo si è laureato con lode in Ingegneria delle Telecomunicazioni all'Università degli studi di Firenze ed è dottorando in Ingegneria Informatica, Multimedialità e Telecomunicazioni, sempre nella stessa facoltà. Le sue competenze tecniche includono lo sviluppo software, sia orientato al web che desktop, in C/C++ e Java (J2EE, J2SE, J2ME), l'amministrazione di macchine Unix-based, la gestione di reti di telecomunicazioni, ed il design di database relazionali.

4 pensieri riguardo “Creare thumbnail in Java usando ImageMagick”

  1. I have no clue about using terminal and just foelowld instructions Does anybody know why I got the errors here below when I tried to install ImageMagick?I just installed the MacPorts and ran a selfupdate, so I suppose they are fine.Error: Target org.macports.build returned: shell command cd /opt/local/var/macports/build/_opt_local_var_macports_sources_rsync.macports.org_release_ports_archivers_bzip2/work/bzip2-1.0.5 && make all PREFIX= /opt/local returned error 127Command output: sh: line 1: make: command not foundError: The following dependencies failed to build: bzip2 expat fontconfig freetype zlib libiconv gperf jpeg libpng libxml2 pkgconfig tiff xorg-libXext xorg-libX11 xorg-bigreqsproto xorg-inputproto xorg-kbproto xorg-libXau xorg-xproto xorg-libXdmcp xorg-xcmiscproto xorg-xextproto xorg-xf86bigfontproto xorg-xtrans xorg-libXt autoconf help2man gettext ncurses ncursesw p5-locale-gettext perl5 perl5.8 m4 automake libtool xorg-libsm xorg-libice xorg-util-macrosError: Status 1 encountered during processing.Thank you for your help in advance!

    1. It looks like you have dependency issues. BTW the ImageMagick website provides already compiled binaries for Lion (which still miss the ‘identify’ utility). Another option is to install a Linux VM (via Parallels, VMWare Fusion, or VirtualBox) and to install IM via a package repository manager. I’ve never tried Mac Ports sorry 🙁

      1. I’m also having isuses installing ImageMagick on OS X 10.6.2 with MAMP 1.8.4I know the issue is related to MAMP running in 32-bit universal mode and ImageMagick being built in 64-bit when you use sudo port install ImageMagick . To overcome this, I’ve edited /opt/local/etc/macports/variants.conf and added +universal to the last line of the file. This causes universal compilations to take place. So I know my dynamic libraries are universal.Problem is, all my make builds are still defaulting to 64-bit. So when I check imagick.so, it still shows up as imagick.so: Mach-O 64-bit bundle x86_64 This causes my PHP log to show:PHP Warning: PHP Startup: Unable to load dynamic library /Applications/MAMP/bin/php5/lib/php/extensions/no-debug-non-zts-20060613/imagick.so’ (null) in Unknown on line 0So I know MAMP cannot load the .so file because it is incompatible. I’m still trying to compile in universal mode, but running into way too many road blocks.Anyone got any pointers?

        1. Hi Daniele, I haven’t tried the MacPorts stuff sorry :(. It looks like you should check your linker options imho, or upgrade to Lion which I was unfortunately forced to do to be able to build for iOS 5.1.1 (no Xcode 4.3 for SL…hurray for Apple upgrade policy).

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *