I am trying to take input from webcam and draw it in the canvas.
For this I wrote following block of code:
void addImageToCanvas(Image img){
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.drawImage(img, 0, 0, canvas.getWidth(), canvas.getHeight());
}
If I pass only one Image in that function by hand, it works.
If I pass two Images then it only draws the last one.
But I am implementing thread and calling it continuously from the thread. The function is then comes to stand still on the line:
gc.drawImage(img, 0, 0, canvas.getWidth(), canvas.getHeight());
The part of code from where the method is invoked:
public void run() {
try {
FrameGrabber grabber = new VideoInputFrameGrabber(cameraId);
CvMemStorage storage = CvMemStorage.create();
grabber.start();
IplImage grabbedImage;
BufferedImage bufImg;
int counter = 0;
while (primaryCameraChosen != null) {
grabbedImage = grabber.grab();
bufImg = grabbedImage.getBufferedImage();
addImageToCanvas(SwingFXUtils.toFXImage(bufImg, null));
try{
Thread.sleep(10000);
} catch(InterruptedException e){}
}
grabber.stop();
grabber.release();
} catch (Exception ex) {
}
}
Could you please tell me what I am doing wrong here? What is the solution? Thanks.
So you have actually two problems
1) If I pass two Images then it only draws the last one.
You are drawing images to the same X, Y coordinates, with the same height and width. The result is (like in real life if your paint something on an existing painting) that only the last drawing is visible as it hides the first one. This is the expected working.
Update after comment from OP:
The problem with this line: gc.drawImage(img, 0, 0, canvas.getWidth(), canvas.getHeight()); is, that at the first Image it tries to draw the Image using the actual height and width of the Canvas, but most probably at this moment the Canvas itself was not drawn, so the actual height and width are both zero, therefore the picture gets drawn with zero width and height.
Two possible solutions are:
1) Only start the Thread when the Stage that contains the Canvas is actually showing. This will ensure that the Canvas has the correct height and width even at the first picture.
2) Listen to the width and height change of the canvas and redraw the Image if needed. This will ensure, that whenever the canvas is resized, the Image is redrawn (it has the advantage that the picture fits to the screen all the time so this one is the suggested way)
The example contains both approaches.
2) But I am implementing thread and calling it continuously from the
thread. The function is then comes to stand still on the line
We shall see the full code what can be wrong, but until you update the post to include every part that is needed to detect the problem, here is an example which updates a canvas with an Image from a background thread every 3 seconds:
public class Main extends Application {
Canvas canvas;
#Override
public void start(Stage primaryStage) {
try {
BorderPane root = new BorderPane();
Scene scene = new Scene(root,400,400);
canvas = new Canvas();
Pane pane = new Pane();
pane.getChildren().add(canvas);
canvas.widthProperty().bind(pane.widthProperty());
canvas.heightProperty().bind(pane.heightProperty());
root.setCenter(pane);
Thread thread = new Thread(new Runnable() {
private ObjectProperty<Image> imageToBeDrawn = new SimpleObjectProperty<Image>(new Image(getClass().getResource("a.jpg").toExternalForm()));
#Override
public void run() {
// Option 2: Listen to the width and height property change of the canvas and redraw the image
canvas.widthProperty().addListener(event -> addImageToCanvas(imageToBeDrawn.get()));
canvas.heightProperty().addListener(event -> addImageToCanvas(imageToBeDrawn.get()));
imageToBeDrawn.addListener(event -> addImageToCanvas(imageToBeDrawn.get()));
while(true){
Random rand = new Random();
if(rand.nextBoolean())
imageToBeDrawn.set(new Image(getClass().getResource("a.jpg").toExternalForm()));
else
imageToBeDrawn.set(new Image(getClass().getResource("b.jpg").toExternalForm()));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
thread.setDaemon(true);
thread.start();
primaryStage.setScene(scene);
// Option 2: start the Thread only when the stage is showing
// primaryStage.showingProperty().addListener(new ChangeListener<Boolean>() {
//
// #Override
// public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
// if(newValue)
// thread.start();
//
// }
// });
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
void addImageToCanvas(Image img){
Platform.runLater(new Runnable(){
#Override
public void run() {
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.drawImage(img, 0, 0, canvas.getWidth(), canvas.getHeight());
}});
}
public static void main(String[] args) {
launch(args);
}
}
Related
We are developing video streaming app using JavaFx and JavaCv.
We Have GridPane of 8X8 and each cell contains Pane layout and each pane
containing ImageView.
Every time i am calling play function whenever user drag url to particular
cell.
we have 64 Urls,So we are calling play function 64 times(all 64 url are playing
in background thread).
for(int i=0;i<8;i++){
for(int j=0;j<8;j++){
//grid08(8X8)
Pane p=new Pane();
ImageView im=new ImageView();
Label l=new Label("");
im.fitWidthProperty().bind(p.widthProperty());
im.fitHeightProperty().bind(p.heightProperty());
l.prefWidthProperty().bind(p.widthProperty());
l.prefHeightProperty().bind(p.heightProperty());
p.getChildren().addAll(im,l);
l.setVisible(false);
l.setStyle("-fx-text-fill: #fff; -fx-font-size: 12px");
l.setAlignment(Pos.CENTER);
p.setStyle("-fx-background-color: black;");
grid.add(p,j,i);//grid->instance of gridPane
p.setOnDragOver(new EventHandler<DragEvent>() {
#Override
public void handle(DragEvent event) {
if(!l.isVisible()){
event.acceptTransferModes(TransferMode.ANY);
}
}
});
p.setOnDragDropped(new EventHandler<DragEvent>() {
#Override
public void handle(DragEvent event) {
play(im,event.getDragboard().getString());
}
});
}
}
play(ImageView im,String Url){
new Thread(new Runnable(){
frameGrabber = new FFmpegFrameGrabber(Url);
frameGrabber.setVideoOption("preset","ultrafast");
frameGrabber.setOption("rtsp_transport","tcp");
frameGrabber.setOption("stimeout" , "60000");
frameGrabber.setAudioChannels(0);
frameGrabber.setAudioCodec(0);
try {
frameGrabber.start();
start=true;
} catch (Exception e) {
}
JavaFXFrameConverter jconverter=new JavaFXFrameConverter();
while(true){
Frame frame = frameGrabber.grabImage();
Image image=jconverter.convert(frame);
Platform.runlater(new Runnable(){
im.setImage(image);
});
}
}).start();
}
Problem:
We are facing White Screen Problem,after increasing some threshold Width and
Height.
Here i am attaching Snap of my application with different screen size;
Screen1
Since We are facing this problem from long time.
Any Lead will be highly appreciable.
Thanks in advance.
Just a guess here, but when the image view is a different size to the image, the image view has quite a lot of work to do when you call setImage(...) (it has to resize the image). Since you have a lot of these happening, potentially every pulse, it's possible you are submitting more runnables to Platform.runLater(...) than it can handle (i.e. you submit another Platform.runLater(...) before the previous one submitted from the same thread can complete). This will cause rendering issues, because the rendering system can't keep up.
I can't test this, so I'm not sure if it will fix the issue, but it's probably a good idea to only submit a new call to Platform.runLater(...) when the previous one has completed (i.e. you should do this even if it's not the cause of the problem).
You can do this with
void play(ImageView im,String Url){
new Thread(() -> {
frameGrabber = new FFmpegFrameGrabber(Url);
frameGrabber.setVideoOption("preset","ultrafast");
frameGrabber.setOption("rtsp_transport","tcp");
frameGrabber.setOption("stimeout" , "60000");
frameGrabber.setAudioChannels(0);
frameGrabber.setAudioCodec(0);
try {
frameGrabber.start();
start=true;
} catch (Exception e) {
e.printStackTrace();
}
JavaFXFrameConverter jconverter=new JavaFXFrameConverter();
AtomicReference<Image> imageHolder = new AtomicReference<>(null);
while(true){
Frame frame = frameGrabber.grabImage();
Image image=jconverter.convert(frame);
if (imageHolder.getAndSet(image) == null) {
Platform.runLater(() -> im.setImage(imageHolder.getAndSet(null)));
}
}
}).start();
}
Note that in this code (and the original), all the images are being resized on the same thread (the FX Application Thread). If possible, it would probably be more efficient to resize each image in its own thread. You can't absolutely guarantee this (because there's no way to guarantee the image view doesn't change size between the image being resized in the background thread and the image view displaying the image), so you still need to bind the fitWidth and fitHeight properties, but this should reduce the amount of image resizing that happens on the UI thread, and consequently should improve performance. This relies on there being some mechanism in your frame grabber API (which I'm not familiar with) to resize images.
for(int i=0;i<8;i++){
for(int j=0;j<8;j++){
//grid08(8X8)
Pane p=new Pane();
AtomicReference<Bounds> bounds = new AtomicReference<>(p.getBoundsInLocal());
p.boundsInLocalProperty().addListener((obs, oldBounds, newBounds) -> bounds.set(newBounds));
ImageView im=new ImageView();
Label l=new Label("");
im.fitWidthProperty().bind(p.widthProperty());
im.fitHeightProperty().bind(p.heightProperty());
l.prefWidthProperty().bind(p.widthProperty());
l.prefHeightProperty().bind(p.heightProperty());
p.getChildren().addAll(im,l);
l.setVisible(false);
l.setStyle("-fx-text-fill: #fff; -fx-font-size: 12px");
l.setAlignment(Pos.CENTER);
p.setStyle("-fx-background-color: black;");
grid.add(p,j,i);//grid->instance of gridPane
p.setOnDragOver(new EventHandler<DragEvent>() {
#Override
public void handle(DragEvent event) {
if(!l.isVisible()){
event.acceptTransferModes(TransferMode.ANY);
}
}
});
p.setOnDragDropped(new EventHandler<DragEvent>() {
#Override
public void handle(DragEvent event) {
play(im,bounds,event.getDragboard().getString());
}
});
}
}
and
void play(ImageView im, AtomicReference<Bounds> bounds, String Url){
new Thread(() -> {
frameGrabber = new FFmpegFrameGrabber(Url);
frameGrabber.setVideoOption("preset","ultrafast");
frameGrabber.setOption("rtsp_transport","tcp");
frameGrabber.setOption("stimeout" , "60000");
frameGrabber.setAudioChannels(0);
frameGrabber.setAudioCodec(0);
try {
frameGrabber.start();
start=true;
} catch (Exception e) {
e.printStackTrace();
}
JavaFXFrameConverter jconverter=new JavaFXFrameConverter();
AtomicReference<Image> imageHolder = new AtomicReference<>(null);
while(true){
Frame frame = frameGrabber.grabImage();
Bounds imgBounds = bounds.get();
double width = imgBounds.getWidth();
double height = imgBounds.getHeight();
// resize frame to width, height....
Image image=jconverter.convert(frame);
if (imageHolder.getAndSet(image) == null) {
Platform.runLater(() -> im.setImage(imageHolder.getAndSet(null)));
}
}
}).start();
}
I am using a transparent Stage in JavaFX which contains a Canvas.
The user can draw a rectangle in the canvas by dragging the mouse.
When he hits enter i am saving the image using:
try {
ImageIO.write(bufferedImage,extension,outputFile);
} catch (IOException e) {
e.printStackTrace();
}
To capture the image from screen i am using:
gc.clearRect(0, 0, getWidth(), getHeight()); // [gc] is the graphicsContext2D of the Canvas
// Start the Thread
new Thread(() -> {
try {
// Sleep some time
Thread.sleep(100);
// Capture the Image
BufferedImage image;
int[] rect = calculateRect();
try {
image = new Robot().createScreenCapture(new Rectangle(rect[0], rect[1], rect[2], rect[3]));
} catch (AWTException e) {
e.printStackTrace();
return;
}
} catch (Exception ex) {
ex.printStackTrace();
}
}).start();
The problem:
I don't know when the JavaFX Thread will clear the Canvas so i am using a Thread as you can see above which is waiting 100 milliseconds and then capture the screen.But it is not working all the times,what i mean is that sometimes the Canvas has not been cleared and i get this image(cause Canvas has not been cleared yet):
instead of this:
The question:
How i know when the JavaFX has cleared the Canvas so i can capture the screen after that?
Mixing Swing with JavaFX is not recommended although that's the only way i know to capture screen with Java...
I really hope there's a better way to achieve what you want than this, but the canvas will be cleared the next time a "pulse" is rendered. So one approach would be to start an AnimationTimer and wait until the second frame is rendered:
gc.clearRect(0, 0, getWidth(), getHeight());
AnimationTimer waitForFrameRender = new AnimationTimer() {
private int frameCount = 0 ;
#Override
public void handle(long timestamp) {
frameCount++ ;
if (frameCount >= 2) {
stop();
// now do screen capture...
}
}
};
waitForFrameRender.start();
Obviously, if you are doing the screen capture in a background thread, you need to also make sure you don't draw back onto the canvas before the capture occurs...
I have made a class that displays a series of images to create an animation. I would like the animation to take place on a separate thread than the rest of the program. However, when I try to start the animation class, I get an error.
This is some of the animation class (There is more but is is irrelevant):
/**
* Starts a new thread to play the animation on
*/
public void start()
{
playing = true;
animateThread = (new Thread(() -> run()));
animateThread.start();
}
/**
* Will go through sprites and display the images
*/
public void run()
{
int index = 0;
while (playing)
{
if (index > sprites.length)
{
index = 0;
}
try
{
g.drawImage(sprites[index].getImage(), x, y, null);
animateThread.sleep(speed);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
index++;
}
}
I have also attempted at making the animation class Runnable, then making the whole object a new Thread but I received the same error.
This is the class which holds the JFrame and starts the animation (There is more but is is irrelevant):
public static void main(String[] args)
{
AnimationTester tester = new AnimationTester();
tester.frame.setResizable(false);
tester.frame.setTitle("Tester");
tester.frame.add(tester);
tester.frame.pack();
tester.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
tester.frame.setLocationRelativeTo(null);
tester.frame.setVisible(true);
//Make the window not have to be clicked on to get input (Set is as the main focus when it begins)
tester.requestFocusInWindow();
//Start the program
tester.start();
}
public void start()
{
createGraphics();
animation.start();
}
public void createGraphics()
{
BufferStrategy bs = getBufferStrategy();
//Checks to see if the BufferStrategy has already been created, it only needs to be created once
if (bs == null)
{
//Always do triple buffering (put 3 in the param)
createBufferStrategy(3);
return;
}
//Links the bufferStrategy and graphics, creating a graphics context
g = bs.getDrawGraphics();
try
{
animation = new Animation(ImageIO.read(getClass().getResource("/SpriteSheet.jpg")), 16, 2, 200, 250, 250, 2.0);
animation.addGraphics(g);
}
catch (IOException e)
{
e.printStackTrace();
}
}
That's not really how BufferStrategy works, instead of using createGraphics, you should be calling it to update the state and render it to the screen
So, in your Thread, it should be updating the state in some way and call some "render" method which would get the next page, render the state and push that state to the screen.
Take a closer look at BufferStrategy and BufferStrategy and BufferCapabilities for more details about how it's suppose to work
For example
This question may be a simple matter of me lacking a fundamental understanding of Java Swing or Graphics, and if so, I apologize.
I'm trying to develop a GUI application using Java Swing that can be controlled by an external device that sends pitch, yaw, and roll values via bluetooth to the Application. My idea is to create a cursor (perhaps an empty circle) that moves around when the external device moves around. I have no problems with receiving the data from the device, just the part where I need to actually paint something over all of my components.
I figured that a GlassPane was the easiest way to show a cursor over the entire application, and have it move around when the external device is moved around. I use a Thread to capture the data, and I'm trying to call repaint() afterwards, but it doesn't seem to be triggering.
Here is the relevant code:
JFrame:
public class Frame extends JFrame {
/**
* Launch the application.
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
//Thread myoHandlerThread = new Thread(myoHandler);
//myoHandlerThread.start();
Frame frame = new Frame();
GlassPane glassPane = new GlassPane();
glassPane.setVisible(true);
frame.setGlassPane(glassPane);
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Create the frame.
*/
public Frame() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(50, 50, 1000, 650);
/* Code to add and place components */
}
}
And my GlassPane:
public class GlassPane extends JComponent {
private static double pitch;
private static double yaw;
private static double roll;
Point point;
public void setPoint(Point p) {
this.point = p;
}
public void paintComponent(Graphics g) {
if (point != null) {
System.out.println("Test print statement");
g.setColor(Color.red);
g.fillOval(point.x - 10, point.y - 10, 20, 20);
}
}
public GlassPane() {
Thread handler = new Thread(deviceHandler);
handler.start();
}
private Runnable deviceHandler = new Runnable() {
#Override
public void run() {
Hub hub = new Hub("com.garbage");
System.out.println("Attempting to find device...");
Device externalDevice = hub.waitForDevice(10000);
if (externalDevice == null) {
throw new RuntimeException("Unable to find device!");
}
System.out.println("Connected");
DataCollector dataCollector = new DataCollector();
hub.addListener(dataCollector);
while (true) {
hub.run(1000/20); //gathers data and stores in dataCollector
roll = dataCollector.getRoll();
pitch = dataCollector.getPitch();
yaw = dataCollector.getYaw();
Point p = new Point();
p.setLocation(Math.abs(pitch) * 10, Math.abs(yaw) * 10);
setPoint(p);
repaint();
}
}
};
}
What I would like to happen is for a red circle to be drawn somewhere on the GUI depending on the orientation of the external device. At this point, my "test print statement" doesn't fire even once.
My guess is that I'm lacking some sort of basic understanding of Java's GlassPane or even how paint, paintComponent, and repaint even works. Could anyone point out what I'm doing wrong?
The likely cause of your frustration is trying to set the glass pane visible (Swing components are visible by default), before setting it as the frames GlassPane.
The JFrame is likely resetting the glass pane to be invisible, meaning that it won't be painted (no point painting something that's not visible)
Try setting the glass pane visible AFTER you apply it to the frame
I'm looking for a way to load up a page and save the rendering as an image just as you would do with CutyCapt (QT + webkit EXE to do just that).
At the moment and without JavaFX, I do it by calling an external process from java and rendering to file than loading that file into an ImageBuffer... Neither very optimized nor practical and even less cross platform...
Using JavaFX2+ I tried playing with the WebView & WebEngine:
public class WebComponentTrial extends Application {
private Scene scene;
#Override
public void start(final Stage primaryStage) throws Exception {
primaryStage.setTitle("Web View");
final Browser browser = new Browser();
scene = new Scene(browser, 1180, 800, Color.web("#666970"));
primaryStage.setScene(scene);
scene.getStylesheets().add("webviewsample/BrowserToolbar.css");
primaryStage.show();
}
public static void main(final String[] args) {
launch(args);
}
}
class Browser extends Region {
static { // use system proxy settings when standalone application
// System.setProperty("java.net.useSystemProxies", "true");
}
final WebView browser = new WebView();
final WebEngine webEngine = browser.getEngine();
public Browser() {
getStyleClass().add("browser");
webEngine.load("http://www.google.com/");
getChildren().add(browser);
}
#Override
protected void layoutChildren() {
final double w = getWidth();
final double h = getHeight();
layoutInArea(browser, 0, 0, w, h, 0, HPos.CENTER, VPos.CENTER);
}
#Override
protected double computePrefWidth(final double height) {
return 800;
}
#Override
protected double computePrefHeight(final double width) {
return 600;
}
}
There is a deprecated method : renderToImage in Scene (see links below) that would do something that comes close and with which I'd might be able to work but it is deprecated...
It being deprecated in JavaFX seems to mean that there is no javadoc advertising the replacement method and because I don't have access to the code, I cannot see how it was done...
Here are a couple of sites where I found some information but nothing to render a webpage to an image:
http://tornorbye.blogspot.com/2010/02/how-to-render-javafx-node-into-image.html
canvasImage and saveImage(canvasImage, fc.getSelectedFile()) from this one :
http://javafx.com/samples/EffectsPlayground/src/Main.fx.html
Others:
http://download.oracle.com/javafx/2.0/webview/jfxpub-webview.htm
http://download.oracle.com/javafx/2.0/get_started/jfxpub-get_started.htm
http://fxexperience.com/2011/05/maps-in-javafx-2-0/
I have done this by launching JavaFX WebView on a Swing JFrame and JFXPanel. And then I use the paint() method on JFXPanel once the WebEngine status is SUCCEEDED.
You may follow this tutorial to make a WebView: Integrating JavaFX into Swing Applications
The code below demonstrate how I capture the rendered screen from JFXPanel.
public static void main(String args[]) {
jFrame = new JFrame("Demo Browser");
jfxPanel = new JFXPanel();
jFrame.add(jfxPanel);
jFrame.setVisible(true);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
Platform.runLater(new Runnable() {
#Override
public void run() {
browser = new FXBrowser();
jfxPanel.setScene(browser.getScene());
jFrame.setSize((int) browser.getWebView().getWidth(), (int) browser.getWebView().getHeight());
browser.getWebEngine().getLoadWorker().stateProperty().addListener(
new ChangeListener() {
#Override
public void changed(ObservableValue observable,
Object oldValue, Object newValue) {
State oldState = (State) oldValue;
State newState = (State) newValue;
if (State.SUCCEEDED == newValue) {
captureView();
}
}
});
}
});
}
});
}
private static void captureView() {
BufferedImage bi = new BufferedImage(jfxPanel.getWidth(), jfxPanel.getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics graphics = bi.createGraphics();
jfxPanel.paint(graphics);
try {
ImageIO.write(bi, "PNG", new File("demo.png"));
} catch (IOException e) {
e.printStackTrace();
}
graphics.dispose();
bi.flush();
}
For JavaFX 2.2 users there is a much more clean and elegant solution based on JavaFX Node snapshot. You can take a look to JavaFX node documentation at:
http://docs.oracle.com/javafx/2/api/javafx/scene/Node.html
Here is an example of taking a capture from a WebView Node (as webView)
File destFile = new File("test.png");
WritableImage snapshot = webView.snapshot(new SnapshotParameters(), null);
RenderedImage renderedImage = SwingFXUtils.fromFXImage(snapshot, null);
try {
ImageIO.write(renderedImage, "png", destFile);
} catch (IOException ex) {
Logger.getLogger(GoogleMap.class.getName()).log(Level.SEVERE, null, ex);
}
I had no problems with this solution, and the webview is rendered perfectly in the PNG according to the node size. Any JavaFx node can be rendered and saved to a file with this method.
Hope this help!
Workaround posted by JavaFX engineers: Snapshot does not work with (invisible) WebView nodes.
When taking a snapshot of a scene that contains a WebView node, wait for at least 2 frames before issuing the snapshot command. This can be done by using a counter in an AnimationTimer to skip 2 pulses and take the snapshot on the 3rd pulse.
Once you have got your snapshot, you can convert the image to an awt BufferedImage and encode the image to format like png or jpg, using ImageIO.