0

I have a JFrame containing a JLabel displaying a BufferedImage. When I hover my mouse on this image, I need to display a circular overlay where the mouse which contains another image.

My code so far:

JFrame frame = new JFrame();
GridBagLayout layout = new GridBagLayout();
frame.getContentPane().setLayout(gLayout);
JLabel myLabel= new JLabel(new ImageIcon(baseImage));
GridBagConstraints constraints = new GridBagConstraints();
...
frame.getContentPane().add(myLabel, constraints);

Now I need to display a circular overlay at the location of the mouse displaying another BufferedImage.

So I need something like this:

myLabel.onMouseHover(event -> {
    Pane p = new Pane();
    x = event.x;
    y = event.y;
    p.setImage(newImage);
    // draw this pane on the label but with an offset for it to be at the center
    myLabel.draw(pane, x - offset, y - offset);
})
Yashwanth
  • 37
  • 1
  • 7
  • 2
    *I need to display a circular overlay where the mouse* - are you stating this "overlay" needs to follow the mouse as you move the mouse around the image, or is the overlay displayed in a fixed location on the label? – camickr Sep 16 '21 at 01:18
  • 1
    Note that you could draw the overlay image directly onto the `baseImage`, or to another image that is copied from `baseImage`. This way you do not need to mess around with multiple layers, and you simply use the mouse over event and the mouse co-ordinates to work out which pixels to draw from one image to the `baseImage`. – sorifiend Sep 16 '21 at 01:18
  • @camickr yes, I need the overlay to move with the mouse – Yashwanth Sep 16 '21 at 01:24
  • 3
    Check out [How to Decorate Components With the JLayer Class](https://docs.oracle.com/javase/tutorial/uiswing/misc/jlayer.html). The section on `Responding to Events` sounds like what you want. Instead of painting a "spotlight" you paint an image. – camickr Sep 16 '21 at 01:32
  • @camickr I'm gonna try that. Thanks! – Yashwanth Sep 16 '21 at 01:55

1 Answers1

3

If I understand correctly, then you kind of want a "see through" style effect. Event if you don't and you want to display a static image at the point of the mouse, the "basic" idea will work, you'll just have to reposition the overlaid image to the correct position.

This example takes two images which are the same size and as you move the mouse around, it will make it appear as if you're "seeing through" the main image. It's illusion, but most effects like this are.

Simple

import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Ellipse2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class Test {

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

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private Point mousePoint;

        private BufferedImage background;
        private BufferedImage seeThrough;

        public TestPane() {

            try {
                background = ImageIO.read(BYO your own image));
                seeThrough = ImageIO.read(BYO your own image));
            } catch (IOException ex) {
                ex.printStackTrace();;
            }

            MouseAdapter ma = new MouseAdapter() {
                @Override
                public void mouseMoved(MouseEvent e) {
                    mousePoint = e.getPoint();
                    repaint();
                }

                @Override
                public void mouseExited(MouseEvent e) {
                    mousePoint = null;
                    repaint();
                }

            };

            addMouseMotionListener(ma);
            addMouseListener(ma);
        }

        @Override
        public Dimension getPreferredSize() {
            if (background != null) {
                return new Dimension(background.getWidth(), background.getHeight());
            }

            return new Dimension(200, 200);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();

            g2d.addRenderingHints(new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON));
            g2d.addRenderingHints(new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY));

            if (background != null) {
                g2d.drawImage(background, 0, 0, this);
            }

            if (seeThrough != null && mousePoint != null) {
                double radius = 45;
                Ellipse2D.Double clip = new Ellipse2D.Double(mousePoint.x - radius, mousePoint.y - radius, radius * 2, radius * 2);
                g2d.setClip(clip);
                g2d.drawImage(seeThrough, 0, 0, this);
            }

            g2d.dispose();
        }

    }
}

But this doesn't use a JLabel

No, it doesn't. JLabel is a pain in the ... code. There's no way to ascertain the location of the image, assuming that's important to you, but in most cases, I'd prefer to have control. You could do something similar with a JLabel, conceptually it's the same idea.

Another solution might be to use a type of "overlay" panel directly on top of the target component, for example

Pointy

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Ellipse2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;

public class Test {

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

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        public TestPane() {
            setLayout(new BorderLayout());
            JLabel label = new JLabel();
            OverlayPane overlayPane = new OverlayPane(label);
            try {
                label.setIcon(new ImageIcon(ImageIO.read(BYO your own image))));
            } catch (IOException ex) {
                ex.printStackTrace();;
            }

            add(overlayPane);
        }

    }

    public class OverlayPane extends JPanel {

        public OverlayPane(JComponent child) {
            setLayout(new GridBagLayout());
            GridBagConstraints gbc = new GridBagConstraints();
            gbc.gridx = 0;
            gbc.gridy = 0;
            gbc.fill = GridBagConstraints.BOTH;

            add(new GlassPane(), gbc);
            add(child, gbc);
        }

        protected class GlassPane extends JPanel {

            private Point mousePoint;
            private BufferedImage pointer;

            public GlassPane() {
                try {
                    pointer = ImageIO.read(BYO your own image));
                } catch (IOException ex) {
                    ex.printStackTrace();
                }

                MouseAdapter ma = new MouseAdapter() {
                    @Override
                    public void mouseMoved(MouseEvent e) {
                        mousePoint = e.getPoint();
                        repaint();
                    }

                    @Override
                    public void mouseExited(MouseEvent e) {
                        mousePoint = null;
                        repaint();
                    }

                };

                addMouseMotionListener(ma);
                addMouseListener(ma);

                setOpaque(false);
            }

            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);
                Graphics2D g2d = (Graphics2D) g.create();

                g2d.addRenderingHints(new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON));
                g2d.addRenderingHints(new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY));

                if (pointer != null && mousePoint != null) {
                    double radius = Math.max(pointer.getWidth() + 10, pointer.getHeight() + 10) / 2;
                    g2d.setColor(Color.WHITE);
                    Ellipse2D.Double clip = new Ellipse2D.Double(mousePoint.x - radius, mousePoint.y - radius, radius * 2, radius * 2);
                    g2d.fill(clip);
                    int x = (int) (mousePoint.x - radius) + 5;
                    int y = (int) (mousePoint.y - radius) + 5;
                    g2d.drawImage(pointer, x, y, this);
                }

                g2d.dispose();
            }
        }

    }
}

Alternatively, you could just use the frame's glassPane directly, but needs drives musts

Annnd JLayer style concept - sure, it allows you to draw dots, but it should give you what you need to make an image move over the top

MadProgrammer
  • 343,457
  • 22
  • 230
  • 366