Clear portion of graphics with underlying image
Asked Answered
T

1

1

I'm making a 'game' of sorts where the player has to click on an image bouncing around the screen. The catch is that the screen is in darkness and the mouse cursor is a 'flashlight' which 'light up' a small circle around it.

I have a JFrame in one class consisting of:

public class GameFrame {

public static void main(String[] args) throws IOException {

    Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
    JFrame jf = new JFrame("Flashlight Game");
    jf.setVisible(true);
    jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    jf.setSize(d);
    jf.setLocationRelativeTo(null);
    GamePanel gp = new GamePanel();
    jf.add(gp);
}

}

I have another class which extends JPanel. Here are its fields relevant to my problems:

private Point mouse; //location set by a MouseMotionListener
private BufferedImage myImage;
private int imageX;
private int imageY;
private int imageSpeedX;
private int imageSpeedY;

My first problem lies in the flashlight. In my paint method, I set the graphics background color to the panel background color and use the clearRect method to clear an area around the mouse cursor.

public void paint(Graphics g) {
    Graphics2D g2 = (Graphics2D) g;
    super.paint(g2);
    checkBounce();
    move();
    g2.drawImage(myImage, imageX, imageY, null);
    g2.setColor(Color.BLACK);
    g2.fillRect(0, 0, this.getWidth(), this.getHeight());
    g2.setBackground(Color.WHITE);
    g2.clearRect((int) mouse.getX() - 25, (int) mouse.getY() - 25, 50, 50);
    try {
        Thread.sleep(10);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    repaint();
}

There are actually two problems here. 1.) How can I create a clearOval effect since a flashlight doesn't shine in a rectangle, and 2.) How can I get the bouncing image to show up through the flashlight beam? I know that calling g2.setBackground(Color.WHITE) will use the set color as the 'background' for the cleared area, but I need a way to clear all graphics except for the backmost JFrame or JPanel background color.

My last problem is sorta weird, but occasionally when I change the value of an int, the window will appear as blank and needs resizing before any of the code will execute.

Trihedron answered 22/8, 2013 at 19:9 Comment(2)
For better help sooner, post an SSCCE. For JPanel override paintComponent() rather than paint().Baste
1- Don't use paint, use paintComponent, 2- don't change the state of your program from within the paint method, 3- don't sleep within in the EDT, 4- don't call repaint or any method that might call repaint from within any paint method. You can "fake" this effect by subtracting the oval from the back ground rectangle and painting the resultSerenata
S
2

The basic idea is to create a Rectangle big enough to cover the component, create a Ellipse2D to act as the "hole" or "spot light" and subtract the Ellipse2D from Rectangle so as to create a hole in it, then paint it.

  • You should avoid overridding paint where possible and instead, use paintComponent, paint tends to be to high in the paint chain and comes with a number of complications which are best avoided
  • You should avoid changing the state the state of your component or model within any paintXxx method. Paint can be called for any number of reasons, many of which you won't instantiate. This could be putting your models state into a inconsistent state. Instead, you should using something like a javax.swing.Timer to regulate when the model is updated and simply call repaint
  • Don't ever call Thread.sleep, Threasd.wait or perform any kind of long running loop or I/O operation within the context of the Event Dispatching Thread. Swing is a single threaded environment. That is, all the updates to the UI and event processing are done from within a single thread. If you do anything that blocks the EDT, it will be unable to process these events and update the UI until you stop blocking...
  • Don't call repaint or any method that might invoke a repaint from within in paintXxx method. This will place your program into a spiral of death that will see it consume your CPU...

For more details, take a look at...

enter image description here

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Spotlight {

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

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

                JFrame frame = new JFrame("Testing");
                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 {

        public static final int RADIUS = 80;

        private BufferedImage img;
        private Point mousePoint;

        public TestPane() {
            try {
                img = ImageIO.read(new File("C:\\hold\\thumbnails\\Rampage_Small.png"));
            } catch (IOException ex) {
                Logger.getLogger(Spotlight.class.getName()).log(Level.SEVERE, null, ex);
            }

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

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

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            if (img != null) {
                Graphics2D g2d = (Graphics2D) g.create();
                g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
                g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
                g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
                g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
                g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
                g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
                g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
                g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);

                int x = (getWidth() - img.getWidth()) / 2;
                int y = (getHeight() - img.getHeight()) / 2;

                g2d.drawImage(img, x, y, this);

                x = mousePoint == null ? getWidth() / 2 : mousePoint.x;
                y = mousePoint == null ? getHeight() / 2 : mousePoint.y;

                Rectangle rect = new Rectangle(0, 0, getWidth(), getHeight());
                Ellipse2D spot = new Ellipse2D.Float(
                        (float) x - (RADIUS / 2f),
                        (float) y - (RADIUS / 2f),
                        (float) RADIUS,
                        (float) RADIUS);

                Area area = new Area(rect);
                area.subtract(new Area(spot));

                g2d.setColor(Color.BLACK);
                g2d.fill(area);

                g2d.dispose();
            }
        }
    }

}
Serenata answered 23/8, 2013 at 0:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.