After a day of searching, I have given up and decided to ask this question: I can't seem to stop the constant flickering of this program:
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
public class Paint {
BufferedImage image0, image1, image2;
double rotation0 = 0.0001;
double rotation1 = 0.0501;
double rotation2 = 3.0001;
double add0 = 0.0001;
double add1 = 0.0016;
double add2 = 0.000001;
private int x() {
return Main.getX() / 2;
}
private int y() {
return Main.getY() / 2;
}
public Paint() {
try {
image0 = ImageIO.read(new File("circle1.jpg"));
image1 = ImageIO.read(new File("circle2.jpg"));
image2 = ImageIO.read(new File("circle3.jpg"));
} catch (IOException e) {
e.printStackTrace();
}
}
public void paint(Graphics g) {
// create the transform, note that the transformations happen
// in reversed order (so check them backwards)
AffineTransform at0 = new AffineTransform();
AffineTransform at1 = new AffineTransform();
AffineTransform at2 = new AffineTransform();
// 4. translate it to the center of the component
at0.translate(x(), y() + 10);
at1.translate(x(), y() + 10);
at2.translate(x(), y() + 10);
// 3. do the actual rotation
rotation0 += add0;
rotation1 += add1;
rotation2 += add2;
at0.rotate(rotation0);
at1.rotate(rotation1);
at2.rotate(rotation2);
// 2. just a scale because this image is big
at0.scale(1, 1);
at1.scale(1, 1);
at2.scale(1, 1);
// 1. translate the object so that you rotate it around the
// center (easier :))
at0.translate(-image0.getWidth()/2, -image0.getHeight()/2);
at1.translate(-image1.getWidth()/2, -image1.getHeight()/2);
at2.translate(-image2.getWidth()/2, -image2.getHeight()/2);
// draw the image
Graphics2D g2d = (Graphics2D) g;
g2d.drawImage(image0, at0, null);
g2d.drawImage(image1, at1, null);
g2d.drawImage(image2, at2, null);
}
}
IMPORTANT: Everything works but the double buffering, which causes the image to flicker. If you don't mind, please provide code along with the answer.
Double buffering consists into drawing your content to an internal image and then draw that image on screen. By doing that, the image is never partially printed.
So, instead of drawing your 3 images directly in your Graphics object, try drawing them into a new BufferedImage and draw that image in the Graphics object (screen).
See this official page for more info: https://docs.oracle.com/javase/tutorial/extra/fullscreen/doublebuf.html
I assume the usage is in a JPanel child. By default a JPanel is double-buffered.
JPanel panel new JPanel() {
Paint paint = new Paint();
#Override
public void paintComponent(Graphics g) {
paint.paint(g);
}
};
.. add the panel to the JFrame.
This should work in java swing. Java awt components is another obsolete matter.
Related
I develop a game and rotating images currently takes most of the time in the calculation process of a frame. For optimization I'm searching for the fastest way to rotate a buffered-image. I already tried two methods shown down there.
slowest method:
public static BufferedImage rotate(BufferedImage imgOld, int deg){ //Parameter for this method are the picture to rotate and the rotation in degrees
AffineTransform at = AffineTransform.getRotateInstance(Math.toRadians(deg), (int)(imgOld.getWidth()/2), (int)(imgOld.getHeight()/2)); //initialize and configure transformation
BufferedImage imgNew = new BufferedImage(imgOld.getWidth(), imgOld.getHeight(), imgOld.getType()); //create new bufferedimage with the properties of the image to rotate
Graphics2D g = (Graphics2D) imgNew.getGraphics(); //create Graphics
g.setTransform(at); //apply transformation
g.drawImage(imgOld, 0, 0, null); //draw rotated image
g.dispose();
imgOld.flush();
return imgNew;
}
little bit faster method :
public static BufferedImage rotate(BufferedImage imgOld, int deg){ //parameter same as method above
BufferedImage imgNew = new BufferedImage(imgOld.getWidth(), imgOld.getHeight(), imgOld.getType()); //create new buffered image
Graphics2D g = (Graphics2D) imgNew.getGraphics(); //create new graphics
g.rotate(QaDMath.toRadians(deg), imgOld.getWidth()/2, imgOld.getHeight()/2); //configure rotation
g.drawImage(imgOld, 0, 0, null); //draw rotated image
return imgNew; //return rotated image
}
}
I found many topics related to rotating an image but not a single one discussing the fastest, most solution.
I hope i didn't miss any topic and this isn't a duplicate.
Hopefully there is someone more skilled than me out there knowing a solution
I would guess that part of the problem is that you are continually creating new BufferedImages to do the rotation. This results in you doing the painting twice, once when you paint onto the BufferedImage and the second time when you paint the BufferedImage on the frame.
You could try to just paint the existing BufferedImage rotated. For example you could use the Rotated Icon and then just paint the icon using
rotated.paintIcon(...);
Whenever you need to rotate the image you just use:
rotated.setDegrees(...);
Simple example:
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.event.*;
public class Rotation3 extends JPanel
{
private Icon icon;
private RotatedIcon rotated;
private int degrees;
public Rotation3(Image image)
{
icon = new ImageIcon( image );
rotated = new RotatedIcon(icon, 0);
rotated.setCircularIcon( true );
setDegrees( 0 );
setPreferredSize( new Dimension(600, 600) );
}
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
double radians = Math.toRadians( degrees );
// translate x/y so Icon rotated around a specific point (300, 300)
int x = 300 - (rotated.getIconWidth() / 2);
int y = 300 - (rotated.getIconHeight() / 2);
rotated.paintIcon(this, g, x, y);
g.setColor(Color.RED);
g.fillOval(295, 295, 10, 10);
}
public void setDegrees(int degrees)
{
this.degrees = degrees;
rotated.setDegrees(degrees);
repaint();
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
try
{
String path = "dukewavered.gif";
ClassLoader cl = Rotation3.class.getClassLoader();
BufferedImage bi = ImageIO.read(cl.getResourceAsStream(path));
final Rotation3 r = new Rotation3(bi);
final JSlider slider = new JSlider(JSlider.HORIZONTAL, 0, 360, 0);
slider.addChangeListener(new ChangeListener()
{
public void stateChanged(ChangeEvent e)
{
int value = slider.getValue();
r.setDegrees( value );
}
});
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new JScrollPane(r));
f.add(slider, BorderLayout.SOUTH);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
catch(IOException e)
{
System.out.println(e);
}
}
});
}
}
Just drag the slider to see the rotation.
I have a problem. I want to be able to zoom into my Graphics2D screen using the mouse wheel but I want to be able to translate the Graphics2D as well so that it's right on the spot that I scaled. Here's what's happening so far: "http://cdn.makeagif.com/media/6-11-2015/E0kYGY.gif" It's translating to the player's position correctly but I want it to be zoomable in and out with the scrolling of the mouse wheel. Here's the code I have so far to do it:
private AffineTransform getCamTransform()
{
AffineTransform t = new AffineTransform();
float a = 400 - player.getX();
float b = 250 - player.getY();
t.translate(a, b);
/*
* Here's where I want to scale using the zoom
* variable and also translate the g2d according
* to the zoom variable.
*/
return t;
}
private double zoom = 2.0D;
#Override
public void mouseWheelMoved(MouseWheelEvent e)
{
zoom += e.getPreciseWheelRotation();
}
This is just a small segment of the code I have. I have a method that draws everything to the screen but this is just to return the transform that must be applied to the camera. The transform is then set to the previous transform of the Graphics2D.
Let me know if there's an easier way to do this or I'm doing it all wrong. I do also want to be efficient on the CPU as well. Thanks to whoever answers!
The basic idea is, you want to make it appear as if the graphics is remaining centred within the viewable area. I first thought about trying to scale the translation points, and maybe that might work, but I couldn'y get it to work (3 year old wanting to read books didn't give me much time to experiment).
Basically, you need to add another translation in which moves the x/y point the appropriate amount based on the viewable area and the zoom so the image appears to remain stationary within the viewable area, then apply the zoom and the normal translation on-top of it...
double width = getWidth();
double height = getHeight();
double zoomWidth = width * zoom;
double zoomHeight = height * zoom;
double anchorx = (width - zoomWidth) / 2;
double anchory = (height - zoomHeight) / 2;
AffineTransform at = new AffineTransform();
at.translate(anchorx, anchory);
at.scale(zoom, zoom);
at.translate(-100, -100);
So, the anchorx/y represent the amount the resulting graphics would need be offset by, based on the viewable areas width and height, so that the image will remain "centred" within in it...
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseWheelEvent;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.text.NumberFormat;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private double zoom = 1d;
private BufferedImage img;
public TestPane() {
try {
img = ImageIO.read(new File("..."));
} catch (IOException ex) {
ex.printStackTrace();
}
addMouseWheelListener(new MouseAdapter() {
#Override
public void mouseWheelMoved(MouseWheelEvent e) {
if (e.getPreciseWheelRotation() < 0) {
zoom -= 0.1;
} else {
zoom += 0.1;
}
// zoom += e.getPreciseWheelRotation();
if (zoom < 0.01) {
zoom = 0.01;
}
repaint();
}
});
}
public String format(double value) {
return NumberFormat.getNumberInstance().format(value);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
double width = getWidth();
double height = getHeight();
double zoomWidth = width * zoom;
double zoomHeight = height * zoom;
double anchorx = (width - zoomWidth) / 2;
double anchory = (height - zoomHeight) / 2;
AffineTransform at = new AffineTransform();
at.translate(anchorx, anchory);
at.scale(zoom, zoom);
at.translate(-100, -100);
g2d.setTransform(at);
g2d.drawImage(img, 0, 0, this);
g2d.dispose();
}
}
}
There's probably a really awesome mathematical formula which you could use instead, but me be dumb ;)
I'm writing a tiled game board with hexagonal shape tiles using java swing. I'm able to draw polygon with help of this SOF thread.
Now I want to add background image to these hexagons and I have totally no idea how to do this. Here is a tutorial that draws background on "Rectangle" but how can I do the same on Hexagon?
Create the hexagon using a Shape. Probably a Polygon for this.
Set the Shape as a clip for the Graphics2D object.
Paint the image.
Move the Shape to the next location using an AffineTransform, a translate instance.
Rinse and repeat.
Like this:
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.image.BufferedImage;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.*;
class TexturedShape {
public static BufferedImage getTexturedImage(
BufferedImage src, Shape shp, int x, int y) {
Rectangle r = shp.getBounds();
// create a transparent image with 1 px padding.
BufferedImage tmp = new BufferedImage(
r.width+2,r.height+2,BufferedImage.TYPE_INT_ARGB);
// get the graphics object
Graphics2D g = tmp.createGraphics();
// set some nice rendering hints
g.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.setRenderingHint(
RenderingHints.KEY_COLOR_RENDERING,
RenderingHints.VALUE_COLOR_RENDER_QUALITY);
// create a transform to center the shape in the image
AffineTransform centerTransform = AffineTransform.
getTranslateInstance(-r.x+1, -r.y+1);
// set the transform to the graphics object
g.setTransform(centerTransform);
// set the shape as the clip
g.setClip(shp);
// draw the image
g.drawImage(src, x, y, null);
// clear the clip
g.setClip(null);
// draw the shape as an outline
g.setColor(Color.RED);
g.setStroke(new BasicStroke(1f));
g.draw(shp);
// dispose of any graphics object we explicitly create
g.dispose();
return tmp;
}
public static Shape getPointedShape(int points, int radius) {
double angle = Math.PI * 2 / points;
GeneralPath p = new GeneralPath();
for (int ii = 0; ii < points; ii++) {
double a = angle * ii;
double x = (Math.cos(a) * radius) + radius;
double y = (Math.sin(a) * radius) + radius;
if (ii == 0) {
p.moveTo(x, y);
} else {
p.lineTo(x, y);
}
}
p.closePath();
return p;
}
public static void main(String[] args) throws Exception {
URL url = new URL("http://i.stack.imgur.com/7bI1Y.jpg");
BufferedImage bi = ImageIO.read(url);
Shape hxgn = getPointedShape(6, 32);
final BufferedImage txtr = getTexturedImage(bi, hxgn, -200, -120);
Runnable r = new Runnable() {
#Override
public void run() {
JOptionPane.showMessageDialog(null,
new JLabel(new ImageIcon(txtr)));
}
};
// Swing GUIs should be created and updated on the EDT
// http://docs.oracle.com/javase/tutorial/uiswing/concurrency
SwingUtilities.invokeLater(r);
}
}
I am making a basic java application and trying to rotate an image. I wrote the following quick method
private Image rotate(double degs){
ImageIcon img = new ImageIcon("src/inc/img/char_male.png");
Image temp = new BufferedImage(img.getIconWidth(), img.getIconHeight(), BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = (Graphics2D) temp.getGraphics();
g2.rotate(Math.toRadians(degs));
g2.drawImage(img.getImage(), 0, 0, Color.WHITE, null);
System.out.println("Rotating "+degs);
g2.dispose();
return temp;
}
The problem is when I run this and repaint the GUI, the image turns pure black. Am I doing something wrong with the BufferedImage creation? I am changing the GUI in the repaint using a JLabel,
label.setIcon(new ImageIcon(rotate(90)));
You need to rotate and translate at the same time so that the center of rotation is the center of the image. The AffineTransform rotate method has an overload for this as does the Graphics2D rotate method. For e.g., what if you try,...
private Image rotate(double degs){
ImageIcon img = new ImageIcon("src/inc/img/char_male.png"); // why an ImageIcon???
Image temp = new BufferedImage(img.getIconWidth(), img.getIconHeight(), BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = (Graphics2D) temp.getGraphics();
g2.rotate(Math.toRadians(degs), img.getIconWidth()/2, img.getIconHeight()/2); // changed
g2.drawImage(img.getImage(), 0, 0, Color.WHITE, null);
System.out.println("Rotating "+degs);
g2.dispose();
return temp;
}
Sorry, here's the corrected code:
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
public class ImageRotate {
private static final String IMAGE_PATH = "src/yr2011/images/guitar_3406632_n.jpg";
public static void main(String[] args) {
try {
BufferedImage image = ImageIO.read(new File(IMAGE_PATH));
ImageIcon icon = new ImageIcon(image);
JOptionPane.showMessageDialog(null, new JLabel(icon));
icon = new ImageIcon(rotate(image, 90));
JOptionPane.showMessageDialog(null, new JLabel(icon));
} catch (IOException e) {
e.printStackTrace();
}
}
private static Image rotate(Image image, double degs) {
int width = image.getWidth(null);
int height = image.getHeight(null);
BufferedImage temp = new BufferedImage(height, width, BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = temp.createGraphics();
g2.rotate(Math.toRadians(degs), height / 2, height / 2);
g2.drawImage(image, 0, 0, Color.WHITE, null);
g2.dispose();
return temp;
}
}
I don't use Java, so that I can't tell if there's something wrong with your code, but a common error when doing image rotation is rotating out of the viewport. When you imagine the original image sitting at the coordinate origin, rotating it by 90 degrees moves the image below the x-axis (or left of the y-axis, depending on the direction). In both cases the rotated image leaves the viewport and you get an empty image. The solution is to shift the rotated image back to place.
I had the same probleme and I solved it in an other way:
My picture had a transparent background but I didn't know it because I look it always on white background. Anyway, I solved my problem when I use
BufferedImage temp = new BufferedImage(height, width, BufferedImage.TYPE_INT_ARGB);
instead of
BufferedImage temp = new BufferedImage(height, width, BufferedImage.TYPE_INT_RGB);
I think that graphics2d work with a black background if you don't precise transparent type.
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...