bikecalibration.fxml.controller.MainWindowController.java Source code

Java tutorial

Introduction

Here is the source code for bikecalibration.fxml.controller.MainWindowController.java

Source

/*
 * 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;
        }
    }
}