Java tutorial
/* * To change this license header, choose License Headers in Project Properties. * To change this template file, choose Tools | Templates * and open the template in the editor. */ package bikecalibration.fxml.controller; import bikecalibration.Node; import bikecalibration.OpenCvUtils; import bikecalibration.Properties; import bikecalibration.Utils; import bikecalibration.VideoInfo; import java.io.File; import java.net.URL; import java.util.ResourceBundle; import java.util.logging.Handler; import java.util.logging.Level; import java.util.logging.LogRecord; import java.util.logging.Logger; import java.util.logging.SimpleFormatter; import javafx.application.Platform; import javafx.beans.property.BooleanProperty; import javafx.beans.property.DoubleProperty; import javafx.beans.property.IntegerProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleDoubleProperty; import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.concurrent.Service; import javafx.concurrent.Task; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; import javafx.scene.control.Button; import javafx.scene.control.ColorPicker; import javafx.scene.control.Label; import javafx.scene.control.MenuItem; import javafx.scene.control.ScrollPane; import javafx.scene.control.Slider; import javafx.scene.control.TableCell; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; import javafx.scene.control.TextArea; import javafx.scene.control.TextField; import javafx.scene.control.ToggleButton; import javafx.scene.control.cell.PropertyValueFactory; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.input.MouseEvent; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import javafx.scene.paint.Color; import javafx.stage.FileChooser; import javafx.util.Callback; import org.opencv.videoio.VideoCapture; import org.opencv.core.Mat; import org.opencv.core.Rect; import org.opencv.videoio.Videoio; /** * * @author vasja */ public class MainWindowController implements Initializable { // private properties private volatile BooleanProperty videoForwardPlayProperty = new SimpleBooleanProperty(false); private volatile BooleanProperty videoBackwardsPlayProperty = new SimpleBooleanProperty(false); private volatile IntegerProperty currentVideoFrameNumberProperty = new SimpleIntegerProperty(0); private volatile DoubleProperty imageViewCanvasWidthProperty = new SimpleDoubleProperty(); private volatile DoubleProperty imageViewCanvasHeightProperty = new SimpleDoubleProperty(); private volatile ObjectProperty<Image> currentVideoFrameProperty = new SimpleObjectProperty<>(); private IntegerProperty fpsProperty = new SimpleIntegerProperty(); private ObservableList<Properties> propertiesData = FXCollections.observableArrayList(); private ObservableList<Node> nodeData = FXCollections.observableArrayList(); // private varialbes final double NODE_WIDTH = 20; private Node[][] nodes; private boolean isAddingNode = false; private static final Logger LOGGER = Logger.getLogger(MainWindowController.class.getName()); private SimpleFormatter formatter = null; private volatile VideoCapture cap = null; private VideoInfo videoInfo = null; private final Service streamVideoService = new Service<Void>() { @Override protected Task createTask() { return new Task<Void>() { @Override protected Void call() throws Exception { int frameNumber = new Integer(currentFrameNumberField.getText()); if (videoForwardPlayProperty.get()) { forwardPlayVideo(frameNumber); } if (videoBackwardsPlayProperty.get()) { reversePlayVideo(frameNumber); } return null; } }; } }; // FXML stuff @FXML MenuItem fileClose; @FXML MenuItem fileOpen; @FXML BorderPane borderPane; @FXML Slider videoSlider; @FXML TextField currentFrameNumberField; @FXML Button btnRewind; @FXML ToggleButton btnPlayBackwards; @FXML ToggleButton btnPlayForwards; @FXML Button btnFastForward; @FXML BorderPane bp; @FXML Label lblStatus; @FXML TextArea taConsole; @FXML TableView tblProperties; @FXML TableView tableviewNodes; @FXML TableColumn tablecolumnNodesNo; @FXML TableColumn tablecolumnNodesColor; @FXML TableColumn tablecolumnNodesROI; @FXML TableColumn tblcolName; @FXML TableColumn tblcolValue; @FXML Button btnNewNode; @FXML ColorPicker cpickerNode; @FXML ScrollPane imageViewPane; @FXML Canvas imageViewCanvas; @FXML void canvasOnMouseClickedHandler(MouseEvent event) { if (!isAddingNode) { return; } createAndDrawNode(event); isAddingNode = false; btnNewNode.setVisible(true); } @FXML void btnNewNodeOnActionHandler(ActionEvent event) { btnNewNode.setVisible(false); isAddingNode = true; } @FXML void fileCloseOnActionHandler(ActionEvent event) { // exits the application // but first we have to check for all the // threads and close them System.exit(0); } @FXML void fileOpenActionHandler(ActionEvent event) { // first we create an filechoser dialog FileChooser fc = new FileChooser(); fc.setTitle("Open Video File"); fc.getExtensionFilters().addAll(new FileChooser.ExtensionFilter("AVI file", "*.avi"), new FileChooser.ExtensionFilter("MP4 file", "*.mp4")); File videoFile = fc.showOpenDialog(borderPane.getScene().getWindow()); if (videoFile == null) { return; } String videoFileName = videoFile.getAbsolutePath(); cap = new VideoCapture(videoFileName); double totalFrames = cap.get(Videoio.CAP_PROP_FRAME_COUNT); //GraphicsContext gc = imageViewCanvas.getGraphicsContext2D(); videoSlider.setMax(totalFrames); videoSlider.setBlockIncrement(1 / cap.get(Videoio.CAP_PROP_FPS)); currentFrameNumberField.setText("0"); LOGGER.log(Level.INFO, "{0} loaded.", videoFileName); videoInfo = null; videoInfo = new VideoInfo(videoFileName); // print the video file properties propertiesData.clear(); propertiesData.add(new Properties("Filename", videoInfo.FileName.get())); propertiesData.add(new Properties("Size", String.format("%d", videoInfo.FileSize.get()))); propertiesData.add(new Properties("Width", String.format("%d", videoInfo.Width.get()))); propertiesData.add(new Properties("Height", String.format("%d", videoInfo.Height.get()))); propertiesData.add(new Properties("FPS", String.format("%f", videoInfo.FPS.get()))); propertiesData.add(new Properties("Duration", Utils.secondsToString(videoInfo.Duration.get()))); propertiesData .add(new Properties("Total frames", String.format("%d", videoInfo.TotalNumberOfFrames.get()))); // crate a nodes array nodes = new Node[videoInfo.TotalNumberOfFrames.get()][]; // at the end we draw the starting frame Image img = Utils.matToImage(OpenCvUtils.getImageFromVideo(0, cap)); drawImage(img); } @FXML private void btnPlayForwardsActionHandler(ActionEvent event) { if (!videoForwardPlayProperty.get()) { if (streamVideoService.isRunning()) { streamVideoService.cancel(); } } else if (!streamVideoService.isRunning()) { streamVideoService.reset(); streamVideoService.start(); } } @FXML private void btnPlayBackwardsActionHandler(ActionEvent event) { if (!videoBackwardsPlayProperty.get()) { if (streamVideoService.isRunning()) { streamVideoService.cancel(); } } else if (!streamVideoService.isRunning()) { streamVideoService.reset(); streamVideoService.start(); } } @FXML private void btnFastForwardActionHandler(ActionEvent event) { if (videoBackwardsPlayProperty.get()) { videoBackwardsPlayProperty.set(false); } if (videoForwardPlayProperty.get()) { videoForwardPlayProperty.set(false); } currentVideoFrameNumberProperty.set((int) (videoInfo.TotalNumberOfFrames.get()) - 1); } @FXML private void btnRewindActionHandler(ActionEvent event) { if (videoBackwardsPlayProperty.get()) { videoBackwardsPlayProperty.set(false); } if (videoForwardPlayProperty.get()) { videoForwardPlayProperty.set(false); } currentVideoFrameNumberProperty.set(1); } @Override public void initialize(URL url, ResourceBundle rb) { formatter = new SimpleFormatter(); LOGGER.addHandler(new Handler() { @Override public void publish(LogRecord record) { Platform.runLater(() -> { taConsole.appendText(formatter.format(record)); }); } @Override public void flush() { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } @Override public void close() throws SecurityException { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } }); LOGGER.log(Level.INFO, "Initialization started!"); borderPane.widthProperty().addListener(new ChangeListener() { @Override public void changed(ObservableValue observable, Object oldValue, Object newValue) { //imageViewCanvasWidthProperty.set((double) newValue - 245); imageViewCanvasWidthProperty.set((double) newValue); if (videoInfo != null && !videoForwardPlayProperty.get() && !videoBackwardsPlayProperty.get()) { Platform.runLater(() -> { GraphicsContext gc = imageViewCanvas.getGraphicsContext2D(); gc.clearRect(0, 0, imageViewCanvas.getWidth(), imageViewCanvas.getHeight()); gc.save(); Image img = Utils .matToImage(OpenCvUtils.getImageFromVideo((int) videoSlider.getValue(), cap)); drawImage(img); }); } } }); borderPane.heightProperty().addListener(new ChangeListener() { @Override public void changed(ObservableValue observable, Object oldValue, Object newValue) { //imageViewCanvasHeightProperty.set((double) newValue - 150); imageViewCanvasHeightProperty.set((double) newValue); if (videoInfo != null && !videoForwardPlayProperty.get() && !videoBackwardsPlayProperty.get()) { Platform.runLater(() -> { GraphicsContext gc = imageViewCanvas.getGraphicsContext2D(); gc.clearRect(0, 0, imageViewCanvas.getWidth(), imageViewCanvas.getHeight()); gc.save(); Image img = Utils .matToImage(OpenCvUtils.getImageFromVideo((int) videoSlider.getValue(), cap)); drawImage(img); }); } } }); imageViewCanvas.widthProperty().bind(imageViewCanvasWidthProperty); imageViewCanvas.heightProperty().bind(imageViewCanvasHeightProperty); videoSlider.valueProperty().addListener(new ChangeListener<Number>() { @Override public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) { if (!videoBackwardsPlayProperty.get() && !videoForwardPlayProperty.get()) { currentVideoFrameNumberProperty.set(newValue.intValue()); currentFrameNumberField.setText(String.format("%d", newValue.intValue())); drawImage(Utils.matToImage(OpenCvUtils.getImageFromVideo(newValue.intValue(), cap))); } currentFrameNumberField.setText(String.format("%d", newValue.intValue())); } }); taConsole.textProperty().addListener(new ChangeListener<Object>() { @Override public void changed(ObservableValue<? extends Object> observable, Object oldValue, Object newValue) { taConsole.setScrollTop(Double.MAX_VALUE); } }); btnPlayBackwards.selectedProperty().bindBidirectional(videoBackwardsPlayProperty); btnPlayBackwards.setText("\u23F5"); btnPlayForwards.selectedProperty().bindBidirectional(videoForwardPlayProperty); btnPlayForwards.setText("\u23F4"); btnFastForward.setText("\u23ED"); btnRewind.setText("\u23EE"); tblcolName.setCellValueFactory(new PropertyValueFactory<Properties, String>("name")); tblcolValue.setCellValueFactory(new PropertyValueFactory<Properties, String>("value")); tblProperties.setItems(propertiesData); tablecolumnNodesNo.setCellValueFactory(new PropertyValueFactory<>("id")); tablecolumnNodesColor.setCellValueFactory(new PropertyValueFactory<>("color")); tablecolumnNodesColor.setCellFactory(new Callback<TableColumn<Node, String>, TableCell<Node, String>>() { @Override public TableCell<Node, String> call(TableColumn<Node, String> param) { TableCell cell = new TableCell<Node, String>() { ColorPicker cp = new ColorPicker(); @Override public void updateItem(String item, boolean empty) { if (item != null) { cp.setValue(Color.web(item)); HBox hbox = new HBox(); hbox.getChildren().add(cp); //hbox.setStyle("-fx-background-color: " + item); setGraphic(hbox); } } }; return cell; } }); tablecolumnNodesROI.setCellValueFactory(new PropertyValueFactory<>("roi")); tablecolumnNodesROI.setCellFactory(new Callback<TableColumn<Node, Object>, TableCell<Node, Object>>() { @Override public TableCell<Node, Object> call(TableColumn<Node, Object> param) { TableCell cell = new TableCell<Node, Object>() { ImageView imageView = new ImageView(); @Override public void updateItem(Object item, boolean empty) { if (item != null) { HBox hbox = new HBox(); imageView.setImage(Utils.matToImage((Mat) item)); imageView.setPreserveRatio(true); hbox.getChildren().add(imageView); setGraphic(hbox); } } }; return cell; } }); tableviewNodes.setItems(nodeData); LOGGER.log(Level.INFO, "Initialization ended!"); } private void forwardPlayVideo(int start) throws Exception { LOGGER.log(Level.INFO, "forwardPlayVideo Started"); cap.set(Videoio.CAP_PROP_POS_FRAMES, start); Mat frame = new Mat(); int frameCounter = start; while (cap.read(frame)) { if (!videoForwardPlayProperty.get()) { Thread.currentThread().interrupt(); break; } long startMillis = System.currentTimeMillis(); final int currentCounter = frameCounter; final Image currentImage = Utils.matToImage(frame); Platform.runLater(() -> { videoSlider.setValue(currentCounter); }); Platform.runLater(() -> { drawImage(currentImage); }); currentVideoFrameNumberProperty.set(currentCounter); long endMillis = System.currentTimeMillis(); long sleepMillis = (long) (1000 / videoInfo.FPS.get()) - (endMillis - startMillis); if (sleepMillis > 0) { try { Platform.runLater(() -> { lblStatus.setText(String.format("%d FPS", (long) videoInfo.FPS.get())); }); Thread.sleep(sleepMillis); } catch (InterruptedException ex) { LOGGER.log(Level.SEVERE, ex.getMessage()); Thread.currentThread().interrupt(); } } else { final long fps = Utils.calculateFPS(startMillis, endMillis, 1); Platform.runLater(() -> { lblStatus.setText(String.format("%d FPS", fps)); }); } frameCounter++; } videoForwardPlayProperty.set(false); LOGGER.log(Level.INFO, "forwardPlayVideo Ended"); } private void reversePlayVideo(int start) throws Exception { LOGGER.log(Level.INFO, "reversePlayVideo started."); cap.set(Videoio.CAP_PROP_POS_FRAMES, start); Mat frame = new Mat(); int frameCounter = start; while (cap.read(frame)) { if (!videoBackwardsPlayProperty.get()) { break; } long startMillis = System.currentTimeMillis(); final int currentCounter = frameCounter; final Image currentImage = Utils.matToImage(frame); Platform.runLater(() -> { videoSlider.setValue(currentCounter); }); Platform.runLater(() -> { drawImage(currentImage); }); Platform.runLater(() -> { currentFrameNumberField.setText(String.format("%d", currentCounter)); }); currentVideoFrameNumberProperty.set(currentCounter); long endMillis = System.currentTimeMillis(); long sleepMillis = (long) (1000 / videoInfo.FPS.get()) - (endMillis - startMillis); frameCounter--; if (frameCounter >= 0) { cap.set(Videoio.CAP_PROP_POS_FRAMES, frameCounter); } else { break; } if (sleepMillis > 0) { try { Platform.runLater(() -> { lblStatus.setText(String.format("%d FPS", (long) videoInfo.FPS.get())); }); Thread.sleep(sleepMillis); } catch (InterruptedException ex) { LOGGER.log(Level.SEVERE, ex.getMessage()); Thread.currentThread().interrupt(); } } else { final long fps = Utils.calculateFPS(startMillis, endMillis, 1); Platform.runLater(() -> { lblStatus.setText(String.format("%d FPS", fps)); }); } } videoBackwardsPlayProperty.set(false); LOGGER.log(Level.INFO, "reversePlayVideo ended."); } private void drawImage(Image img) { GraphicsContext gc = imageViewCanvas.getGraphicsContext2D(); double ratio = img.getWidth() / img.getHeight(); gc.drawImage(img, 0, 0, img.getWidth(), img.getHeight(), 0, 0, imageViewCanvas.getWidth(), imageViewCanvas.getWidth() / ratio); Node[] currentNodes = nodes[currentVideoFrameNumberProperty.get()]; double[] w = Utils.calculateResizedCoordinates(videoInfo.Width.get(), imageViewCanvas.getWidth(), new double[] { NODE_WIDTH }); if (currentNodes != null) { for (Node node : currentNodes) { double[] coords = Utils.calculateResizedCoordinates(videoInfo.Width.get(), imageViewCanvas.getWidth(), new double[] { node.getX(), node.getY() }); gc.setStroke(Color.web(node.getColor())); gc.setLineWidth(2); gc.strokeRect(coords[0] - w[0] / 2, coords[1] - w[0] / 2, w[0], w[0]); } } gc.save(); } private boolean createAndDrawNode(MouseEvent event) { try { // create the node Mat original_mat = OpenCvUtils.getImageFromVideo(currentVideoFrameNumberProperty.get(), cap); double original_width = original_mat.cols(); double resized_width = imageViewCanvas.getWidth(); double[] orig_coords = Utils.calculateOriginalCoordinates(original_width, resized_width, new double[] { event.getX(), event.getY() }); double[] orig_width = Utils.calculateOriginalCoordinates(original_width, resized_width, new double[] { NODE_WIDTH }); Rect roi = new Rect((int) orig_coords[0], (int) orig_coords[1], (int) orig_width[0], (int) orig_width[0]); Mat roi_mat = original_mat.submat(roi); Node n = new Node((int) orig_coords[0], (int) orig_coords[1], cpickerNode.getValue().toString(), currentVideoFrameNumberProperty.getName(), null); n.setRoi(roi_mat); n.setId(nodeData.size()); nodeData.add(n); // add the node to the nodes array Node[] currentFrameNodes = nodes[currentVideoFrameNumberProperty.get()]; if (currentFrameNodes == null) { currentFrameNodes = new Node[1]; currentFrameNodes[0] = n; nodes[currentVideoFrameNumberProperty.get()] = currentFrameNodes; } else { Node[] tmp = new Node[currentFrameNodes.length + 1]; int i = 0; for (Node currentFrameNode : currentFrameNodes) { tmp[i] = currentFrameNode; ++i; } tmp[i] = n; nodes[currentVideoFrameNumberProperty.get()] = tmp; } // start editing current image drawImage(Utils.matToImage(original_mat)); return true; } catch (Exception ex) { return false; } } }