0

So I have a DisplayPanel class that extends JPanel and that also paints my numerous images for my program using Graphics2D. In order to be able to easily customly use this I set it up so that every time the panel is repainted it uses a List, that I can add to or remove from as the program processes. My problem is with layering. I've run into an issue where the List must have reached its resizing point (or something whacky like that) and so the images i want to display end up beneath all of the other images already on the screen. I've come to the community for an answer because I have faith you will provide a good one.

Picture of problem: only the picture in the far corner is highlighted by the mouse. The highlight on the rest is beneath... (http://imgur.com/LY41q)

Code from DisplayPanel:

@Override
public void paintComponent(Graphics g) {
    super.paintComponent(g);
    Graphics2D g2d = (Graphics2D) g;
    g2d.clearRect(0, 0, 800, 640);
    for(int i = 0; i < images.size(); i++) g2d.drawImage(
            images.get(i).getImage(), images.get(i).getX(), 
            images.get(i).getY(), null);
}

public void paintImage(ImageMap[] images, ImageMap[] clearImages, boolean clear) {
    if(clear) this.images.clear();
    else if(clearImages!=null) for(int i = 0; i < clearImages.length; i++) this.images.remove(clearImages[i]);
    if(images!=null) for(int i = 0; i<images.length; i++) this.images.add(images[i]);            
    refresh();
}

Code from Game:

    private void handleMoveClick(int identity) {

    int index = -1;
    if(identity >=0 && identity < 36) {
        System.out.println(identity);
        index = identity;
        identity = 0;
    }

    switch(identity) {

        case startButtonIdentity:
            if(!tempButtonImages.contains(startButtonLight)) {
                tempButtonImages.add(startButtonLight);
                display.panel.addImage(startButtonLight);
            }
            break;

        case 0:
            if(!tempButtonImages.contains(field.getFieldHighlightImageAt(index))) {
                tempButtonImages.add(field.getFieldHighlightImageAt(index));
                display.panel.addImage(field.getFieldHighlightImageAt(index));
            }
            break;

        default:
            ImageMap[] tempImages = tempButtonImages.toArray(new ImageMap[tempButtonImages.size()]);
            for(int i = 0; i<tempImages.length; i++) {
                display.panel.removeImage(tempImages[i]);
            }
            tempButtonImages.clear();
            break;
    }

Where highlight images have been being created

Code from Field:

public void makeFieldHighlightImages() {
    fieldHighlightImages = new ImageMap[fieldTable.length*fieldTable[0].length];
    for(int i = 0; i < fieldTable.length; i++)
        for(int j = 0; j < fieldTable[0].length; j++)
            fieldHighlightImages[i*fieldTable[0].length+j] = 
                    new ImageMap(Deck.getCardImage(56), 
                    startX+j*horizontalSpacing, 
                    startY+i*verticalSpacing);
}

public ImageMap getFieldHighlightImageAt(int index) {
    System.out.println(fieldHighlightImages[index].getImage() + " " + fieldHighlightImages[index].getX() + " " + fieldHighlightImages[index].getY());
    return fieldHighlightImages[index];
}
  • For better help sooner, post an [SSCCE](http://sscce.org/). Hot-link to a small (in bytes) image if needed. (Let's see if this message gets across faster than [last time](http://stackoverflow.com/questions/13328307/drawing-an-image-at-a-point-of-another-image#comment18184748_13328307) I tried..) – Andrew Thompson Nov 11 '12 at 05:20
  • Added a picture and removed everything but two important methods in class for you. – Mirrorcrazy Nov 11 '12 at 05:31
  • 1
    *"for you."* Don't do anything for my sake. If **you** want my help, post an SSCCE (if not, don't stress over it, I see MP has already alluded to providing a complete example, maybe the little one will be asleep soon, and that will appear). Any which way it goes, I don't really have a vested interest, it was just a tip for you. – Andrew Thompson Nov 11 '12 at 05:33

1 Answers1

1

What might be easier is to use a backing buffer of some kind and only paint to it when something changes.

This means that calls to paintComponent are only painting the backing buffer, making it faster, and would allow to paint the backing buffer in a second thread if the list become to large.

Updated with example

enter image description here

public class BackingBuffer {

    public static void main(String[] args) {
        new BackingBuffer();
    }

    public BackingBuffer() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException ex) {
                } catch (InstantiationException ex) {
                } catch (IllegalAccessException ex) {
                } catch (UnsupportedLookAndFeelException ex) {
                }

                File[] imageFiles = new File("D:/hold/ScaledImages").listFiles(new FileFilter() {
                    @Override
                    public boolean accept(File pathname) {
                        String name = pathname.getName().toLowerCase();
                        return name.endsWith(".png") || name.endsWith(".jpg") || name.endsWith(".gif");
                    }
                });

                ImagesPane imagesPane = new ImagesPane();
                for (File file : imageFiles) {
                    try {
                        BufferedImage image = ImageIO.read(file);
                        imagesPane.addImage(image);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(imagesPane);
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class ImagesPane extends JPanel {

        private BufferedImage backingBuffer;
        private List<Image> images;
        private Timer updateTimer;

        public ImagesPane() {
            images = new ArrayList<Image>(25);
            updateTimer = new Timer(125, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    updateBuffer();
                    repaint();
                }
            });
            updateTimer.setRepeats(false);
            updateTimer.setCoalesce(true);
        }

        public void addImage(Image image) {
            // You could devise some kind of algorithim to determine if was possible
            // to image the image into the existing backing buffer or not.
            // It would save having to recreate the backing buffer unless it
            // really was required...
            images.add(image);
            invalidate();
        }

        public void removeImage(Image image) {
            images.remove(image);
            invalidate();
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(400, 400);
        }

        protected void updateBuffer() {
            if (backingBuffer == null || backingBuffer.getWidth() != getWidth() || backingBuffer.getHeight() != getHeight()) {
                if (getWidth() > 0 && getHeight() > 0) {
                    backingBuffer = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
                }
            }
            if (backingBuffer != null) {
                Graphics2D g2d = backingBuffer.createGraphics();
                int y = 0;
                int x = 0;
                int rowHeight = 0;
                for (Image image : images) {
                    rowHeight = Math.max(image.getHeight(this), rowHeight);
                    if (x + image.getWidth(this) > getWidth() && x != 0) {
                        x = 0;
                        y += rowHeight;
                    }
                    g2d.drawImage(image, x, y, this);
                    x += image.getWidth(this);
                    if (x > getWidth()) {
                        x = 0;
                        y += rowHeight;
                        rowHeight = 0;
                    }
                }
                g2d.dispose();
            }
        }

        @Override
        public void invalidate() {
            // This method can be called repeatly in quick sucession, rather then
            // reacting to each call, I want to delay performing the update,
            // which might be costly in time and memory until it's all settled down
            // a little...
            super.invalidate();
            updateTimer.restart();
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            if (backingBuffer != null) {
                g.drawImage(backingBuffer, 0, 0, this);
            }
        }
    }
}

Now, if you wanted to use a back ground thread of some sort, I'd probably use a SwingWorker. The only thing you really would need in additional is some kind of flag you could raise so you knew that a worker was updating the buffer (as workers are non-reentrant (the same instance can't be run twice))

The worker would create a new, temporary buffer it could work, as you don't want to interfere with the one that is currently being used to paint on the screen (or you will end up with dirty paints) and once complete, you could switch the buffers in the done method and call repaint on the component to have it updated on the screen...

UPDATED with selection highlighting

enter image description here

You could highlight each image directly in the backing buffer, but I personally think this is an expensive exercise, as you would need to update the backing buffer on each click.

A better approach would be to maintain a Map of image bounds keyed back to the individual images. When you updated the buffer, you would recreate this map.

Using this map, you could determine if any "images" where clicked. I would then place a reference of the image into a list, which I would then use when painting the component...

public class BackingBuffer {

    public static void main(String[] args) {
        new BackingBuffer();
    }

    public BackingBuffer() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException ex) {
                } catch (InstantiationException ex) {
                } catch (IllegalAccessException ex) {
                } catch (UnsupportedLookAndFeelException ex) {
                }

                File[] imageFiles = new File("C:/hold/ScaledImages").listFiles(new FileFilter() {
                    @Override
                    public boolean accept(File pathname) {
                        String name = pathname.getName().toLowerCase();
                        return name.endsWith(".png") || name.endsWith(".jpg") || name.endsWith(".gif");
                    }
                });

                ImagesPane imagesPane = new ImagesPane();
                for (File file : imageFiles) {
                    try {
                        BufferedImage image = ImageIO.read(file);
                        imagesPane.addImage(image);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(imagesPane);
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class ImagesPane extends JPanel {

        private Map<Image, Rectangle> mapBounds;
        private BufferedImage backingBuffer;
        private List<Image> images;
        private Timer updateTimer;
        private List<Image> selected;

        public ImagesPane() {
            images = new ArrayList<Image>(25);
            selected = new ArrayList<Image>(25);
            updateTimer = new Timer(125, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    updateBuffer();
                    repaint();
                }
            });
            updateTimer.setRepeats(false);
            updateTimer.setCoalesce(true);

            addMouseListener(new MouseAdapter() {
                @Override
                public void mouseClicked(MouseEvent e) {
                    if (mapBounds != null) {
                        boolean shouldPaint = false;
                        for (Image image : mapBounds.keySet()) {
                            Rectangle bounds = mapBounds.get(image);
                            if (bounds.contains(e.getPoint())) {
                                if (selected.contains(image)) {
                                    shouldPaint = true;
                                    selected.remove(image);
                                } else {
                                    shouldPaint = true;
                                    selected.add(image);
                                }
                                // In it's current form, there is not overlapping, if you
                                // have overlapping images, you may want to reconsider this
                                break;
                            }
                        }
                        if (shouldPaint) {
                            repaint();
                        }
                    }
                }
            });
        }

        public void addImage(Image image) {
            // You could devise some kind of algorithim to determine if was possible
            // to image the image into the existing backing buffer or not.
            // It would save having to recreate the backing buffer unless it
            // really was required...
            images.add(image);
            invalidate();
        }

        public void removeImage(Image image) {
            images.remove(image);
            if (mapBounds != null) {
                mapBounds.remove(image);
            }
            if (selected.contains(image)) {
                selected.remove(image);
            }
            invalidate();
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(400, 400);
        }

        protected void updateBuffer() {
            if (backingBuffer == null || backingBuffer.getWidth() != getWidth() || backingBuffer.getHeight() != getHeight()) {
                if (getWidth() > 0 && getHeight() > 0) {
                    backingBuffer = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
                }
            }
            if (backingBuffer != null) {
                mapBounds = new WeakHashMap<Image, Rectangle>(images.size());
                Graphics2D g2d = backingBuffer.createGraphics();
                int y = 0;
                int x = 0;
                int rowHeight = 0;
                for (Image image : images) {
                    rowHeight = Math.max(image.getHeight(this), rowHeight);
                    if (x + image.getWidth(this) > getWidth() && x != 0) {
                        x = 0;
                        y += rowHeight;
                    }
                    mapBounds.put(image, new Rectangle(x, y, image.getWidth(this), image.getHeight(this)));
                    g2d.drawImage(image, x, y, this);
                    x += image.getWidth(this);
                    if (x > getWidth()) {
                        x = 0;
                        y += rowHeight;
                        rowHeight = 0;
                    }
                }
                g2d.dispose();
            }
        }

        @Override
        public void invalidate() {
            // This method can be called repeatly in quick sucession, rather then
            // reacting to each call, I want to delay performing the update,
            // which might be costly in time and memory until it's all settled down
            // a little...
            super.invalidate();
            updateTimer.restart();
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            if (backingBuffer != null) {
                g.drawImage(backingBuffer, 0, 0, this);
                if (selected != null) {
                    Graphics2D g2d = (Graphics2D) g;
                    g2d.setColor(UIManager.getColor("List.selectionBackground"));
                    for (Image image : selected) {
                        Rectangle bounds = mapBounds.get(image);
                        if (bounds != null) {
                            Composite composite = g2d.getComposite();
                            g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f));
                            g2d.fill(bounds);
                            g2d.setComposite(composite);
                            g2d.draw(bounds);
                        }
                    }
                }
            }
        }
    }
}
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • Yep, working on example, six month old who doesn't want to take their nap...I'd have it! – MadProgrammer Nov 11 '12 at 05:11
  • Lol that probably slows things down – Mirrorcrazy Nov 11 '12 at 05:20
  • Thank you very much, I don't have time to look over everything now and implement it. But it looks like it should work – Mirrorcrazy Nov 11 '12 at 05:50
  • @AndrewThompson I know the problem :P – MadProgrammer Nov 11 '12 at 07:05
  • Alright so I tweaked things around and was able to pretty flawlessly implement your example code using the swing timer but I still have the same issue I had before with the highlight only appearing one of the images. – Mirrorcrazy Nov 11 '12 at 20:06
  • I've added highlighting to the original code ;), check updated example – MadProgrammer Nov 11 '12 at 23:25
  • Alright, so the code you've added here is great, but while troubleshooting I'm a little concerned this issue might come up again during the creation of this code because of some very strange glitchy results that occurred while troubleshooting. I was thinking that issue might not be with the display but after vigorous testing its still the only place I can find a possible conflict in. I'm very confused. I thank you for all the wonderful examples you have provided though. – Mirrorcrazy Nov 12 '12 at 01:24
  • Okay, so maybe you need to change approaches. It sounds likes the old approach is coursing you some trouble. Start with a fresh sheet and see if you can make it work, it might highlight the problem area in your original code – MadProgrammer Nov 12 '12 at 01:32
  • So I found the location of the actual problem and it seems as if the image im using isnt working ill post code. – Mirrorcrazy Nov 12 '12 at 01:59
  • Whenever I use anything from the Field class to create the highlights its just goes whack :/ – Mirrorcrazy Nov 12 '12 at 03:09