Absolute Positioning Graphic JPanel Inside JFrame Blocked by Blank Sections

I'm trying to improve my understanding of Java, particularly Java GUI, by making a puzzle program. Currently the user selects an image, which is cut up into a specified number of pieces. The pieces are drawn randomly to the screen but they seem to be covered by blank portions of other pieces, and not all of them show up, but I can print out all the coordinates. I am using absolute positioning because a LayoutManager didn't seem to work. I briefly tried layeredPanes but they confused me and didn't seem to solve the problem. I would really appreciate some help.
Here are the 2 relevant classes:

import javax.swing.*;
import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;

public class PuzzlePieceDriver extends JFrame
{
  private static Dimension SCREENSIZE = Toolkit.getDefaultToolkit().getScreenSize();
  private static final int HEIGHT = SCREENSIZE.height;
  private static final int WIDTH = SCREENSIZE.width;

  public static int MY_WIDTH;
  public static int MY_HEIGHT;

  private static BufferedImage image;


  private int xPieces = PuzzleMagicDriver.getXPieces();
  private int yPieces = PuzzleMagicDriver.getYPieces();

  private PuzzlePiece[] puzzle = new PuzzlePiece[xPieces*yPieces];

  public Container pane = this.getContentPane();
  private JLayeredPane layeredPane = new JLayeredPane();


  public PuzzlePieceDriver(ImageIcon myPuzzleImage)
  {
    MY_WIDTH = myPuzzleImage.getIconWidth()+(int)myPuzzleImage.getIconHeight()/2;
    MY_HEIGHT = myPuzzleImage.getIconHeight()+(int)myPuzzleImage.getIconHeight()/2;
    setTitle("Hot Puzz");
setSize(MY_WIDTH,MY_HEIGHT);
setLocationByPlatform(true);

pane.setLayout(null);


image = iconToImage(myPuzzleImage); //pass image into bufferedImage form

puzzle = createClip(image);

//pane.add(layeredPane);


setVisible(true);
  }//end constructor



  public static BufferedImage iconToImage(ImageIcon icon)
  {
    Image img = icon.getImage();
 int w = img.getWidth(null);
 int h = img.getHeight(null);
 BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
 Graphics g = image.createGraphics();
     // Paint the image onto the buffered image
    g.drawImage(img, 0, 0, null);
    g.dispose();

    return image;
  }//end BufferedImage

  protected int randomNumber(int min, int max)
  {
    int temp = 
    min + (int)(Math.random() * ((max - min) + 1));

 return temp;
  }//end randomNumber


  private PuzzlePiece[] createClip(BufferedImage passedImage)
  {

 int cw, ch;
 int w,h;
 w = image.getWidth(null);
 h = image.getHeight(null);
 cw = w/xPieces;
     ch = h/yPieces;

 int[] cells=new int[xPieces*yPieces];

 int dx, dy;

 BufferedImage clip = passedImage;

 //layeredPane.setPreferredSize(new Dimension(w,h));

    for (int x=0; x<xPieces; x++) 
      {
        int sx = x*cw;
        for (int y=0; y<yPieces; y++) 
            {
            int sy = y*ch;
            int cell = cells[x*xPieces+y];
            dx = (cell / xPieces) * cw;
            dy = (cell % yPieces) * ch;

            clip= passedImage.getSubimage(sx, sy, cw, ch);
    int myX = randomNumber(0,(int)w);
    int myY = randomNumber(0,(int)h);

    PuzzlePiece piece=new PuzzlePiece(clip,myX,myY);
    puzzle[x*xPieces+y]=piece;
    piece.setBounds(myX,myY,w,h);
    //layeredPane.setBounds(myX,myY,w,h);
    //layeredPane.add(piece,new Integer(x*xPieces+y));
    pane.add(piece);
    piece.repaint();

        }//end nested for
}//end for
return puzzle;
  }//end createClip

}//end class

Sorry if the spacing is a little messed up!

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;

public class PuzzlePiece extends JPanel
{
private Point imageCorner;      //the image's top-left corner location
private Point prevPt;               //mouse location for previous event
private Boolean insideImage =false;

private BufferedImage image;

public PuzzlePiece(BufferedImage clip, int x, int y)
{
 image = clip;
 imageCorner = new Point(x,y);
 //repaint();
}//end constructor

public void paintComponent(Graphics g)
{
     super.paintComponent(g);
 g.drawImage(image, (int)getImageCornerX(),(int)getImageCornerY(), this);
     System.out.println("paint "+getImageCornerX()+"   "+getImageCornerY());
    //repaint(); 
//g.dispose();
}//end paintComponent

public Point getImageCorner()
{
  return imageCorner;
}//end getImageCorner
public double getImageCornerY()
{
  return imageCorner.getY();
}//end getImageCornerY
public double getImageCornerX()
{
  return imageCorner.getX();
}//end getPoint


}//end class PuzzlePiece

Any help would be appreciated, I've gotten really stuck! Thanks!!


Solution 1:

I was really intrigued by this idea, so I made another example, using a custom layout manager.

public class MyPuzzelBoard extends JPanel {

    public static final int GRID_X = 4;
    public static final int GRID_Y = 4;
    private BufferedImage image;

    public MyPuzzelBoard(BufferedImage image) {
        setLayout(new VirtualLayoutManager());
        setImage(image);

        addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                if (e.getClickCount() == 2) {
                    removeAll();
                    generatePuzzel();
                } else {
                    Component comp = getComponentAt(e.getPoint());
                    if (comp != null && comp != MyPuzzelBoard.this) {
                        setComponentZOrder(comp, 0);
                        invalidate();
                        revalidate();
                        repaint();
                    }
                }
            }
        });
    }

    public void setImage(BufferedImage value) {
        if (value != image) {
            image = value;
            removeAll();
            generatePuzzel();
        }
    }

    public BufferedImage getImage() {
        return image;
    }

    protected float generateRandomNumber() {
        return (float) Math.random();
    }

    protected void generatePuzzel() {
        BufferedImage image = getImage();

        if (image != null) {
            int imageWidth = image.getWidth();
            int imageHeight = image.getHeight();

            int clipWidth = imageWidth / GRID_X;
            int clipHeight = imageHeight / GRID_Y;
            for (int x = 0; x < GRID_X; x++) {
                for (int y = 0; y < GRID_Y; y++) {

                    float xPos = generateRandomNumber();
                    float yPos = generateRandomNumber();
                    Rectangle bounds = new Rectangle((x * clipWidth), (y * clipHeight), clipWidth, clipHeight);
                    MyPiece piece = new MyPiece(image, bounds);
                    add(piece, new VirtualPoint(xPos, yPos));

                }
            }
        }

        invalidate();
        revalidate();
        repaint();
    }

    public class VirtualPoint {

        private float x;
        private float y;

        public VirtualPoint(float x, float y) {
            this.x = x;
            this.y = y;
        }

        public float getX() {
            return x;
        }

        public float getY() {
            return y;
        }

        public void setX(float x) {
            this.x = x;
        }

        public void setY(float y) {
            this.y = y;
        }
    }

    public class VirtualLayoutManager implements LayoutManager2 {

        private Map<Component, VirtualPoint> mapConstraints;

        public VirtualLayoutManager() {
            mapConstraints = new WeakHashMap<>(25);
        }

        @Override
        public void addLayoutComponent(Component comp, Object constraints) {
            if (constraints instanceof VirtualPoint) {
                mapConstraints.put(comp, (VirtualPoint) constraints);
            }
        }

        @Override
        public Dimension maximumLayoutSize(Container target) {
            return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
        }

        @Override
        public float getLayoutAlignmentX(Container target) {
            return 0.5f;
        }

        @Override
        public float getLayoutAlignmentY(Container target) {
            return 0.5f;
        }

        @Override
        public void invalidateLayout(Container target) {
        }

        @Override
        public void addLayoutComponent(String name, Component comp) {
        }

        @Override
        public void removeLayoutComponent(Component comp) {
            mapConstraints.remove(comp);
        }

        @Override
        public Dimension preferredLayoutSize(Container parent) {
            return new Dimension(400, 400);
        }

        @Override
        public Dimension minimumLayoutSize(Container parent) {
            return preferredLayoutSize(parent);
        }

        @Override
        public void layoutContainer(Container parent) {
            int width = parent.getWidth();
            int height = parent.getHeight();

            for (Component comp : parent.getComponents()) {

                VirtualPoint p = mapConstraints.get(comp);
                if (p != null) {

                    int x = Math.round(width * p.getX());
                    int y = Math.round(height * p.getY());

                    Dimension size = comp.getPreferredSize();

                    x = Math.min(x, width - size.width);
                    y = Math.min(y, height - size.height);

                    comp.setBounds(x, y, size.width, size.height);

                }
            }
        }
    }
}

Basically, this uses a "virtual" coordinate system, where by rather then supply absolute x/y positions in pixels, you provide them as percentage of the parent container. Now, to be honest, it wouldn't take much to convert back to absolute positioning, just this way, you also get layout scaling.

The example also demonstrates Z-reording (just in case) and the double click simple re-randomizes the puzzel

Oh, I also made the piece transparent (opaque = false)

Randomized layout

Oh, one thing I should mention, while going through this example, I found that it was possible to have pieces placed off screen (completely and partially).

You may want to check your positioning code to make sure that the images when they are laid out aren't been moved off screen ;)

Solution 2:

Try using setBorder(new LineBorder(Color.RED)) in your puzzle piece constructor to see where the bounds of your puzzle pieces are. If they are where you'd expect them to be, it's likely that your positioning is wrong. Also make your puzzle pieces extend JComponent instead, or use setOpaque(false) if you're extending JPanel.