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...
Related
I am working on adding cutscenes to a game mod I am writing. The game renders GUI elements using JOGL, so I have created a render event for the Player's Heads Up Display (HUD) and I want to render an overlay on this HUD which plays an embedded youtube video and then cancel the render once the youtube video stops.
#SubscribeEvent(priority=EventPriority.LOWEST)
public void onRender(RenderGameOverlayEvent.Post event) {
player = mc.thePlayer;
int height = event.resolution.getScaledHeight();
int width = event.resolution.getScaledWidth();
if(isVisible){
GL11.glPushMatrix();
this.mc.getTextureManager().bindTexture(texture);
drawPlayerHUD(videoCutScenePath, width, height, event);
GL11.glEnable(GL11.GL_BLEND);
GL11.glDisable(GL11.GL_DEPTH_TEST);
GL11.glDepthMask(false);
GL11.glColor4f(1.0F, 1.0F, 1.0F, guiOpacity);
GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
GL11.glPopMatrix();
}
}
When isVisible is set true - play cut scene from embeded youtube video inside the frame. I have access to the resolution of the frame (Height and Width), mouseX, mouseY, partialTick count and I have successfully displayed an image texture in png format on this frame already. I am looking to now render a video from youtube specifically. How would you accomplish this?
private void drawPlayerHUD(String path, int width, int height, RenderGameOverlayEvent.Post event){
//getYouTube video so the onRender method can render it??
}
[Edit] I have tried to open the Youtube URL using the following method which also returns the status for debugging purposes and this works but it opens in a new window. I now want to merge the two and am unsure how.
public static String openWebpage() {
if (Desktop.isDesktopSupported()) {
try {
Desktop.getDesktop().browse(new URI("https://www.youtube.com/watch_popup?v=[videoID]?&autoplay=1"));
} catch (IOException e) {
e.printStackTrace();
return "Can't open browser for some reason";
} catch (URISyntaxException e) {
e.printStackTrace();
return "Bug Detected: The URL is invalid for some reason.";
}
} else {
return "For some reason You Can't open the browser";
}
return null;
}
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);
}
}
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
I am facing an issue that I am not able to draw an png image into my swing GUI into JPanel (which I am adding into JScrollPane after).
Here is my code I am trying to use for this.
if(processCreated == true){
System.out.println("updating tree of organisms");
processCreated = false;
updateTree();
try {
Thread.sleep(100);
} catch (InterruptedException ex) {
Logger.getLogger(Svet.class.getName()).log(Level.SEVERE, null, ex);
}
tree = new File(path+"/out.png"); //File
try {
treeImage = ImageIO.read(tree); //BufferedImage
tp = new TreePane(treeImage.getScaledInstance( 500, 500 ,treeImage.SCALE_SMOOTH)); // my class, implementation below
treeOutput.add(tp); //adding "JPanel" with picture into GUI
treeOutput.repaint();
} catch (IOException ex) {
Logger.getLogger(Svet.class.getName()).log(Level.SEVERE, null, ex);
}
}
After this the JScrollPane remains empty. Is there anything wrong with my code? You can find what is what in comments.
Here is my TreePane class
public class TreePane extends JPanel{
Image img;
public TreePane( Image img ){
this.img = img;
}
public void paintComponent(Graphics g){
super.paintComponent(g);
int imgX = 0;
int imgY = 0;
g.drawImage(img, imgX, imgY, this);
}
}
Very likely your TreePane is layed out to size 0,0 and therefore you'll never see anything. Try adding a breakpoint and inspecting in paintComponent - it might not even ever get called.
To fix, you should explicitly set the size of your TreePane before adding it to the JScrollPane.
tp = new TreePane(treeImage.getScaledInstance( 500, 500 ,treeImage.SCALE_SMOOTH)); // my class, implementation below
tp.setSize(myWidth, myHeight);
treeOutput.add(tp); //adding "JPanel" with picture into GUI
treeOutput.repaint();
I wanted to draw an image on my panel based on the data I receive from another thread. I am sure the data and consequent pixel array works well, but the repaint() would never work. Can anyone tell me what's going wrong here?
import javax.swing.*;
import java.awt.*;
import java.awt.image.*;
/** Create an image from a pixel array. **/
public class PicturePlaza extends JApplet
{
ImagePanel fImagePanel;
ReadCom readComPort;
Thread readPortThread;
public void init () {
// initiate the read port thread so that it can receive data
readComPort = new ReadCom();
readPortThread = new Thread(readComPort,"ReadCom");
readPortThread.start();
Container content_pane = getContentPane ();
fImagePanel = new ImagePanel ();
content_pane.add (fImagePanel);
}
// Tell the panel to create and display the image, if pixel data is ready.
public void start () {
while(true){
if(readComPort.newPic){
fImagePanel.go();
}
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
/** Create an image from a pixel array. **/
class ImagePanel extends JPanel{
Image fImage;
int fWidth = ReadCom.row, fHeight = ReadCom.col;
void go() {
//update the image if newPic flag is set to true
fImage = createImage (new MemoryImageSource (fWidth, fHeight, ReadCom.fpixel, 0, fWidth));
repaint();
readComPort.newPic = false; //disable the flag, indicating the image pixel has been used
}
/** Paint the image on the panel. **/
public void paintComponent (Graphics g) {
super.paintComponent (g);
g.drawImage (fImage, 0, 0, this );
}
}
}
Thanks
Just a little note on repaint(). repaint() schedules a repaint of the screen, it won't always do it immediately in my experience. I found the best solution is to directly call paint() yourself.
Graphics g;
g = getGraphics();
paint(g);
I put this as a new function to call in my code when I wanted it to paint immediately. Also this will not erase the previous graphics on the screen, you will have to do that manually.
Try repaint(); and then validate(); in your Applet (PicturePlaza).