JPanel flickering issue - java

For my game, I paint to a java.awt.Image then draw the Image onto a JPanel. I do this for a couple of reasons, mainly because I didn't want game rendering to hog up cpu cycles on the EDT, and for portability.
A problem arised which caused flickering on the java.awt.JPanel when using
graphics#drawImage(Image img, int x, int y, int width, int height,
ImageObserver observer)
.
However,
graphics#drawImage(Image img, int x, int y, ImageObserver observer)
did not cause this issue.
Here is my code:
Sandbox.java
public class Sandbox implements Paintable {
public static void main(String[] args) throws Exception {
GameWindow window = new GameWindow("Test", 800, 600);
GameScreen screen = new GameScreen(800, 600);
Sandbox sandbox = new Sandbox();
window.add(screen);
window.setVisible(true);
boolean running = true;
while(running) {
sandbox.update();
screen.getPaintBuffer().clear();
screen.getPaintBuffer().paint(sandbox);
screen.repaint();
Thread.sleep(1000 / 60);
}
}
private int x = 0, y = 0;
public void update() {
x++;
y++;
}
public void paint(Graphics g) {
g.drawRect(x, y, 50, 50);
}
}
GameWindow.java
public class GameWindow extends JFrame {
public GameWindow(String title, int width, int height) {
setTitle(title);
setSize(width, height);
setResizable(false);
setLocationByPlatform(true);
setDefaultCloseOperation(3);
}
}
GameScreen.java
public class GameScreen extends JPanel {
private ImageBuffer buffer;
public GameScreen(int width, int height) {
buffer = new ImageBuffer(width, height);
}
public void paint(Graphics g) {
g.drawImage(getPaintBuffer().getBuffer(), 0, 0, getWidth(), getHeight(), null);
}
public ImageBuffer getPaintBuffer() {
return buffer;
}
}
Paintable.java
public interface Paintable {
public void paint(Graphics g);
}
ImageBuffer.java
public class ImageBuffer {
private final Image buffer;
private int width, height;
public ImageBuffer(int width, int height) {
buffer = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
this.width = width;
this.height = height;
}
public void paint(Paintable paintable) {
paintable.paint(buffer.getGraphics());
}
public void clear() {
buffer.getGraphics().clearRect(0, 0, width, height);
}
public Image getBuffer() {
return buffer;
}
}

Change the GameScreen's paint method to...
Override paintComponent instead
Call super.paintComponent in order to maintain the paint chain's contract
Pass this as the ImageObsever parameter to drawImage
For example...
public class GameScreen extends JPanel {
private ImageBuffer buffer;
public GameScreen(int width, int height) {
buffer = new ImageBuffer(width, height);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(getPaintBuffer().getBuffer(), 0, 0, getWidth(), getHeight(), this);
}
public ImageBuffer getPaintBuffer() {
return buffer;
}
}
Have a look at Painting in AWT and Swing and Performing Custom Painting for more details about how painting works
Updated
The basic problem is a scaling issue...
The image you're using to draw with is 800x600, but the GameScreen is actually 794x572 (on my PC), this causes the image to be scaled. Now, Swing's default scaling isn't pretty and is based on speed over quality generally.
Now, there are a number of ways you could improve this, but a quick way would be to apply some higher rendering hints, for example
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
g2d.drawImage(getPaintBuffer().getBuffer(), 0, 0, getWidth(), getHeight(), this);
g2d.dispose();
}
Now, I've gone overboard with this, so you might want to remove some and see what changes
Updated
Rendering hints could slow the rendering process down, so a better solution might to override the getPreferredSize method of GameScreen and return the expected size
public static class GameScreen extends JPanel {
private ImageBuffer buffer;
private Dimension size;
public GameScreen(int width, int height) {
buffer = new ImageBuffer(width, height);
this.size = new Dimension(width, height);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(800, 600);
}
Then, rather then passing the size to GameWindow, simply call pack
GameWindow window = new GameWindow("Test");
GameScreen screen = new GameScreen(800, 600);
Sandbox sandbox = new Sandbox();
window.add(screen);
window.pack();
window.setVisible(true);
This way, everybody's on the same size

Related

Creating smooth edges on a BufferedImage while using Graphics2D in java

I created the following code example to demonstrate my problem. I want to draw into a BufferedImage by a Graphics2D object, but the edges are not sharp. I tried using different renderinghints, but it didn't help. I also tried a BufferedImageOp as you can see in the code, but I don't understand its meaning in drawImage and don't know its possibilites. Can you help me please?
import javax.swing.*;
import java.awt.*;
import java.awt.geom.*;
import java.awt.image.*;
public class SmoothGraphics extends JComponent{
private BufferedImage image;
private static JFrame frame;
private int x = 100;
private int y = 100;
private int size = 200;
public static void main(String[] args){
frame = new JFrame();
SmoothGraphics component = new SmoothGraphics();
frame.add(component);
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
frame.setVisible(true);
frame.pack();
}
public SmoothGraphics(){
super();
setPreferredSize(new Dimension(400,400));
setBounds(100,100,400,400);
}
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
int width = getWidth();
int height = getHeight();
GraphicsConfiguration gc = frame.getGraphicsConfiguration();
image = gc.createCompatibleImage(width, height);
Graphics2D imageGraphics = (Graphics2D) image.createGraphics();
imageGraphics.setColor(Color.RED);
/*imageGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);*/
drawExample(imageGraphics);
imageGraphics.dispose();
/*the following kernel helps with the smoothness of the rectangle, but not of the circle
int kernelWidth = 3;
int kernelHeight = 3;
float[] data = new float[kernelWidth*kernelHeight];
data[4]=1;
Kernel kernel = new Kernel(kernelWidth, kernelHeight, data);
BufferedImageOp op = new ConvolveOp(kernel);
*/
//draw image
BufferedImageOp op = null;
g2d.drawImage(image,op,0,0);
//I don't know if this line is necessary
image.flush();
}
private void drawExample(Graphics2D g2d){
//Square as path
Path2D.Double path = new Path2D.Double();
path.moveTo(x,y);
path.lineTo(x+size,y);
path.lineTo(x+size,y+size);
path.lineTo(x,y+size);
path.closePath();
g2d.draw(path);
//Circle
g2d.fillOval(x+size/4,y+size/4,size/2,size/2);
}
}
The output of my code. Notice that the edges are not very clean/sharp!
My result with antialiasing and kernel usage:
My wished result:
Okay... I believe the real problem you are facing is initial scaling of the component graphics (g), which makes your image stretched during drawImage(...). I don't get the same problem here.
To get proper smooth (or "sharp" as you call it) rendering, you do want to enable antialiasing. But you also need to paint without pixel scaling (it's possible with image too, by creating a larger image, however, I don't see why you need the extra image in this case).
Here's your sample program rewritten:
import javax.swing.*;
import java.awt.*;
public class SmoothGraphics extends JComponent {
private final int x = 100;
private final int y = 100;
private final int size = 200;
public static void main(String[] args) {
JFrame frame = new JFrame("Smooth");
SmoothGraphics component = new SmoothGraphics();
frame.getContentPane().add(component, BorderLayout.CENTER); // Add to content pane
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.pack(); // Pack *before* setVisible(true)!
frame.setLocationRelativeTo(null); // Center the frame after pack()
frame.setVisible(true);
}
public SmoothGraphics() {
setOpaque(true); // Minor optimization
setPreferredSize(new Dimension(400, 400)); // No need to use setBounds, pack() will fix that for you
}
#Override
public void paintComponent(Graphics g) {
// No need to call super, as we repaint everything
Graphics2D g2d = (Graphics2D) g;
// Clear background to black
g2d.setColor(Color.BLACK);
g2d.fillRect(0, 0, getWidth(), getHeight());
// Paint in read, with antialiasing
g2d.setColor(Color.RED);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
drawExample(g2d);
}
private void drawExample(Graphics2D g2d) {
// Square (this way avoids the odd skewed lines)
g2d.drawRect(x, y, size, size);
// Circle
g2d.fillOval(x + size / 4, y + size / 4, size / 2, size / 2);
}
}
Result:
If you really want/need the BufferedImage, I found a way that works for me (MacOS). It might need some minor tweaks to work perfectly on other platforms.
#Override
public void paintComponent(Graphics g) {
// No need to call super, as we repaint everything
Graphics2D g2d = (Graphics2D) g;
GraphicsConfiguration gc = g2d.getDeviceConfiguration();
AffineTransform transform = gc.getNormalizingTransform();
BufferedImage image = gc.createCompatibleImage((int) ceil(getWidth() * transform.getScaleX()), (int) ceil(getHeight() * transform.getScaleY()));
Graphics2D graphics = image.createGraphics();
try {
graphics.setTransform(transform);
graphics.setColor(Color.RED);
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // Need antialiasing on
drawExample(graphics);
}
finally {
graphics.dispose();
}
try {
AffineTransform originalTransform = g2d.getTransform();
originalTransform.concatenate(transform.createInverse());
g2d.setTransform(originalTransform);
}
catch (NoninvertibleTransformException e) {
throw new RuntimeException(e); // API oddity, this should have been a RuntimeException
}
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); // Need render QUALITY here, to avoid the stretching/tearing
// g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); // Don't need this on MacOS, but may be needed on other platforms
g2d.drawImage(image, null, 0, 0);
}
Result:

i can't draw anything using getGraphics() method

I'm trying to draw in JPANEL using getgraphics but it still doesn't work, I don't understand why, I don't know where the error is.
maybe it happens because i'm using threads,also this part of code is based on a game made with java swing (Dragon tale).
my code
public class GamePanel extends JPanel implements Runnable {
private BufferedImage image;
private Graphics2D g;
private Thread thread;
public static final int WIDTH = 320;
public static final int HEIGHT = 240;
public static final int SCALE = 2;
public GamePanel() {
setPreferredSize(new Dimension(WIDTH, HEIGHT));
setFocusable(true);
requestFocus();
}
public void addNotify() {
super.addNotify();
if (thread == null) {
thread = new Thread(this);
thread.start();
}
}
private void init() {
image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
g = (Graphics2D)image.getGraphics();
}
#Override
public void run() {
init();
draw(g);
drawToScreen();
}
public void draw(Graphics2D g) {
g.drawRect(0, 0, 100, 1);
}
private void drawToScreen() {
Graphics g2 = this.getGraphics();
g2.drawImage(image, 0, 0, WIDTH * 2, HEIGHT * SCALE, null);
g2.dispose();
}
}

BufferedImage black and white after scaling

After scaling BufferdImages of nation flags about 50% of all flags are displayed in black and white (not grayscale) and few do look weird, like turky:
Here's a black and white example
Here I set the flag
protected void updateFlag(BufferedImage flag){
int height = pnlFlag.getHeight();
int width = (int)(1f * flag.getWidth() / flag.getHeight() * height);
BufferedImage scaledFlag = new BufferedImage(width, height, flag.getType());
Graphics2D g2d = scaledFlag.createGraphics();
g2d.drawImage(flag, 0, 0, scaledFlag.getWidth(), scaledFlag.getHeight(), null);
g2d.dispose();
pnlFlag.flag = scaledFlag;
pnlFlag.repaint();
}
And my JPanel
class FlagPanel extends JPanel{
private BufferedImage flag;
#Override
protected void paintComponent(Graphics g){
super.paintComponent(g);
if(flag == null) return;
g.drawImage(flag, 0, 0, null);
}
}
I'm pretty sure you can not just change the height and width of a Buffered Image and it will scale for you.
Try using getScaledInstance() like in this question.
The code bellow works fine, but you need to call the repaint method because the getScaledInstance method take a time to generate the scaloned image.
public class Main extends JFrame {
private ImageIcon ico;
private boolean resize = true;
private Image scale;
public Main() {
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
ico = new ImageIcon("image.jpg");
}
#Override
public void paint(Graphics g) {
super.paint(g);
Graphics2D gg = (Graphics2D) g;
if (!resize) {
g.drawImage(ico.getImage(), 0, 0, null);
} else {
if (scale == null) {
scale = ico.getImage().getScaledInstance(ico.getIconWidth() / 2, ico.getIconHeight() / 2, Image.SCALE_SMOOTH);
}
g.drawImage(scale, 0, 0, null);
// gg.scale(0.5, 0.5);
// gg.drawImage(ico.getImage(), 0, 0, null);
}
}
public static void main(String[] args) {
Main m = new Main();
m.setSize(600, 600);
m.setVisible(true);
}
}

Rectangle not drawing on BufferedImage

I'e been learning java for a while and I've just started a project to make a functional drawing program. However the code below is supposed to draw a rectangle on a bufferedimage but it does not work.
Code for drawing rectangle
public class DrawRectangle extends Panel {
public void drawRect(int x, int y, int width, int height) {
System.out.println("new Rectangle = X:" + x + " Y:" + y + " Width:" + width + " height:" + height);
canvas.createGraphics().draw(new Rectangle2D.Double(x, y, width, height));
}}
public class Panel extends JPanel {
BufferedImage canvas = new BufferedImage(400,400, BufferedImage.TYPE_INT_ARGB);
......
public void paintComponent(Graphics g) {
super.paintComponent(g);
System.out.println("Repainting");
g.drawImage(canvas, 25, 25, null);
}}
Note: All the methods are going off correctly so it is not simply me neglecting to initiate drawRectangle()
Edit my bad: you're not setting color properly. To wit:
e.g.,
import java.awt.*;
import java.awt.geom.*;
import java.awt.image.*;
import javax.swing.*;
public class FunnyDraw {
private static void createAndShowGui() {
DrawRectangle mainPanel = new DrawRectangle();
mainPanel.drawRect(10, 10, 100, 100);
mainPanel.betterDrawRect(200, 200, 200, 200);
JFrame frame = new JFrame("FunnyDraw");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
class HisPanel extends JPanel {
private static final Color COLOR = Color.black;
private static final int PREF_W = 600;
private static final int PREF_H = 450;
protected BufferedImage canvas = new BufferedImage(PREF_W, PREF_H,
BufferedImage.TYPE_INT_ARGB);
public void paintComponent(Graphics g) {
super.paintComponent(g);
System.out.println("Repainting");
g.drawImage(canvas, 25, 25, null);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
public void draw(Shape shape) {
Graphics2D g2 = canvas.createGraphics();
g2.setColor(COLOR);
g2.draw(shape);
g2.dispose();
repaint();
}
}
class DrawRectangle extends HisPanel {
public void drawRect(int x, int y, int width, int height) {
Graphics2D g2 = canvas.createGraphics();
g2.setColor(Color.black);
g2.draw(new Rectangle2D.Double(x, y, width, height));
g2.dispose();
repaint();
}
public void betterDrawRect(int x, int y, int width, int height) {
draw(new Rectangle2D.Double(x, y, width, height));
}
}

Graphics2D draw image inside of defined quadrilateral

I cannot find a draw image overload inside of Graphics2D which will enable me to perform such a task, can someone help me figure out how one might do this - preferably without swapping to more advanced graphics frameworks such as OpenGl,
thanks.
To clarify, a quad can be defined by anything with four-sides; that means a diamond or a rectangle or more elaborate shapes.
Mre has removed many of his remarks and so It seems as though I am responding to no-one, however all I have said in the comments were responses to what mre had said.
See Andrew Thomson's solution for the basics.
Instead of using a "text shape", I created a Shape using:
Polygon polygon = new Polygon();
polygon.addPoint(250, 50);
polygon.addPoint(350, 50);
polygon.addPoint(450, 150);
polygon.addPoint(350, 150);
g.setClip(polygon);
g.drawImage(originalImage, 0, 0, null);
Inherited Graphicsimage drawing methods
drawImage(Image img, int x, int y, Color bgcolor, ImageObserver observer)
drawImage(Image img, int x, int y, ImageObserver observer)
drawImage(Image img, int x, int y, int width, int height, Color bgcolor, ImageObserver observer)
drawImage(Image img, int x, int y, int width, int height, ImageObserver observer)
drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, Color bgcolor, ImageObserver observer)
drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, ImageObserver observer)
Choose your poison. Since you weren't even able to locate these, I'm assuming that going into detail about Intermediate Images when faced with scaling and frequent rendering would be futile.
Example 1 -- drawing a circle in a square
public class DrawCircleInSquare {
public static void main(String[] args){
SwingUtilities.invokeLater(new Runnable(){
#Override
public void run() {
createAndShowGUI();
}
});
}
private static void createAndShowGUI(){
final JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final JPanel panel = new JPanel(){
#Override
protected void paintComponent(Graphics g){
Graphics2D g2 = (Graphics2D)g.create();
// Clear background to white
g2.setColor(Color.WHITE);
g2.clearRect(0, 0, getWidth(), getHeight());
// Draw square
g2.setColor(Color.BLACK);
g2.drawRect(50, 50, 100, 100);
// Draw circle inside square
g2.setColor(Color.RED);
g2.fillOval(88, 88, 24, 24);
g2.dispose();
}
#Override
public Dimension getPreferredSize(){
return new Dimension(200, 200);
}
};
frame.add(panel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
Output
Example 2 -- draw an image in a square
public class DrawImageInSquare {
private static BufferedImage bi;
public static void main(String[] args){
try {
// Load image
loadImage();
// Create and show GUI
SwingUtilities.invokeLater(new Runnable(){
#Override
public void run() {
createAndShowGUI();
}
});
} catch (IOException e) {
// handle exception
}
}
private static void loadImage() throws IOException{
bi = ImageIO.read(new File("src/resources/psyduck.png"));
}
private static void createAndShowGUI(){
final JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final JPanel panel = new JPanel(){
#Override
protected void paintComponent(Graphics g){
Graphics2D g2 = (Graphics2D)g.create();
// Clear background to white
g2.setColor(Color.WHITE);
g2.clearRect(0, 0, getWidth(), getHeight());
// Draw square
g2.setColor(Color.BLACK);
g2.drawRect(50, 50, 100, 100);
// Draw image inside square
g2.setRenderingHint(
RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BICUBIC);
g2.drawImage(bi, 50, 50, 100, 100, null);
g2.dispose();
}
#Override
public Dimension getPreferredSize(){
return new Dimension(200, 200);
}
};
frame.add(panel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
Output

Categories

Resources