-1

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!

Anonymous
  • 738
  • 4
  • 14
  • 36
Anton
  • 137
  • 2
  • 14
  • 1
    I'm not understanding your problem. When the frame is displayed the original state of the chessboard is painted. If you make a change to any of the ArrayList's then yes you need to invoke repaint() to paint the new state. Post a proper [mre] demonstrating the problem. Also note you should NOT be setting the preferred size of your frame. Your calculation does not take into account the size of the titlebar and borders. You should instead be setting the preferred size of the squares and then pack() the frame. – camickr Mar 20 '21 at 14:42
  • When I run my program, only the squares are shown. However, I would like the pieces to als be shown when starting the program. But, as stated above, the pieces only show themselves after f.repaint() is called (in this case, when the mouse is pressed). I hope this clears things up. – Anton Mar 20 '21 at 14:45
  • Concerning the preferred size, I did this on purpose since I do not know the size of the titlebar in advance (or is there another, cleaner way to get this working?). – Anton Mar 20 '21 at 14:47
  • No it doesn't clear things up. Your painting logic paints the squares and pieces at the same time. If the pieces don't appear, then you have a logic problem that is not initially setting the pieces correctly. The problem has nothing to do with the painting logic. – camickr Mar 20 '21 at 14:47
  • this is exactly why I do not understand why it's not working. When I call white_pieces.size() in the paint component, I can clearly see that my pieces are created. However they do not drawn themselves until f.repaint is called. I also noted that paintComponent is executed 2 times before any interaction. – Anton Mar 20 '21 at 14:51
  • *is there another, cleaner way to get this working?* - it is the responsibility of every Swing component to determine its own size. Since you are doing custom painting, you would set the preferred size of your `Chessboard` by overriding the `getPreferredSize()` method. – camickr Mar 20 '21 at 14:52
  • Ok thank you for the suggestion, I will have a look at it. For the minimal reproducible example, please look at the GitHub link. – Anton Mar 20 '21 at 14:53
  • (1-) and how do you expect us to understand why it is not working when we can't even see the code? *the full code can be found here* - We are not interested in seeing your application posted on github. That is why we ask for an [mre]. The code should be posted in the forum. It is meant for you to do basic debugging and isolate the problem code. – camickr Mar 20 '21 at 14:55
  • I could not include the full code since StackOverflow said the code was too long. – Anton Mar 20 '21 at 14:56
  • *the code was too long* - exactly. We have limited time to answer questions. We are not going to read your application code to understand what you are doing. We are NOT interested in your entire application. We are looking for an [mre]. They are NOT the same thing. Follow the link and read up on an "MRE". – camickr Mar 20 '21 at 14:57
  • I have also 'accepted' the answers of my previous posts, thank you for telling me (sorry this was not done, but I obviously didn't know). – Anton Mar 20 '21 at 15:11
  • I already told you. You create an MRE!!! Your question is about displaying a chess piece. You don't need a king, queen, biship, rook , knight, castle and pawn to display a single chess piece. You don't need images for every chess piece. You can display paint a character like "Q" for the Queen to represent the chess piece. You don't need the logic for all the moves of each chess piece. You don't need any of the game logic. All you need is a frame, the chess board and you add a single piece. Chances are while you simplify the code you find the solution. – camickr Mar 20 '21 at 15:11
  • Here is an example of an MRE: https://stackoverflow.com/questions/6811247/drawing-in-jlayeredpane-over-exising-jpanels/6811800#6811800. The code is self contained in a single class. It demonstrates a single piece of functionality, that is to drag a "piece" to a square. The code can be copy/pasted/compiled and tested. The only change required is to add your own image to represent a chess piece. Your MRE will be simpler because all you are trying to demonstrate is painting a chess piece on the chessboard. – camickr Mar 20 '21 at 15:16
  • I updated the question accordingly to your remarks. – Anton Mar 20 '21 at 15:43
  • When I run this, I see the "B" in the bottom left square even with the repaint() commented out. – NomadMaker Mar 20 '21 at 15:55
  • Yes, but the image is not drawing (see the very last line of code) – Anton Mar 20 '21 at 15:56
  • In the [mre] that I copied from your question, where does the image come from? Even after clicking and forcing repaints, I never see an image. Other than the letter – NomadMaker Mar 20 '21 at 15:58
  • the image is stored on my computer. You can download the image that I am using here: https://github.com/h-anton/Chess/blob/main/Chess/src/pieces/black_bishop.png – Anton Mar 20 '21 at 16:00
  • Yes, better MRE. It helped provide information related to the problem. That is, when you painted a String on your piece it displayed. So in fact you proved that the "piece" is being painted. The real issue is that the "image" of the "piece" is not painted. This is because the Toolkit getImage() method executes in a separate Thread and the image was not read into memory at the time the frame was first displayed. By adding the ImageObserver to the painting method it sets up a notify for the component to be repainted once the image has been completely loaded. – camickr Mar 20 '21 at 19:49

1 Answers1

2

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).

Some suggestions:

In my opinion, it will be far less complicated to lay out some JLabels in a JPanel with GridLayout (for example new GridLayout(0, 8);) and then set the images as Icons (via myLabel.setIcon(new ImageIcon(myImage));) to the JLabels...

gthanop
  • 3,035
  • 2
  • 10
  • 27
  • No problem. Your MRE helped a lot. – gthanop Mar 20 '21 at 16:12
  • 1
    Yeah sorry for not including it right away, I will keep it in mind for the next time. Thanks for your time! – Anton Mar 20 '21 at 16:14
  • 1
    One way to get image(s) for an (MR) example is to hot link to images seen in [this Q&A](http://stackoverflow.com/q/19209650/418556). E.G. The code in [this answer](https://stackoverflow.com/a/10862262/418556) hot links to an image embedded in [this question](https://stackoverflow.com/q/10861852/418556). – Andrew Thompson Mar 20 '21 at 20:33