martes
mar062012
Java Zone Espia Web
Nota del editor: este post describe un sencillo programa Swing que no requiere de absolutamente ninguna librería, y que se conecta con varias cámaras web que publican sus imágenes en Internet. La versión que se describe aquí es una segunda versión creada por el autor mejorando una versión publicada recientemente.
Java Zone Espia Web
Mencionaremos los arreglos hechos con respecto a la primera entrega:
- Se carga la ventana mas rápido.
- Se puede seleccionar una cámara y ver en tamaño completo.
- Las imágenes se extienden al tamaño total de la ventana.
- Mayor velocidad de refresco de la secuencia de imágenes.
- Icono de cámara, que muestra que hay una cámara cargando y que se mostrara en instantes.
Ahora mencionaremos algunos aspectos a mejorar:
- Mayor cantidad de cámaras para ver.
- Que podamos automatizar la búsqueda de cámaras activas en Internet y que el programa las muestre.
- Poder escoger favoritos y que guarde las preferencias por usuario.
- Mejorar la interfaz, mas opciones.
Imagenes
Codigo
Clase Principal
package clases; import java.awt.GridLayout; import java.util.ArrayList; import javax.swing.JFrame; import javax.swing.JPanel; public class Principal extends JFrame { public JPanel panelPrincipal; public ArrayList urls = new ArrayList(); public ArrayList camaras = new ArrayList(); public Principal() { urls.add("http://146.186.123.229/axis-cgi/jpg/image.cgi?resolution=352x240"); urls.add("http://131.111.133.11/axis-cgi/jpg/image.cgi?resolution=480x360&dummy=1267804722739"); urls.add("http://fotogermanoviseu.dyndns.info/axis-cgi/jpg/image.cgi?resolution=640x480&compression=10&color=1&clock=1&date=1"); urls.add("http://198.82.159.134/axis-cgi/jpg/image.cgi?resolution=640x480&dummy=1152818432828"); urls.add("http://98.238.252.97/axis-cgi/jpg/image.cgi?resolution=800x600&dummy=1280477002758"); urls.add("http://80.24.195.19/axis-cgi/jpg/image.cgi?resolution=480x360"); urls.add("http://216.8.159.21/axis-cgi/jpg/image.cgi?resolution=640x480"); urls.add("http://hncam1.hn.psu.edu/axis-cgi/jpg/image.cgi?resolution=320x240"); panelPrincipal = new JPanel(); panelPrincipal.setLayout(new GridLayout(2, 4, 5, 5)); for (int i = 0; i < urls.size(); i++) { PanelCamara pc = new PanelCamara(i,urls.get(i),this); panelPrincipal.add(pc); camaras.add(pc); } add(panelPrincipal); } ... }
Clase PanelCamara
package clases; import java.awt.BorderLayout; import java.awt.Graphics; import java.awt.GridLayout; import java.awt.Image; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.net.MalformedURLException; import java.net.URL; import javax.swing.ImageIcon; import javax.swing.JPanel; public class PanelCamara extends JPanel { String url = ""; int id=0; Image img; Principal prin; HiloFoto hf; boolean seleccionado=false; public PanelCamara(int id,String url, Principal p) { this.url = url; this.prin=p; this.id=id; addMouseListener(new MouseListener() { @Override public void mouseReleased(MouseEvent e) { } @Override public void mousePressed(MouseEvent e) { } @Override public void mouseExited(MouseEvent e) { } @Override public void mouseEntered(MouseEvent e) { } @Override public void mouseClicked(MouseEvent e) { if(seleccionado){ seleccionado=false; }else{ seleccionado=true; } if(seleccionado){ prin.DesactivarHilos(); hf=new HiloFoto(PanelCamara.this); hf.start(); prin.panelPrincipal.removeAll(); prin.panelPrincipal.setLayout(new BorderLayout()); prin.panelPrincipal.add(prin.camaras.get(PanelCamara.this.id)); prin.setBounds(0, 0, 500, 400); prin.setLocationRelativeTo(null); }else{ hf.stop(); hf=null; prin.panelPrincipal.removeAll(); prin.panelPrincipal.setLayout(new GridLayout(2, 4, 5, 5)); prin.AgregarPaneles(); prin.ActivarHilos(); prin.setBounds(0, 0, 600, 250); prin.setLocationRelativeTo(null); } } }); img = new ImageIcon(this.getClass().getResource("/lib/webcam.png")).getImage(); this.setDoubleBuffered(true); IniciarHilo(); } public void PararHilo(){ hf=null; } public void IniciarHilo(){ hf=new HiloFoto(this); hf.start(); } @Override public void paintComponent(Graphics g) { super.paintComponents(g); g.drawImage(img, 0, 0, getWidth(), getHeight(), this); } }
Clase HiloFoto
package clases; import java.awt.Image; import java.net.MalformedURLException; import java.net.URL; import javax.swing.ImageIcon; public class HiloFoto extends Thread{ PanelCamara pc; public HiloFoto(PanelCamara pc){ this.pc=pc; } public void run(){ try { Thread.sleep(500); while(true){ pc.img.flush(); pc.img=new ImageIcon(new URL(pc.url)).getImage(); pc.repaint(); pc.updateUI(); Thread.sleep(500); } } catch (InterruptedException e) { e.printStackTrace(); } catch (MalformedURLException e) { e.printStackTrace(); } } }
Descargar
El proyecto completo lo pueden descargar desde aquí, se incluyen los archivos fuentes, las imágenes y el Jar ejecutable.
Recuerden visitarnos en Java Zone
Nota: noticia enviada por JavaZone
Reader Comments (23)
Nota para el autor,
No funciona a través de Proxy, echadle un ojo a esto.
http://www.javahispano.org/antiguo_javahispano_org/2010/9/27/auto-deteccion-de-proxy-para-la-jvm.html.
Hay un par de pegas en la técnica que estas utilizando (nada que no sea salvable) pero no es necesario discutirlas en público.
Por lo demás, felicitaciones.
Un saludo.
Un detalle nomás:
52.if(seleccionado){
53.seleccionado=false;
54.}else{
55.seleccionado=true;
56.}
No sería mejor algo como:
seleccionado = !seleccionado;
Saludos
Una pregunta: ¿Qué software puedes utilizar para hacer envío de imágenes y que este programa las consuma?
Estoy pensando en algo que no necesariamente sea utilizar una cámara web, sino más bien un programa que publica imágenes cada cierto tiempo o bien muestra videos.
Saludos.
Veo un par de problemas, que te comento:
1.- Los componentes de Swing deben crearse dentro del EDT. Al igual que todos los métodos que usen el RepaintManager. En tu main() deberías usar ésto:
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
Principal p = new Principal();
p.setVisible(true);
p.setBounds(0, 0, 600, 250);
p.setLocationRelativeTo(null);
p.setTitle("Java Zone Espia Web");
p.setDefaultCloseOperation(EXIT_ON_CLOSE);
}
});
Lo mismo vale para los repaint, update y similares que usas dentro de los Threads.
2.- El método stop() para detener un Thread está "deprecated". Aquí tienes varias explicaciones detalladas, y métodos alternativos.
http://docs.oracle.com/javase/1.4.2/docs/guide/misc/threadPrimitiveDeprecation.html
Aunque no es un defecto, si usas addMouseListener(new MouseAdapter() en vez de MouseListener, no necesitas declarar todos los métodos del Listener, sino solo los que vayas a usar.
Podrías en tu caso eliminar tranquilamente todo ésto:
@Override
public void mouseReleased(MouseEvent e) {
}
@Override
public void mousePressed(MouseEvent e) {
}
@Override
public void mouseExited(MouseEvent e) {
}
@Override
public void mouseEntered(MouseEvent e) {
}
y dejar únicamente el método que sobreescribes, lo que haría el código más legible.
@diego
Hay otra manera, menos conocida y más eficiente, de hacer lo mismo:
seleccionado ^= true;
Yo me refería a la técnica que utiliza para acceder y manejar las cámaras, no a la codificación.
:(
@efrigerio
Creo que ha quedado claro a qué te referías. ¿A qué viene esa cara triste? ;D
Podrías comentar sobre esas técnicas, así todos aprenderíamos algo más.
No tiene nada de extraordinario.
El problema está en esta línea.
pc.img=new ImageIcon(new URL(pc.url)).getImage();
¿Por qué?
Porque los proxy's, e incluso algunas versiones de la JVM, tienden a cachear recursos, especialmente imágenes.
Lo que debería hacer es extender la clase URL, sobrecargar openConnection() y setear a false UseCaches antes de devolver el conector.
Ay otra forma menos invasiva, pero esta es la mas sencilla.
Otra historia es que esta técnica solo funciona con algunos modelos / marcas de CCTV en particular.
¿Que yo sepa?.
Flexwatch
Axis
Y algún modelo de Siemens
Otras marcas no tienen esta facilidad, pero te permiten hacer Streaming por lo que debes pasar por JMF (o el FMJ como te guste) y rezar para que el codec sea compatible.
Otras como Pelco directamente te hacen la vida imposible.
Esto es lo que suelo hacer, aunque lo que dices sobre los proxies me ha dado en qué pensar:
@Override
protected Void doInBackground() {
final int panelLenght = jListImages.getSize().width;
final Dimension imagePanelSize = new Dimension(panelLenght, panelLenght);
final Toolkit toolkit = Toolkit.getDefaultToolkit();
// Now creates the right scaled images
final MediaTracker tracker = new MediaTracker(MusicInfoPanel.this);
for (final Iterator<String> it = artistInfo.getImagesURL().iterator(); it.hasNext();) {
try {
final Image image = toolkit.getImage(new URL(it.next()));
tracker.addImage(image, 0);
tracker.waitForID(0);
if (!tracker.isErrorID(0)) {
final JPSimpleImagePanel imagePanel = new JPSimpleImagePanel();
imagePanel.setPreferredSize(imagePanelSize);
imagePanel.setFitStyle(ImageFitStyle.aspectRatio);
imagePanel.setImage(image);
imagesListModel.addElement(imagePanel);
}
tracker.removeImage(image, 0);
TimeUnit.MILLISECONDS.sleep(1000);
} catch (MalformedURLException | InterruptedException ex) {
JPToolsLogService.getLogger().error(ex.getMessage(), ex);
}
}
return null;
}
¿Ésto no es suficiente?
http://docs.oracle.com/javase/6/docs/api/java/net/URLConnection.html#setUseCaches(boolean)
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setUseCaches(false);
if(usrPass != null)
{ String encoded = new sun.misc.BASE64Encoder().encode(usrPass.getBytes());
urlConnection.setRequestProperty("Authorization", "Basic " + encoded);
}
InputStream inputStream = urlConnection.getInputStream();
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
for(int acumBufSize = 0; (urlConnection.getContentLength()-acumBufSize)!=0;)
{ byte[] buf = null;
int bufSize = 0;
if(urlConnection.getContentLength() > 0 )
{ buf = new byte[urlConnection.getContentLength()];
bufSize = inputStream.read(buf, 0, urlConnection.getContentLength());
}
else
{ buf = new byte[1024*64];
bufSize = inputStream.read(buf);
}
if(bufSize != -1)
{ outputStream.write(buf, 0, bufSize);
acumBufSize += bufSize;
}
else
acumBufSize=urlConnection.getContentLength();
}
inputStream.close();
outputStream.close();
ImageIcon image = new ImageIcon(outputStream.toByteArray());
@efrigerio
Gracias a tu sugerencia he modificado mi código, para incorporar setUseCaches(false) ;)
Todavía me queda una duda, no resuelta, respecto a Toolkit.
Por lo que sé, los métodos de Toolkit getImage y createImage, a partir de una URL, usan código nativo, no código Java.
Me pregunto si el rendimiento de esos métodos es comparable al uso de streams.
@choces
Nota: lo de
if(usrPass != null)
{ String encoded = new sun.misc.BASE64Encoder().encode(usrPass.getBytes());
urlConnection.setRequestProperty("Authorization", "Basic " + encoded);
}
Va de que mis cámaras están "serradas con llave" no de otra cosa.
En cuanto a tu pregunta.
Por lo que recuerdo la diferencia entre ImageIcon y toolkit.getImage está en que este ultimo funciona como un Cache L2 por lo que si la idea es hacer un simil de streaming con imagenes en remoto (que es de lo que va Java Zone Espia Web) o un tracking fotográfico de eventos en una planta (que es lo mío) no parece buena idea utilizar toolkit.getImage.
Además con esta técnica el mayor costo, no lo tienes en el decoder de la imagen o en el renderizado, si no en el acceso al recurso remoto (TCP).
Con eso en claro un par de técnicas sencillas para ganar rendimiento están en jugar con el tamaño de la imagen en la llamada al recurso, y con el formato de salida y nivel de compresión de la imagen fuente en la configuración de la cámara.
Un ejemplo de lo primero
http://fotogermanoviseu.dyndns.info/axis-cgi/jpg/image.cgi?resolution=640x480
Es más pesado que
http://fotogermanoviseu.dyndns.info/axis-cgi/jpg/image.cgi?resolution=320x240
Mi cuestión sobre Toolkit viene a cuento de lo siguiente:
public ImageIcon (byte[] imageData) {
this.image = Toolkit.getDefaultToolkit().createImage(imageData);
if (image == null) {
return;
}
Object o = image.getProperty("comment", imageObserver);
if (o instanceof String) {
description = (String) o;
}
loadImage(image);
}
protected void loadImage(Image image) {
MediaTracker mTracker = getTracker();
synchronized(mTracker) {
int id = getNextID();
mTracker.addImage(image, id);
try {
mTracker.waitForID(id, 0);
} catch (InterruptedException e) {
System.out.println("INTERRUPTED while loading Image");
}
loadStatus = mTracker.statusID(id, false);
mTracker.removeImage(image, id);
width = image.getWidth(imageObserver);
height = image.getHeight(imageObserver);
}
}
que es el código fuente en JavaSE 1.7 de uno de los constructores de ImageIcon. Todos usan Toolkit, excepto el que usa como parámetro un Image, como era de suponer.
Por eso no veo la ventaja de usar ImageIcon para obtener una imagen, a menos que se necesiten algunos de sus métodos específicos. Y más si con ello se precisa del manejo de streams, que suponen, aunque poco, más recursos.
No hace falta mencionar que el método createImage de Toolkit acepta una URL como parámetro.
Es de esperar que los mayores retardos se generen en los accesos a las URL, y que, comparados con ellos, el procesamiento para la obtención de la imagen, y su renderizado sean ridículos.
Si ese es el codigo real, le haria falta un repasillo de buenas practicas, como no llamar repetitivamente a un metodo que no cambia de valor en un bucle, no usar APIs internos del JDK susceptibles a cambiar (y que en el OpenJDK no funcionarían)... eso suponiendo que el tratamiento de excepciones no está reflejado por brevedad.
Aparte de eso, recordad que el código se escribe una vez y se lee muchas, por lo que es mejor escribirlo clarito para que sea facil leerlo. Como ejemplo, la condición de finalizacion del bucle del for es de todo menos clara, o llamar a una variable bufSize cuando no es en realidad el tamaño del bufer si no los bytes leidos efectivamente tampoco lo es.
@choces
je je je
Me parece haber visto (en GNU Classpath, creo) exactamente lo opuesto. es decir a Toolkit utilizando internamente a ImageIcon.
:)
En todo caso para mí la ventaja de utilizar ImageIcon está en que es un Icon.
@choces
Si te interesa el manejo de imágenes en swing que tal si le echas un ojo al soporte de HTML5 – Canvas en Form4G.
Quien sabe, puede que hasta te guste y quieras colaborar.
:)
@MeAndMyself
Como ejemplo, la condición de finalizacion del bucle del for es de todo menos clara, o llamar a una variable bufSize cuando no es en realidad el tamaño del bufer si no los bytes leidos efectivamente tampoco lo es.
Una confesión.
Ese copy and paste es mío, no del OpenJDK, tiene más de 10 años, ha pasado por más proyectos de los que recuerdo prácticamente sin modificaciones, soporta todo tipo de protocolos, y la última vez que se utilizó fue hace 3 o 4 años.
Como expresé más arriba.
Ay otra forma menos invasiva de resolver esto, pero esta es la más fácil de explicar.
Un saludo.
@efrigerio
He cargado ya Form4G en NetBeans, y... ¡No ha explotado nada... todavía! ;P
Tengo que resolver algunas dependencias, y echarle ambos ojos en profundidad.
Si crees que puedo echarte una mano... aunque mi "terreno" son las aplicaciones de escritorio con NetBeans Platform.
En ésto es en lo que ando por ahora:
http://www.javaforge.com/project/jplay
@choces:
Hola, muy bueno y raro lo tuyo (seleccionado ^= true;)
pregunta: ¿porque es mas eficiente? porque sino me quedo con mi versión que es mas legible ;)
¿no debería el compilador poder generar el mismo bytecode con ambas versiones?
Saludos
Las operaciones bitwise son más rápidas; pero no es raro :D
http://www.javaspecialists.co.za/archive/newsletter.do?issue=042&locale=en_US
@diego
Ya que te interesas por el bytecode generado en cada caso, he creado una clase de prueba para comparar los bytecode:
public class NewClass {
private boolean negate, invert;
public NewClass() {
negate = !negate;
invert ^= true;
}
}
Y éste es el resultado, con el compilador de JavaSE 1.7u3 (he eliminado lo que no interesa, dejando solamente las instrucciones de inversión)
flags: ACC_PUBLIC
Code:
stack=3, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."
":()V
4: aload_0
5: aload_0
6: getfield #2 // Field negate:Z
9: ifne 16
12: iconst_1
13: goto 17
16: iconst_0
17: putfield #2 // Field negate:Z
20: aload_0
21: dup
22: getfield #3 // Field invert:Z
25: iconst_1
26: ixor
27: putfield #3 // Field invert:Z
30: return
Ya puestos, excepto en entornos muy muy concretos, prefiero una versión legible a ahorrarme un par de bytecodes. Teniendo en cuenta que es una aplicación con tráfico de red, no creo que en este caso sea posible ni medir la diferencia.
Pero para gustos, colores.