How do you write every paintComponent call simultaneously to BufferedImage? - java

How do you write every every repaint call (direct or not) by a JPanel-type (i.e. a custom class which extends/inherits from JPanel) to a BufferedImage ?
Doing this sort of thing inside the custom class' paintComponent does not 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 resulting BufferedImage is the correct size of the JPanel-type class which calls paintComponent, but the image is black (i.e. empty) - which is entirely logical because createGraphics() alone does nothing.
I know of Rob Camick's ScreenImage code - but that seems intended for a single screenshot at initialisation of the program.
What leaves me confused is that what paintComponent does must be held in memory before being displayed on-screen... So is there a way of grabbing that every time and saving it into a BufferedImage ?

What about this approach? The difference from you posted code is that you need to do all your custom drawing onto your BufferedImage. Then you draw just one thing onto the component: the 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
}

The description and some details may not be entirely clear. But it basically sounds like you wanted to create a component that can be used like a normal JPanel, but everything that is painted in the paintComponent method should also be saved as a BufferedImage.
There are different solutions for this. You could make dedicated calls, like
myComponent.paintComponent(bufferedImageGraphics);
and process the image accordingly, as proposed in one answer. Another answer suggested creating a custom paintComponent method that does the image handling.
However, I'd like to propose the following solution: You could create a ImageSavingComponent class that extends JPanel. In this class, you don't override the paintComponent method, but the paint method. The paint method does two things:
It calls super.paint(bufferedImageGraphics), painting into an image
It calls super.paint(componentGraphics), painting to the screen
This way, you may simply extend this class, and implement the paintComponent method like you would do for any JPanel. The image handling can be done completely transparently.
In the example below, I added a Consumer interface that receives the images and handles them appropriately. If the Consumer is null, then no images will be produced. For the test, I created an implementation of this interface that simply stores the images in files every 200ms. (This interface may be replaced with the Java 8 Consumer interface, if desired)
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);
}
}

You must create the BufferedImage outside the paint function of your component, and then call this paint function with the BufferedImage graphics as parameter.
BufferedImage imageBuffer = new BufferedImage(comp.getWidth(), cmp.getHeight(), BufferedImage.TYPE_INT_RGB);
cmp.paint(imageBuffer.getGraphics());

Related

displaying pictures side by side in java

is it possible to display two pictures, next to each other with BufferedImage and Graphics2D ? or should I do it with other method ?
In my code below, I was able to display two images, but the picture 1 overlaps to the picture 2.
package zdjecie;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JPanel;
public class ObrazPanel extends JPanel {
private BufferedImage image;
private BufferedImage image2;
public ObrazPanel() {
super();
File imageFile = new File("C:\\Users\\KostrzewskiT\\eclipse-workspace\\zdjecie\\src\\zdjecie\\java.jpg");
File imageFile2 = new File("C:\\Users\\KostrzewskiT\\eclipse-workspace\\zdjecie\\src\\zdjecie\\java2.jpg");
try {
image = ImageIO.read(imageFile);
image2 = ImageIO.read(imageFile2);
} catch (IOException e) {
System.err.println("Blad odczytu obrazka");
e.printStackTrace();
}
Dimension dimension = new Dimension(image.getWidth(), image.getHeight());
setPreferredSize(dimension);
Dimension dimension2 = new Dimension(image2.getWidth(), image2.getHeight());
setPreferredSize(dimension2);
}
#Override
public void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.drawImage(image, 0, 0, this);
g2d.drawImage(image2, 200, 200, this);
}
}
You call setPreferredSize twice, which results in the first call doing basically nothing. That means you always have a preferredSize equal to the dimensions of the second image. What you should do is to set the size to new Dimension(image.getWidth() + image2.getWidth(), image2.getHeight()) assuming both have the same height. If that is not the case set the height as the maximum of both images.
Secondly you need to offset the second image from the first image exactly by the width of the first image:
g2d.drawImage(image, 0, 0, this);
g2d.drawImage(image2, image.getWidth(), 0, this);
The logic of the math was incorrect. See the getPreferredSize() method for the correct way to calculate the required width, and the changes to the paintComponent(Graphics) method to place them side-by-side.
An alternative (not examined in this answer) is to put each image into a JLabel, then add the labels to a panel with an appropriate layout.
This is the effect of the changes:
import java.awt.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
import java.net.*;
import javax.imageio.ImageIO;
public class ObrazPanel extends JPanel {
private BufferedImage image;
private BufferedImage image2;
public ObrazPanel() throws MalformedURLException {
super();
URL imageFile = new URL("https://i.stack.imgur.com/7bI1Y.jpg");
URL imageFile2 = new URL("https://i.stack.imgur.com/aH5zB.jpg");
try {
image = ImageIO.read(imageFile);
image2 = ImageIO.read(imageFile2);
} catch (Exception e) {
System.err.println("Blad odczytu obrazka");
e.printStackTrace();
}
}
#Override
public Dimension getPreferredSize() {
int w = image.getWidth() + image2.getWidth();
int h1 = image.getHeight();
int h2 = image2.getHeight();
int h = h1>h2 ? h1 : h2;
return new Dimension(w,h);
}
#Override
public void paintComponent(Graphics g) {
// always best to start with this..
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.drawImage(image, 0, 0, this);
g2d.drawImage(image2, image.getWidth(), 0, this);
}
public static void main(String[] args) {
Runnable r = new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception useDefault) {
}
ObrazPanel o;
try {
o = new ObrazPanel();
JFrame f = new JFrame(o.getClass().getSimpleName());
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
f.setLocationByPlatform(true);
f.setContentPane(o);
f.pack();
f.setMinimumSize(f.getSize());
f.setVisible(true);
} catch (MalformedURLException ex) {
ex.printStackTrace();
}
}
};
SwingUtilities.invokeLater(r);
}
}
I would join the images whenever something changes and draw them to another buffered image. Then I can just redraw the combined image whenever the panel needs to be redrawn.
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.*;
public class SideBySideImagePanel extends JPanel {
private static final long serialVersionUID = 5868633578732134172L;
private BufferedImage left;
private BufferedImage right;
private BufferedImage join;
public SideBySideImagePanel() {
ClassLoader loader = this.getClass().getClassLoader();
BufferedImage left = null, right = null;
try {
left = ImageIO.read(loader.getResourceAsStream("resources/Android.png"));
right = ImageIO.read(loader.getResourceAsStream("resources/Java.png"));
} catch (IOException e) {
e.printStackTrace();
}
this.setLeft(left);
this.setRight(right);
}
public BufferedImage getLeft() {
return left;
}
public void setLeft(BufferedImage left) {
this.left = left;
}
public BufferedImage getRight() {
return right;
}
public void setRight(BufferedImage right) {
this.right = right;
}
#Override
public void invalidate() {
super.invalidate();
join = combineImages(left, right);
setPreferredSize(new Dimension(join.getWidth(), join.getHeight()));
}
#Override
public void paintComponent(Graphics g) {
g.drawImage(join, 0, 0, null);
}
private BufferedImage combineImages(BufferedImage left, BufferedImage right) {
int width = left.getWidth() + right.getWidth();
int height = Math.max(left.getHeight(), right.getHeight());
BufferedImage combined = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics g = combined.getGraphics();
g.drawImage(left, 0, 0, null);
g.drawImage(right, left.getWidth(), 0, null);
return combined;
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame("Image Joiner");
SideBySideImagePanel panel = new SideBySideImagePanel();
frame.getContentPane().add(panel);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.pack();
frame.setVisible(true);
}
});
}
}
I found some errors in your code and I did not got what are you trying to do...
1] Over there you are actually not using the first setup
Dimension dimension = new Dimension(image.getWidth(), image.getHeight());
setPreferredSize(dimension); //not used
Dimension dimension2 = new Dimension(image2.getWidth(), image2.getHeight());
setPreferredSize(dimension2); //because overridden by this
It means, panel is having dimensions same as the image2, you should to set it as follows:
height as max of the heights of both images
width at least as summarize of widths of both pictures (if you want to paint them in same panel, as you are trying)
2] what is the image and image2 datatypes? in the block above you have File but with different naming variables, File class ofcourse dont have width or height argument
I am assuming its Image due usage in Graphics.drawImage, then:
You need to setup preferred size as I mentioned:
height to max value of height from images
width at least as summarize value of each widths
Dimensions things:
Dimension panelDim = new Dimension(image.getWidth() + image2.getWidth(),Math.max(image.getHeight(),image2.getHeight()));
setPreferredSize(panelDim)
Then you can draw images in the original size
- due coordinates are having 0;0 in the left top corner and right bottom is this.getWidth(); this.getHeight()
- check eg. this explanation
- you need to start paint in the left bottom corner and then move to correct position increase "X" as the width of first image
#Override
public void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
/* public abstract boolean drawImage(Image img,
int x,
int y,
Color bgcolor,
ImageObserver observer)
*/
//start to paint at [0;0]
g2d.drawImage(image, 0, 0, this);
//start to paint just on the right side of first image (offset equals to width of first picture)- next pixel on the same line, on the bottom of the screen
g2d.drawImage(image2,image2.getWidth()+1, 0, this);
}
I didn't had a chance to test it, but it should be like this.
Important things are
you need to have proper dimensions for fitting both images
screen coordinates starts in the left top corner of the screens [0;0]

Java (Graphics2D): triangle drawn by created Graphics2D not visible until second repaint

I have the following minimal code to draw a line with an arrow head:
package gui;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.geom.Line2D;
import javax.swing.JPanel;
public class StateBtn extends JPanel {
private static final long serialVersionUID = -431114028667352251L;
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// enable antialiasing
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
// draw the arrow
Line2D.Double line = new Line2D.Double(0, getHeight()/2, 20, getHeight()/2);
drawArrowHead(g2, line);
g2.draw(line);
// If I call repaint() here (like in my answer below), it works
}
private void drawArrowHead(Graphics2D g2d, Line2D.Double line) {
AffineTransform tx = new AffineTransform();
tx.setToIdentity();
double angle = Math.atan2(line.y2-line.y1, line.x2-line.x1);
tx.translate(line.x2, line.y2);
tx.rotate((angle-Math.PI/2d));
Polygon arrowHead = new Polygon();
arrowHead.addPoint(0,5);
arrowHead.addPoint(-5,-5);
arrowHead.addPoint(5,-5);
Graphics2D g = (Graphics2D) g2d.create();
g.setTransform(tx);
g.fill(arrowHead);
g.dispose();
}
}
It is created like this:
package gui;
import java.awt.EventQueue;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
public class Main extends JFrame {
private static final long serialVersionUID = 4085389089535850911L;
private JPanel contentPane;
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
Main frame = new Main();
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
public Main() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(500, 500);
setLocation(0, 0);
contentPane = new JPanel();
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
setContentPane(contentPane);
contentPane.setLayout(null);
StateBtn stateBtn = new StateBtn();
stateBtn.setBounds(200,200,35,35);
contentPane.add(stateBtn);
}
}
The line is drawn properly but the arrow head is invisible until I call repaint(). The problem is that the element is a draggable one, so I would have to call repaint() twice every time the position is changed. This would make the code more complex and the GUI would be laggy.
Why can't the arrow head just be drawn together with the line? Is there really no one who can help me?
You've not posted a true MCVE, so it's impossible to know what you could be doing wrong, but there's no need for the kludge you've used in your answer, where you re-call repaint() within paintComponent. If you still need help with your own code, then please post a valid MCVE, code we can compile and run without modification. For an example of what I mean by MCVE, please read the MCVE link and look at the example MCVE that I've posted in my answer below.
Having said this, understand that generally Swing graphics are passive, meaning that you would have your program change its state based on an event, then call repaint() and this suggests to the Swing repaint manager to call paint. There is no guarantee that painting will occur, since repaint requests that have "stacked", that are backing up due to many being called in a short time, may be ignored.
So in your case, we can use your code and modify it to see how this works. Say I give my JPanel a MouseAdapter -- a class that is both a MouseListener and MouseMotionListener, and in this adapter I simply set two Point instance fields, p0 -- for where the mouse was initially pressed, and p1 -- for where the mouse drags or releases. I can set these fields and then call repaint, and let my painting methods use p0 and p1 to draw my arrow. So the mouse adapater could look like so:
private class MyMouse extends MouseAdapter {
private boolean settingMouse = false;
#Override
public void mousePressed(MouseEvent e) {
if (e.getButton() != MouseEvent.BUTTON1) {
return;
}
p0 = e.getPoint();
p1 = null;
settingMouse = true; // drawing a new arrow
repaint();
}
#Override
public void mouseReleased(MouseEvent e) {
setP1(e);
settingMouse = false; // no longer drawing the new arrow
}
#Override
public void mouseDragged(MouseEvent e) {
setP1(e);
}
private void setP1(MouseEvent e) {
if (settingMouse) {
p1 = e.getPoint();
repaint();
}
}
}
And then within my painting code, I'd use your code, modified to make it use my p0 and p1 points:
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
if (p0 != null && p1 != null) {
Line2D.Double line = new Line2D.Double(p0.x, p0.y, p1.x, p1.y);
drawArrowHead(g2, line);
g2.draw(line);
}
}
The whole shebang would look like so:
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.*;
import javax.swing.*;
#SuppressWarnings("serial")
public class StateBtn extends JPanel {
// constants to size the JPanel
private static final int PREF_W = 800;
private static final int PREF_H = 650;
private static final int AH_SIZE = 5; // size of arrow head -- avoid "magic"
// numbers!
// our start and end Points for the arrow
private Point p0 = null;
private Point p1 = null;
public StateBtn() {
// create and add a label to tell the user what to do
JLabel label = new JLabel("Click Mouse and Drag");
label.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 42));
label.setForeground(new Color(0, 0, 0, 50));
setLayout(new GridBagLayout());
add(label); // add it to the center
// create our MouseAdapater and use it as both MouseListener and
// MouseMotionListener
MyMouse myMouse = new MyMouse();
addMouseListener(myMouse);
addMouseMotionListener(myMouse);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
// only do this if there are points to draw!
if (p0 != null && p1 != null) {
Line2D.Double line = new Line2D.Double(p0.x, p0.y, p1.x, p1.y);
drawArrowHead(g2, line);
g2.draw(line);
}
}
private void drawArrowHead(Graphics2D g2d, Line2D.Double line) {
AffineTransform tx = new AffineTransform();
tx.setToIdentity();
double angle = Math.atan2(line.y2 - line.y1, line.x2 - line.x1);
tx.translate(line.x2, line.y2);
tx.rotate((angle - Math.PI / 2d));
Polygon arrowHead = new Polygon();
arrowHead.addPoint(0, AH_SIZE); // again avoid "magic" numbers
arrowHead.addPoint(-AH_SIZE, -AH_SIZE);
arrowHead.addPoint(AH_SIZE, -AH_SIZE);
Graphics2D g = (Graphics2D) g2d.create();
g.setTransform(tx);
g.fill(arrowHead);
g.dispose(); // we created this, so we can dispose of it
// we should **NOT** dispose of g2d since the JVM gave us that
}
#Override
public Dimension getPreferredSize() {
// size our JPanel
return new Dimension(PREF_W, PREF_H);
}
private class MyMouse extends MouseAdapter {
private boolean settingMouse = false;
#Override
public void mousePressed(MouseEvent e) {
// if we press the wrong mouse button, exit
if (e.getButton() != MouseEvent.BUTTON1) {
return;
}
p0 = e.getPoint(); // set the start point
p1 = null; // clear the end point
settingMouse = true; // tell mouse listener we're creating a new arrow
repaint(); // suggest a repaint
}
#Override
public void mouseReleased(MouseEvent e) {
setP1(e);
settingMouse = false; // no longer drawing the new arrow
}
#Override
public void mouseDragged(MouseEvent e) {
setP1(e);
}
private void setP1(MouseEvent e) {
if (settingMouse) {
p1 = e.getPoint(); // set the end point
repaint(); // and paint!
}
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
private static void createAndShowGui() {
StateBtn mainPanel = new StateBtn();
JFrame frame = new JFrame("StateBtn");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
This code is what I meant by my example of a MCVE. Actually it's a little large for a decent MCVE, but it will do. Please compile and run the code to see that it works. If this doesn't help you, if you still must use a kludge with your repaint calls, then I urge you to create your own MCVE and post it with your question, and then comment to me so I can see it.
An aside, someone questioned if it was OK to create a new Graphics object as you're doing within drawArrowHead(...) method, and yes this not only OK, it's the preferred thing to do when dealing with AffineTransforms, since this way you don't have to worry about down-stream effects that the transform might have on border and child components that might share the original Graphics object. Again this is OK, as long as you follow the rule of disposing Graphics objects that you yourself create, and not disposing Graphics objects given to you by the JVM.
OK, it seems, there is no other way than to call repaint again. I did like this at the end of the paintComponent-method:
if (repaint == false) {
repaint = true;
} else {
repaint = false;
repaint();
}
Hence, it repaints exactly one time. But is there no cleaner solution?

Java graphics cannot relocate rectangle

I am trying to relocate a rectangle but for I cannot figure out why it stays in the same location.
It creates a red rectangle but does not change color or move to a new location.
Here is my code:
package grap_prj.dom.shenkar;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.util.concurrent.TimeUnit;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class graphic_main extends JPanel{
static Rectangle rec = new Rectangle ();
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
rec.setSize(10, 10);
rec.setLocation(10, 10);
g2d.setColor(Color.RED);
g2d.drawRect((int)rec.getX(),(int)rec.getY(), 10, 10);
g2d.fillRect((int)rec.getX(),(int)rec.getY(), 10, 10);
}
public static void update_ui (Graphics g)
{
System.out.println("in update");
rec.setLocation(50, 50);
Graphics2D g2d = (Graphics2D) g;
g2d.drawRect((int)rec.getX(),(int)rec.getY(), 10, 10);
g2d.fillRect((int)rec.getX(),(int)rec.getY(), 10, 10);
}
public static void main(String[] args) {
JFrame frame = new JFrame("Simple Graphics");
frame.add(new graphic_main());
frame.setSize(300, 300);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
graphic_main.update_ui(frame.getGraphics());
frame.revalidate();
}
}
Update:
I have made a few changes in the code, but still the same situation. I change the location but a new rectangle is added instead of moving the existing one.
You are continuosly setting location at 10,10 so the rectangle will always be drawn at 10,10.
After setting location 50,50 you aren't drawing anything. Next step you will set 10,10 again.
Whenever you override paintComponent(), you need to call super.paintComponent().
You are also calling repaint() from repaint(). You need to decide on some action that will cause it to repaint.
You should never call update() or repaint() inside of a paintComponent(...) method. Ever. This risks recursion or ineffective uncontrolled animation.
Don't change the state of your object inside of a paint or paintComponent method. You don't have full control over when or even if these methods get called.
Don't forget to call the super's method inside your paintComponent override to allow the JPanel to do its housekeeping graphics including erasing old dirty pixels.
Even though you change the Graphics context's Color to blue, it will change right back to red anytime the paintComponent is called. So your color change is futile code. Solution: make the Color a variable that can be set.
If you want to do Swing animation, use a Swing Timer.
For an example of Swing animation, have a look at my example here.
For another example, have a look at this:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
#SuppressWarnings("serial")
public class SimpleAnimation extends JPanel {
private static final int PREF_W = 800;
private static final int PREF_H = 650;
private static final Color[] COLORS = { Color.red, Color.orange,
Color.yellow, Color.green, Color.blue, Color.magenta };
private static final int RECT_WIDTH = 40;
private static final int TIMER_DELAY = 10;
private int x = 0;
private int y = 0;
private int colorIndex = 0;
private Color color = COLORS[colorIndex];
public SimpleAnimation() {
new Timer(TIMER_DELAY, new TimerListener()).start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(color);
g.fillRect(x, y, RECT_WIDTH, RECT_WIDTH);
}
private class TimerListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
x++;
y++;
if (x + RECT_WIDTH > getWidth()) {
x = 0;
}
if (y + RECT_WIDTH > getHeight()) {
y = 0;
}
if (x % 40 == 0) {
colorIndex++;
colorIndex %= COLORS.length;
color = COLORS[colorIndex];
}
repaint();
}
}
private static void createAndShowGui() {
JFrame frame = new JFrame("SimpleAnimation");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new SimpleAnimation());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
Your changing the location before the frame gets the chance to render it. So its creating it at 10, 10 and then when its rendered changes it to 50 50 then to 10, 10.

Unable Take screenshot of JFrame Java Swing

I have tried to save the JFrame as an image using the following approach.
try
{
BufferedImage image = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
this.paint(image.getGraphics());
ImageIO.write(image,"png", new File("Test.png"));
}
catch(Exception exception)
{
//code
System.out.print("Exception unable to write image");
}
I am trying to save a screenshot as follows:
I would like to have even the title in my screenshot
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class DividedSquare {
public static void main(String[] args) {
new DividedSquare();
}
public DividedSquare() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private TriangleShape baseTriangle;
private Color[] colors;
public TestPane() {
colors = new Color[]{Color.RED, Color.GREEN, Color.BLUE, Color.MAGENTA};
}
#Override
public void invalidate() {
super.invalidate();
baseTriangle = new TriangleShape(
new Point(0, 0),
new Point(getWidth(), 0),
new Point(getWidth() / 2, getHeight() / 2));
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
String text[] = new String[]{
"123.123",
"456.789",
"012.315",
"678.921"
};
FontMetrics fm = g2d.getFontMetrics();
double angel = 0;
for (int index = 0; index < 4; index++) {
g2d.setColor(colors[index]);
Path2D rotated = rotate(baseTriangle, angel);
g2d.fill(rotated);
Rectangle bounds = rotated.getBounds();
int x = bounds.x + ((bounds.width - fm.stringWidth(text[0])) / 2);
int y = bounds.y + (((bounds.height - fm.getHeight()) / 2) + fm.getAscent());
g2d.setColor(Color.WHITE);
g2d.drawString(text[index], x, y);
angel += 90;
}
g2d.setColor(Color.BLACK);
g2d.drawLine(0, 0, getWidth(), getHeight());
g2d.drawLine(getWidth(), 0, 0, getHeight());
g2d.dispose();
try
{
BufferedImage image = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
frame.paint(image.getGraphics());
ImageIO.write(image,"png", new File("Practice.png"));
}
catch(Exception exception)
{
//code
System.out.print("Exception to write image");
}
}
public Path2D rotate(TriangleShape shape, double angel) {
Rectangle bounds = shape.getBounds();
int x = bounds.width / 2;
int y = bounds.width / 2;
return new Path2D.Float(shape, AffineTransform.getRotateInstance(
Math.toRadians(angel),
x,
y));
}
}
public class TriangleShape extends Path2D.Double {
public TriangleShape(Point2D... points) {
moveTo(points[0].getX(), points[0].getY());
lineTo(points[1].getX(), points[1].getY());
lineTo(points[2].getX(), points[2].getY());
closePath();
}
}
}
But I the image does not get created. I am unable to understand why.
I looked at this but am unable to understand how to incorporate it in my case.
Edit
Based on comments, I tried using robot class but am unable to know where to call this function from. If I call this function from the paint() method, I am unable to get the colors and text.
void screenshot()
{
try
{
Robot robot = new Robot();
// Capture the screen shot of the area of the screen defined by the rectangle
Point p = frame.getLocationOnScreen();
System.out.print("point" + p);
BufferedImage bi=robot.createScreenCapture(new Rectangle((int)p.getX(),(int)p.getY(),frame.getWidth(),frame.getHeight()));
ImageIO.write(bi, "png", new File("imageTest.png"));
}
catch(Exception exception)
{
//code
System.out.print("Exception to write image");
}
}
There are at least two ways you might achieve this...
You Could...
Use Robot to capture a screen shot. For example
The problem with this is it takes a little effort to target the component you want to capture. It also only captures a rectangular area, so if the component is transparent, Robot won't respect this...
You Could...
Use printAll to render the component to your own Graphics context, typically from a BufferedImage
printAll allows you to print a component, because the intention is not to print this to the screen, printAll disables double buffering, making it more efficient to use when you don't want to render the component to the screen, such printing it to a printer...
Forexample
You can use Robot to capture screenshot. But it not gives Jframe Screenshot. We need to give correct coordinates and refer the frame. gFrame is my frame name and below code works for only Jframe area screenshot.
try {
Robot cap=new Robot();
Rectangle rec=new Rectangle(gFrame.getX(),gFrame.getY(),gFrame.getWidth(),gFrame.getHeight());
BufferedImage screenshot=cap.createScreenCapture(rec);
ImageIO.write(screenshot, "JPG",
new File("C:\\Users\\ruwan\\Downloads\\screenshot.jpg");
} catch (Exception e) {
e.printStackTrace();
}

Java 2D game graphics

Next semester we have a module in making Java applications in a team. The requirement of the module is to make a game. Over the Christmas holidays I've been doing a little practice, but I can't figure out the best way to draw graphics.
I'm using the Java Graphics2D object to paint shapes on screen, and calling repaint() 30 times a second, but this flickers terribly. Is there a better way to paint high performance 2D graphics in Java?
What you want to do is to create a canvas component with a BufferStrategy and render to that, the code below should show you how that works, I've extracted the code from my self written Engine over here.
Performance solely depends on the stuff you want to draw, my games mostly use images. With around 1500 of them I'm still above 200 FPS at 480x480. And with just 100 images I'm hitting 6k FPS when disabling the frame limiting.
A small game (this one has around 120 images at once at the screen) I've created can be found here (yes the approach below also works fine as an applet.)
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
import java.awt.Toolkit;
import java.awt.Transparency;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import javax.swing.WindowConstants;
public class Test extends Thread {
private boolean isRunning = true;
private Canvas canvas;
private BufferStrategy strategy;
private BufferedImage background;
private Graphics2D backgroundGraphics;
private Graphics2D graphics;
private JFrame frame;
private int width = 320;
private int height = 240;
private int scale = 1;
private GraphicsConfiguration config =
GraphicsEnvironment.getLocalGraphicsEnvironment()
.getDefaultScreenDevice()
.getDefaultConfiguration();
// create a hardware accelerated image
public final BufferedImage create(final int width, final int height,
final boolean alpha) {
return config.createCompatibleImage(width, height, alpha
? Transparency.TRANSLUCENT : Transparency.OPAQUE);
}
// Setup
public Test() {
// JFrame
frame = new JFrame();
frame.addWindowListener(new FrameClose());
frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
frame.setSize(width * scale, height * scale);
frame.setVisible(true);
// Canvas
canvas = new Canvas(config);
canvas.setSize(width * scale, height * scale);
frame.add(canvas, 0);
// Background & Buffer
background = create(width, height, false);
canvas.createBufferStrategy(2);
do {
strategy = canvas.getBufferStrategy();
} while (strategy == null);
start();
}
private class FrameClose extends WindowAdapter {
#Override
public void windowClosing(final WindowEvent e) {
isRunning = false;
}
}
// Screen and buffer stuff
private Graphics2D getBuffer() {
if (graphics == null) {
try {
graphics = (Graphics2D) strategy.getDrawGraphics();
} catch (IllegalStateException e) {
return null;
}
}
return graphics;
}
private boolean updateScreen() {
graphics.dispose();
graphics = null;
try {
strategy.show();
Toolkit.getDefaultToolkit().sync();
return (!strategy.contentsLost());
} catch (NullPointerException e) {
return true;
} catch (IllegalStateException e) {
return true;
}
}
public void run() {
backgroundGraphics = (Graphics2D) background.getGraphics();
long fpsWait = (long) (1.0 / 30 * 1000);
main: while (isRunning) {
long renderStart = System.nanoTime();
updateGame();
// Update Graphics
do {
Graphics2D bg = getBuffer();
if (!isRunning) {
break main;
}
renderGame(backgroundGraphics); // this calls your draw method
// thingy
if (scale != 1) {
bg.drawImage(background, 0, 0, width * scale, height
* scale, 0, 0, width, height, null);
} else {
bg.drawImage(background, 0, 0, null);
}
bg.dispose();
} while (!updateScreen());
// Better do some FPS limiting here
long renderTime = (System.nanoTime() - renderStart) / 1000000;
try {
Thread.sleep(Math.max(0, fpsWait - renderTime));
} catch (InterruptedException e) {
Thread.interrupted();
break;
}
renderTime = (System.nanoTime() - renderStart) / 1000000;
}
frame.dispose();
}
public void updateGame() {
// update game logic here
}
public void renderGame(Graphics2D g) {
g.setColor(Color.BLACK);
g.fillRect(0, 0, width, height);
}
public static void main(final String args[]) {
new Test();
}
}
The flickering is due to you writing direct to the screen. Use a buffer to draw on and then write the entire screen in 1 go. This is Double Buffering which you may have heard of before. Here is the simplest form possible.
public void paint(Graphics g)
{
Image image = createImage(size + 1, size + 1);
Graphics offG = image.getGraphics();
offG.setColor(Color.BLACK);
offG.fillRect(0, 0, getWidth(), getHeight());
// etc
See the use of the off screen graphics offG. It is expensive to create the off screen image so I would suggest creating it only on first call.
There's other areas you can improve this further eg creating a compatible image, using clipping etc. For more fine tuned control of animation you should look into active rendering.
There's a decent page I have bookmarked discussing game tutorials here.
Good luck!
Java OpenGL (JOGL) is one way.
I think you made an override from paint(Graphics g)? This is not the good way. Use the same code but in paintComponent(Graphics g) instead of paint(Graphics g).
A label you can search is doublebuffer. That is what will be done automatically by overriding paintComponent.
There is a simple way to optimize your program. Get rid of any complex code and just use JComponent instead Canvas and paint your objects on it. Thats all. Enjoy it...

Categories