I'll start explaining from the bottom to top, so you will actually understand what I was trying to do, and understand my code better.
I am creating a library, that let's you capture an area, whether if the capture is a gif animation or an image. After capturing is finished, the library will return an object that contains ByteArrayInputStream
and util methods like createImage
etc.
While reading this, you can have access to the library here: https://github.com/BenBeri/WiseCapturer/
Now this is a dummy example on how my library works:
Your application creates an instance of bootstrap with the capturer class, and begins a capture:
public static void main(String[] args) throws AWTException {
final Bootstrap b = new Bootstrap(new ScreenshotCapturer());
b.beginCapture(new ScreenCaptureCallback() {
@Override
public void captureEnded(CapturedImage img) {
b.beginCapture(new ScreenCaptureCallback() {
@Override
public void captureEnded(CapturedImage img) {
JFrame frame = new JFrame();
frame.add(new JLabel(new ImageIcon(img.getBufferedImage())));
frame.pack();
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
});
}
});
}
The listener will return CapturedImage
which you can use to do whatever you wish to.
Now with this example, this should let you capture twice, once and again after you're done, and once it's done, it will show the 2nd capture in a JFrame window.
Now I am not talking about this JFrame.
The problem only occurs with ScreeenshotCapturer
, it will work fine with GifCapturer
instance.
The problem
After finishing the first capture, the second capture JFrame transparent window will not come up, I don't see it in the windows toolbar, nor anywhere, but the application still runs.
However, as I said it does work if I use GifCapturer instance.
Now let's debug how my library works:
Bootstrap constructor:
/**
* Bootstrap consturctor, let it bootstrap!
* @param c
* Capturer instance
* @throws AWTException
*/
public Bootstrap(Capturer c) throws AWTException {
this.capturer = c;
}
Now the Capturer class initializes, it's the abstract class, same constructor for all capturers:
public Capturer() throws AWTException {
this.camera = new CaptureCamera(this);
}
This creates a new capture camera, which is where I am having the problem. The purpose of CaptureCamera is to have the whole JFrame, transparent sized the same as my screen, and contain in it the JPanel that is responsible to do the selection rectangle drawing.
The constructor of it:
public CaptureCamera(Capturer c) throws AWTException {
this.c = c;
this.toolkit = Toolkit.getDefaultToolkit();
this.screen = this.toolkit.getScreenSize();
this.robot = new Robot();
this.selector = new SelectionCamera();
super.setSize(this.screen);
super.setUndecorated(true);
super.setBackground(new Color(255, 255, 255, 1));
// Listeners for area selection
super.addMouseListener(new SelectionAdapter(this, this.selector));
super.addMouseMotionListener(new SelectionMotion(this, this.selector));
super.add(this.selector);
}
Okay, now let's take a look at how the capturing begins.
the beginCapture
method in bootstrap:
/**
* Starts capturing the screen, sends back a callback event with the
* captured file.
*
* The system saves a temporary file to send the file.
* @param c
* Callback instance
*/
public void beginCapture(ScreenCaptureCallback c) {
this.capturer.setCallback(c);
this.capturer.beginSelection();
}
setCallback
is not really important for this problem, so beginSelection
method:
The same for all capturers
@Override
public void beginSelection() {
super.init();
this.setHotkeys();
super.getCamera().startSelection();
}
startSelection method (sorry for the dumb duplicated names with the same meaning ill change later):
/**
* Starts area selection event
* @param c Capturer instance
*/
public void startSelection() {
super.setVisible(true);
}
Okay, this is where it should make the JFrame visible, I've tried printing before and it showed true, but the JFrame didn't show on the second attempt.
Now the frame is visible, and the user can select an area.
once selected, the mouese adapter will execute startCapturing
method.
startCapturing
in GifCapturer:
@Override
public void startCapturing(final int x, final int y, final int width, final int height) {
this.border = new GifCaptureBorder(x, y, width, height);
this.process = new TimerCaptureProcess(this, x, y, width, height);
Timer timer = new Timer();
timer.schedule(this.process, 0, 600);
}
`startCapturing in ScreenshotCapturer:
@Override
public void startCapturing(int x, int y, int width, int height) {
Robot robot = super.getCamera().getRobot();
BufferedImage image = robot.createScreenCapture(new Rectangle(x, y, width, height));
super.disableSelectionFrame();
try {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
ImageIO.write(image, "png", stream);
super.setCaptureResult(stream);
super.finish();
} catch (IOException e) {
e.printStackTrace();
}
}
Now in GifCapturer the process is longer, because it actually starts a Timer
to take screenshots of every frame every 60ms
.
To finish capturing a gif, you click on "enter", I used JKeyMaster to detect hotkeys.
after clicking "ENTER", this method will be executed in GifCapturer
public void createGif() {
super.disableSelectionFrame();
AnimatedGifEncoder gif = new AnimatedGifEncoder();
ByteArrayOutputStream stream = new ByteArrayOutputStream();
gif.start(stream);
gif.setDelay(1000);
this.border.updateProgress(10);
for(int i = 0; i < this.frames.size(); i++) {
gif.addFrame(this.frames.get(i));
}
this.border.updateProgress(50);
gif.finish();
super.setCaptureResult(stream);
this.border.updateProgress(100);
super.finish();
this.border.setVisible(false);
this.border = null;
}
This is pretty much it, if I will use GifCapturer
twice, everything works fine, but if I will use ScreenshotCapturer
JFrame will NOT show up!
I am not really sure why, could this be a bug in java? Maybe because GifCapturer
takes longer before it makes the frame visible?
What did I do wrong?