2

So I'm trying to find a way to modify an image in Java. In other words, if user clicks on the image, a mark will be put at the point where the user just clicked. I have an ImageIcon which I put in a JLabel. So far, the approach I took was to use JLayeredPanel to put another JPanel on top of the JLabel and draw on this JPanel:

//...
ImageIcon icon = new ImageIcon("foo.jpg");
JLabel lb = new JLabel(icon);
JPanel glass = new JPanel();
lb.setBounds(0, 0, 100, 100);
glass.setBounds(0, 0, 100, 100);
glass.setOpaque(false);
LayeredPane container = new LayeredPane();
container.add(lb, 1);
container.add(glass, 2);

//...

But this way doesn't seem to work. I never see the background image (the image in lb). So I was wondering if I'm even on the right track at all? Or is there a cleaner way to achieve this?

Roman C
  • 49,761
  • 33
  • 66
  • 176
0x56794E
  • 20,883
  • 13
  • 42
  • 58

2 Answers2

3

You're on the right track with wanting to use another pane. In Java, there actually already is a glass pane that is designed for just this purpose. Read through this tutorial http://docs.oracle.com/javase/tutorial/uiswing/components/rootpane.html and it should help you understand.

Jeff Storey
  • 56,312
  • 72
  • 233
  • 406
  • Oh I've read about that but the problem is my image doesn't cover the entire root pane so I obviously don't want the glass pane to do so either. I only want the user to be able to mark points ON the picture. Do you have any suggestion about this? Thanks – 0x56794E Jan 11 '13 at 21:01
  • Even if the glass pane covers the entire frame, you don't actually have to show anything when they click unless they are within the picture bounds. The rest of the glass pane will be transparent, so it shouldn't matter if the glass pane takes up the whole frame. – Jeff Storey Jan 11 '13 at 21:14
  • right [for example](http://stackoverflow.com/a/9734016/714968), where `GlassPane` returns the same `Bound` as `RootPane` – mKorbel Jan 11 '13 at 21:25
  • @Jeff todays `Java7` has implemented `JLayer`, based on `JXLayer(Java6)`, very good API – mKorbel Jan 11 '13 at 21:27
  • @mKorbel Yep, if the OP is using Java 7 that will be useful. – Jeff Storey Jan 11 '13 at 22:02
3

There's nothing wrong with using a JLayeredPane or the glass pane for something like this, personally, I find it troublesome, because in a large application, you tend to want to use these layers for any number of things, so it becomes very complicated very fast.

I prefer to keep it "in the family" so to speak...

Personally, I would use a custom component. This isolates the work flow to a very particular location and makes it easier to provide the customisations that you might like...

enter image description here

public class MarkImage {

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

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

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

        });
    }

    public class TestPane extends JPanel {

        private BufferedImage background;
        private List<Point> clickPoints;

        public TestPane() {
            clickPoints = new ArrayList<>(25);
            try {
                background = ImageIO.read(getClass().getResource("/Miho_Small.png"));
            } catch (IOException ex) {
                ex.printStackTrace();
            }

            addMouseListener(new MouseAdapter() {
                @Override
                public void mouseClicked(MouseEvent e) {
                    clickPoints.add(e.getPoint());
                    repaint();
                }
            });
        }

        @Override
        public Dimension getPreferredSize() {
            return background == null ? super.getPreferredSize() : new Dimension(background.getWidth(), background.getHeight());
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            if (background != null) {
                int x = (getWidth() - background.getWidth()) / 2;
                int y = (getHeight() - background.getHeight()) / 2;
                g.drawImage(background, x, y, this);
            }
            g.setColor(Color.RED);
            for (Point p : clickPoints) {
                g.fillOval(p.x - 4, p.y - 4, 8, 8);
            }
        }

    }

}

I'd also consider using JXLayer (AKA JLayer in Java 7). This is best described as a glass pane for components (on steroids). Check out How to decorate components for more details...

Updated with JLayer Example

This is an example using Java 7's JLayer. There are some slight differences between JLayer and JXLayer, but it wouldn't take much to convert it...

(Sorry, couldn't resist the temptation of having ago)

enter image description here

public class MarkLayer {

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

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

                try {
                    JFrame frame = new JFrame("Testing");
                    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    frame.setLayout(new GridBagLayout());

                    JLabel label = new JLabel(new ImageIcon(ImageIO.read(getClass().getResource("/Miho_Small.png"))));
                    LayerUI<JLabel> layerUI = new MarkLayerUI();
                    JLayer<JLabel> layer = new JLayer<>(label, layerUI);

                    frame.add(layer);
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
                } catch (Exception exp) {
                    exp.printStackTrace();
                }
            }
        });
    }

    public class MarkLayerUI extends LayerUI<JLabel> {

        private Map<JLayer, List<Point>> mapPoints;

        public MarkLayerUI() {
            mapPoints = new WeakHashMap<>(25);
        }

        @Override
        public void installUI(JComponent c) {
            System.out.println("install");
            super.installUI(c);
            JLayer layer = (JLayer) c;
            layer.setLayerEventMask(AWTEvent.MOUSE_EVENT_MASK);
        }

        @Override
        public void uninstallUI(JComponent c) {
            super.uninstallUI(c);
            mapPoints.remove((JLayer) c);
        }

        @Override
        protected void processMouseEvent(MouseEvent e, JLayer<? extends JLabel> l) {
            if (e.getID() == MouseEvent.MOUSE_CLICKED) {

                List<Point> points = mapPoints.get(l);
                if (points == null) {
                    points = new ArrayList<>(25);
                    mapPoints.put(l, points);
                }
                Point p = e.getPoint();
                p = SwingUtilities.convertPoint(e.getComponent(), p, l);
                points.add(p);
                l.repaint();

            }
        }

        @Override
        public void paint(Graphics g, JComponent c) {
            Graphics2D g2d = (Graphics2D) g.create();
            super.paint(g2d, c);
            g2d.setColor(Color.BLUE);
            g2d.drawRect(0, 0, c.getWidth() - 1, c.getHeight() - 1);
            List<Point> points = mapPoints.get((JLayer) c);
            if (points != null && points.size() > 0) {
                g2d.setColor(Color.RED);
                for (Point p : points) {
                    g2d.fillOval(p.x - 4, p.y - 4, 8, 8);
                }
            }
            g2d.dispose();
        }
    }
}

The blue border is renderer as part of the layer, this gives you a guide as to where you can click - I did this for testing and demonstration purposes

MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • To be even more efficient, if it's not desired to keep the original image or to even care about a list of the points, you can take the theImageIcon.getImage().getGraphics() and then draw the points with that graphics object. By doing it that way you'll be drawing directly to the ImageIcon's buffer. – nhydock Jan 11 '13 at 22:19
  • @nhydock That's a fair point - all of it comes down to "what the OP wants" ;) – MadProgrammer Jan 11 '13 at 23:21
  • @abcXYZ I've added a `JLayer` example as well, which might meet your needs better – MadProgrammer Jan 12 '13 at 01:29
  • MadProgrammer Thanks so much! this definitely helps me a lot! @nhydock I was trying to write directly to the original image using the graphics of the image icon but it doesn't seem to work. So I'm doing something like this in the paintComponent() method of the TestPane `ImageIcon icon = new ImageIcon(backgroundd);` `Graphics origG = new i.getImage().getGraphics();` `//... as is` `origG.setColor(Color.RED);` `for (Point p : clickPoints) origG.fillOval(p.x - 4, p.y - 4, 8, 8);` It does NOT paint the circles where I click. Did I miss something? Thanks. – 0x56794E Jan 12 '13 at 01:50
  • @abcXYZ You need to convert the point from the parent context to the local context. You may need to update you question with a sample of your code. – MadProgrammer Jan 12 '13 at 02:21
  • haha sorry guys. it worked out well. it was my fault. I messed up somewhere else. Thanks so much! – 0x56794E Jan 12 '13 at 02:51