viernes, 23 de marzo de 2012

Escalar imagen con Swing, Zoom a una imagen

Vamos a ver como agrandar o reducir el zoom de una imagen con Swing. Esta entrada es una sugerencia de un compañero, por lo que aprovecho para deciros que si tenéis alguna sugerencia sobre lo que os gustaría que escribiésemos no tenéis más que decirlo.

Antes de explicar como lo hemos hecho, informamos de que desde el JDK1.1 la clase Image tiene un método de conveniencia llamado getScaledInstace() que devolverá una versión a escala de la imagen de acuerdo a las dimensiones proporcionadas.

Esta método además acepta un "hint" de los 5 que exponemos a continuación:

  • SCALE_REPLICATE: Mucho rendimiento, poca calidad, podemos obtener imágenes pixeladas
  • SCALE_FAST: Es sinónimo de SCALE_REPLICATE.
  • SCALE_AREA_AVERAGING: Es más lento que los anteriores pero proporciona más calidad.
  • SCALE_SMOOTH: Es sinónimo de SCALE_AREA_AVERAGING.
  • SCALE_DEFAULT: Tomaremos el valor por defecto, que será SCALE_FAST.


Más adelante con la introducción del JDK1.2, se nos ofrecieron clases como la BufferredImage y Graphics2D y unos “hint” más flexibles gracias a RenderingHints. A diferencia de los antiguos "hints" estos nos proporcionan un mayor control sobre el tratamiento de las imágenes cuando llamamos a Graphics2D.drawImage(). Estas claves ya las explicamos con anterioridad por lo que si queréis saber más sobre esto es conveniente que reviséis la entrada sobre RenderingHints que escribimos con anterioridad.

RenderingHints.KEY_INTERPOLATION:

  • VALUE_INTERPOLATION_NEAREST_NEIGHBOR: Mucho rendimiento, pero baja calidad.
  • VALUE_INTERPOLATION_BILINEAR: Es algo más lento pero proporciona mayor calidad.
  • VALUE_INTERPOLATION_BICUBIC: Es similar a BILINEAR pero utiliza más muestras a la hora de filtrar las imágenes y suele ofrecer más calidad.

RenderingHints.KEY_RENDER_QUALITY: 

  • VALUE_RENDER_SPEED: Significa “Prefiero velocidad antes que calidad, pero lo dejo en tus manos”, es sinónimo de VALUE_INTERPOLATION_NEAREST_NEIGHBOR.
  • VALUE_RENDER_QUALITY: “Prefiero calidad antes que velocidad, lo dejo en tus manos”, sería como usar VALUE_INTERPOLATION_BILINEAR.
  • VALUE_RENDER_DEFAULT: Significa “no me importa, hazlo sin más”, sinónimo de VALUE_RENDER_SPEED.


Usando RenderintHints, BufferedImage y Graphics2D obtendremos mejores resultados, más velocidad y calidad, por lo que será el método que utilizaremos en nuestro ejemplo.

Pasamos a mostrar el código y dado que es algo complejo explicaremos que hace cada método. En principio simplemente crearemos un panel con un par de botones para aumentar y reducir el nivel de zoom aplicado, y un panel donde pintaremos nuestra imagen con el zoom deseado.
Además de esto necesitamos algún método que nos transforme nuestro objeto Image a BufferredImage, para ello nos serviremos de toBufferredImage(Image imagen) y hasAlpha(Image imagen). Este último nos dirá si la imagen contiene píxeles transparentes ya que la manera de crear la imagen en este caso será distinta.

Después de esto solo nos queda sobreescribir el método paintComponent() del panel donde mostraremos la imagen para utilizar los parámetros de nivel de zoom y claves de renderizado que elegimos y ya lo tendremos todo armado.

package imageZoom;

import java.awt.BorderLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.HeadlessException;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.Transparency;
import java.awt.event.ActionEvent;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.PixelGrabber;
import java.net.URL;

import javax.swing.AbstractAction;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JPanel;

public class ZoomPanel extends JPanel {

 private BufferedImage originalImage;

 public ZoomPanel(String pathToImage) {
  originalImage = toBufferedImage(createImage(pathToImage).getImage());
  initPanel();
 }

 private void initPanel(){
  final Zoom zoom = new Zoom();

  JButton plus = new JButton(new AbstractAction("+") {

   @Override
   public void actionPerformed(ActionEvent e) {
    zoom.increaseZoom();
   }
  });
  JButton minus = new JButton(new AbstractAction("-") {

   @Override
   public void actionPerformed(ActionEvent e) {
    zoom.decreaseZoom();
   }
  });


  this.setLayout(new BorderLayout());
  this.add(minus, BorderLayout.WEST);
  this.add(zoom, BorderLayout.CENTER);
  this.add(plus, BorderLayout.EAST);

 }

 private class Zoom extends JPanel {

  private float xScaleFactor = 1;
  private float yScaleFactor = 1;

  public void increaseZoom() {
   xScaleFactor+= 0.1;
   yScaleFactor+= 0.1;
   repaint();
  }

  public void decreaseZoom() {
   xScaleFactor-= 0.1;
   yScaleFactor-= 0.1;
   repaint();
  }


  @Override
  public void paintComponent(Graphics g) {
   Graphics2D g2 = (Graphics2D) g;
   int newW = (int) (originalImage.getWidth() * xScaleFactor);
   int newH = (int) (originalImage.getHeight() * yScaleFactor);
   g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
   g2.clearRect(0, 0, getWidth(), getHeight());
   g2.drawImage(originalImage, 0, 0, newW, newH, null);
  }

 }

 private ImageIcon createImage(String path) {
  URL imgURL = getClass().getResource(path);
     if (imgURL != null) {
         return new ImageIcon(imgURL);
     } else {
         System.err.println("Couldn't find file: " + path);
         return null;
     }
 }


 /**
  * Obtiene una BufferedImage a partir de una Image.
  * return BufferredImage bi
  */
 public static BufferedImage toBufferedImage(Image image) {
  if (image instanceof BufferedImage) {
   return (BufferedImage) image;
  }

  image = new ImageIcon(image).getImage();

  boolean hasAlpha = hasAlpha(image);

  BufferedImage bimage = null;
  GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
  try {

   int transparency = Transparency.OPAQUE;
   if (hasAlpha) {
    transparency = Transparency.BITMASK;
   }

   GraphicsDevice gs = ge.getDefaultScreenDevice();
   GraphicsConfiguration gc = gs.getDefaultConfiguration();
   bimage = gc.createCompatibleImage(image.getWidth(null), image.getHeight(null), transparency);
  } catch (HeadlessException e) {
   // The system does not have a screen
  }

  if (bimage == null) {
   int type = BufferedImage.TYPE_INT_RGB;
   if (hasAlpha) {
    type = BufferedImage.TYPE_INT_ARGB;
   }
   bimage = new BufferedImage(image.getWidth(null), image.getHeight(null), type);
  }

  Graphics g = bimage.createGraphics();

  g.drawImage(image, 0, 0, null);
  g.dispose();

  return bimage;
 }

 /**
  * Devuelve true si una imagen tiene pixeles transparentes.
  *
  * @param image a testear
  * @return true si tiene pixeles transparentes
  */
 public static boolean hasAlpha(Image image) {

  if (image instanceof BufferedImage) {
   BufferedImage bimage = (BufferedImage) image;
   return bimage.getColorModel().hasAlpha();
  }

  PixelGrabber pg = new PixelGrabber(image, 0, 0, 1, 1, false);
  try {
   pg.grabPixels();
  } catch (InterruptedException e) {
  }

  ColorModel cm = pg.getColorModel();
  return cm.hasAlpha();
 }

}

Lo que obtendríamos sería algo similar a esto que os muestro.




Para lanzar la aplicación de ejemplo, lanzar el código siguiente sustituyendo "images/ER.jpg" por una imagen de vuestro equipo y lo podréis ver en acción.

package imageZoom;

import java.awt.Dimension;

import javax.swing.JFrame;

public class Launch {

 private static void createAndShowGUI() {
  JFrame frame = new JFrame("Zoom a imagen");
  frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

  ZoomPanel zoom = new ZoomPanel("images/ER.jpg");

  frame.add(zoom);
  frame.setPreferredSize(new Dimension(485,300));

  frame.pack();
  frame.setVisible(true);
 }

 public static void main(String[] args) {
  // Schedule a job for the event dispatch thread:
  // creating and showing this application's GUI.
  javax.swing.SwingUtilities.invokeLater(new Runnable() {
   public void run() {
    createAndShowGUI();
   }
  });
 }

}


Saludos y hasta pronto, ya sabéis que se agradecen los comentarios.

3 comentarios:

Anónimo dijo...

Gracias amigo excelente aporte

el que sabe sabe!

Anónimo dijo...

Cola

Unknown dijo...

Saludos, se podrá centra la imagen?

Publicar un comentario