How do you write each call to paintComponent concurrently with the BufferedImage?
How do you write each call to repaint (direct or not) with a type JPanel
(i.e. a custom class that extends
/ inherits from JPanel
) to BufferedImage
?
Doing this sort of thing inside a custom class paintComponent
doesn't work:
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D G2 = (Graphics2D) g;
// ... draw objects
BufferedImage imageBuffer = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
G2 = imageBuffer.createGraphics();
// Which doesn't work, because with that method it seems you would
// need to call paint() on Graphics2D reference here.
// And to do so would then throw an Illegal Exception.
}
The resultant BufferedImage
is the correct size of the JPanel
-type class it calls paintComponent
, but the image is black (i.e. empty) - this is completely logical because it createGraphics()
does nothing.
I know the Rob Camick ScreenImage code , but this seems to be meant to take a single screenshot while initializing the program.
What confuses me is what paintComponent
should be kept in memory before being displayed on the screen ... So is there a way to grab this every time and store it in BufferedImage
?
source to share
How about this approach? The difference from your posted code is that you need to do all your own drawing on BufferedImage
. Then you will cause only one component per component: BufferedImage
.
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
BufferedImage imageBuffer = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
Graphics imageG2 = imageBuffer.createGraphics();
// do custom painting onto imageG2...
// save imageBuffer
g2.drawImage(imageBuffer, 0, 0, this); // draw onto component
}
source to share
The description and some details may not be entirely clear. But basically it looks like you wanted to create a component that can be used like a regular component JPanel
, but everything written in the method paintComponent
must also be saved as BufferedImage
.
There are different solutions for this. You can make special calls like
myComponent.paintComponent(bufferedImageGraphics);
and process the image accordingly, as suggested in one answer. Another answer suggested creating a custom method paintComponent
that does image processing.
However, I would like to suggest the following solution: you can create a class ImageSavingComponent
that extends JPanel
. In this class, you are not overriding a method paintComponent
, but a method paint
. The method paint
has two functions:
- It calls
super.paint(bufferedImageGraphics)
by drawing into an image - It calls
super.paint(componentGraphics)
, draws on the screen
Thus, you can simply extend this class and implement the method paintComponent
as you would for anyone JPanel
. Image processing can be done completely transparently.
In the example below, I've added an interface Consumer
that receives images and processes them appropriately. If Consumer
- null
, then images will not be generated. For testing purposes, I created an implementation of this interface that simply saves images to files every 200ms. (This interface can be replaced with Java 8 interface if needed Consumer
)
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.FileOutputStream;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JToggleButton;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class ImageSavingComponentTest {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
createAndShowGUI();
}
});
}
private static void createAndShowGUI() {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final ImageSavingComponent c = new ExampleImageSavingComponent();
f.getContentPane().add(c);
final Timer timer = new Timer(50, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
c.repaint();
}
});
timer.start();
final Consumer<? super BufferedImage> consumer = new ImageSaver();
final JToggleButton b = new JToggleButton("Capture");
b.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (b.isSelected()) {
c.setConsumer(consumer);
} else {
c.setConsumer(null);
}
}
});
c.add(b);
f.setSize(300, 300);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
class ImageSaver implements Consumer<BufferedImage>
{
private int counter = 0;
private long previousFrameMillis = -1;
private long intervalMillis = 200;
@Override
public void accept(BufferedImage t) {
if (System.currentTimeMillis() < previousFrameMillis + intervalMillis)
{
return;
}
previousFrameMillis = System.currentTimeMillis();
String fileName = String.format("image%04d.png", counter);
counter++;
FileOutputStream fos = null;
try {
fos = new FileOutputStream(fileName);
ImageIO.write(t, "png", fos);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
class ExampleImageSavingComponent extends ImageSavingComponent {
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
double time = (System.currentTimeMillis() % 2000) / 2000.0;
double ca = Math.cos(time * Math.PI * 2);
double sa = Math.sin(time * Math.PI * 2);
g.setColor(Color.RED);
double d = 50;
int x = getWidth() / 2 + (int) (ca * d);
int y = getHeight() / 2 + (int) (sa * d);
int r = 10;
g.fillOval(x - r, y - r, r + r, r + r);
}
}
interface Consumer<T> {
void accept(T t);
}
class ImageSavingComponent extends JPanel {
private Consumer<? super BufferedImage> consumer;
void setConsumer(Consumer<? super BufferedImage> consumer) {
this.consumer = consumer;
}
@Override
public final void paint(Graphics g) {
if (consumer != null && getWidth() > 0 && getHeight() > 0) {
BufferedImage image = new BufferedImage(getWidth(), getWidth(),
BufferedImage.TYPE_INT_ARGB);
Graphics2D imageGraphics = image.createGraphics();
super.paint(imageGraphics);
imageGraphics.dispose();
consumer.accept(image);
}
super.paint(g);
}
}
source to share
You must create BufferedImage
outside the paint function of your component and then call that paint function with a parameter BufferedImage
as a parameter.
BufferedImage imageBuffer = new BufferedImage(comp.getWidth(), cmp.getHeight(), BufferedImage.TYPE_INT_RGB);
cmp.paint(imageBuffer.getGraphics());
source to share