Запускайте javafx и приложение Swing одновременно.

Я использую API захвата веб-камеры в java для доступа к моей веб-камере. API захвата веб-камеры построен на Swing, я знаю это, однако я хочу объединить класс Swing веб-камеры с моим классом JavaFX. Класс JavaFX отображает прямоугольник на экране. Моя цель: я запускаю свой класс JavaFX, который отображает прямоугольник на экране. В какой-то момент (например, щелчок мышью) я хочу запустить веб-камеру. Веб-камера настроена так, чтобы смотреть на экран, а затем должна выполнять определенные действия с изображениями прямоугольника.

Класс JavaFX:

public class JavaFXDisplay extends Application {

    @Override
    public void start(Stage primaryStage) {
        WebcamCapture wc = new WebcamCapture();

        StackPane root = new StackPane();

        Rectangle rectangle = new Rectangle();
        rectangle.setWidth(500);
        rectangle.setHeight(500);

        Scene scene = new Scene(root, 1000, 1000);
        root.getChildren().addAll(rectangle);

        primaryStage.setScene(scene);
        primaryStage.show();

        scene.addEventFilter(MouseEvent.MOUSE_PRESSED, new EventHandler<MouseEvent>() {
            @Override
            public void handle(MouseEvent mouseEvent) {
                wc.doSomething();
            }
        });
   }

   public static void main(String[] args) {
       launch(args);
   }
}

Класс качелей:

public class WebcamCapture extends JFrame implements Runnable, ThreadFactory {

    private static final long serialVersionUID = 6441489157408381878L;

    private Executor executor = Executors.newSingleThreadExecutor(this);

    private Webcam webcam = null;
    private WebcamPanel panel = null;
    private JTextArea textarea = null;

    public WebcamCapture() {
        super();

        setLayout(new FlowLayout());
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        Dimension size = WebcamResolution.QVGA.getSize();

        webcam = Webcam.getWebcams().get(0);
        webcam.setViewSize(size);

        panel = new WebcamPanel(webcam);
        panel.setPreferredSize(size);

        textarea = new JTextArea();
        textarea.setEditable(false);
        textarea.setPreferredSize(size);

        add(panel);
        add(textarea);

        pack();
        setVisible(true);
    }

    public void doSomething() {
        executor.execute(this);
    }

    @Override
    public void run() {
        do {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            BufferedImage image = null;

            if (webcam.isOpen()) {
                if ((image = webcam.getImage()) == null) {
                    continue;
                }

                doSomeStuff;
            }
        } while (true);
    }

    @Override
    public Thread newThread(Runnable r) {
        Thread t = new Thread(r, "example-runner");
        t.setDaemon(true);
        return t;
    }

    public static void main(String[] args) {
        new WebcamCapture();
    }
}

Однако мой класс JavaFX не запускается/не отображается. Что не так с моим кодом?


person Danielle Woods    schedule 15.03.2017    source источник
comment
Я не знаком с используемым вами API захвата веб-камеры; однако, если вы смешиваете Swing и JavaFX, вам необходимо правильно управлять потоками. Пользовательский интерфейс Swing можно манипулировать только в потоке отправки событий AWT; Пользовательским интерфейсом JavaFX можно управлять только в потоке приложения JavaFX. См., например, SwingNode для смешивая два. (Вы можете встроить веб-камеру непосредственно в JavaFX с помощью SwingNode, если компоненты невелики.)   -  person James_D    schedule 15.03.2017
comment
Вы, вероятно, правы, реализация части Swing в JavaFX может быть лучшим решением, однако я недостаточно знаком с JavaFX и API захвата веб-камеры, чтобы сделать это.   -  person Danielle Woods    schedule 15.03.2017
comment
Итак, если вы переместите все в правильный поток, это сработает?   -  person James_D    schedule 15.03.2017
comment
Разве не все уже в правильном потоке? По крайней мере, я так думаю.   -  person Danielle Woods    schedule 15.03.2017
comment
Нет: вы выполняете всю свою работу Swing в потоке приложения FX.   -  person James_D    schedule 15.03.2017
comment
Кроме того (извините за вопрос, включен ли он): оба ваших класса имеют методы main(...). Вы выполняете основной метод в JavaFXDisplay, верно...?   -  person James_D    schedule 15.03.2017
comment
Да, именно я выполняю основной метод в JavaFXDisplay. Я даже вижу, что веб-камера запущена, просто отсутствуют оба интерфейса. Но мне нужно как-то запустить класс Swing из класса JavaFX, поэтому мне нужно запустить if из потока приложения FX.   -  person Danielle Woods    schedule 15.03.2017
comment
Итак, просто для обновления, тестирования на моем Mac, похоже, вы вообще не можете трогать экземпляры Webcam в потоке приложения FX: он просто зависает. Я предполагаю, что в родном графическом конвейере появился какой-то тупик. Вам, вероятно, все равно следует выполнять всю эту работу вне этого потока, но вы должны быть осторожны... В любом случае, я поместил пример кода в качестве сути по адресу gist.github.com/james-d/f826c9f38d53628114124a56fb7c4557. Это просто предоставляет некоторые оболочки для веб-камеры с использованием API-интерфейса параллелизма JavaFX (в основном службы). Также представление службы на основе ImageView. Не качество производства   -  person James_D    schedule 16.03.2017
comment
И где в этом примере поставить исходный JavaFXDisplay? Мне просто объединить это с FXCamTest? Потому что в противном случае мне снова придется вызывать FXCamTest в отдельном потоке, я думаю?   -  person Danielle Woods    schedule 16.03.2017
comment
Нет, FXCamTest — это просто пример использования написанных мной классов. Таким образом, код вашего приложения заменит его и будет использовать эти классы таким же образом. У вас есть только один подкласс Application в приложении JavaFX.   -  person James_D    schedule 16.03.2017
comment
Итак, вот мой вывод: ваше новое решение также отлично работает, однако оно не подходит для моего сценария, потому что мне нужно, чтобы базовая функция оставалась такой же, как в моем исходном классе WebcamCapture. Это означает, что мне нужно, чтобы окно открывалось и отображало веб-камеру, когда я ее вызываю (т.е. я создаю экземпляр класса) и с вызовом метода (метод run()) для захвата изображений и запуска некоторого поведения. Большое спасибо за все усилия, вы мне очень помогли. Я проголосовал за ваш ответ, он просто не виден публике.   -  person Danielle Woods    schedule 16.03.2017
comment
Или есть способ переместить cam.open() и cam.close() из класса WebCamService в класс FXCamTest. Когда я это делаю, камера почему-то не запускается.   -  person Danielle Woods    schedule 16.03.2017
comment
Насколько я могу судить, эмпирически, правила многопоточности выглядят так: 1. Вы должны получить список камер до запуска приложения FX. (Я думаю, что это требование непреднамеренно, т.е. ошибка в коде библиотеки/драйвера.) Так что это работает в init(), но больше нигде. 2. cam.open() и cam.close() должны вызываться в фоновом потоке. (Это кажется более разумным.)   -  person James_D    schedule 16.03.2017
comment
Хорошо, так что это определенно не будет работать с моим желаемым поведением (включено в мой вывод). Еще раз большое спасибо, другое решение с Swing работает с ним.   -  person Danielle Woods    schedule 16.03.2017


Ответы (1)


Я не знаком с API-интерфейсом веб-камеры, который вы используете (поэтому я не знаю, является ли это единственной ошибкой), но вам нужно создать свой контент Swing в потоке отправки событий AWT. В настоящее время вы создаете его в потоке приложения FX.

Вы можете использовать следующую идиому:

public class JavaFXDisplay extends Application {

    private WebcamCapture wc ;

    @Override
    public void init() throws Exception {
        super.init();
        FutureTask<WebcamCapture> launchWebcam = new FutureTask<>(WebcamCapture::new) ;
        SwingUtilities.invokeLater(launchWebcam);

        // block until webcam is started:
        wc = launchWebcam.get();
    }

    @Override
    public void start(Stage primaryStage) throws Exception {

        StackPane root = new StackPane();

        Rectangle rectangle = new Rectangle();
        rectangle.setWidth(500);
        rectangle.setHeight(500);

        Scene scene = new Scene(root, 1000, 1000);
        root.getChildren().addAll(rectangle);

        primaryStage.setScene(scene);
        primaryStage.show();

        scene.addEventFilter(MouseEvent.MOUSE_PRESSED, new EventHandler<MouseEvent>() {
            @Override
            public void handle(MouseEvent mouseEvent) {
                wc.doSomething();
            }
        });
   }

   public static void main(String[] args) {
       launch(args);
   }
}

Для справки, вот средство просмотра веб-камеры только для JavaFX. Я проверил это на MacBookPro под управлением OS X Sierra (10.12.2) с 27-дюймовым дисплеем Thunderbolt 2011 года и камерой.

import java.awt.Dimension;
import java.awt.image.BufferedImage;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

import com.github.sarxos.webcam.Webcam;
import com.github.sarxos.webcam.WebcamResolution;

import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class FXWebCamViewer extends Application {

    private BlockingQueue<Image> imageQueue = new ArrayBlockingQueue<>(5);

    private Executor exec = Executors.newCachedThreadPool(runnable -> {
        Thread t = new Thread(runnable);
        t.setDaemon(true);
        return t ;
    });

    private Webcam webcam;

    @Override
    public void init() {
        webcam = Webcam.getWebcams().get(0);
        Dimension viewSize = WebcamResolution.QVGA.getSize();
        webcam.setViewSize(viewSize);
        webcam.open();
    }

    @Override
    public void start(Stage primaryStage) {
        ImageView imageView = new ImageView();

        StackPane root = new StackPane(imageView);
        imageView.fitWidthProperty().bind(root.widthProperty());
        imageView.fitHeightProperty().bind(root.heightProperty());
        imageView.setPreserveRatio(true);

        AnimationTimer updateImage = new AnimationTimer() {
            @Override
            public void handle(long timestamp) {
                Image image = imageQueue.poll();
                if (image != null) {
                    imageView.setImage(image);
                }
            }
        };
        updateImage.start();

        exec.execute(this::generateImages);

        Scene scene = new Scene(root, 600, 600);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    private void generateImages() {
        while (! Thread.interrupted()) {
            try {
                if (webcam.isOpen() && webcam.isImageNew()) {
                    BufferedImage bimg = webcam.getImage();
                    if (bimg != null) {
                        imageQueue.put(SwingFXUtils.toFXImage(bimg, null));
                    }
                } else {
                    Thread.sleep(250);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}
person James_D    schedule 15.03.2017
comment
К сожалению, моя проблема все еще сохраняется. Просто чтобы было понятно, класс Swing с материалом Webcam Capture API взят из примеров создателя библиотеки и работает (ссылка: github.com/sarxos/webcam-capture/tree/master/) Однако меня сомневает то, что JavaFX пример автора (Ссылка: github .com/sarxos/webcam-capture/tree/master/) тоже не работает. Может быть, кто-нибудь проверит для меня, работает ли он? Может быть, это как-то связано с моей машиной. - person Danielle Woods; 15.03.2017
comment
@DanielleWoods Смотрите обновление. У меня это работает с версией 0.3.10 библиотеки sarxos. Не совсем уверен, почему инициализация должна перейти на init()... - person James_D; 15.03.2017
comment
Я могу подтвердить, что ваше первое решение действительно работает. Сейчас я посмотрю ваше второе решение. Однако мне кажется странным, что вы вызываете webcam.getImage() в методе init(). Нет ли способа вызвать его в методе start(), так как я думал, что init() предназначен только для инициализации, а не для непрерывной работы? - person Danielle Woods; 15.03.2017
comment
На самом деле я не вызываю его в методе init(): я вызываю его в фоновом потоке (а это именно то, что вы хотите сделать). Я просто создаю и запускаю фоновый поток в методе init(). Вы, конечно, можете перенести создание runnable на метод start(), если хотите. Обновленный ответ в этом направлении. - person James_D; 15.03.2017
comment
Еще один вопрос: возможно ли также создать FutureTask для класса с переменными экземпляра, которые создаются в конструкторе класса? - person Danielle Woods; 15.03.2017
comment
@DanielleWoods Да, проблем быть не должно. Не совсем уверен, что вы спрашиваете... - person James_D; 15.03.2017
comment
Для синтаксиса. Куда мне добавить переменные здесь new FutureTask<>(WebcamCapture::new). Я изменил конструктор WebcamCapture на public WebcamCapture(int firstParam, int secondParam) - person Danielle Woods; 15.03.2017
comment
Просто new FutureTask<>(()->new WebcamCapture(firstParam, secondParam));. - person James_D; 15.03.2017
comment
Итак, у меня есть все для работы с вашим первым решением, большое спасибо. Это может быть не очень красиво, но это работает. Так что все, кто ищет что-то подобное, пробуйте. Однако сейчас я пытаюсь использовать ваше второе решение. Как на самом деле можно было бы его использовать. Должен ли я объединить свой первоначальный класс JavaFXViewer и ваш класс FXWebCamViewer в один класс или мне следует снова вызвать его с помощью FutureTask? - person Danielle Woods; 16.03.2017
comment
Добавлено содержание по адресу gist.github.com/james-d/f826c9f38d53628114124a56fb7c4557, что делает его относительно легко отделить все это от остальной части вашего приложения. Кстати, вы должны отметить этот ответ как правильный, если он работает для вас. - person James_D; 16.03.2017