lunes, 16 de septiembre de 2013

Java: crear aplicación para procesamiento digital de imágenes. Procesamiento digital de imágenes III.

Ahora que ya hemos visto cómo se abre una imagen desde archivo (con un JFileChooser), y cómo pasarla a escala de grises (visto en esta entrada), vamos a empezar a crear una nueva aplicación a la que iremos añadiendo funcionalidades. Lo primero es crear un proyecto nuevo en Netbeans (lo llamaremos JavaImagenes), le vamos a añadir 4 clases. La primera la denominaremos BaseImagenes, a la segunda AbrirImagen, la tercera CambiarFormatos y por último TransformarImagen. En qué consistirán, ahora lo explicamos. Primero vamos a ver tres imágenes de cómo añadir el package y las clases a nuestro proyecto.

Añadiendo package

Añadiendo clases

Clases creadas

BaseImagenes


Esta será la clase base de todas (excepto de CambiarFormatos), es decir, la clase BaseImagenes será la clase padre de AbrirImagen y TransformarImagen, y, ¿para qué queremos que sea así? Pues para ir almacenando todas las imágenes abiertas o transformadas que vayan pasando por las clases AbrirImagen y TransformarImagen para así tenerlas disponibles, y posteriormente poder deshacer/rehacer imagen.
Esta clase tendrá los siguientes métodos y propiedades.

Propiedades:
  • todasImagenes (private): esta propiedad será un Arraylist de BufferedImage y almacenará todas las imágenes que se vayan abriendo o que sufran alguna transformación (escala de grises, blanco y negro, etc.). El listado es accesible desde el método getTodasImagenes.
  • infoTodasImagenes (private): esta propiedad es un Arraylist de String y está relacionado con la propiedad anterior, ya que irá almacenando la información relativa a la transformación sufrida por la imagen almacenada. Por ejemplo, cuando se almacene una imagen (dentro de todasImagenes) porque se ha transformado en blanco y negro, también se almacenará en esta propiedad (infoTodasImagenes) un String informando que la imagen se ha transformado a blanco y negro. El listado es accesible desde el método getInfoTodasImagenes.
  • contadorImagen (private): esta propiedad sirve para ir llevando constancia de en qué posición estamos actualmente en los Arraylist anteriores. Es una variable entera (int).

Métodos/Funciones:
  • actualizarImagen (protected): este método se utilizar para almacenar la imagen transformada y la información en los Arraylist todasImagenes e infoTodasImagenes. Recibe dos parámetros, la imagen (BufferedImage) y la información de la transformación (String).
  • deshacerImagen (public): devuelve la imagen (BufferedImage) anterior a la actual. Esta imagen se extrae de la propiedad todasImagenes.
  • rehacerImagen (public): devuelve la imagen (BuffereImage) posterior a la actual. Esta imagen se extrae de la propiedad todasImagenes.
  • informacionImagenActual (public): devuelve la información correspondiente a la imagen actual. Esta función se debe llamar justo después de hacer la llamada a la función deshacerImagen/rehacerImagen, para saber a qué corresponde la imagen.
Esta clase de lo que trata es de ir almacenando todas las imágenes (con su correspondiente explicación) para que luego éstas sean accesibles desde el exterior de las clases, en nuestro caso, desde nuestra aplicación con interfaz de usuario. El código fuente es el siguiente:

package ClasesImagenes;

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

/**
 *
 * @author Luis Marcos
 */
public class BaseImagenes {
 
    private static ArrayList<BufferedImage> todasImagenes=new ArrayList<>();
    private static ArrayList<String> infoTodasImagenes=new ArrayList<>();
    static private int contadorImagen=-1;

    /**
     * Devuelve el listado completo de todas las imágenes
     */
    public static ArrayList<BufferedImage> getTodasImagenes() {
        return todasImagenes;
    }

    /**
     * Devuelve el listado completo de toda la información de las imágenes almacenadas
     * en todasImagenes
     * @return
     */
    public static ArrayList<String> getInfoTodasImagenes() {
        return infoTodasImagenes;
    }
 
    /**
     * Almacena una imagen junto con su información para que luego sea accesible
     * @param imagen imagen que se quiere almacenar
     * @param informacion información de la imagen almacenada
     */
    protected void actualizarImagen(BufferedImage imagen, String informacion){
        todasImagenes.add(imagen);
        infoTodasImagenes.add(informacion);
        BaseImagenes.contadorImagen+=1;
    }
 
    /**
     * Retorna la imagen anterior con respecto a la actual
     * En caso de no haber imagen posterior, retorna null
     */
    public BufferedImage deshacerImagen(){
        //Comprueba si hay imágenes anteriores a la actual
         if((BaseImagenes.contadorImagen)>0){
            BaseImagenes.contadorImagen-=1;
            return BaseImagenes.todasImagenes.get(BaseImagenes.contadorImagen);
        }else{
            return null;
        }
     
    }
 
    /**
     * Retorna la imagen posterior a la actual.
     * En caso de no haber imagen posterior, retorna null
     * @return
     */
    public BufferedImage rehacerImagen(){
        //Comprueba si hay imágenes posteriores a la actual
        if((BaseImagenes.contadorImagen)<BaseImagenes.todasImagenes.size()-1){
            BaseImagenes.contadorImagen+=1;
            return BaseImagenes.todasImagenes.get(BaseImagenes.contadorImagen);
        }else{
            return null;
        }
    }
 
    /**
     * Devuelve la información de la imagen actual
     */
    public String informacionImagenActual(){
        return BaseImagenes.infoTodasImagenes.get(BaseImagenes.contadorImagen);
    }
}

AbrirImagen

Esta clase se extiende (es hija) de la clase anterior (BaseImagenes), y aquí lo que conseguimos es abrir una imagen local o a través de una URL. Además de esto, cada vez que se haya abierto una imagen, se llamará al método actualizarImagen, de la clase anterior (BaseImagenes), para que quede almacenada y luego sea accesible. Los métodos de la clase son los siguientes.

Métodos
  • errorImagen (private): muestra un mensaje de error en pantalla en caso de que no se pueda abrir la imagen.
  • abrirJFileChooser (private): muestra el cuadro de diálogo para cargar una imagen local, y devuelve true si se ha seleccionado algún archivo y false en caso de que se cancele.
  • abrirImagenLocal (public): muestra un cuadro de diálogo para abrir una imagen. En caso de que la imagen seleccionada sea correcta, devuelve un BufferedImage de la misma. Devuelve null si no se ha podido cargar.
  • abrirImagenURL (public): devuelve un BufferedImage a partir de una URL que contenga una imagen. En caso de que no se pueda cargar, devuelve null.
Esta clase, como hemos dicho, de lo que se encarga es de abrir una imagen ya sea a partir de una URL o desde archivo. El código fuente es el siguiente:

package ClasesImagenes;

import java.awt.image.BufferedImage;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;

/**
 *
 * @author Luis Marcos
 */
public class AbrirImagen extends BaseImagenes {
     private JFileChooser selectorImage;
     /**
      * Muestra un mensaje de error avisando que no se ha podido cargar la imagen
      */
     private void errorImagen(){
          JOptionPane.showMessageDialog(null,"No se pudo cargar la imagen","Error",JOptionPane.ERROR_MESSAGE);
     }
     
     /**
      * Carga el JFIleChooser para seleccionar imagen
      * @return devuelve false si la acción fue cancelada por el usuario y true si
      * ha seleccionado algún archivo
      */
     private boolean abrirJFileChooser(){
        this.selectorImage=new JFileChooser();
        this.selectorImage.setDialogTitle("Choose an image");
        int flag=this.selectorImage.showOpenDialog(null);
        if (flag==JFileChooser.APPROVE_OPTION){
            return true;
        }else{
           return false;
        }
    }   
     
     /**
      * Muestra un cuadro de diálogo para abrir una imagen desde archivo
      * @return devuelve la imagen seleccionada o null si no se pudo cargar
      */
    public BufferedImage abrirImagenLocal(){
        BufferedImage imagenRetorno=null;
        if(this.abrirJFileChooser()==true){
            try {
                imagenRetorno = ImageIO.read(this.selectorImage.getSelectedFile());
                if (imagenRetorno!=null){
                    super.actualizarImagen(imagenRetorno, "Imagen abierta desde archivo");
                }else{
                    errorImagen();
                }
            } catch (Exception e) {
                errorImagen();
            }
        }        
        return imagenRetorno;
    }
    
    /**
     * Carga una imagen a partir de una URL de internet
     * @param URLimagen URL de la imagen que se desea abrir
     * @return devuelve la imagen obtenida o null si no se pudo cargar
     */
    public BufferedImage abrirImagenURL(String URLimagen){
        BufferedImage imagenRetorno=null;
        try {
            URL url = new URL(URLimagen);
            imagenRetorno = ImageIO.read(url);
            super.actualizarImagen(imagenRetorno, "Imagen abierta desde URL: " + URLimagen);
        } catch (Exception e) {
            errorImagen();
        }
        return imagenRetorno;
    }
    
}

CambiarFormatos 

Esta clase ya se la hemos mostrado en otra entrada (aquí), y lo que hace es poder transformar objetos de tipo BufferedImage, Image, ImageIcon e Icon entre ellos. Sin más comentarios, os dejo el código.

package ClasesImagenes;

import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.image.BufferedImage;
import javax.swing.Icon;
import javax.swing.ImageIcon;
 
/**
 *
 * @author Luis Marcos
 */
public class CambiarFormatos{     
 
    public Image bufferedImageToImage(BufferedImage bufferImage){
        Image imgReturn = (Image)bufferImage;
        return imgReturn;
    }
 
    public ImageIcon bufferedImageToImageIcon(BufferedImage bufferImage){
        ImageIcon imgIconReturn = new ImageIcon(bufferImage);
        return imgIconReturn;
    }
 
 
    public Icon bufferedImageToIcon(BufferedImage bufferImage){
        ImageIcon imgIcon=new ImageIcon(bufferImage);
        Icon iconReturn = (Icon)(imgIcon);
        return iconReturn;
    }
 
      
  
    public BufferedImage iconToBufferedImage(Icon icon){
      BufferedImage bufferImage=(BufferedImage)this.iconToImage(icon);
      return bufferImage;
    }
     
      /**
      * Convierte un Icon en Image https://groups.google.com/forum/#!topic/comp.lang.java.programmer/OI_IdebPL68
      */
    public Image iconToImage(Icon icon) {
          if (icon instanceof ImageIcon) {
              return ((ImageIcon)icon).getImage();
          } else {
              int w = icon.getIconWidth();
              int h = icon.getIconHeight();
              GraphicsEnvironment ge = 
                GraphicsEnvironment.getLocalGraphicsEnvironment();
              GraphicsDevice gd = ge.getDefaultScreenDevice();
              GraphicsConfiguration gc = gd.getDefaultConfiguration();
              BufferedImage image = gc.createCompatibleImage(w, h);
              Graphics2D g = image.createGraphics();
              icon.paintIcon(null, g, 0, 0);
              g.dispose();
              return image;
          }
      }
     
   
    public ImageIcon iconToImageIcon(Icon icon){
        ImageIcon imageIconRetur=new ImageIcon(this.iconToImage(icon));
        return imageIconRetur;
    }
     
    public BufferedImage imageToBufferedImage(Image image){
        BufferedImage bufferReturn=(BufferedImage)image;
        return bufferReturn;
    }
     
    public ImageIcon imageToImageIcon(Image image){
        ImageIcon imageIconReturn=new ImageIcon(image);
        return imageIconReturn;
    }
 
    public Icon imageToIcon(Image image){
        ImageIcon imgIcon=new ImageIcon(image);
        Icon iconReturn=(Icon)imgIcon;
        return iconReturn;
    }
     
    public BufferedImage imageIconToBufferedImage(ImageIcon imageIcon){
        BufferedImage bufferReturn=(BufferedImage)((Image)imageIcon.getImage());
        return bufferReturn;
    }
     
    public Image imageIconToImage(ImageIcon imageIcon){
        Image imgReturn=(Image)imageIcon.getImage();
        return imgReturn;
    }
     
    public Icon imageIconToIcon(ImageIcon imageIcon){
        Icon iconReturn=(Icon)imageIcon;
        return iconReturn;
    }
}

TransformarImagen

En esta clase (extendida de BaseImagenes) se harán las transformaciones a las imágenes. En esta entrada, vamos a incluir, escala de grises, blanco y negro, invertir imagen y filtros (rojo, verde, azul). Y como hemos puesto de manifiesto anteriormente, además de hacer la transformación, se va a almacenar la imagen con la información de la transformación, a través del método actualizarImagen, de la clase base. En las posteriores entradas iremos añadiendo más transformaciones. A continuación explicaremos un poco las transformaciones:

Métodos
  • colorRGBaSRGB (private): devuelve, a partir de una variable Color, el entero correspondiente al sistemaSRGB.
  • clonarBufferedImage (private): crea una copia exacta del BufferedImage recibido.
  • calcularMediaColor (private): recibe una variable de tipo Color, y calcula la media de las tres componentes (RGB), devolviendo un entero con dicho valor.
  • chequearUmbral (private): recibe una variable de tipo Color y una variable de tipo int (umbral). Comprueba si el color recibido es mayor o igual que el umbral, y en caso afirmativo devuelve una variable Color blanco (255,255,255), en caso contrario negra (0,0,0).
  • blancoNegro (public): transforma la imagen recibida, en función del umbral seleccionado (entre 0 y 255), a blanco y negro.
  • escalaGrises (public): transforma la imagen recibida a escala de grises, calculando, para cada píxel, la media de sus tres componentes RGB.
  • invertirImagen (public): invierte los colores de la imagen, calculando, para cada píxel, 255 menos el canal seleccionado (RGB).

Como vemos, lo único que hay que hacer es enviar un BufferedImage a la función que queramos, y ésta nos devolverá otro BufferedImage con la imagen transformada.
El siguiente paso será crear la interfaz de usuario en la que utilizaremos las clases creadas.

Añadir Formulario

Una vez hecho, vamos a añadir los controles y el resultado sería algo así.

Resultado formulario

El código fuente de este formulario es:

package javaimagenes;

import ClasesImagenes.AbrirImagen;
import ClasesImagenes.BaseImagenes;
import ClasesImagenes.CambiarFormatos;
import ClasesImagenes.TransformarImagen;
import java.awt.image.BufferedImage;

/**
 *
 * @author Luis Marcos
 */
public class FormPrincipal extends javax.swing.JFrame {
    
    //Creamos los objetos que manejeramos en los eventos de los diferentes controles
    //Los objetos están instanciados
    BaseImagenes ObjBase=new BaseImagenes();
    AbrirImagen ObjAbrir=new AbrirImagen();
    CambiarFormatos ObjCambiarFormat=new CambiarFormatos();
    TransformarImagen ObjTransformaImg=new TransformarImagen();
    
    public FormPrincipal() {
        initComponents();
    }

    
    private void ButtonAbrirActionPerformed(java.awt.event.ActionEvent evt) {                                            
        BufferedImage imagen=ObjAbrir.abrirImagenLocal();
        if (imagen!=null){
            jLabel_Imagen.setIcon(ObjCambiarFormat.bufferedImageToIcon(imagen));
        }
    }                                           

    private void ButtonAbrirURLActionPerformed(java.awt.event.ActionEvent evt) {                                               
        BufferedImage imagen=ObjAbrir.abrirImagenURL(TextField_URL.getText());
        if (imagen!=null){
            jLabel_Imagen.setIcon(ObjCambiarFormat.bufferedImageToIcon(imagen));
        }
    }                                              

    private void ButtonBlancoNegroActionPerformed(java.awt.event.ActionEvent evt) {                                                  
        BufferedImage imagen=ObjCambiarFormat.iconToBufferedImage(jLabel_Imagen.getIcon());
        imagen=ObjTransformaImg.blancoNegro(imagen, 128);
        jLabel_Imagen.setIcon(ObjCambiarFormat.bufferedImageToIcon(imagen));
    }                                                 

    private void ButtonEscalaGrisesActionPerformed(java.awt.event.ActionEvent evt) {                                                   
        BufferedImage imagen=ObjCambiarFormat.iconToBufferedImage(jLabel_Imagen.getIcon());
        imagen=ObjTransformaImg.escalaGrises(imagen);
        jLabel_Imagen.setIcon(ObjCambiarFormat.bufferedImageToIcon(imagen));
    }                                                  

    private void ButtonInvertirActionPerformed(java.awt.event.ActionEvent evt) {                                               
        BufferedImage imagen=ObjCambiarFormat.iconToBufferedImage(jLabel_Imagen.getIcon());
        imagen=ObjTransformaImg.invertirImagen(imagen);
        jLabel_Imagen.setIcon(ObjCambiarFormat.bufferedImageToIcon(imagen));
    }                                              

    private void ButtonFiltroRojoActionPerformed(java.awt.event.ActionEvent evt) {                                                 
        BufferedImage imagen=ObjCambiarFormat.iconToBufferedImage(jLabel_Imagen.getIcon());
        imagen=ObjTransformaImg.filtroRojo(imagen);
        jLabel_Imagen.setIcon(ObjCambiarFormat.bufferedImageToIcon(imagen));
    }                                                

    private void ButtonFiltroVerdeActionPerformed(java.awt.event.ActionEvent evt) {                                                  
        BufferedImage imagen=ObjCambiarFormat.iconToBufferedImage(jLabel_Imagen.getIcon());
        imagen=ObjTransformaImg.filtroVerde(imagen);
        jLabel_Imagen.setIcon(ObjCambiarFormat.bufferedImageToIcon(imagen));
    }                                                 

    private void ButtonFiltroAzulActionPerformed(java.awt.event.ActionEvent evt) {                                                 
        BufferedImage imagen=ObjCambiarFormat.iconToBufferedImage(jLabel_Imagen.getIcon());
        imagen=ObjTransformaImg.filtroAzul(imagen);
        jLabel_Imagen.setIcon(ObjCambiarFormat.bufferedImageToIcon(imagen));
    }                                                

    private void ButtonDeshacerActionPerformed(java.awt.event.ActionEvent evt) {                                               
        BufferedImage imagen=ObjBase.deshacerImagen();
        if (imagen!=null){
            jLabel_Imagen.setIcon(ObjCambiarFormat.bufferedImageToIcon(imagen));
            TextField_Info.setText(ObjBase.informacionImagenActual());
        }
    }                                              

    private void ButtonRehacerActionPerformed(java.awt.event.ActionEvent evt) {                                              
        BufferedImage imagen=ObjBase.rehacerImagen();
        if (imagen!=null){
            jLabel_Imagen.setIcon(ObjCambiarFormat.bufferedImageToIcon(imagen));
            TextField_Info.setText(ObjBase.informacionImagenActual());
        }
    }                                             
       
}

De lo que se trata, en resumen es, de al evento clic de cada botón asignar la transformación u operación que queramos realizar.
El siguiente paso es eliminar la clase de inicio de nuestro proyecto (o bien abrir nuestro formulario desde el main de esa clase) y ejecutar nuestro proyecto.

Eliminamos clase inicial


Asignamos el inicio de la aplicación a nuestro formulario

Ejecutamos y ya tenemos nuestra aplicación funcionando, el resultado es el siguiente.

Resultado final

Si has llegado leyendo hasta aquí, enhorabuena, ha sido una entrada un poco larga, y podrías plantearte, ¿y tanto código para esto? Tienes razón, se podía haber resumido bastante, pero lo que hemos tratado es de obtener las bases para desarrollar nuestra aplicación, es decir, con esto, podemos ir añadiendo diferentes transformaciones a la clase TransformarImagen de una forma muy sencilla.
Como siempre digo, el código se podría mejorar haciendo cambios que faciliten la reutilización de código, pero creo que así queda más claro.
Puedes descargar el proyecto aquí. Cualquier duda/crítica/sugerencia, como siempre, en los comentarios.

10 comentarios:

  1. Hola, buenas tardes..necesito ayuda con el tratamiento de imagenes en netbeans... cómo puedo conocer el rgb de una imagen? o.O

    ResponderEliminar
  2. que tal amigo tengo un problema me gustaria guardar la imagen en la base de datos postgres y despues llamarlo de vuelta y acer uso de ello ejemplo la foto de un cliente...te agardecere mucho si me puedes ayudar...

    ResponderEliminar
  3. de casualidad tienes algún proyecto para hacer que la imagen se haga como efecto espejo ? gracias

    ResponderEliminar
    Respuestas
    1. Es bastante simple. Si tienes una imagen de 400 píxeles de ancho, tienes que hacer que el píxel 1 pase a ser el 400, el píxel 2 pase a ser el 399, y así sucesivamente.
      Un saludo.

      Eliminar
  4. Hola buenas tardes tengo que hacer un programa que cifre imágenes el primer paso es asignarle una imagen a dos variables pero la imagen debe de recorrer sus pixeles ya que después esta matriz de pixeles la usare en un algoritmo agradecería me pudieras ayudar con el código

    ResponderEliminar
    Respuestas
    1. Aquí explico cómo leer valores de una imagen
      http://algoimagen.blogspot.com.es/2013/08/java-abrir-imagen-leer-pixeles-y-pasar.html

      Eliminar
    2. muchas gracias!!! ahora tengo un problema más, ya intente correr el programa pero al añadir el código de abrir imagen y pasar escala a grises me marca en error en ObjProcesamiento me dice can not find symbol!! como puedo corregir ese error??

      Eliminar
  5. hola que tal , quisiera saber si como poder calcular los colores en RGB cada vez que el cursor o puntero que pase sobre una area de una imagen

    ResponderEliminar
  6. Hola!! oye de casualidad no sabes como rotar una imagen a 90° o 180°?? gracias.

    ResponderEliminar
  7. Hola!! esta muy bien tu proyecto solo una duda.. como obtengo la imagen del arreglo desde el Formprincipal? gracias

    ResponderEliminar