I am working on a chess engine, and I have the following problem: my chess pieces are not drawn unless repaint()
is called. However, the squares (= the chess field) appear just fine.
I read I should avoid using repaint()
inside the paintComponent
since this will make the function be called continuously. How can I avoid calling repaint()
, but still have my image drawn?
This is the code:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Toolkit;
import java.util.ArrayList;
import javax.swing.JFrame;
import javax.swing.JPanel;
@SuppressWarnings("serial")
public class ChessBoard extends JPanel {
ArrayList<Square> squares = new ArrayList<Square>();
Piece piece = new Piece("B", 0);
public ChessBoard() {
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
squares.add(new Square(i, j));
}
}
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
for (Square square : squares) {
square.draw(g);
}
piece.draw(g);
//repaint(); //Image only appears if this is called
}
public static void main(String[] args) {
// TODO Auto-generated method stub
ChessBoard chessboard = new ChessBoard();
chessboard.createFrame();
}
public void createFrame() {
JFrame f = new JFrame("ChessBoard");
f.getContentPane().setPreferredSize(new Dimension(squareSize()*8, squareSize()*8));
f.setResizable(false);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setTitle("Chessboard");
f.setLocation((int)(screenSize().width-squareSize()*8)/2, (int)(screenSize().height-squareSize()*8)/2);
f.add(this);
f.pack();
Frame.getFrames();
f.setVisible(true);
}
public static Dimension screenSize() {
return Toolkit.getDefaultToolkit().getScreenSize();
}
public static int squareSize() {
return (int)screenSize().height/12;
}
}
class Square {
int start_x, start_y;
int square_size;
Color color;
public Square(int x, int y) {
// constructor
start_x = x*ChessBoard.squareSize();
start_y = y*ChessBoard.squareSize();
square_size = ChessBoard.squareSize();
if ((x+y)%2 == 0) {
// Color of the white squares
color = new Color(209, 192, 148);
} else {
// Color of the black squares
color = new Color(110, 83, 43);
}
}
public void draw(Graphics g) {
g.setColor(this.color);
g.fillRect(this.start_x, this.start_y, square_size, square_size);
}
}
class Piece {
String type;
int coordinate, square_size, piece_size;
int[] draw_coordinates = {7, 6, 5, 4, 3, 2, 1, 0};
Image image;
public Piece(String type, int coordinate) {
this.type = type;
this.coordinate = coordinate;
this.square_size = ChessBoard.squareSize();
this.piece_size = (int)ChessBoard.squareSize()*2/3;
this.image = Toolkit.getDefaultToolkit().getImage("src/pieces/black_bishop.png");
}
public int co_x_board() {
return coordinate - ((int)coordinate/8)*8;
}
public int co_y_board() {
return (int)coordinate/8;
}
public int co_x_draw() {
return co_x_board()*square_size+((int)square_size/6);
}
public int co_y_draw() {
return draw_coordinates[co_y_board()]*square_size+((int)square_size/6);
}
public void draw(Graphics g) {
g.drawString(type, co_x_draw(), co_y_draw()); // does work
g.drawImage(image, co_x_draw(), co_y_draw(), piece_size, piece_size, null); // does not work
}
}
Thank you in advance!
The problem lies in the fact that that you didn't supply the ImageObserver
argument of the drawImage
method call.
I can reproduce the error you are seeing in your MRE.
I have worked with reading images via Toolkit.getDefaultToolkit().getImage("...");
(or Toolkit.getDefaultToolkit().createImage("...");
) and sometimes it does not work if you don't supply the ImageObserver
, or don't call for example getWidth(null)
on the image before painting it, as well as other symptoms, for which I don't know the cause. But what I do know is that if you supply the ImageObserver
argument then it will work.
Remember, Component
is an ImageObserver
... So you need to actually supply the Component
(to which the Graphics
object belongs at) to the drawImage
last argument.
So, you can change your Piece#draw
method to accept the Component
on which it is drawn, like so:
public void draw(Graphics g, final Component observer) {
g.drawString(type, co_x_draw(), co_y_draw());
g.drawImage(image, co_x_draw(), co_y_draw(), piece_size, piece_size, observer);
}
Then remember to call it properly, by changing the ChessBoard#paintComponent
like so:
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
for (Square square : squares) {
square.draw(g);
}
piece.draw(g, this);
}
Another way that usually corrects the error, even with null
as the ImageObserver
is to use an ImageIO#read
method to read in the image as a BufferedImage
. Again, I don't know why this works, but it does. I also did not test your case with ImageIO#read
, but I still tested it with Toolkit.getDefaultTooklit().getImage("...");
and it works (but you need to supply the ImageObserver
argument).
In my opinion, it will be far less complicated to lay out some JLabel
s in a JPanel
with GridLayout
(for example new GridLayout(0, 8);
) and then set the images as Icon
s (via myLabel.setIcon(new ImageIcon(myImage));
) to the JLabel
s...