summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke Shumaker <lukeshu@sbcglobal.net>2016-03-09 15:49:29 -0500
committerLuke Shumaker <lukeshu@sbcglobal.net>2016-03-09 15:49:29 -0500
commit9b51480997c6fd7627b065df3c9222389f7243ec (patch)
tree9eb4c11d38b3b9c66299c2fb4ddc508dcc4f284d
parentfbf6bc127c8a941e1cc746c8a5efd473936b9f7a (diff)
Add a generalized VideoURLViewer widget
-rw-r--r--smartdashboard/src/edu/wpi/first/smartdashboard/gui/elements/DefaultDisplayElementRegistrar.java2
-rw-r--r--smartdashboard/src/edu/wpi/first/smartdashboard/gui/elements/VideoURLViewer.java215
2 files changed, 217 insertions, 0 deletions
diff --git a/smartdashboard/src/edu/wpi/first/smartdashboard/gui/elements/DefaultDisplayElementRegistrar.java b/smartdashboard/src/edu/wpi/first/smartdashboard/gui/elements/DefaultDisplayElementRegistrar.java
index e5854d5..db42035 100644
--- a/smartdashboard/src/edu/wpi/first/smartdashboard/gui/elements/DefaultDisplayElementRegistrar.java
+++ b/smartdashboard/src/edu/wpi/first/smartdashboard/gui/elements/DefaultDisplayElementRegistrar.java
@@ -29,8 +29,10 @@ public class DefaultDisplayElementRegistrar {
DisplayElementRegistry.registerStaticWidget(ConnectionIndicator.class);
DisplayElementRegistry.registerStaticWidget(Label.class);
DisplayElementRegistry.registerStaticWidget(RobotPreferences.class);
+
DisplayElementRegistry.registerStaticWidget(RobotCameraViewer.class);
DisplayElementRegistry.registerStaticWidget(AxisCameraViewer.class);
+ DisplayElementRegistry.registerStaticWidget(VideoURLViewer.class);
}
}
diff --git a/smartdashboard/src/edu/wpi/first/smartdashboard/gui/elements/VideoURLViewer.java b/smartdashboard/src/edu/wpi/first/smartdashboard/gui/elements/VideoURLViewer.java
new file mode 100644
index 0000000..04c79c1
--- /dev/null
+++ b/smartdashboard/src/edu/wpi/first/smartdashboard/gui/elements/VideoURLViewer.java
@@ -0,0 +1,215 @@
+package edu.wpi.first.smartdashboard.gui.elements;
+
+import edu.wpi.first.smartdashboard.gui.DashboardPrefs;
+import edu.wpi.first.smartdashboard.gui.StaticWidget;
+import edu.wpi.first.smartdashboard.properties.IntegerProperty;
+import edu.wpi.first.smartdashboard.properties.Property;
+import edu.wpi.first.smartdashboard.properties.StringProperty;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.geom.AffineTransform;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+
+import javax.imageio.ImageIO;
+
+/**
+ * SmartDashboard widget for vieweing an abitrary video URL. It can
+ * deal with the types supported by javax.imageio.ImageIO.
+ *
+ * To use with an Axis webcam, use http://HOSTNAME/mjpg/video.mjpg.
+ *
+ * @author Greg Granito
+ */
+public class VideoURLViewer extends StaticWidget {
+
+ public static final String NAME = "Video URL Viewer";
+
+
+ private static final int[] START_BYTES = new int[]{0xFF, 0xD8};
+ private static final int[] END_BYTES = new int[]{0xFF, 0xD9};
+
+ private boolean urlChanged = true;
+ private String urlString = null;
+ private double rotateAngleRad = 0;
+ private long lastFPSCheck = 0;
+ private int lastFPS = 0;
+ private int fpsCounter = 0;
+ public class BGThread extends Thread {
+
+ boolean destroyed = false;
+
+ public BGThread() {
+ super(NAME+" Background");
+ }
+
+ long lastRepaint = 0;
+ @Override
+ public void run() {
+ URLConnection connection = null;
+ InputStream stream = null;
+ ByteArrayOutputStream imageBuffer = new ByteArrayOutputStream();
+ while (!destroyed) {
+ try{
+ System.out.println("Connecting to camera");
+ urlChanged = false;
+ URL url = new URL(urlString);
+ connection = url.openConnection();
+ connection.setReadTimeout(250);
+ stream = connection.getInputStream();
+
+ while(!destroyed && !urlChanged){
+ while(System.currentTimeMillis()-lastRepaint<10){
+ stream.skip(stream.available());
+ Thread.sleep(1);
+ }
+ stream.skip(stream.available());
+
+ imageBuffer.reset();
+ for(int i = 0; i<START_BYTES.length;){
+ int b = stream.read();
+ if(b==START_BYTES[i])
+ i++;
+ else
+ i = 0;
+ }
+ for(int i = 0; i<START_BYTES.length;++i)
+ imageBuffer.write(START_BYTES[i]);
+
+ for(int i = 0; i<END_BYTES.length;){
+ int b = stream.read();
+ imageBuffer.write(b);
+ if(b==END_BYTES[i])
+ i++;
+ else
+ i = 0;
+ }
+
+ fpsCounter++;
+ if(System.currentTimeMillis()-lastFPSCheck>500){
+ lastFPSCheck = System.currentTimeMillis();
+ lastFPS = fpsCounter*2;
+ fpsCounter = 0;
+ }
+
+ lastRepaint = System.currentTimeMillis();
+ ByteArrayInputStream tmpStream = new ByteArrayInputStream(imageBuffer.toByteArray());
+ imageToDraw = ImageIO.read(tmpStream);
+ System.out.println(System.currentTimeMillis()-lastRepaint);
+ repaint();
+ }
+
+ } catch(Exception e){
+ imageToDraw = null;
+ repaint();
+ e.printStackTrace();
+ }
+
+ if(!urlChanged){
+ try {
+ Thread.sleep(500);
+ } catch (InterruptedException ex) {}
+ }
+ }
+
+ }
+
+ @Override
+ public void destroy() {
+ destroyed = true;
+ }
+ }
+ private BufferedImage imageToDraw;
+ private BGThread bgThread = new BGThread();
+ public final StringProperty urlProperty = new StringProperty(this, "Video URL", "http://axis-camera.local/mjpg/video.mjpg");
+ public final IntegerProperty rotateProperty = new IntegerProperty(this, "Degrees Rotation", 0);
+
+ @Override
+ public void init() {
+ setPreferredSize(new Dimension(100, 100));
+ urlString = urlProperty.getSaveValue();
+ rotateAngleRad = Math.toRadians(rotateProperty.getValue());
+ bgThread.start();
+ revalidate();
+ repaint();
+ }
+
+ @Override
+ public void propertyChanged(Property property) {
+ if (property == urlProperty) {
+ urlString = urlProperty.getSaveValue();
+ urlChanged = true;
+ }
+ if (property == rotateProperty) {
+ rotateAngleRad = Math.toRadians(rotateProperty.getValue());
+ }
+
+ }
+
+ @Override
+ public void disconnect() {
+ bgThread.destroy();
+ super.disconnect();
+ }
+
+ @Override
+ protected void paintComponent(Graphics g) {
+ BufferedImage drawnImage = imageToDraw;
+
+ if (drawnImage != null) {
+ // cast the Graphics context into a Graphics2D
+ Graphics2D g2d = (Graphics2D)g;
+
+ // get the existing Graphics transform and copy it so that we can perform scaling and rotation
+ AffineTransform origXform = g2d.getTransform();
+ AffineTransform newXform = (AffineTransform)(origXform.clone());
+
+ // find the center of the original image
+ int origImageWidth = drawnImage.getWidth();
+ int origImageHeight = drawnImage.getHeight();
+ int imageCenterX = origImageWidth/2;
+ int imageCenterY = origImageHeight/2;
+
+ // perform the desired scaling
+ double panelWidth = getBounds().width;
+ double panelHeight = getBounds().height;
+ double panelCenterX = panelWidth/2.0;
+ double panelCenterY = panelHeight/2.0;
+ double rotatedImageWidth = origImageWidth * Math.abs(Math.cos(rotateAngleRad)) + origImageHeight * Math.abs(Math.sin(rotateAngleRad));
+ double rotatedImageHeight = origImageWidth * Math.abs(Math.sin(rotateAngleRad)) + origImageHeight * Math.abs(Math.cos(rotateAngleRad));
+
+ // compute scaling needed
+ double scale = Math.min(panelWidth / rotatedImageWidth, panelHeight / rotatedImageHeight);
+
+ // set the transform before drawing the image
+ // 1 - translate the origin to the center of the panel
+ // 2 - perform the desired rotation (rotation will be about origin)
+ // 3 - perform the desired scaling (will scale centered about origin)
+ newXform.translate(panelCenterX, panelCenterY);
+ newXform.rotate(rotateAngleRad);
+ newXform.scale(scale, scale);
+ g2d.setTransform(newXform);
+
+ // draw image so that the center of the image is at the "origin"; the transform will take care of the rotation and scaling
+ g2d.drawImage(drawnImage, -imageCenterX, -imageCenterY, null);
+
+ // restore the original transform
+ g2d.setTransform(origXform);
+
+ g.setColor(Color.PINK);
+ g.drawString("FPS: "+lastFPS, 10, 10);
+ } else {
+ g.setColor(Color.PINK);
+ g.fillRect(0, 0, getBounds().width, getBounds().height);
+ g.setColor(Color.BLACK);
+ g.drawString("NO CONNECTION", 10, 10);
+ }
+ }
+}