package nbtool.gui.logviews.images; import java.awt.Graphics; import java.awt.Color; import java.awt.Graphics2D; import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.geom.Ellipse2D; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.event.MouseEvent; import java.awt.event.ActionListener; import javax.swing.event.*; import java.awt.event.ItemListener; import java.awt.event.ItemEvent; import java.awt.event.ActionEvent; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.util.Vector; import java.util.ArrayList; import java.util.List; import javax.swing.JComboBox; import javax.swing.JCheckBox; import javax.swing.JSlider; import javax.swing.JPanel; import java.awt.GridLayout; import nbtool.data.SExpr; import nbtool.data.log.Log; import nbtool.gui.logviews.misc.ViewParent; import nbtool.gui.logviews.misc.VisionView; import nbtool.images.DebugImage; import nbtool.images.EdgeImage; import nbtool.images.Y8Image; import nbtool.images.Y8ThreshImage; import nbtool.io.CommonIO.IOFirstResponder; import nbtool.io.CommonIO.IOInstance; import nbtool.util.Debug; import nbtool.util.Utility; public class YUVExplore extends VisionView implements ActionListener, ChangeListener, MouseMotionListener { // Values according to nbcross/vision_defs.cpp - must be kept in sync static final int YIMAGE = 0; static final int WHITE_IMAGE = 1; static final int GREEN_IMAGE = 2; static final int ORANGE_IMAGE = 3; static final int SEGMENTED = 4; static final int EDGE_IMAGE = 5; static final int LINE_IMAGE = 6; static final int BALL_IMAGE = 7; static final int CENTER_CIRCLE = 8; static final int DRAWING = 9; static final int THRESH = 10; static final int LEARN = 11; static final int Y = 12; static final int U = 13; static final int V = 14; static final int EDGEPLUS = 15; static final int ORIGINAL = 16; static final int DEFAULT_WIDTH = 320; static final int DEFAULT_HEIGHT = 240; static final int BUFFER = 5; static final int STARTSIZE = 3; static final int FIELDW = 640; static final int FIELDH = 554; // Images that we can view in this view using the combo box String[] imageViews = { "Green", "Orange", "White", "Edge", "Thresh", "Y", "U", "V", "EdgePlus" }; JComboBox viewList; JSlider edgeThreshold; ChangeListener sliderListener; static int thresh = 12; // Dimensions of the image that we are working with int width; int height; // Dimensions as we want to display them int displayw; int displayh; BufferedImage originalImage; // what the robot saw BufferedImage displayImages[] = new BufferedImage[ORIGINAL+1]; // our images Y8ThreshImage greenCheck; Y8Image green8; private String label = null; static int currentBottom; // track current selection static boolean firstLoad = true; boolean newLogLoaded = true; public YUVExplore() { super(); setLayout(null); // set up combo box to select views viewList = new JComboBox(imageViews); viewList.setSelectedIndex(0); viewList.addActionListener(this); // set up slider edgeThreshold = new JSlider(JSlider.HORIZONTAL, 0, 60, thresh); edgeThreshold.addChangeListener(this); edgeThreshold.setMajorTickSpacing(10); edgeThreshold.setMinorTickSpacing(1); edgeThreshold.setPaintTicks(true); edgeThreshold.setPaintLabels(true); add(viewList); add(edgeThreshold); this.addMouseListener(new DistanceGetter()); this.addMouseMotionListener(this); // default image to display - save across instances if (firstLoad) { firstLoad = false; currentBottom = ORIGINAL; } else { System.out.println("Reloading"); newLogLoaded = true; } } @Override protected void setupVisionDisplay() { width = this.originalWidth() / 2; height = this.originalHeight() / 2; displayw = 640; //width*2; displayh = 480; //height*2; displayImages[ORIGINAL] = this.getOriginal().toBufferedImage(); } public void paintComponent(Graphics g) { final int BOX_HEIGHT = 25; super.paintComponent(g); if (this.displayedLog != null) { g.drawImage(displayImages[ORIGINAL], 0, 0, displayw, displayh, null); if (currentBottom == Y || currentBottom == U || currentBottom == V) { drawEdge(g); } else if (currentBottom == EDGEPLUS) { drawEdgePlus(g); } if (currentBottom < LEARN) { g.drawImage(displayImages[currentBottom], 0, displayh + 25, displayw / 2, displayh / 2, null); } else if (currentBottom == LEARN) { findGreen(g); } else { displayYUV(g); } viewList.setBounds(displayw / 2 + 10, displayh + 10, displayw / 2, BOX_HEIGHT); if (label != null) { g.setColor(Color.BLACK); g.drawString(label, 10, displayh + 20); } edgeThreshold.setBounds(displayw / 2, displayh + 15 + BOX_HEIGHT, 500, BOX_HEIGHT+20); edgeThreshold.repaint(); } } /* Called when our display conditions have changed, but we still want to * run on the current log. */ public void rerunLog() { this.callVision(); } /* Currently only called by the JComboBox, if we start adding more actions * then we will need to update this accordingly. */ public void actionPerformed(ActionEvent e) { JComboBox cb = (JComboBox)e.getSource(); String viewName = (String)cb.getSelectedItem(); updateView(viewName); } /* Updates the image displayed on the bottom according to the user's * selection. */ public void updateView(String viewName) { if (viewName == "Green") { currentBottom = GREEN_IMAGE; } else if (viewName == "White") { currentBottom = WHITE_IMAGE; } else if (viewName == "Orange") { currentBottom = ORANGE_IMAGE; } else if (viewName == "Edge") { currentBottom = EDGE_IMAGE; } else if (viewName == "Original") { currentBottom = ORIGINAL; } else if (viewName == "Thresh") { currentBottom = THRESH; } else if (viewName == "Learn") { currentBottom = LEARN; } else if (viewName == "Y") { currentBottom = Y; } else if (viewName == "U") { currentBottom = U; } else if (viewName == "V") { currentBottom = V; } else if (viewName == "EdgePlus") { currentBottom = EDGEPLUS; } else { currentBottom = ORIGINAL; } repaint(); } public void stateChanged(ChangeEvent e) { JSlider source = (JSlider)e.getSource(); if (!source.getValueIsAdjusting()) { thresh = (int)source.getValue(); System.out.println("New value is "+thresh); greenCheck.setThresh(thresh); displayImages[THRESH] = greenCheck.toBufferedImage(); if (currentBottom == THRESH) { repaint(); } } //parent.adjustParams(); //parent.repaint(); } class DistanceGetter implements MouseListener { public void mouseClicked(MouseEvent e) { repaint(); } public void mousePressed(MouseEvent e) { } public void mouseReleased(MouseEvent e) { repaint(); } public void mouseEntered(MouseEvent e) {} public void mouseExited(MouseEvent e) {} } @Override public void mouseDragged(MouseEvent e) {} @Override public void mouseMoved(MouseEvent e) { if (this.displayedLog == null) { return; } int col = e.getX(); int row = e.getY(); if (col < 0 || row < 0 || col >= displayw || row >= displayh) { return; } boolean first = (col & 1) == 0; int cbase = (col & ~1); int i = (row * displayw * 2) + (cbase * 2); byte[] data = originalImageBytes(); int y = data[first ? i : i + 2] & 0xff; int u = data[i + 1] & 0xff; int v = data[i + 3] & 0xff; label = String.format("(%d,%d): y=%d u=%d v=%d", col, row, y, u, v); repaint(); } /* Experimental code to see if we can generate green on the fly. * For now it cheats by using the green image. In the future it * should try and figure it out from scratch. */ public void findGreen(Graphics g) { int max = 0; int maxY = 0; int maxU = 0; int maxV = 0; for (int col = 0; col < width; col++) { for (int row = 0; row < height; row++) { int gr = (green8.data[row * width + col]) & 0xFF; if (gr > max) { boolean first = (col & 1) == 0; int cbase = (col & ~1); int i = (row * displayw * 2) + (cbase * 2); byte[] data = originalImageBytes(); maxY = data[first ? i : i + 2] & 0xff; maxU = data[i + 1] & 0xff; maxV = data[i + 3] & 0xff; } } } for (int col = 0; col < width * 2; col++) { for (int row = 0; row < height * 2; row++) { boolean first = (col & 1) == 0; int cbase = (col & ~1); int i = (row * width * 2 * 2) + (cbase * 2); byte[] data = originalImageBytes(); int y = data[first ? i : i + 2] & 0xff; int u = data[i + 1] & 0xff; int v = data[i + 3] & 0xff; if (Math.abs(y - maxY) < 15 && Math.abs(u - maxU) < 10 && Math.abs(v - maxV) < 10) { g.setColor(Color.GREEN); } else { g.setColor(Color.BLACK); } g.fillRect(col/2, row/2+displayh+30, 1, 1); } } } /* Draws edges on the original image in the Y, U, or V channel. */ public void drawEdge(Graphics g) { g.setColor(Color.MAGENTA); for (int col = 2; col < width * 2 - 4; col++) { for (int row = 2; row < height * 2 - 4; row++) { boolean first = (col & 1) == 0; int cbase = (col & ~1); int i = (row * width * 2 * 2) + (cbase * 2); byte[] data = originalImageBytes(); int y = data[first ? i : i + 2] & 0xff; int u = data[i + 1] & 0xff; int v = data[i + 3] & 0xff; int color = y; if (currentBottom == U) { color = u; } else if (currentBottom == V) { color = v; } for (int k = -2; k < 3; k++) { for (int j = -2; j < 3; j++) { if (!(k == 0 && j == 0)) { cbase = ((col+j) & ~1); i = ((row+k)*width*2*2) + (cbase*2); int y1 = data[first ? i : i + 2] & 0xff; int u1 = data[i + 1] & 0xff; int v1 = data[i + 3] & 0xff; int color1 = y1; if (currentBottom == U) { color1 = u1; } else if (currentBottom == V) { color1 = v1; } if (Math.abs(color1 - color) > thresh) { g.fillRect(col, row, 1, 1); } } } } } } } /* Right now this is a crude version of an edge image that takes * Y, U and V into account simultaneously. Just looks for jumps * in any dimension in the neighborhood of each pixel. Doesn't * figure the gradient or anything like that. Like I said, crude. */ public void drawEdgePlus(Graphics g) { g.setColor(Color.BLUE); for (int col = 2; col < width * 2 - 4; col++) { for (int row = 2; row < height * 2 - 4; row++) { boolean first = (col & 1) == 0; int cbase = (col & ~1); int i = (row * width * 2 * 2) + (cbase * 2); byte[] data = originalImageBytes(); int y = data[first ? i : i + 2] & 0xff; int u = data[i + 1] & 0xff; int v = data[i + 3] & 0xff; for (int k = 0; k < 3; k++) { for (int j = 0; j < 3; j++) { if (!(k == 0 && j == 0)) { cbase = ((col+j) & ~1); i = ((row+k)*width*2*2) + (cbase*2); int y1 = data[first ? i : i + 2] & 0xff; int u1 = data[i + 1] & 0xff; int v1 = data[i + 3] & 0xff; if (Math.abs(y - y1) > thresh || Math.abs(u - u1) > thresh || Math.abs(v - v1) > thresh) { g.fillRect(col, row, 1, 1); } } } } } } } /* Draw a Y, U, or V image depending on what is selected. * @param g the current drawing context */ public void displayYUV(Graphics g) { // loop through the original image grabbing the YUV for (int col = 0; col < width * 2; col++) { for (int row = 0; row < height * 2; row++) { // magic courtesy of Phil boolean first = (col & 1) == 0; int cbase = (col & ~1); int i = (row * width * 2 * 2) + (cbase * 2); byte[] data = originalImageBytes(); int y = data[first ? i : i + 2] & 0xff; int u = data[i + 1] & 0xff; int v = data[i + 3] & 0xff; int color = y; if (currentBottom == U) { color = u; } else if (currentBottom == V) { color = v; } Color display = new Color(color, color, color); g.setColor(display); g.fillRect(col/2, row/2+displayh+30, 1, 1); } } } @Override public void ioFinished(IOInstance instance) {} @Override public void ioReceived(IOInstance inst, int ret, Log... out) { System.out.println("IO received in Debug"); //yuv = out[0].bytes; if (this.getGreenBlock() != null) { green8 = new Y8Image(width, height, this.getGreenBlock().data); displayImages[GREEN_IMAGE] = green8.toBufferedImage(); greenCheck = new Y8ThreshImage(width, height, this.getGreenBlock().data); greenCheck.setThresh(thresh); displayImages[THRESH] = greenCheck.toBufferedImage(); } if (this.getWhiteBlock() != null) { Y8Image white8 = new Y8Image(width, height, this.getWhiteBlock().data); displayImages[WHITE_IMAGE] = white8.toBufferedImage(); } if (this.getOrangeBlock() != null) { Y8Image orange8 = new Y8Image(width, height, this.getOrangeBlock().data); displayImages[ORANGE_IMAGE] = orange8.toBufferedImage(); } if (this.getEdgeBlock() != null) { EdgeImage ei = new EdgeImage(width, height, this.getEdgeBlock().data); displayImages[EDGE_IMAGE] = ei.toBufferedImage(); } if (newLogLoaded) { newLogLoaded = false; } repaint(); } }