viernes, 23 de marzo de 2012

Escalar imagen con Swing, Zoom a una imagen

3 comentarios
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.

miércoles, 21 de marzo de 2012

GradientPaint demo, ejemplos del uso de gradientes en java

4 comentarios
Hoy hablaremos de los gradientes o degradados de color tan usados como relleno del fondo en multitud de situaciones.
Para ponernos en situación explicaremos primero que es un gradiente.
Básicamente un gradiente nos permite rellenar una forma con una degradación lineal del color. Es decir, si tenemos un punto A con un color X y un punto B con otro color Y, el espacio entre ambos puntos va cambiando progresivamente de color desde el color X al color Y.

Una vez aclarado esto, paso a enseñaros unos ejemplos del uso de gradientes. Para crearlos haremos uso de la clase GradientPaint que nos permite aplicar esta degradación de color entre 2 puntos que explicábamos arriba.
Los constructores de esta clase nos permiten especificar los 2 puntos y los 2 colores que utilizaremos para crear el gradiente.

GradientPaint gp = new GradientPaint(Point2D pt1, Color color1, Point2D pt2, Color color2, boolean cyclic);

Estos puntos podemos especificarlos mediante un objeto Point o bien mediante coordenadas:

GradientPaint gp = new GradientPaint(float x1, float y1, Color color1, float x2, float y2, Color color2, boolean cyclic);

El último parámetro indica si el gradiente debe repetirse en todo el espacio de manera cíclica o si fuera del espacio contenido entre ambos puntos el color debe ser sólido.

Pasamos ahora a ver algunos ejemplos del uso de gradientes.

Gradiente horizontal

package gradientPaintDemo;

import java.awt.Color;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;

import javax.swing.JPanel;

public class HorizontalGradientPanel extends JPanel {

 @Override
 public void paint(Graphics g) {
  Graphics2D g2d = (Graphics2D) g;

  /*
   * Para crear un gradiente horizontal,
   * las coordenadas a usar seran del {0,0} al {anchura del componente, 0}
   */
  GradientPaint horizontalGradient = new GradientPaint(0, 0, Color.RED, getWidth(), 0, Color.BLUE);
  g2d.setPaint(horizontalGradient);

  g2d.fillRect(0, 0, getWidth(), getHeight());

 }

}

Gradiente vertical

package gradientPaintDemo;

import java.awt.Color;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;

import javax.swing.JPanel;

public class VerticalGradientPanel extends JPanel {

 @Override
 public void paint(Graphics g) {
  Graphics2D g2d = (Graphics2D) g;

  /*
   * Para crear un gradiente vertical,
   * las coordenadas a usar seran del {0,0} al {0, altura del componente}
   */
  GradientPaint verticalGradient = new GradientPaint(0, 0, Color.GREEN, 0, getHeight(), Color.YELLOW);
  g2d.setPaint(verticalGradient);

  g2d.fillRect(0, 0, getWidth(), getHeight());

 }

}

Gradiente diagonal


package gradientPaintDemo;

import java.awt.Color;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;

import javax.swing.JPanel;

public class DiagonalGradientPanel extends JPanel {

 @Override
 public void paint(Graphics g) {
  Graphics2D g2d = (Graphics2D) g;

  /*
   * Para crear un gradiente diagonal,
   * las coordenadas a usar seran del {0,0} al {anchura del componente / ,
   * altura del componente / 2}
   */
  GradientPaint diagonalGradient = new GradientPaint(0, 0, Color.GRAY, getWidth() / 2, getHeight() / 2, Color.ORANGE);
  g2d.setPaint(diagonalGradient);

  g2d.fillRect(0, 0, getWidth(), getHeight());

 }

}

Gradiente redondo
Este degradado es un tanto especial, no usaremos la clase GradientPaint para crearlo, en lugar de eso implementaremos nuestro propio Paint. La interfaz Paint define como generar los patrones de color usados para operar con Graphics2D. Este ejemplo no es tan fácil de entender como los demás que exponemos en esta entrada, pero podéis reutilizar el código a placer.
package gradientPaintDemo;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.PaintContext;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.ColorModel;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;

import javax.swing.JPanel;

public class RoundGradientPanel extends JPanel {

 @Override
 public void paint(Graphics g) {
  Graphics2D g2d = (Graphics2D) g;

  /* Para crear un gradiente circular. */
  RoundGradient roundGradient = new RoundGradient(getWidth() / 2, getHeight() / 2, Color.PINK, new Point(0, 100), Color.CYAN);
  g2d.setPaint(roundGradient);

  g2d.fillRect(0, 0, getWidth(), getHeight());
 }

 private class RoundGradient implements Paint {

  protected Point2D point;

  protected Point2D mRadius;

  protected Color color1, color2;

  public RoundGradient(double x, double y, Color color1, Point2D radius, Color color2) {
   if (radius.distance(0, 0) <= 0) {
    throw new IllegalArgumentException("Radius must be greater than 0.");
   }
   point = new Point2D.Double(x, y);
   this.color1 = color1;
   mRadius = radius;
   this.color2 = color2;
  }

  @Override
  public int getTransparency() {
   int a1 = color1.getAlpha();
   int a2 = color2.getAlpha();
   return (((a1 & a2) == 0xff) ? OPAQUE
          : TRANSLUCENT);
  }

  @Override
  public PaintContext createContext(ColorModel cm, Rectangle deviceBounds, Rectangle2D userBounds,
    AffineTransform xform, RenderingHints hints) {
   Point2D transformedPoint = xform.transform(point, null);
   Point2D transformedRadius = xform.deltaTransform(mRadius, null);
   return new RoundGradientContext(transformedPoint, color1, transformedRadius, color2);
  }

 }

 private class RoundGradientContext implements PaintContext {
  protected Point2D mPoint;

  protected Point2D mRadius;

  protected Color color1, color2;

  public RoundGradientContext(Point2D p, Color c1, Point2D r, Color c2) {
   mPoint = p;
   color1 = c1;
   mRadius = r;
   color2 = c2;
  }

  public void dispose() {
  }

  public ColorModel getColorModel() {
   return ColorModel.getRGBdefault();
  }

  public Raster getRaster(int x, int y, int w, int h) {
   WritableRaster raster = getColorModel().createCompatibleWritableRaster(w, h);

   int[] data = new int[w * h * 4];
   for (int j = 0; j < h; j++) {
    for (int i = 0; i < w; i++) {
     double distance = mPoint.distance(x + i, y + j);
     double radius = mRadius.distance(0, 0);
     double ratio = distance / radius;
     if (ratio > 1.0)
      ratio = 1.0;

     int base = (j * w + i) * 4;
     data[base + 0] = (int) (color1.getRed() + ratio * (color2.getRed() - color1.getRed()));
     data[base + 1] = (int) (color1.getGreen() + ratio * (color2.getGreen() - color1.getGreen()));
     data[base + 2] = (int) (color1.getBlue() + ratio * (color2.getBlue() - color1.getBlue()));
     data[base + 3] = (int) (color1.getAlpha() + ratio * (color2.getAlpha() - color1.getAlpha()));
    }
   }
   raster.setPixels(0, 0, w, h, data);

   return raster;
  }
 }

}


Gradiente horizontal cíclico

package gradientPaintDemo;

import java.awt.Color;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;

import javax.swing.JPanel;

public class HorizontalCyclicGradientPanel extends JPanel {

 @Override
 public void paint(Graphics g) {
  Graphics2D g2d = (Graphics2D) g;

  /*
   * Para crear un gradiente horizontal cíclico, simplemente especificar
   * la anchura del color
   * y crear el gradiente con la propiedad cíclica a "true"
   */
  GradientPaint diagonalGradient = new GradientPaint(0, 0, Color.RED, 20, 0, Color.BLUE, true);
  g2d.setPaint(diagonalGradient);

  g2d.fillRect(0, 0, getWidth(), getHeight());

 }

}

Gradiente vertical cíclico

package gradientPaintDemo;

import java.awt.Color;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;

import javax.swing.JPanel;

public class VerticalCyclicGradientPanel extends JPanel {

 @Override
 public void paint(Graphics g) {
  Graphics2D g2d = (Graphics2D) g;

  /*
   * Para crear un gradiente horizontal cíclico, simplemente especificar
   * la altura del color
   * y crear el gradiente con la propiedad cíclica a "true"
   */
  GradientPaint diagonalGradient = new GradientPaint(0, 0, Color.GREEN, 0, 20, Color.YELLOW, true);
  g2d.setPaint(diagonalGradient);

  g2d.fillRect(0, 0, getWidth(), getHeight());

 }

}

Este sería el resultado de juntar todos estos ejemplos en un panel.


Para ver todo esto en funcionamiento, aquí os dejo la clase que carga la aplicación.

package gradientPaintDemo;

import java.awt.GridLayout;

import javax.swing.JFrame;
import javax.swing.JPanel;

public class GradientsDemo extends JPanel {

 private static void createAndShowGUI() {
  // Crea y prepara la ventana.
  JFrame frame = new JFrame("Ejemplos de gradientes");
  frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

  frame.getContentPane().setLayout(new GridLayout(3, 3, 10, 10));

  frame.getContentPane().add(new HorizontalGradientPanel());
  frame.getContentPane().add(new VerticalGradientPanel());
  frame.getContentPane().add(new DiagonalGradientPanel());
  frame.getContentPane().add(new RoundGradientPanel());
  frame.getContentPane().add(new HorizontalCyclicGradientPanel());
  frame.getContentPane().add(new VerticalCyclicGradientPanel());

  frame.setSize(400, 400);
  frame.setVisible(true);
 }

 public static void main(String[] args) {
  javax.swing.SwingUtilities.invokeLater(new Runnable() {
   public void run() {
    createAndShowGUI();
   }
  });

 }
}

Eso es todo por hoy, espero que lo aquí expuesto os haya sido útil de alguna manera.

Un saludo

miércoles, 7 de marzo de 2012

Java GUI, Swing o AWT

0 comentarios
Muchas veces ante la necesidad de crear una interfaz gráfica nos vemos en la necesidad de decidir si utilizar Swing o AWT. Bien, veamos similitudes y diferencias entre uno y otro.

Un repaso a AWT

AWT son las siglas de Abstract Window ToolKit, las herramientas originales de Java para crear interfaces gráficas.
Es una librería GUI portable para aplicaciones autónomas y/o applets, proporciona la conexión entre nuestra aplicación y el GUI nativo.

Las prestaciones de AWT incluyen:

  • Un amplio grupo de componentes de usuario
  • Un modelo de manejo de eventos robusto
  • Herramientas gráficas y de imágenes (clases de Formas, colores y fuentes)
  • Manejadores de diseño que no dependen del tamaño de pantalla o resolución
  • Clases de transferencia de datos, para copiar-pegar a través del portapapeles de la plataforma

Los componentes de AWT dependen de componentes de código nativo, por lo que a los componentes se les suele llamar “heavyweight components” (componentes pesados).
AWT está pensado para aplicaciones que corran en navegadores antiguos y definido con los mínimos de cualquier aplicación, es por esto que no incluye componentes complejos como pueden ser tablas, vistas de árbol, barras de progreso y otros.

Un vistazo a Swing


Swing implementa un juego de componentes construidos sobre AWT y además proporciona un “look and feel” conectable/intercambiable. Está escrito 100% en código Java y basado en el framework Lightweight UI de la JDK 1.1.

Sus características incluyen:

  • Todas las prestaciones de AWT
  • Componentes 100% Java de las versiones de los componentes de AWT
  • Un rico conjunto de componentes de alto nivel (listas en árbol, paneles de pestañas, etc...)
  • Un diseño Java puro, no depende de terceros.
  • Look and feel intercambiable

Al no depender de componentes de la plataforma, a los componentes de Swing se les llama “lightweight components”.
Swing es parte de las JFC (Java Foundation Classes), se creó para solucionar muchas de las limitaciones de AWT. Swing se creó como un bien diseñado, flexible y poderoso juego de herramientas GUI.

Si nos paramos a comparar sus componentes, vemos que son bastante similares. Os dejo una tabla con la correspondencia de algunos componentes AWT con su análogo Swing.

AWTSwing
AppletJApplet
FrameJFrame
WindowJWindow
DialogJDialog
ComponentJComponent
PanelJPanel
ButtonJButton
CanvasPanel
CheckboxJCheckBox o JRadioButton
ChoiceJComboBox
LabelJLabel
ListJList
TextAreaJTextArea
TextFieldJTextField
MenuJMenu
MenuItemJMenuItem

Como conclusión, podríamos decir que si queremos que nuestra aplicación corra en cualquier entorno, independientemente de la antigüedad del mismo, deberíamos usar AWT; ahora bien, si lo que queremos es una herramienta potente, flexible, usar tablas y otros componentes complejos; y completamente adaptable a nuestras necesidades, desde luego nuestra decisión está clara, usaremos la tecnología Swing.

Como punto final vamos a ver como crear un programa sencillo, un panel al que añadimos 2 botones, usando ambas tecnologías para poder ver las diferencias.

Ejemplo con AWT
package AWTvsSwing;

import java.applet.Applet;
import java.awt.Button;
import java.awt.FlowLayout;

public class MainAWT extends Applet {

 @Override
 public void init() {

        Button button1 = new Button();

        button1.setLabel("My Button 1");

        Button button2 = new Button("My Button 2");

        setLayout(new FlowLayout());

        add(button1);
        add(button2);
 }

}


Ejemplo con Swing
package AWTvsSwing;

import java.awt.FlowLayout;

import javax.swing.JApplet;
import javax.swing.JButton;

public class MainSwing extends JApplet {

 @Override
 public void init() {
  JButton button1 = new JButton();

        button1.setText("My Button 1");

        JButton button2 = new JButton("My Button 2");

        setLayout(new FlowLayout());

        add(button1);
        add(button2);
 }

}