简体   繁体   中英

Java Swing painting & Mouse event flicker

I have a class that extends JPanel (below), this panel sits inside a JScrollPane. It listens (to itself) for mouse events and tries to reposition itself (on drag) and rescale itself (on wheel) to simulate mouse movement and zooming. The panel is also responsible for the main visual output of my application. It stores a BufferedImage that is rendered in the viewable area of the JScrollPane (but on the panel's graphics). The size and shape of the image is maintained to match the viewable area.

My problems are as such;

1) On mouse events I get a massive amount of flicker and performance drops 2) If I override the paint or paintComponent methods with my own painting method, which is desirable to get rid of flicker and other painting issues, I still get the same flicker effect and graphics drawn from loaded images that have a transparent area then colour that area black. When I call my paint method manually without overriding the paint and paintComponent methods, I still get flicker but the transparent areas display properly.

I'm new to Swing painting and obviously doing something wrong, could anyone point me in the right direction to fix this?

Thanks

    import jSim.simulation.Simulation;
    import java.awt.Color;
    import java.awt.Cursor;
    import java.awt.Dimension;
    import java.awt.Graphics;
    import java.awt.Graphics2D;
    import java.awt.Image;
    import java.awt.Point;
    import java.awt.Rectangle;
    import java.awt.Toolkit;
    import java.awt.event.MouseEvent;
    import java.awt.event.MouseWheelEvent;
    import java.awt.event.MouseWheelListener;
    import java.awt.image.BufferStrategy;
    import java.awt.image.BufferedImage;
    import javax.swing.JPanel;
    import javax.swing.JViewport;
    import javax.swing.event.MouseInputListener;

    public class SimPanel extends JPanel implements MouseWheelListener, MouseInputListener {
        //Simulation

        Simulation sim;
        //Viewer
        JViewport viewport;
        Dimension viewSize;
        BufferStrategy strat;
        //Drawing
        Image renderImage;
        Graphics2D g2d;
        boolean draw = true;
        double scale = 1.0;
        Object drawLock = new Object();
        //Mouse events
        int m_XDifference, m_YDifference;

        public SimPanel(JViewport viewport) {
            this.viewport = viewport;
            this.addMouseListener(this);
            this.addMouseMotionListener(this);
            this.addMouseWheelListener(this);

            //this.setup();
        }

        public SimPanel(Simulation sim, JViewport viewport) {
            this.sim = sim;
            this.viewport = viewport;
            this.addMouseListener(this);
            this.addMouseMotionListener(this);
            this.addMouseWheelListener(this);
            //this.setup();
        }

        //Used to initialise the buffered image once drawing begins
        private void setup() {
            synchronized (drawLock) {
                viewSize = viewport.getExtentSize();
                renderImage = new BufferedImage(viewSize.width, viewSize.height, BufferedImage.TYPE_INT_RGB);
                g2d = (Graphics2D) renderImage.getGraphics();
            }
        }

    //    @Override
    //    public void paint(Graphics g)
    //    {
    //        synchronized(drawLock) {
    //        //super.paintComponent(g);
    //        paintSimulation();
    //        }
    //    }
        //Paint the screen for a specific simulation
        public void paintSimulation(Simulation sim) {
            synchronized (drawLock) {
                setSimulation(sim);
                paintSimulation();
            }
        }

        //Paint the screen with the panels simulation
        public void paintSimulation() {
            synchronized (drawLock) {
                //if no image, then init
                if (renderImage == null) {
                    setup();
                }
                //clear the screen
                resetScreen();
                //draw the simulation if not null, to the image
                if (sim != null) {
                    sim.draw(this);
                }
                //paint the screen with the image
                paintScreen();
            }
        }

        private void resetScreen() {
            Dimension newSize = viewport.getExtentSize();
            if (viewSize.height != newSize.height || viewSize.width != newSize.width || renderImage == null) {
                //System.out.println("Screen Size Changed: " + viewSize + "   " + newSize);
                viewSize = newSize;
                renderImage = new BufferedImage(viewSize.width, viewSize.height, BufferedImage.TYPE_INT_RGB);
                g2d = (Graphics2D) renderImage.getGraphics();
            } else {
                g2d.setBackground(Color.DARK_GRAY);
                g2d.clearRect(0, 0, (int) (viewSize.width), (int) (viewSize.height));
            }
        }

        private void paintScreen() {
            Graphics g;
            Graphics2D g2;
            try {
                //g = viewport.getGraphics();
                g = this.getGraphics();
                g2 = (Graphics2D) g;
                if ((g != null) && (renderImage != null)) {
                    g2.drawImage(renderImage, (int) viewport.getViewPosition().getX(), (int) viewport.getViewPosition().getY(), null);
                }
                Toolkit.getDefaultToolkit().sync();  // sync the display on some systems
                g.dispose();
                g2.dispose();
                this.revalidate();
            } catch (Exception e) {
                System.out.println("Graphics context error: " + e);
            }
        }

        //Simulation makes calls to this method to draw items on the image
        public void draw(BufferedImage image, int x, int y, Color colour) {
            synchronized (drawLock) {
                Rectangle r = viewport.getViewRect();
                if (g2d != null && draw) {
                    Point p = new Point((int) (x * scale), (int) (y * scale));
                    if (r.contains(p)) {
                        if (scale < 1) {
                            Graphics2D g2 = (Graphics2D) image.getGraphics();
                            Image test = image.getScaledInstance((int) (image.getWidth(null) * scale), (int) (image.getHeight(null) * scale), Image.SCALE_FAST);
                            g2d.drawImage(test, (int) ((x * scale - r.x)), (int) ((y * scale - r.y)), null);
                        } else {
                            g2d.drawImage(image, x - r.x, y - r.y, null);
                        }
                    }
                }
            }
        }

        public void setDraw(boolean draw) {
            this.draw = draw;
        }

        public void setSimulation(Simulation sim) {
            synchronized (drawLock) {
                if (!(this.sim == sim)) {
                    this.sim = sim;
                }
            }
        }

        public void mouseWheelMoved(MouseWheelEvent e) {
            synchronized (drawLock) {
                updatePreferredSize(e.getWheelRotation(), e.getPoint());
            }
        }

        private void updatePreferredSize(int wheelRotation, Point stablePoint) {
            double scaleFactor = findScaleFactor(wheelRotation);
            if (scale * scaleFactor < 1 && scale * scaleFactor > 0.05) {
                scaleBy(scaleFactor);
                Point offset = findOffset(stablePoint, scaleFactor);
                offsetBy(offset);
                this.getParent().doLayout();
            }
        }

        private double findScaleFactor(int wheelRotation) {
            double d = wheelRotation * 1.08;
            return (d > 0) ? 1 / d : -d;
        }

        private void scaleBy(double scaleFactor) {
            int w = (int) (this.getWidth() * scaleFactor);
            int h = (int) (this.getHeight() * scaleFactor);
            this.setPreferredSize(new Dimension(w, h));
            this.scale = this.scale * scaleFactor;
        }

        private Point findOffset(Point stablePoint, double scaleFactor) {
            int x = (int) (stablePoint.x * scaleFactor) - stablePoint.x;
            int y = (int) (stablePoint.y * scaleFactor) - stablePoint.y;
            return new Point(x, y);
        }

        private void offsetBy(Point offset) {
            Point location = viewport.getViewPosition();
            //this.setLocation(location.x - offset.x, location.y - offset.y);
            viewport.setViewPosition(new Point(location.x + offset.x, location.y + offset.y));
        }

        public void mouseDragged(MouseEvent e) {
            synchronized (drawLock) {
                //Point p = this.getLocation();
                Point p = viewport.getViewPosition();
                int newX = p.x - (e.getX() - m_XDifference);
                int newY = p.y - (e.getY() - m_YDifference);
                //this.setLocation(newX, newY);
                viewport.setViewPosition(new Point(newX, newY));
                //this.getParent().doLayout();
            }
        }

        public void mousePressed(MouseEvent e) {
            synchronized (drawLock) {
                setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
                m_XDifference = e.getX();
                m_YDifference = e.getY();
            }
        }

        public void mouseReleased(MouseEvent e) {
            synchronized (drawLock) {
                setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
            }
        }

        public void mouseClicked(MouseEvent e) {
            //throw new UnsupportedOperationException("Not supported yet.");
        }

        public void mouseEntered(MouseEvent e) {
            //throw new UnsupportedOperationException("Not supported yet.");
        }

        public void mouseExited(MouseEvent e) {
            //throw new UnsupportedOperationException("Not supported yet.");
        }

        public void mouseMoved(MouseEvent e) {
            //throw new UnsupportedOperationException("Not supported yet.");
        }
    }

In short, look up double buffering.

The longer answer...

Override paintComponent. Create an offscreen graphics object. Do your painting on that object. Copy that over to the graphics objects passed into the paint method.

Oh, and get rid of all the synchronization. You don't need it.

If you add a setOpaque(true) on the viewport you inform Swing that you'll do all the painting (especially the background) yourself. That may already help a bit.

EDIT

I've looked around a bit more, and think you should override paintComponent.

You could have 2 images, and four references:

  • imageToPaint = null
  • imageToWriteTo = null
  • bufferImageOne initialized to appropriately sized BufferedImage
  • bufferImageTwo initialized to appropriately sized BufferedImage

You'd override paintComponent to draw the background and then drawImage(imageToPaint) (if it's not null, which it shouldn't be)

You'll have a thread which does the custom painting of imageToWriteTo. At the end it swaps imageToPaint and imageToWriteTo.

Then you call repaint(). This requests a repaint, with the added advantage that all repaint requests on the Swing queue get taken together and result in a single paint. No revalidate or sync please. This repainting is automatically done on your second thread, the Event Dispatch Thread.

That way updating the simulation image is decoupled from actual painting, and paint updates need not wait for the simulation to finish drawing. It may result from slightly outdated images (a bit implicit in using buffering) but it should yield better results.

In short, expensive writing is done to imageToWriteTo. Painting is done using imageToPaint. Expensive writing ends with swapping imageToWriteTo and imageToPaint.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM